Merge pull request #1115 from ing-bank/fix/overlays-focus

Fix/overlays focus
This commit is contained in:
Joren Broekema 2020-12-02 14:37:56 +01:00 committed by GitHub
commit 18f47a8d21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 18 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/overlays': patch
---
Only set inert when placementMode is global, otherwise events are broken inside local overlay content

View file

@ -0,0 +1,5 @@
---
'@lion/overlays': patch
---
Fix deep contains util to properly check multiple nested shadow roots, and do not set containsTarget to false when children deepContains returns false. This would undo a found target in another child somewhere that was found in an earlier recursion.

View file

@ -1042,7 +1042,7 @@ export class OverlayController extends EventTargetShim {
this._containFocusHandler = containFocus(this.contentNode); this._containFocusHandler = containFocus(this.contentNode);
this.__hasActiveTrapsKeyboardFocus = true; this.__hasActiveTrapsKeyboardFocus = true;
if (this.manager) { if (this.manager) {
this.manager.informTrapsKeyboardFocusGotEnabled(); this.manager.informTrapsKeyboardFocusGotEnabled(this.placementMode);
} }
} }

View file

@ -153,8 +153,11 @@ export class OverlaysManager {
}); });
} }
informTrapsKeyboardFocusGotEnabled() { /**
if (this.siblingsInert === false) { * @param {'local' | 'global' | undefined} placementMode
*/
informTrapsKeyboardFocusGotEnabled(placementMode) {
if (this.siblingsInert === false && placementMode === 'global') {
if (OverlaysManager.__globalRootNode) { if (OverlaysManager.__globalRootNode) {
setSiblingsInert(this.globalRootNode); setSiblingsInert(this.globalRootNode);
} }

View file

@ -10,15 +10,13 @@ export function deepContains(el, targetEl) {
return true; return true;
} }
/** @param {HTMLElement} elem */ /** @param {HTMLElement|ShadowRoot} elem */
function checkChildren(elem) { function checkChildren(elem) {
for (let i = 0; i < elem.children.length; i += 1) { for (let i = 0; i < elem.children.length; i += 1) {
const child = /** @type {HTMLElement} */ (elem.children[i]); const child = /** @type {HTMLElement} */ (elem.children[i]);
if (child.shadowRoot) { if (child.shadowRoot && deepContains(child.shadowRoot, targetEl)) {
containsTarget = deepContains(child.shadowRoot, targetEl); containsTarget = true;
if (containsTarget) { break;
break;
}
} }
if (child.children.length > 0) { if (child.children.length > 0) {
checkChildren(child); checkChildren(child);
@ -27,14 +25,12 @@ export function deepContains(el, targetEl) {
} }
// If element is not shadowRoot itself // If element is not shadowRoot itself
if (el instanceof HTMLElement) { if (el instanceof HTMLElement && el.shadowRoot) {
if (el.shadowRoot) { containsTarget = deepContains(el.shadowRoot, targetEl);
containsTarget = deepContains(el.shadowRoot, targetEl); if (containsTarget) {
if (containsTarget) { return true;
return true;
}
} }
checkChildren(el);
} }
checkChildren(el);
return containsTarget; return containsTarget;
} }

View file

@ -19,14 +19,20 @@ describe('deepContains()', () => {
<button id="light-el-1"></button> <button id="light-el-1"></button>
</div> </div>
`)); `));
const lightEl = /** @type {HTMLElement} */ (element.querySelector('#light-el-1')); const lightChildEl = /** @type {HTMLElement} */ (element.querySelector('#light-el-1'));
expect(deepContains(element, element)).to.be.true;
expect(deepContains(element, shadowElement)).to.be.true; expect(deepContains(element, shadowElement)).to.be.true;
expect(deepContains(element, shadowElementChild)).to.be.true; expect(deepContains(element, shadowElementChild)).to.be.true;
expect(deepContains(element, lightEl)).to.be.true; expect(deepContains(element, lightChildEl)).to.be.true;
expect(deepContains(shadowElement, shadowElement)).to.be.true; expect(deepContains(shadowElement, shadowElement)).to.be.true;
expect(deepContains(shadowElement, shadowElementChild)).to.be.true; expect(deepContains(shadowElement, shadowElementChild)).to.be.true;
expect(deepContains(shadowRoot, shadowElementChild)).to.be.true; expect(deepContains(shadowRoot, shadowElementChild)).to.be.true;
// Siblings
expect(deepContains(element.firstElementChild, element.lastElementChild)).to.be.false;
// Unrelated
expect(deepContains(lightChildEl, shadowElementChild)).to.be.false;
}); });
it('returns true if element contains a target element with a shadow boundary in between, for multiple shadowroots', async () => { it('returns true if element contains a target element with a shadow boundary in between, for multiple shadowroots', async () => {
@ -63,4 +69,42 @@ describe('deepContains()', () => {
expect(deepContains(shadowElement2, shadowElementChild2)).to.be.true; expect(deepContains(shadowElement2, shadowElementChild2)).to.be.true;
expect(deepContains(shadowRoot2, shadowElementChild2)).to.be.true; expect(deepContains(shadowRoot2, shadowElementChild2)).to.be.true;
}); });
it('can detect containing elements inside multiple nested shadow roots', async () => {
const shadowNestedElement = await fixture('<div id="shadow-nested"></div>');
const shadowNestedRoot = shadowNestedElement.attachShadow({ mode: 'open' });
shadowNestedRoot.innerHTML = `
<button id="nested-el-1">Button</button>
<a id="nested-el-2" href="#foo">Href</a>
<input id="nested-el-3">
`;
const shadowElement = /** @type {HTMLElement} */ (await fixture('<div id="shadow"></div>'));
const shadowRoot = shadowElement.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<button id="el-1">Button</button>
<input id="el-3">
`;
shadowRoot.insertBefore(shadowNestedElement, shadowRoot.lastElementChild);
const element = /** @type {HTMLElement} */ (await fixture(html`
<div id="light">
${shadowElement}
<button id="light-el-1"></button>
</div>
`));
expect(deepContains(element, element.firstElementChild)).to.be.true;
expect(deepContains(element, element.firstElementChild.shadowRoot)).to.be.true;
expect(deepContains(element, element.firstElementChild.shadowRoot.children[1])).to.be.true;
expect(deepContains(element, element.firstElementChild.shadowRoot.children[1].shadowRoot)).to.be
.true;
expect(
deepContains(
element,
element.firstElementChild.shadowRoot.children[1].shadowRoot.lastElementChild,
),
).to.be.true;
});
}); });