diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.test.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.test.tsx index 7ca3dd8e86..995081d006 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.test.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.test.tsx @@ -197,6 +197,54 @@ describe("", () => { }); }); + describe("pointer drag activation", () => { + beforeEach(() => { + (Sections.args.changeRoomSection as any).mockClear?.(); + }); + + it("does not start a drag when a finger moves (touch scrolling the list)", async () => { + // For touch, dragging only activates after a 250ms hold; moving the finger first aborts + // it so the list scrolls instead of dragging a room. Simulate a finger press that moves + // immediately (as when scrolling) and assert no drag ever starts. + const user = userEvent.setup(); + renderWithMockContext(); + + const status = screen.getByRole("status"); + const roomButton = await screen.findByRole("button", { name: "Open room General" }); + + await user.pointer([ + { keys: "[TouchA>]", target: roomButton, coords: { x: 20, y: 20 } }, + { pointerName: "TouchA", coords: { x: 20, y: 140 } }, + { keys: "[/TouchA]" }, + ]); + + expect(status).toHaveTextContent(""); + expect(Sections.args.changeRoomSection).not.toHaveBeenCalled(); + }); + + it("starts a drag when the mouse moves past the activation distance", async () => { + // For mouse, dragging activates as soon as the pointer moves past 5px, so the same + // press-and-move gesture that scrolls on touch drags the room into another section. + const user = userEvent.setup(); + renderWithMockContext(); + + const status = screen.getByRole("status"); + const roomButton = await screen.findByRole("button", { name: "Open room General" }); + + await user.pointer([ + { keys: "[MouseLeft>]", target: roomButton, coords: { x: 20, y: 20 } }, + { coords: { x: 20, y: 140 } }, + ]); + + // The drag has activated: the live region reflects the ongoing drag. + await waitFor(() => expect(status).toHaveTextContent("General")); + + await user.pointer({ keys: "[/MouseLeft]" }); // release to drop + + await waitFor(() => expect(Sections.args.changeRoomSection).toHaveBeenCalled()); + }); + }); + describe("scrollToSectionTag", () => { it("skips scroll when scrollToSectionTag does not match any section", () => { const roomListState = { diff --git a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx index 927f87fd6e..13ae53f6bd 100644 --- a/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx +++ b/packages/shared-components/src/room-list/VirtualizedRoomListView/VirtualizedRoomListView.tsx @@ -600,10 +600,17 @@ export function VirtualizedRoomListView({ vm, renderAvatar, onKeyDown }: Virtual } }} sensors={[ - // By default, the PointerSensor activates dragging immediately on pointer down, which interferes with keyboard navigation. - // So we start dragging after the pointer has moved by 5 pixels, to allow for click without dragging + // By default, PointerSensor activates dragging immediately on mouse pointer down, which interferes + // with clicking a room and keyboard navigation, so for mouse/pen we require a small drag distance + // before a drag starts (allowing a plain click without dragging). + // For touch, a Delay constraint that aborts the drag if the finger moves before the delay elapses is used to avoid accidental drags when scrolling the list with a finger. PointerSensor.configure({ - activationConstraints: [new PointerActivationConstraints.Distance({ value: 5 })], + activationConstraints(event) { + if (event.pointerType === "touch") { + return [new PointerActivationConstraints.Delay({ value: 250, tolerance: 5 })]; + } + return [new PointerActivationConstraints.Distance({ value: 5 })]; + }, }), // By default, the KeyboardSensor uses both space and enter to start dragging, which interferes with the keyboard enter shortcut to open a room. KeyboardSensor.configure({