From 9167a92dce3ea88ecfe4d06855343020d9a8538f Mon Sep 17 00:00:00 2001 From: varun-srinivasa Date: Thu, 6 Aug 2026 04:42:49 +0000 Subject: [PATCH 1/2] Prevent focus in collapsed card content --- .changeset/friendly-card-list-focus.md | 5 +++ .../src/components/card-list/card-list.tsx | 29 ++++++++++++ .../components/card-list/test/card-list.ct.ts | 44 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 .changeset/friendly-card-list-focus.md diff --git a/.changeset/friendly-card-list-focus.md b/.changeset/friendly-card-list-focus.md new file mode 100644 index 00000000000..98604e2f1be --- /dev/null +++ b/.changeset/friendly-card-list-focus.md @@ -0,0 +1,5 @@ +--- +'@siemens/ix': patch +--- + +Prevent focus from remaining inside collapsed card lists. \ No newline at end of file diff --git a/packages/core/src/components/card-list/card-list.tsx b/packages/core/src/components/card-list/card-list.tsx index 9bd1f40f0b6..0c6caf4d814 100644 --- a/packages/core/src/components/card-list/card-list.tsx +++ b/packages/core/src/components/card-list/card-list.tsx @@ -9,6 +9,7 @@ import { Listen, Prop, State, + Watch, } from '@stencil/core'; import { createMutationObserver } from '../utils/mutation-observer'; import { iconChevronUp, iconMoreMenu } from '@siemens/ix-icons/icons'; @@ -25,6 +26,7 @@ function CardListTitle(props: { labelShowLess: string; showLess: boolean; hideShowAll: boolean; + collapseButtonRef: (element?: HTMLIxIconButtonElement) => void; }) { if (!props.label) { return null; @@ -42,6 +44,7 @@ function CardListTitle(props: { CardList__Title__Button__Collapsed: props.isCollapsed, }} aria-label={props.ariaLabelExpandButton} + ref={props.collapseButtonRef} > {props.label} @@ -178,11 +181,34 @@ export class CardList { private observer?: MutationObserver; + private collapseButton?: HTMLIxIconButtonElement; + private onCardListVisibilityToggle() { this.collapse = !this.collapse; this.collapseChanged.emit(this.collapse); } + @Watch('collapse') + protected handleCollapseChange(isCollapsed: boolean) { + if (isCollapsed && this.hasFocusWithinListContent()) { + this.collapseButton?.focus(); + } + } + + private hasFocusWithinListContent() { + const activeElement = document.activeElement; + + if (!activeElement) { + return false; + } + + return this.getListChildren().some( + (child) => + child === activeElement || + (child instanceof HTMLElement && child.contains(activeElement)) + ); + } + private handleClick(emitter: EventEmitter, event: MouseEvent) { const { defaultPrevented } = emitter.emit({ nativeEvent: event, @@ -337,6 +363,7 @@ export class CardList { this.onCardListVisibilityToggle()} onShowAllClick={(e) => this.onShowAllClick(e)} hideShowAll={this.hideShowAll} + collapseButtonRef={(element) => (this.collapseButton = element)} >
this.onCardListScroll()} + inert={this.collapse} > { diff --git a/packages/core/src/components/card-list/test/card-list.ct.ts b/packages/core/src/components/card-list/test/card-list.ct.ts index 586163bca94..672a5ff5473 100644 --- a/packages/core/src/components/card-list/test/card-list.ct.ts +++ b/packages/core/src/components/card-list/test/card-list.ct.ts @@ -18,6 +18,50 @@ const CARDS_HTML = ` Card 5 `; +regressionTest.only( + 'prevents focus from remaining in collapsed card content', + async ({ mount, page }) => { + await mount(` + + + + + + + + + + `); + + const cardList = page.locator('ix-card-list'); + const collapseButton = cardList.getByRole('button', { + name: 'Toggle card list', + }); + const cardAction = page.locator('#card-action'); + const content = cardList.locator('.CardList__Content'); + const after = page.locator('#after'); + + await expect(cardList).toHaveClass(/\bhydrated\b/); + + await cardAction.focus(); + await expect(cardAction).toBeFocused(); + + await cardList.evaluate((element: HTMLIxCardListElement) => { + element.collapse = true; + }); + + await expect(collapseButton).toBeFocused(); + await expect(content).toHaveJSProperty('inert', true); + + await page.keyboard.press('Tab'); + await expect(after).toBeFocused(); + } +); + regressionTest( 'show all button reveals all hidden cards', async ({ mount, page }) => { From 45644fae2d46eae3bf1fdf3ecd5d88a225b5325b Mon Sep 17 00:00:00 2001 From: varun-srinivasa Date: Fri, 7 Aug 2026 06:26:22 +0000 Subject: [PATCH 2/2] updated failing test case --- packages/core/src/components/card-list/test/card-list.ct.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/card-list/test/card-list.ct.ts b/packages/core/src/components/card-list/test/card-list.ct.ts index 672a5ff5473..3cbfe20da86 100644 --- a/packages/core/src/components/card-list/test/card-list.ct.ts +++ b/packages/core/src/components/card-list/test/card-list.ct.ts @@ -18,7 +18,7 @@ const CARDS_HTML = ` Card 5 `; -regressionTest.only( +regressionTest( 'prevents focus from remaining in collapsed card content', async ({ mount, page }) => { await mount(`