From 84173cdba16df6eaf2a96dd7ef1776600f9f10ce Mon Sep 17 00:00:00 2001 From: Danny Moerkerke Date: Thu, 9 Feb 2023 18:41:18 +0100 Subject: [PATCH] fix(@lion/ui): [select-rich] listbox element always focused in overlay when the overlay is shown, the "autofocus" attribute is added to _listboxNode (_inputNode) to make sure that keyboard navigation continues to work when the element is inside a bottomsheet. When the overlay is closed the attribute is removed. --- .changeset/rich-apricots-argue.md | 5 +++ .../select-rich/src/LionSelectRich.js | 6 ++++ .../select-rich/test/lion-select-rich.test.js | 33 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 .changeset/rich-apricots-argue.md diff --git a/.changeset/rich-apricots-argue.md b/.changeset/rich-apricots-argue.md new file mode 100644 index 000000000..0b2622783 --- /dev/null +++ b/.changeset/rich-apricots-argue.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +lion-select-rich: when the overlay is shown, the "autofocus" attribute is added to \_listboxNode (\_inputNode) to make sure that keyboard navigation continues to work when the element is inside a an element with `trapsKeyboardFocus:true`, like the bottomsheet created via `withBottomSheetConfig()`. When the overlay is closed the attribute is removed. diff --git a/packages/ui/components/select-rich/src/LionSelectRich.js b/packages/ui/components/select-rich/src/LionSelectRich.js index 4681eb636..560b7ee7b 100644 --- a/packages/ui/components/select-rich/src/LionSelectRich.js +++ b/packages/ui/components/select-rich/src/LionSelectRich.js @@ -369,6 +369,11 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L if (this.hasNoDefaultSelected) { this._noDefaultSelectedInheritsWidth(); } + + // When `trapsKeyboardFocus:true` is configured in our overlay, we need to make sure + // that our element with [role=listbox] gets focus. The `containFocus` util will look + // for an element with [autofocus] (otherwise it sets focus to `contentNode` (the 'root' of the overlay)) + this._listboxNode.setAttribute('autofocus', ''); } /** @private */ @@ -382,6 +387,7 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L /** @private */ __overlayOnHide() { this._invokerNode.focus(); + this._listboxNode.removeAttribute('autofocus'); } /** diff --git a/packages/ui/components/select-rich/test/lion-select-rich.test.js b/packages/ui/components/select-rich/test/lion-select-rich.test.js index cd6d2a77b..2e74a806f 100644 --- a/packages/ui/components/select-rich/test/lion-select-rich.test.js +++ b/packages/ui/components/select-rich/test/lion-select-rich.test.js @@ -424,6 +424,39 @@ describe('lion-select-rich', () => { expect(el.singleOption).to.be.false; expect(_invokerNode.singleOption).to.be.false; }); + + it('adds/removes the autofocus attribute to/from _listboxNode', async () => { + const el = await fixture(html` `); + const { _listboxNode } = getSelectRichMembers(el); + + expect(_listboxNode.hasAttribute('autofocus')).to.be.false; + + el.opened = true; + await el.updateComplete; + expect(_listboxNode.hasAttribute('autofocus')).to.be.true; + + el.opened = false; + await el.updateComplete; + await el.updateComplete; // safari takes a little longer + expect(_listboxNode.hasAttribute('autofocus')).to.be.false; + }); + + it('adds focus to element with [role=listbox] when trapsKeyboardFocus is true', async () => { + const el = await fixture( + html` `, + ); + const { _listboxNode } = getSelectRichMembers(el); + expect(document.activeElement).to.not.equal(_listboxNode); + + el.opened = true; + await el.updateComplete; + expect(document.activeElement).to.equal(_listboxNode); + + el.opened = false; + await el.updateComplete; + await el.updateComplete; // safari takes a little longer + expect(document.activeElement).to.not.equal(_listboxNode); + }); }); describe('interaction-mode', () => {