diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fac3fc02..225d511d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `loadLayout` now copies only the serialized tile properties: the spans, the positions, the flags and `id`. A layout from storage or a server can no longer set `innerHTML` on a tile or replace its prototype through `__proto__`. A value that is not an array, and an entry that is not an object, are ignored. ### Fixed +- #### Accordion + - The arrow keys, `Home` and `End` now skip a panel that does not render, such as a panel with the `hidden` attribute or `display: none`. Before, the focus stayed on the current panel. - #### QR code - Versions 30 to 40 at the `M` error correction level now use the data codeword counts of ISO/IEC 18004. Before, these codes had the wrong block structure. - Without `error-level`, a logo larger than the safe area of level `M` now raises the error correction level to the smallest one that holds the logo, as documented. Before, the default `M` always applied, so the logo shrank. An explicit `error-level`, `M` included, still caps the logo. diff --git a/src/components/accordion/accordion.spec.ts b/src/components/accordion/accordion.spec.ts index eec935c60..2e0ec80be 100644 --- a/src/components/accordion/accordion.spec.ts +++ b/src/components/accordion/accordion.spec.ts @@ -150,6 +150,57 @@ describe('Accordion', () => { expect(panels[2].matches(':focus')).to.be.true; }); + it('should skip a hidden panel on ArrowDown keypress', async () => { + panels[1].hidden = true; + + simulateKeyboard(panels[0], arrowDown); + await elementUpdated(accordion); + + expect(panels[2].matches(':focus')).to.be.true; + }); + + it('should skip a hidden panel on ArrowUp keypress', async () => { + panels[1].hidden = true; + + simulateKeyboard(panels[2], arrowUp); + await elementUpdated(accordion); + + expect(panels[0].matches(':focus')).to.be.true; + }); + + it('should skip a panel that its styles do not render', async () => { + panels[1].style.display = 'none'; + + simulateKeyboard(panels[0], arrowDown); + await elementUpdated(accordion); + + expect(panels[2].matches(':focus')).to.be.true; + }); + + it('should navigate to the first rendered panel on Home keypress', async () => { + panels[0].hidden = true; + + simulateClick(getPanelHeader(panels[2])); + await elementUpdated(accordion); + + simulateKeyboard(panels[2], homeKey); + await elementUpdated(accordion); + + expect(panels[1].matches(':focus')).to.be.true; + }); + + it('should navigate to the last rendered panel on End keypress', async () => { + panels[2].hidden = true; + + simulateClick(getPanelHeader(panels[0])); + await elementUpdated(accordion); + + simulateKeyboard(panels[0], endKey); + await elementUpdated(accordion); + + expect(panels[1].matches(':focus')).to.be.true; + }); + it('should navigate to the panel below on ArrowDown keypress', async () => { for (let i = 0; i < panels.length; i++) { simulateKeyboard(panels[i], arrowDown); diff --git a/src/components/accordion/accordion.ts b/src/components/accordion/accordion.ts index f78336986..5f4a56e14 100644 --- a/src/components/accordion/accordion.ts +++ b/src/components/accordion/accordion.ts @@ -47,6 +47,17 @@ export default class IgcAccordionComponent extends LitElement { return this._panels.filter((panel) => !panel.disabled); } + /** + * The interactive panels that the keyboard navigation can focus. A panel + * that is not rendered, for example with the `hidden` attribute, cannot + * take focus, so the navigation would stop at it. + */ + private get _navigablePanels(): IgcExpansionPanelComponent[] { + return this._interactivePanels.filter((panel) => + this._getPanelHeader(panel)?.checkVisibility({ visibilityProperty: true }) + ); + } + //#endregion //#region Public attributes and properties @@ -115,11 +126,11 @@ export default class IgcAccordionComponent extends LitElement { } private _navigateToFirst(): void { - this._getPanelHeader(firstOf(this._interactivePanels))?.focus(); + this._getPanelHeader(firstOf(this._navigablePanels))?.focus(); } private _navigateToLast(): void { - this._getPanelHeader(lastOf(this._interactivePanels))?.focus(); + this._getPanelHeader(lastOf(this._navigablePanels))?.focus(); } private _navigateToPrevious(event: KeyboardEvent): void { @@ -177,7 +188,7 @@ export default class IgcAccordionComponent extends LitElement { panel: IgcExpansionPanelComponent, dir: 1 | -1 = 1 ): IgcExpansionPanelComponent { - const panels = this._interactivePanels; + const panels = this._navigablePanels; const idx = panels.indexOf(panel); return panels[idx + dir] || panel; diff --git a/src/components/accordion/spec.md b/src/components/accordion/spec.md index fa56ccc8d..1b540b278 100644 --- a/src/components/accordion/spec.md +++ b/src/components/accordion/spec.md @@ -36,9 +36,10 @@ ## Revision history -| Version | Date | Notes | -| ------: | ---------- | --------------------- | -| 1 | 2026-09-21 | Initial specification | +| Version | Date | Notes | +| ------: | ---------- | ---------------------------------------------- | +| 1 | 2026-09-21 | Initial specification | +| 2 | 2026-10-01 | The navigation skips panels that do not render | ## Overview @@ -175,7 +176,8 @@ The keys apply while a panel header inside the accordion has focus. | Shift + Alt + Arrow Down | Expands every panel, unless single expand is on. | | Shift + Alt + Arrow Up | Collapses every panel. | -Disabled panels are skipped by the navigation. +The navigation skips disabled panels, and panels that do not render, for example a panel with the `hidden` +attribute or `display: none`. ## API @@ -231,7 +233,8 @@ with `@open-wc/testing` fixtures and assertions. The groups below mirror the `de the last. 8. Alt with the arrow keys expands and collapses the focused panel. 9. Shift + Alt with the arrow keys expands and collapses every panel. -10. Disabled panels are skipped by the navigation. +10. The navigation skips disabled panels, and panels that do not render, with the arrow keys and with Home + and End. ### Nested @@ -242,6 +245,8 @@ with `@open-wc/testing` fixtures and assertions. The groups below mirror the `de - Only direct `igc-expansion-panel` children are members of the accordion. - The accordion has no expansion events of its own; the panels emit them. - The accordion does not persist which panels were expanded. +- The navigation skips a panel that does not render, but Shift + Alt with the arrow keys, + `showAll` and `hideAll` still expand and collapse it. ## Accessibility @@ -249,7 +254,8 @@ with `@open-wc/testing` fixtures and assertions. The groups below mirror the `de - The accordion is a container; the header and region semantics come from the individual [expansion panels](../expansion-panel/spec.md#aria-roles-and-properties). -- The panel headers form the navigable set, and disabled panels are announced as disabled and skipped. +- The panel headers form the navigable set. Disabled panels are announced as disabled and skipped, and panels that + do not render are skipped. ### Keyboard support