authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-10-08 19:52:56-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 00:52:56+00:00
logb0ee7dd48f868c5c02884345f723bebaf616b13d
tree8c0e22c93cb2c84949babc40cfa4c83f9770057d
parent29980a31409234b676f97bd477c4e46fdb61a929
signature Signed by PGP key B5690EEEBB952194

fix(web): drop sidebar context before cancelling pointer drag (#17373)


2 files changed, 48 insertions(+), 3 deletions(-)

apps/web/src/components/Sidebar.pointer.test.ts+42
...@@ -147,6 +147,48 @@ describe("sidebar pointer lifecycle", () => {...@@ -147,6 +147,48 @@ describe("sidebar pointer lifecycle", () => {
147 expect(inside.onCancel).not.toHaveBeenCalled();147 expect(inside.onCancel).not.toHaveBeenCalled();
148 });148 });
149149
150 it("drops context when a zero-button move precedes pointerup", () => {
151 const onDrop = vi.fn((point: { x: number; y: number }) => point.x > 50);
152 const drag = gesture({ onMove: (point) => point.x > 50, onDrop });
153 document.dispatchEvent(pointer("pointermove", { clientY: 20 }));
154 document.dispatchEvent(pointer("pointermove", { clientX: 90, clientY: 30 }));
155
156 // Native desktop releases can report the released button on the final move.
157 document.dispatchEvent(pointer("pointermove", { buttons: 0, clientX: 94, clientY: 32 }));
158 document.dispatchEvent(pointer("pointerup", { buttons: 0, clientX: 94, clientY: 32 }));
159 document.dispatchEvent(pointer("pointermove", { buttons: 0, clientX: 100, clientY: 40 }));
160
161 expect(onDrop).toHaveBeenCalledExactlyOnceWith({ x: 94, y: 32 });
162 expect(drag.onCancel).toHaveBeenCalledOnce();
163 expect(drag.onEnd).not.toHaveBeenCalled();
164 expect(drag.onFinish).toHaveBeenCalledExactlyOnceWith(true);
165 });
166
167 it("cancels a missed sidebar release without committing a reorder", () => {
168 const onDrop = vi.fn((point: { x: number; y: number }) => point.x > 50);
169 const drag = gesture({ onMove: (point) => point.x > 50, onDrop });
170 document.dispatchEvent(pointer("pointermove", { clientX: 90, clientY: 30 }));
171 document.dispatchEvent(pointer("pointermove", { buttons: 0, clientX: 30, clientY: 40 }));
172 document.dispatchEvent(pointer("pointerup", { buttons: 0, clientX: 30, clientY: 40 }));
173
174 expect(onDrop).toHaveBeenCalledExactlyOnceWith({ x: 30, y: 40 });
175 expect(drag.onCancel).toHaveBeenCalledOnce();
176 expect(drag.onEnd).not.toHaveBeenCalled();
177 expect(drag.onFinish).toHaveBeenCalledExactlyOnceWith(true);
178 });
179
180 it("does not drop context when the button is released before activation", () => {
181 const onDrop = vi.fn(() => true);
182 const drag = gesture({ onDrop });
183 document.dispatchEvent(pointer("pointermove", { buttons: 0, clientX: 90, clientY: 30 }));
184 document.dispatchEvent(pointer("pointerup", { buttons: 0, clientX: 90, clientY: 30 }));
185
186 expect(onDrop).not.toHaveBeenCalled();
187 expect(drag.onStart).not.toHaveBeenCalled();
188 expect(drag.onAbort).toHaveBeenCalledOnce();
189 expect(drag.onFinish).toHaveBeenCalledExactlyOnceWith(false);
190 });
191
150 it("resumes reordering when a context drag returns to the sidebar", () => {192 it("resumes reordering when a context drag returns to the sidebar", () => {
151 const drag = gesture({193 const drag = gesture({
152 onMove: (point) => point.x > 50,194 onMove: (point) => point.x > 50,
apps/web/src/components/Sidebar.pointer.ts+6-3
...@@ -68,10 +68,13 @@ export class SidebarPointerSensor {...@@ -68,10 +68,13 @@ export class SidebarPointerSensor {
6868
69 private move = (event: PointerEvent) => {69 private move = (event: PointerEvent) => {
70 if (this.phase === "finished" || event.pointerId !== this.pointer.pointerId) return;70 if (this.phase === "finished" || event.pointerId !== this.pointer.pointerId) return;
71 // A release outside the window can be missed. Never activate or continue
72 // a drag when the initiating button is no longer held.
73 if ((event.buttons & 1) === 0) return this.cancel();
74 const coordinates = { x: event.clientX, y: event.clientY };71 const coordinates = { x: event.clientX, y: event.clientY };
72 // Desktop can report buttons=0 on the final move before pointerup. Let an
73 // active context drag drop there before cancelling the sidebar sort.
74 if ((event.buttons & 1) === 0) {
75 if (this.phase === "dragging") this.props.options.onDrop?.(coordinates);
76 return this.cancel();
77 }
75 if (this.phase === "pending") {78 if (this.phase === "pending") {
76 const offset = {79 const offset = {
77 x: event.clientX - this.pointer.clientX,80 x: event.clientX - this.pointer.clientX,