diff --git a/packages/calcite-components/src/components/list-item/list-item.tsx b/packages/calcite-components/src/components/list-item/list-item.tsx
index 961b39793f7..49afdd5ed6c 100644
--- a/packages/calcite-components/src/components/list-item/list-item.tsx
+++ b/packages/calcite-components/src/components/list-item/list-item.tsx
@@ -12,7 +12,12 @@ import {
VNode,
Watch,
} from "@stencil/core";
-import { getElementDir, slotChangeHasAssignedElement, toAriaBoolean } from "../../utils/dom";
+import {
+ getElementDir,
+ getFirstTabbable,
+ slotChangeHasAssignedElement,
+ toAriaBoolean,
+} from "../../utils/dom";
import {
connectInteractive,
disconnectInteractive,
@@ -344,7 +349,7 @@ export class ListItem
const focusIndex = focusMap.get(parentListEl);
if (typeof focusIndex === "number") {
- const cells = [actionsStartEl, contentEl, actionsEndEl].filter(Boolean);
+ const cells = [actionsStartEl, contentEl, actionsEndEl].filter((el) => el && !el.hidden);
if (cells[focusIndex]) {
this.focusCell(cells[focusIndex]);
} else {
@@ -737,7 +742,7 @@ export class ListItem
const composedPath = event.composedPath();
const { containerEl, contentEl, actionsStartEl, actionsEndEl, open, openable } = this;
- const cells = [actionsStartEl, contentEl, actionsEndEl].filter(Boolean);
+ const cells = [actionsStartEl, contentEl, actionsEndEl].filter((el) => el && !el.hidden);
const currentIndex = cells.findIndex((cell) => composedPath.includes(cell));
if (
@@ -790,16 +795,20 @@ export class ListItem
focusMap.set(parentListEl, null);
}
- [actionsStartEl, contentEl, actionsEndEl].filter(Boolean).forEach((tableCell, cellIndex) => {
- const tabIndexAttr = "tabindex";
- if (tableCell === focusEl) {
- tableCell.setAttribute(tabIndexAttr, "0");
- saveFocusIndex && focusMap.set(parentListEl, cellIndex);
- } else {
- tableCell.removeAttribute(tabIndexAttr);
- }
- });
+ const focusedEl = getFirstTabbable(focusEl);
+
+ [actionsStartEl, contentEl, actionsEndEl]
+ .filter((el) => el && !el.hidden)
+ .forEach((tableCell, cellIndex) => {
+ const tabIndexAttr = "tabindex";
+ if (tableCell === focusEl) {
+ focusEl === focusedEl && tableCell.setAttribute(tabIndexAttr, "0");
+ saveFocusIndex && focusMap.set(parentListEl, cellIndex);
+ } else {
+ tableCell.removeAttribute(tabIndexAttr);
+ }
+ });
- focusEl?.focus();
+ focusedEl?.focus();
};
}
diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts
index d875c5bd247..2a581e52dfc 100755
--- a/packages/calcite-components/src/components/list/list.e2e.ts
+++ b/packages/calcite-components/src/components/list/list.e2e.ts
@@ -504,6 +504,70 @@ describe("calcite-list", () => {
expect(await isElementFocused(page, "calcite-filter", { shadowed: true })).toBe(true);
});
+
+ it("should navigate via ArrowRight and ArrowLeft", async () => {
+ const page = await newE2EPage();
+ await page.setContent(html`
+
+
+
+
+
+
+
+
+
+ `);
+ await page.waitForChanges();
+ const list = await page.find("calcite-list");
+ await list.callMethod("setFocus");
+ await page.waitForChanges();
+
+ const one = await page.find("#one");
+ expect(await one.getProperty("open")).toBe(false);
+
+ expect(await isElementFocused(page, "#one")).toBe(true);
+
+ await list.press("ArrowRight");
+
+ expect(await isElementFocused(page, "#one")).toBe(true);
+ expect(await one.getProperty("open")).toBe(true);
+
+ await list.press("ArrowRight");
+
+ expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
+
+ await list.press("ArrowRight");
+
+ expect(await isElementFocused(page, "calcite-action")).toBe(true);
+
+ await list.press("ArrowLeft");
+
+ expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
+
+ await list.press("ArrowLeft");
+
+ expect(await isElementFocused(page, "#one")).toBe(true);
+ expect(await one.getProperty("open")).toBe(true);
+
+ await list.press("ArrowLeft");
+
+ expect(await isElementFocused(page, "#one")).toBe(true);
+ expect(await one.getProperty("open")).toBe(false);
+ });
});
describe("drag and drop", () => {
diff --git a/packages/calcite-components/src/utils/dom.ts b/packages/calcite-components/src/utils/dom.ts
index c6e6e987eba..d6ab2dec7e6 100644
--- a/packages/calcite-components/src/utils/dom.ts
+++ b/packages/calcite-components/src/utils/dom.ts
@@ -285,16 +285,26 @@ export async function focusElement(el: FocusableElement): Promise {
}
/**
- * Helper to focus the first tabbable element.
+ * Helper to get the first tabbable element.
*
* @param {HTMLElement} element The html element containing tabbable elements.
+ * @returns the first tabbable element.
*/
-export function focusFirstTabbable(element: HTMLElement): void {
+export function getFirstTabbable(element: HTMLElement): HTMLElement {
if (!element) {
return;
}
- (tabbable(element, tabbableOptions)[0] || element).focus();
+ return (tabbable(element, tabbableOptions)[0] ?? element) as HTMLElement;
+}
+
+/**
+ * Helper to focus the first tabbable element.
+ *
+ * @param {HTMLElement} element The html element containing tabbable elements.
+ */
+export function focusFirstTabbable(element: HTMLElement): void {
+ getFirstTabbable(element)?.focus();
}
interface GetSlottedOptions {