Room list: fix scrolling with touch device (#34381)

* Fix touchscreen scroll room list

* Add unit tests
This commit is contained in:
Florian Duros
2026-07-24 10:18:59 +00:00
committed by GitHub
parent 2ae68b8b20
commit 8337e16f9f
2 changed files with 58 additions and 3 deletions
@@ -197,6 +197,54 @@ describe("<VirtualizedRoomListView />", () => {
});
});
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(<Sections />);
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(<Sections />);
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 = {
@@ -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({