Skip to content
Open
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
285 changes: 285 additions & 0 deletions packages/main/cypress/specs/List.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,291 @@ describe("List Tests", () => {
cy.get("[ui5-li-custom]").first().should("be.focused");
});

it("keyboard handling on F7", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>First</Button>
<Button>Second</Button>
</ListItemCustom>
</List>
);

cy.get("[ui5-li-custom]").realClick();
cy.get("[ui5-li-custom]").should("be.focused");

// F7 goes to first focusable element
cy.realPress("F7");
cy.get("[ui5-button]").first().should("be.focused");

// Tab to second button
cy.realPress("Tab");
cy.get("[ui5-button]").last().should("be.focused");

// F7 returns to list item
cy.realPress("F7");
cy.get("[ui5-li-custom]").should("be.focused");

// F7 remembers last focused element (second button)
cy.realPress("F7");
cy.get("[ui5-button]").last().should("be.focused");
});

it("keyboard handling on F7 after TAB navigation", () => {
cy.mount(
<div>
<button>Before</button>
<List>
<ListItemCustom>
<Button>First</Button>
<Button>Second</Button>
</ListItemCustom>
</List>
</div>
);

cy.get("button").realClick();
cy.get("button").should("be.focused");

// Tab into list item
cy.realPress("Tab");
cy.get("[ui5-li-custom]").should("be.focused");

// Tab into internal elements (goes to first button)
cy.realPress("Tab");
cy.get("[ui5-button]").first().should("be.focused");

// Tab to second button
cy.realPress("Tab");
cy.get("[ui5-button]").last().should("be.focused");

// F7 should store current element and return to list item
cy.realPress("F7");
cy.get("[ui5-li-custom]").should("be.focused");

// F7 should remember the second button (not go to first)
cy.realPress("F7");
cy.get("[ui5-button]").last().should("be.focused");
});

it("keyboard handling on F7 maintains focus position across list items", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Item 1 - First</Button>
<Button>Item 1 - Second</Button>
<Button>Item 1 - Third</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 2 - First</Button>
<Button>Item 2 - Second</Button>
<Button>Item 2 - Third</Button>
</ListItemCustom>
</List>
);

// Focus first list item
cy.get("[ui5-li-custom]").first().realClick();
cy.get("[ui5-li-custom]").first().should("be.focused");

// F7 to enter (should go to first button)
cy.realPress("F7");
cy.get("[ui5-button]").eq(0).should("be.focused");

// Tab to second button
cy.realPress("Tab");
cy.get("[ui5-button]").eq(1).should("be.focused");

// F7 to exit back to list item
cy.realPress("F7");
cy.get("[ui5-li-custom]").first().should("be.focused");

// Navigate to second list item with ArrowDown
cy.realPress("ArrowDown");
cy.get("[ui5-li-custom]").last().should("be.focused");

// F7 should focus the second button (same index as previous item)
cy.realPress("F7");
cy.get("[ui5-button]").eq(4).should("be.focused").and("contain", "Item 2 - Second");
});

it("arrow down navigates to same-index element in next custom item", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Item 1 - First</Button>
<Button>Item 1 - Second</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 2 - First</Button>
<Button>Item 2 - Second</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 3 - First</Button>
<Button>Item 3 - Second</Button>
</ListItemCustom>
</List>
);

// Focus first button in first item
cy.get("[ui5-button]").first().realClick();
cy.get("[ui5-button]").first().should("be.focused");

// Arrow down should move to first button in second item
cy.realPress("ArrowDown");
cy.get("[ui5-button]").eq(2).should("be.focused").and("contain", "Item 2 - First");

// Arrow down again should move to first button in third item
cy.realPress("ArrowDown");
cy.get("[ui5-button]").eq(4).should("be.focused").and("contain", "Item 3 - First");
});

it("arrow up navigates to same-index element in previous custom item", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Item 1 - First</Button>
<Button>Item 1 - Second</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 2 - First</Button>
<Button>Item 2 - Second</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 3 - First</Button>
<Button>Item 3 - Second</Button>
</ListItemCustom>
</List>
);

// Focus second button in last item
cy.get("[ui5-button]").eq(5).realClick();
cy.get("[ui5-button]").eq(5).should("be.focused");

// Arrow up should move to second button in second item
cy.realPress("ArrowUp");
cy.get("[ui5-button]").eq(3).should("be.focused").and("contain", "Item 2 - Second");

// Arrow up again should move to second button in first item
cy.realPress("ArrowUp");
cy.get("[ui5-button]").eq(1).should("be.focused").and("contain", "Item 1 - Second");
});

it("arrow navigation skips standard list items", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Custom 1</Button>
</ListItemCustom>
<ListItemStandard>Standard Item</ListItemStandard>
<ListItemStandard>Another Standard</ListItemStandard>
<ListItemCustom>
<Button>Custom 2</Button>
</ListItemCustom>
</List>
);

// Focus button in first custom item
cy.get("[ui5-button]").first().realClick();
cy.get("[ui5-button]").first().should("be.focused");

// Arrow down should skip standard items and focus button in second custom item
cy.realPress("ArrowDown");
cy.get("[ui5-button]").last().should("be.focused").and("contain", "Custom 2");

// Arrow up should skip standard items and return to first custom item
cy.realPress("ArrowUp");
cy.get("[ui5-button]").first().should("be.focused").and("contain", "Custom 1");
});

it("arrow navigation works across groups", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Before Group</Button>
</ListItemCustom>
<ListItemGroup headerText="Group 1">
<ListItemCustom>
<Button>In Group 1</Button>
</ListItemCustom>
</ListItemGroup>
<ListItemGroup headerText="Group 2">
<ListItemCustom>
<Button>In Group 2</Button>
</ListItemCustom>
</ListItemGroup>
<ListItemCustom>
<Button>After Group</Button>
</ListItemCustom>
</List>
);

// Focus button before groups
cy.get("[ui5-button]").first().realClick();

// Navigate down through groups
cy.realPress("ArrowDown");
cy.get("[ui5-button]").eq(1).should("be.focused").and("contain", "In Group 1");

cy.realPress("ArrowDown");
cy.get("[ui5-button]").eq(2).should("be.focused").and("contain", "In Group 2");

cy.realPress("ArrowDown");
cy.get("[ui5-button]").last().should("be.focused").and("contain", "After Group");
});

it("arrow navigation handles items with different element counts", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>Item 1 - A</Button>
<Button>Item 1 - B</Button>
<Button>Item 1 - C</Button>
<Button>Item 1 - D</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Item 2 - A</Button>
<Button>Item 2 - B</Button>
</ListItemCustom>
</List>
);

// Focus fourth button (index 3) in first item
cy.get("[ui5-button]").eq(3).realClick();
cy.get("[ui5-button]").eq(3).should("be.focused");

// Arrow down should focus last button in second item (index clamped to 1)
cy.realPress("ArrowDown");
cy.get("[ui5-button]").eq(5).should("be.focused").and("contain", "Item 2 - B");
});

it("arrow navigation does nothing at list boundaries", () => {
cy.mount(
<List>
<ListItemCustom>
<Button>First Item</Button>
</ListItemCustom>
<ListItemCustom>
<Button>Last Item</Button>
</ListItemCustom>
</List>
);

// Focus first button
cy.get("[ui5-button]").first().realClick();

// Arrow up should do nothing (at top boundary)
cy.realPress("ArrowUp");
cy.get("[ui5-button]").first().should("be.focused");

// Focus last button
cy.get("[ui5-button]").last().realClick();

// Arrow down should do nothing (at bottom boundary)
cy.realPress("ArrowDown");
cy.get("[ui5-button]").last().should("be.focused");
});

it("keyboard handling on TAB when 2 level nested UI5Element is focused", () => {
cy.mount(
<div>
Expand Down
12 changes: 8 additions & 4 deletions packages/main/src/List.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ class List extends UI5Element {
_beforeElement?: HTMLElement | null;
_afterElement?: HTMLElement | null;
_startMarkerOutOfView: boolean = false;
_lastFocusedElementIndex?: number;

handleResizeCallback: ResizeObserverCallback;
onItemFocusedBound: (e: CustomEvent) => void;
Expand Down Expand Up @@ -988,8 +989,9 @@ class List extends UI5Element {
}

if (isDown(e)) {
this._handleDown();
e.preventDefault();
if (this._handleDown()) {
e.preventDefault();
}
return;
}

Expand Down Expand Up @@ -1168,10 +1170,10 @@ class List extends UI5Element {

_handleDown() {
if (!this.growsWithButton) {
return;
return false;
}

this._shouldFocusGrowingButton();
return this._shouldFocusGrowingButton();
}

_onfocusin(e: FocusEvent) {
Expand Down Expand Up @@ -1350,7 +1352,9 @@ class List extends UI5Element {

if (currentIndex !== -1 && currentIndex === lastIndex) {
this.focusGrowingButton();
return true;
}
return false;
}

getGrowingButton() {
Expand Down
Loading
Loading