fix(overlays): reinsert missing tab detect el, don't error on disconnect
This commit is contained in:
parent
37f975ea48
commit
a77452b0ac
3 changed files with 46 additions and 2 deletions
5
.changeset/long-fans-flash.md
Normal file
5
.changeset/long-fans-flash.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/overlays': patch
|
||||
---
|
||||
|
||||
Use MutationObserver to watch child changes of the contentNode, and re-insert tab detection element when necessary.
|
||||
|
|
@ -77,6 +77,8 @@ export function containFocus(rootElement) {
|
|||
const initialFocus = focusableElements.find(e => e.hasAttribute('autofocus')) || rootElement;
|
||||
/** @type {HTMLElement} */
|
||||
let tabDetectionElement;
|
||||
/** @type {MutationObserver} */
|
||||
let rootElementMutationObserver;
|
||||
|
||||
// If root element will receive focus, it should have a tabindex of -1.
|
||||
// This makes it focusable through js, but it won't appear in the tab order
|
||||
|
|
@ -101,7 +103,28 @@ export function containFocus(rootElement) {
|
|||
function createHelpersDetectingTabDirection() {
|
||||
tabDetectionElement = document.createElement('div');
|
||||
tabDetectionElement.style.display = 'none';
|
||||
tabDetectionElement.setAttribute('data-is-tab-detection-element', '');
|
||||
rootElement.insertBefore(tabDetectionElement, rootElement.children[0]);
|
||||
|
||||
rootElementMutationObserver = new MutationObserver(mutationsList => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
const tabDetectionElIsMissing = !Array.from(rootElement.children).find(el =>
|
||||
el.hasAttribute('data-is-tab-detection-element'),
|
||||
);
|
||||
const foundTabDetectionElInMutations = Array.from(mutation.addedNodes).find(
|
||||
/** @param {Node} el */ el =>
|
||||
el instanceof HTMLElement && el.hasAttribute('data-is-tab-detection-element'),
|
||||
);
|
||||
// Prevent infinite loop by detecting that mutation event is not from adding the tab detection el
|
||||
if (tabDetectionElIsMissing && !foundTabDetectionElInMutations) {
|
||||
rootElementMutationObserver.disconnect();
|
||||
createHelpersDetectingTabDirection();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
rootElementMutationObserver.observe(rootElement, { childList: true });
|
||||
}
|
||||
|
||||
function isForwardTabInWindow() {
|
||||
|
|
@ -162,7 +185,12 @@ export function containFocus(rootElement) {
|
|||
window.removeEventListener('keydown', handleKeydown);
|
||||
window.removeEventListener('focusin', handleFocusin);
|
||||
window.removeEventListener('focusout', handleFocusout);
|
||||
rootElement.removeChild(tabDetectionElement);
|
||||
// Guard this, since we also disconnect if we notice a missing tab
|
||||
// detection element. We reinsert it, so it's okay to not fail here.
|
||||
rootElementMutationObserver.disconnect();
|
||||
if (Array.from(rootElement.children).includes(tabDetectionElement)) {
|
||||
rootElement.removeChild(tabDetectionElement);
|
||||
}
|
||||
rootElement.style.removeProperty('outline');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture, html, nextFrame } from '@open-wc/testing';
|
||||
import { renderLitAsNode } from '@lion/helpers';
|
||||
import { getDeepActiveElement } from '../../src/utils/get-deep-active-element.js';
|
||||
import { getFocusableElements } from '../../src/utils/get-focusable-elements.js';
|
||||
|
|
@ -164,6 +164,17 @@ describe('containFocus()', () => {
|
|||
});
|
||||
|
||||
describe('Tabbing into window', () => {
|
||||
it('reinserts tab detection element when contentNode changes inner content', async () => {
|
||||
await fixture(lightDomTemplate);
|
||||
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
||||
const { disconnect } = containFocus(root);
|
||||
expect(root.querySelector('[data-is-tab-detection-element]')).to.exist;
|
||||
root.innerHTML = `my content`;
|
||||
await nextFrame();
|
||||
expect(root.querySelector('[data-is-tab-detection-element]')).to.exist;
|
||||
disconnect();
|
||||
});
|
||||
|
||||
it('restores focus within root element', async () => {
|
||||
await fixture(lightDomTemplate);
|
||||
const root = /** @type {HTMLElement} */ (document.getElementById('rootElement'));
|
||||
|
|
|
|||
Loading…
Reference in a new issue