From dee01269ada7374ecf489729080a2808bc032d0f Mon Sep 17 00:00:00 2001 From: Konnor Rogers Date: Tue, 14 Nov 2023 12:22:18 -0500 Subject: [PATCH] Konnorrogers/backport 1711 and 1714 (#11) * backport #1711 & #1714 * remove custom-elements.mjs * prettier --- docs/pages/resources/changelog.md | 1 + scripts/make-react.js | 4 +-- src/internal/modal.ts | 3 +- src/internal/tabbable.test.ts | 51 ++++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index f692203ab..d331a5f9e 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -23,6 +23,7 @@ New versions of Web Awesome are released as-needed and generally occur when a cr ## Next - Added the ability to call `form.checkValidity()` and it will use Shoelace's custom `checkValidity()` handler. [#1708] +- Fixed a bug where nested dialogs were not properly trapping focus. [#1711] - Fixed a bug with form controls removing the custom validity handlers from the form. [#1708] - Fixed a bug in form control components that used a `form` property, but not an attribute. [#1707] - Fixed a bug with bundled components using CDN builds not having translations on initial connect [#1696] diff --git a/scripts/make-react.js b/scripts/make-react.js index fe616bea8..f96ad9221 100644 --- a/scripts/make-react.js +++ b/scripts/make-react.js @@ -25,10 +25,10 @@ for await (const component of components) { const componentFile = path.join(componentDir, 'index.ts'); const importPath = component.path.replace(/\.js$/, '.component.js'); const eventImports = (component.events || []) - .map(event => `import type { ${event.eventName} } from '../../../src/events/events';`) + .map(event => `import type { ${event.eventName} } from '../../events/events';`) .join('\n'); const eventExports = (component.events || []) - .map(event => `export type { ${event.eventName} } from '../../../src/events/events';`) + .map(event => `export type { ${event.eventName} } from '../../events/events';`) .join('\n'); const eventNameImport = (component.events || []).length > 0 ? `import { type EventName } from '@lit/react';` : ``; const events = (component.events || []) diff --git a/src/internal/modal.ts b/src/internal/modal.ts index 9c507d902..19124e48e 100644 --- a/src/internal/modal.ts +++ b/src/internal/modal.ts @@ -61,13 +61,14 @@ export default class Modal { } } } - private handleFocusIn = () => { + if (!this.isActive()) return; this.checkFocus(); }; private handleKeyDown = (event: KeyboardEvent) => { if (event.key !== 'Tab' || this.isExternalActivated) return; + if (!this.isActive()) return; if (event.shiftKey) { this.tabDirection = 'backward'; diff --git a/src/internal/tabbable.test.ts b/src/internal/tabbable.test.ts index 63694db6b..8466efde9 100644 --- a/src/internal/tabbable.test.ts +++ b/src/internal/tabbable.test.ts @@ -1,9 +1,12 @@ -import { elementUpdated, expect, fixture } from '@open-wc/testing'; +import { aTimeout, elementUpdated, expect, fixture } from '@open-wc/testing'; -import '../../dist/webawesome.js'; import { activeElements, getDeepestActiveElement } from './active-elements.js'; +import { clickOnElement } from './test.js'; import { html } from 'lit'; import { sendKeys } from '@web/test-runner-commands'; +import type { WaDialog } from '../webawesome.js'; + +import '../../../dist/webawesome.js'; async function holdShiftKey(callback: () => Promise) { await sendKeys({ down: 'Shift' }); @@ -50,8 +53,8 @@ it('Should allow tabbing to slotted elements', async () => {
Focus 3 - +
@@ -174,3 +177,43 @@ it('Should account for when focus is changed from outside sources (like clicking await holdShiftKey(async () => await sendKeys({ press: tabKey })); expect(activeElementsArray()).to.include(closeButton); }); + +// https://github.com/shoelace-style/shoelace/issues/1710 +it('Should respect nested modal instances', async () => { + const dialogOne = (): WaDialog => document.querySelector('#dialog-1')!; + const dialogTwo = (): WaDialog => document.querySelector('#dialog-2')!; + + // lit-a11y doesn't like the "autofocus" attribute. + /* eslint-disable */ + await fixture(html` +
+ dialogOne().show()}> + + dialogTwo().show()} id="open-dialog-2">Open Dialog 2 + Close + + + + + + Close + +
+ `); + /* eslint-enable */ + + const firstFocusedEl = document.querySelector('#focus-1'); + const secondFocusedEl = document.querySelector('#focus-2'); + + // So we can trigger auto-focus stuff + await clickOnElement(document.querySelector('#open-dialog-1')!); + // These clicks need a ~100ms timeout. I'm assuming for animation reasons? + await aTimeout(100); + await clickOnElement(document.querySelector('#open-dialog-2')!); + await aTimeout(100); + + expect(activeElementsArray()).to.include(firstFocusedEl); + + await sendKeys({ press: tabKey }); + expect(activeElementsArray()).to.include(secondFocusedEl); +});