Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions src/components/utils/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,28 +184,32 @@ export function ListView<Item, Context = any>(props: IListViewProps<Item, Contex

// Guard against null/undefined events and modified keys which we don't want to handle here but do
// at the settings level shortcuts(E.g. Select next room, etc )
if (e || !isModifiedKeyEvent(e)) {
if (e.code === Key.ARROW_UP && currentIndex !== undefined) {
scrollToItem(currentIndex - 1, false);
handled = true;
} else if (e.code === Key.ARROW_DOWN && currentIndex !== undefined) {
scrollToItem(currentIndex + 1, true);
handled = true;
} else if (e.code === Key.HOME) {
scrollToIndex(0);
handled = true;
} else if (e.code === Key.END) {
scrollToIndex(items.length - 1);
handled = true;
} else if (e.code === Key.PAGE_DOWN && visibleRange && currentIndex !== undefined) {
const numberDisplayed = visibleRange.endIndex - visibleRange.startIndex;
scrollToItem(Math.min(currentIndex + numberDisplayed, items.length - 1), true, `start`);
handled = true;
} else if (e.code === Key.PAGE_UP && visibleRange && currentIndex !== undefined) {
const numberDisplayed = visibleRange.endIndex - visibleRange.startIndex;
scrollToItem(Math.max(currentIndex - numberDisplayed, 0), false, `start`);
handled = true;
}
// Guard against null/undefined events and modified keys
if (!e || isModifiedKeyEvent(e)) {
onKeyDown?.(e);
return;
}

if (e.code === Key.ARROW_UP && currentIndex !== undefined) {
scrollToItem(currentIndex - 1, false);
handled = true;
} else if (e.code === Key.ARROW_DOWN && currentIndex !== undefined) {
scrollToItem(currentIndex + 1, true);
handled = true;
} else if (e.code === Key.HOME) {
scrollToIndex(0);
handled = true;
} else if (e.code === Key.END) {
scrollToIndex(items.length - 1);
handled = true;
} else if (e.code === Key.PAGE_DOWN && visibleRange && currentIndex !== undefined) {
const numberDisplayed = visibleRange.endIndex - visibleRange.startIndex;
scrollToItem(Math.min(currentIndex + numberDisplayed, items.length - 1), true, `start`);
handled = true;
} else if (e.code === Key.PAGE_UP && visibleRange && currentIndex !== undefined) {
const numberDisplayed = visibleRange.endIndex - visibleRange.startIndex;
scrollToItem(Math.max(currentIndex - numberDisplayed, 0), false, `start`);
handled = true;
}

if (handled) {
Expand Down
47 changes: 47 additions & 0 deletions test/unit-tests/components/views/utils/ListView-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,53 @@ describe("ListView", () => {
expect(items[lastIndex]).toHaveAttribute("tabindex", "-1");
});

it("should not handle keyboard navigation when modifier keys are pressed", () => {
renderListViewWithHeight();
const container = screen.getByRole("grid");

fireEvent.focus(container);

// Store initial state - first item should be focused
const initialItems = container.querySelectorAll(".mx_item");
expect(initialItems[0]).toHaveAttribute("tabindex", "0");
expect(initialItems[2]).toHaveAttribute("tabindex", "-1");

// Test ArrowDown with Ctrl modifier - should NOT navigate
fireEvent.keyDown(container, { code: "ArrowDown", ctrlKey: true });

let items = container.querySelectorAll(".mx_item");
expect(items[0]).toHaveAttribute("tabindex", "0"); // Should still be on first item
expect(items[2]).toHaveAttribute("tabindex", "-1"); // Should not have moved to third item

// Test ArrowDown with Alt modifier - should NOT navigate
fireEvent.keyDown(container, { code: "ArrowDown", altKey: true });

items = container.querySelectorAll(".mx_item");
expect(items[0]).toHaveAttribute("tabindex", "0"); // Should still be on first item
expect(items[2]).toHaveAttribute("tabindex", "-1"); // Should not have moved to third item

// Test ArrowDown with Shift modifier - should NOT navigate
fireEvent.keyDown(container, { code: "ArrowDown", shiftKey: true });

items = container.querySelectorAll(".mx_item");
expect(items[0]).toHaveAttribute("tabindex", "0"); // Should still be on first item
expect(items[2]).toHaveAttribute("tabindex", "-1"); // Should not have moved to third item

// Test ArrowDown with Meta/Cmd modifier - should NOT navigate
fireEvent.keyDown(container, { code: "ArrowDown", metaKey: true });

items = container.querySelectorAll(".mx_item");
expect(items[0]).toHaveAttribute("tabindex", "0"); // Should still be on first item
expect(items[2]).toHaveAttribute("tabindex", "-1"); // Should not have moved to third item

// Test normal ArrowDown without modifiers - SHOULD navigate
fireEvent.keyDown(container, { code: "ArrowDown" });

items = container.querySelectorAll(".mx_item");
expect(items[0]).toHaveAttribute("tabindex", "-1"); // Should have moved from first item
expect(items[2]).toHaveAttribute("tabindex", "0"); // Should have moved to third item (skipping separator)
});

it("should skip non-focusable items when navigating down", async () => {
// Create items where every other item is not focusable
const mixedItems = [
Expand Down
Loading