diff --git a/.changeset/wise-mirrors-grab.md b/.changeset/wise-mirrors-grab.md new file mode 100644 index 000000000..f0964ebf1 --- /dev/null +++ b/.changeset/wise-mirrors-grab.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +make sure that voiceover + safari modals are accessible diff --git a/packages/ui/components/overlays/src/OverlayController.js b/packages/ui/components/overlays/src/OverlayController.js index fa834fc3e..c64e1baff 100644 --- a/packages/ui/components/overlays/src/OverlayController.js +++ b/packages/ui/components/overlays/src/OverlayController.js @@ -1121,6 +1121,15 @@ export class OverlayController extends EventTarget { if (this.manager) { this.manager.disableTrapsKeyboardFocusForAll(); } + + const isContentShadowHost = Boolean(this.contentNode.shadowRoot); + if (isContentShadowHost) { + // eslint-disable-next-line no-console + console.warn( + '[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root', + ); + } + this._containFocusHandler = containFocus(this.contentNode); this.__hasActiveTrapsKeyboardFocus = true; if (this.manager) { diff --git a/packages/ui/components/overlays/test/OverlayController.test.js b/packages/ui/components/overlays/test/OverlayController.test.js index 7b8640843..025ce71f5 100644 --- a/packages/ui/components/overlays/test/OverlayController.test.js +++ b/packages/ui/components/overlays/test/OverlayController.test.js @@ -603,8 +603,8 @@ describe('OverlayController', () => { event.keyCode = keyCodes.tab; window.dispatchEvent(event); - expect(elOutside).to.not.equal(document.activeElement); - expect(input1).to.equal(document.activeElement); + expect(isActiveElement(elOutside)).to.be.false; + expect(isActiveElement(input1)).to.be.true; }); it('allows to move the focus outside of the overlay if trapsKeyboardFocus is disabled', async () => { @@ -625,7 +625,7 @@ describe('OverlayController', () => { input.focus(); simulateTab(); - expect(elOutside).to.equal(document.activeElement); + expect(isActiveElement(elOutside)).to.be.true; }); it('keeps focus within overlay with multiple overlays with all traps on true', async () => { @@ -648,6 +648,30 @@ describe('OverlayController', () => { expect(ctrl0.hasActiveTrapsKeyboardFocus).to.be.true; expect(ctrl1.hasActiveTrapsKeyboardFocus).to.be.false; }); + + it('warns when contentNode is a host for a shadowRoot', async () => { + const warnSpy = sinon.spy(console, 'warn'); + + const contentNode = /** @type {HTMLDivElement} */ (await fixture(html`
`)); + contentNode.attachShadow({ mode: 'open' }); + const shadowRootContentNode = /** @type {HTMLElement} */ ( + await fixture(html`
`) + ); + contentNode.shadowRoot?.appendChild(shadowRootContentNode); + + const ctrl = new OverlayController({ + ...withGlobalTestConfig(), + trapsKeyboardFocus: true, + contentNode, + }); + await ctrl.show(); + + // @ts-expect-error + expect(console.warn.args[0][0]).to.equal( + '[overlays]: For best accessibility (compatibility with Safari + VoiceOver), provide a contentNode that is not a host for a shadow root', + ); + warnSpy.restore(); + }); }); describe('hidesOnEsc', () => {