From edc4f12de663c2a8b46b063bd81133c53a4f284e Mon Sep 17 00:00:00 2001 From: Steve Matney Date: Fri, 13 Aug 2021 12:19:18 -0600 Subject: [PATCH] [fixed] Ensure we don't check for hidden on Shadow DOM. When inside a Shadow DOM, the loop will eventually reach the Shadow DOM element itself, which cannot be checked for visibility. We can continue our upwards check by skipping to the Shadow DOM host. --- package-lock.json | 6 ++++ package.json | 1 + specs/Modal.helpers.spec.js | 55 +++++++++++++++++++++++++++++++++++++ src/helpers/tabbable.js | 5 ++++ 4 files changed, 67 insertions(+) diff --git a/package-lock.json b/package-lock.json index d4459f00..cdb67a43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1386,6 +1386,12 @@ "@xtuc/long": "4.2.2" } }, + "@webcomponents/custom-elements": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.5.0.tgz", + "integrity": "sha512-c+7jPQCs9h/BYVcZ2Kna/3tsl3A/9EyXfvWjp5RiTDm1OpTcbZaCa1z4RNcTe/hUtXaqn64JjNW1yrWT+rZ8gg==", + "dev": true + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/package.json b/package.json index d48bce1e..f4b614dd 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ ], "license": "MIT", "devDependencies": { + "@webcomponents/custom-elements": "^1.5.0", "babel-cli": "^6.26.0", "babel-core": "^6.25.0", "babel-eslint": "^8.0.1", diff --git a/specs/Modal.helpers.spec.js b/specs/Modal.helpers.spec.js index 11e22a3f..3043cf17 100644 --- a/specs/Modal.helpers.spec.js +++ b/specs/Modal.helpers.spec.js @@ -1,5 +1,6 @@ /* eslint-env mocha */ import "should"; +import "@webcomponents/custom-elements/src/native-shim"; import tabbable from "../src/helpers/tabbable"; import "sinon"; @@ -114,6 +115,60 @@ export default () => { elem.appendChild(button); tabbable(elem).should.not.containEql(button); }); + + describe("inside Web Components", () => { + let wc; + let input; + class TestWebComponent extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + this.attachShadow({ + mode: "open" + }); + this.style.display = "block"; + this.style.width = "100px"; + this.style.height = "25px"; + } + } + + const registerTestComponent = () => { + if (window.customElements.get("test-web-component")) { + return; + } + window.customElements.define("test-web-component", TestWebComponent); + }; + + beforeEach(() => { + registerTestComponent(); + wc = document.createElement("test-web-component"); + + input = document.createElement("input"); + elem.appendChild(input); + + document.body.appendChild(wc); + wc.shadowRoot.appendChild(elem); + }); + + afterEach(() => { + // re-add elem to body for the next afterEach + document.body.appendChild(elem); + + // remove Web Component + document.body.removeChild(wc); + }); + + it("includes elements when inside a Shadow DOM", () => { + tabbable(elem).should.containEql(input); + }); + + it("excludes elements when hidden inside a Shadow DOM", () => { + wc.style.display = "none"; + tabbable(elem).should.not.containEql(input); + }); + }); }); }); }; diff --git a/src/helpers/tabbable.js b/src/helpers/tabbable.js index f2ebbc9c..062216e0 100644 --- a/src/helpers/tabbable.js +++ b/src/helpers/tabbable.js @@ -35,8 +35,13 @@ function hidesContents(element) { function visible(element) { let parentElement = element; + let rootNode = + typeof element.getRootNode === "function" + ? element.getRootNode() + : undefined; while (parentElement) { if (parentElement === document.body) break; + if (rootNode && parentElement === rootNode) parentElement = rootNode.host; if (hidesContents(parentElement)) return false; parentElement = parentElement.parentNode; }