Skip to content

Commit c3fd5ea

Browse files
committed
scroll on drag
1 parent 99011b1 commit c3fd5ea

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

frontend/app/tab/vtabbar.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
124124
const dragSourceRef = useRef<string | null>(null);
125125
const didResetHoverForDragRef = useRef(false);
126126
const scrollContainerRef = useRef<HTMLDivElement>(null);
127+
const scrollAnimFrameRef = useRef<number | null>(null);
128+
const scrollDirectionRef = useRef<number>(0);
129+
const scrollSpeedRef = useRef<number>(0);
127130

128131
useEffect(() => {
129132
setOrderedTabIds(tabIds);
@@ -143,7 +146,59 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
143146
el?.scrollIntoView({ block: "nearest" });
144147
}, [activeTabId, documentHasFocus]);
145148

149+
const stopScrollLoop = useCallback(() => {
150+
if (scrollAnimFrameRef.current != null) {
151+
cancelAnimationFrame(scrollAnimFrameRef.current);
152+
scrollAnimFrameRef.current = null;
153+
}
154+
scrollDirectionRef.current = 0;
155+
}, []);
156+
157+
const startScrollLoop = useCallback(() => {
158+
if (scrollAnimFrameRef.current != null) {
159+
return;
160+
}
161+
const loop = () => {
162+
const container = scrollContainerRef.current;
163+
if (container == null || scrollDirectionRef.current === 0) {
164+
scrollAnimFrameRef.current = null;
165+
return;
166+
}
167+
container.scrollTop += scrollDirectionRef.current * scrollSpeedRef.current;
168+
scrollAnimFrameRef.current = requestAnimationFrame(loop);
169+
};
170+
scrollAnimFrameRef.current = requestAnimationFrame(loop);
171+
}, []);
172+
173+
const updateScrollFromDragY = useCallback(
174+
(clientY: number) => {
175+
const container = scrollContainerRef.current;
176+
if (container == null) {
177+
return;
178+
}
179+
const EdgeZone = 60;
180+
const MaxScrollSpeed = 12;
181+
const rect = container.getBoundingClientRect();
182+
const relY = clientY - rect.top;
183+
const height = rect.height;
184+
if (relY < EdgeZone) {
185+
scrollDirectionRef.current = -1;
186+
scrollSpeedRef.current = MaxScrollSpeed * (1 - relY / EdgeZone);
187+
startScrollLoop();
188+
} else if (relY > height - EdgeZone) {
189+
scrollDirectionRef.current = 1;
190+
scrollSpeedRef.current = MaxScrollSpeed * (1 - (height - relY) / EdgeZone);
191+
startScrollLoop();
192+
} else {
193+
scrollDirectionRef.current = 0;
194+
stopScrollLoop();
195+
}
196+
},
197+
[startScrollLoop, stopScrollLoop]
198+
);
199+
146200
const clearDragState = () => {
201+
stopScrollLoop();
147202
if (dragSourceRef.current != null && !didResetHoverForDragRef.current) {
148203
didResetHoverForDragRef.current = true;
149204
setHoverResetVersion((version) => version + 1);
@@ -185,6 +240,7 @@ export function VTabBar({ workspace, className }: VTabBarProps) {
185240
className="relative flex min-h-0 flex-1 flex-col overflow-y-auto"
186241
onDragOver={(event) => {
187242
event.preventDefault();
243+
updateScrollFromDragY(event.clientY);
188244
if (event.target === event.currentTarget) {
189245
setDropIndex(orderedTabIds.length);
190246
setDropLineTop(event.currentTarget.scrollHeight);

0 commit comments

Comments
 (0)