diff --git a/.changeset/itchy-tools-run.md b/.changeset/itchy-tools-run.md new file mode 100644 index 000000000..d187d2118 --- /dev/null +++ b/.changeset/itchy-tools-run.md @@ -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. diff --git a/packages/overlays/src/utils/deep-contains.js b/packages/overlays/src/utils/deep-contains.js index b5730ab68..0df64dc14 100644 --- a/packages/overlays/src/utils/deep-contains.js +++ b/packages/overlays/src/utils/deep-contains.js @@ -10,15 +10,13 @@ export function deepContains(el, targetEl) { return true; } - /** @param {HTMLElement} elem */ + /** @param {HTMLElement|ShadowRoot} elem */ function checkChildren(elem) { for (let i = 0; i < elem.children.length; i += 1) { const child = /** @type {HTMLElement} */ (elem.children[i]); - if (child.shadowRoot) { - containsTarget = deepContains(child.shadowRoot, targetEl); - if (containsTarget) { - break; - } + if (child.shadowRoot && deepContains(child.shadowRoot, targetEl)) { + containsTarget = true; + break; } if (child.children.length > 0) { checkChildren(child); @@ -27,14 +25,12 @@ export function deepContains(el, targetEl) { } // If element is not shadowRoot itself - if (el instanceof HTMLElement) { - if (el.shadowRoot) { - containsTarget = deepContains(el.shadowRoot, targetEl); - if (containsTarget) { - return true; - } + if (el instanceof HTMLElement && el.shadowRoot) { + containsTarget = deepContains(el.shadowRoot, targetEl); + if (containsTarget) { + return true; } - checkChildren(el); } + checkChildren(el); return containsTarget; } diff --git a/packages/overlays/test/utils-tests/deep-contains.test.js b/packages/overlays/test/utils-tests/deep-contains.test.js index 60b9382a2..fa743a885 100644 --- a/packages/overlays/test/utils-tests/deep-contains.test.js +++ b/packages/overlays/test/utils-tests/deep-contains.test.js @@ -19,14 +19,20 @@ describe('deepContains()', () => { `)); - 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, 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, 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 () => { @@ -63,4 +69,42 @@ describe('deepContains()', () => { expect(deepContains(shadowElement2, 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('
'); + const shadowNestedRoot = shadowNestedElement.attachShadow({ mode: 'open' }); + shadowNestedRoot.innerHTML = ` + + Href + + `; + + const shadowElement = /** @type {HTMLElement} */ (await fixture('
')); + const shadowRoot = shadowElement.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = ` + + + + `; + shadowRoot.insertBefore(shadowNestedElement, shadowRoot.lastElementChild); + + const element = /** @type {HTMLElement} */ (await fixture(html` +
+ ${shadowElement} + +
+ `)); + + 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; + }); });