diff --git a/packages/components/src/components/dropdown-select/dropdown-select.spec.ts b/packages/components/src/components/dropdown-select/dropdown-select.spec.ts index 201b20318e..7aa1bd97da 100644 --- a/packages/components/src/components/dropdown-select/dropdown-select.spec.ts +++ b/packages/components/src/components/dropdown-select/dropdown-select.spec.ts @@ -123,4 +123,65 @@ describe('DropdownSelect', function () { expect(changeSpy).not.toBeCalled(); }); }); + + describe('focus outline (issue #2428)', () => { + it('should NOT have steal-focus part when a non-first item is initially selected and dropdown is closed', async () => { + const page = await newSpecPage({ + components: [DropdownSelect], + html: ` + + Caspar + Cedric + Cem + `, + }); + + const selectEl = page.doc.querySelector('scale-dropdown-select'); + const baseEl = selectEl.shadowRoot.querySelector( + '[part~="select"]' + ) as HTMLElement; + + // The part attribute must NOT contain "steal-focus" while dropdown is closed, + // regardless of which item is initially selected. + expect(baseEl.getAttribute('part')).not.toContain('steal-focus'); + }); + + it('should NOT have steal-focus part when the last item is initially selected and dropdown is closed', async () => { + const page = await newSpecPage({ + components: [DropdownSelect], + html: ` + + Caspar + Cedric + Cem + `, + }); + + const selectEl = page.doc.querySelector('scale-dropdown-select'); + const baseEl = selectEl.shadowRoot.querySelector( + '[part~="select"]' + ) as HTMLElement; + + expect(baseEl.getAttribute('part')).not.toContain('steal-focus'); + }); + + it('should NOT have steal-focus part when first item is initially selected and dropdown is closed', async () => { + const page = await newSpecPage({ + components: [DropdownSelect], + html: ` + + Caspar + Cedric + Cem + `, + }); + + const selectEl = page.doc.querySelector('scale-dropdown-select'); + const baseEl = selectEl.shadowRoot.querySelector( + '[part~="select"]' + ) as HTMLElement; + + expect(baseEl.getAttribute('part')).not.toContain('steal-focus'); + }); + }); }); diff --git a/packages/components/src/components/dropdown-select/dropdown-select.tsx b/packages/components/src/components/dropdown-select/dropdown-select.tsx index 463b79c411..8e5285cf92 100644 --- a/packages/components/src/components/dropdown-select/dropdown-select.tsx +++ b/packages/components/src/components/dropdown-select/dropdown-select.tsx @@ -279,17 +279,24 @@ export class DropdownSelect { @Watch('value') valueChange(newValue) { - this.currentIndex = readOptions(this.hostElement).findIndex( - ({ value }) => value === newValue - ); + // Do not set currentIndex while the dropdown is closed. Doing so would add + // the `steal-focus` CSS part and suppress the focus outline. + // However, when the dropdown is already open, controlled/programmatic value + // updates must keep currentIndex in sync so the highlighted option and + // aria-activedescendant stay aligned with the selected value. + if (this.open && Array.isArray(this.options)) { + this.currentIndex = this.options.findIndex( + (option) => option.value === newValue + ); + } + this.updateInputHidden(newValue); } connectedCallback() { - this.currentIndex = - readOptions(this.hostElement).findIndex( - ({ value }) => value === this.value - ) || -1; + // currentIndex intentionally starts at -1 (the @State() default). + // The dropdown is closed on init, so steal-focus must not be applied. + // currentIndex is set to the selected item's index when the dropdown opens. } componentDidRender() { @@ -376,6 +383,12 @@ export class DropdownSelect { this.comboEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); this.comboEl.focus(); this.currentIndex = -1; + } else { + // Initialize currentIndex to the currently selected item so that keyboard + // navigation starts from the right position and the item is highlighted. + this.currentIndex = readOptions(this.hostElement).findIndex( + ({ value }) => value === this.value + ); } } diff --git a/packages/visual-tests/setup.js b/packages/visual-tests/setup.js index 997ca763c9..a9cb337136 100644 --- a/packages/visual-tests/setup.js +++ b/packages/visual-tests/setup.js @@ -22,7 +22,16 @@ module.exports = async (jestConfig) => { res.sendFile('index.html'); }); - global.__SERVER__ = app.listen(3123); + await new Promise((resolve, reject) => { + const server = app.listen(3123, '0.0.0.0', () => { + console.log( + '\nScale Visual Tests: Storybook server listening on 0.0.0.0:3123' + ); + resolve(); + }); + server.on('error', reject); + global.__SERVER__ = server; + }); await setupPuppeteer(jestConfig); }; diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-1-snap.png new file mode 100644 index 0000000000..bc47410eaa Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-2-snap.png new file mode 100644 index 0000000000..bc47410eaa Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-disabled-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-1-snap.png new file mode 100644 index 0000000000..579ef253a9 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-2-snap.png new file mode 100644 index 0000000000..ed314ade3e Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-error-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-1-snap.png new file mode 100644 index 0000000000..504185ab82 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-2-snap.png new file mode 100644 index 0000000000..28ff4d1abf Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-3-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-3-snap.png new file mode 100644 index 0000000000..ad67a39761 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-3-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-4-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-4-snap.png new file mode 100644 index 0000000000..200e9421de Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-dark-standard-4-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-1-snap.png new file mode 100644 index 0000000000..3e713c34e7 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-2-snap.png new file mode 100644 index 0000000000..3e713c34e7 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-disabled-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-1-snap.png new file mode 100644 index 0000000000..300b29419b Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-2-snap.png new file mode 100644 index 0000000000..125a34d2ce Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-error-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-1-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-1-snap.png new file mode 100644 index 0000000000..5a859df5aa Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-1-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-2-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-2-snap.png new file mode 100644 index 0000000000..12eb4e044c Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-2-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-3-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-3-snap.png new file mode 100644 index 0000000000..593d236e96 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-3-snap.png differ diff --git a/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-4-snap.png b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-4-snap.png new file mode 100644 index 0000000000..9b4d0ab265 Binary files /dev/null and b/packages/visual-tests/src/__image_snapshots__/dropdown-select-visual-spec-js-dropdown-select-light-standard-4-snap.png differ diff --git a/packages/visual-tests/src/dropdown-select.visual.spec.js b/packages/visual-tests/src/dropdown-select.visual.spec.js index 55e8cbe586..be1ececdaa 100644 --- a/packages/visual-tests/src/dropdown-select.visual.spec.js +++ b/packages/visual-tests/src/dropdown-select.visual.spec.js @@ -1,4 +1,4 @@ -describe.skip('DropdownSelect', () => { +describe('DropdownSelect', () => { describe.each(['light', 'dark'])('%p', (mode) => { beforeAll(async () => { await global.runColorSetup('components-dropdown-select--standard', mode);