From aac57c0378a87282385bb6227b03ea164549327a Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 4 Jul 2017 17:03:35 +0200 Subject: [PATCH] feat: Add dom.getComposedParent function --- lib/commons/dom/find-up.js | 5 +- lib/commons/dom/get-composed-parent.js | 19 +++++++ lib/commons/dom/is-visible.js | 3 +- test/commons/dom/get-composed-parent.js | 72 +++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 lib/commons/dom/get-composed-parent.js create mode 100644 test/commons/dom/get-composed-parent.js diff --git a/lib/commons/dom/find-up.js b/lib/commons/dom/find-up.js index 0b0714cb3c..06e20ffe05 100644 --- a/lib/commons/dom/find-up.js +++ b/lib/commons/dom/find-up.js @@ -22,11 +22,8 @@ dom.findUp = function (element, target) { return null; } - parent = (element.assignedSlot) ? element.assignedSlot : element.parentNode; - if (parent.nodeType === 11) { - parent = parent.host; - } // recursively walk up the DOM, checking each parent node + parent = dom.getComposedParent(element); while (parent && matches.indexOf(parent) === -1) { parent = (parent.assignedSlot) ? parent.assignedSlot : parent.parentNode; if (parent && parent.nodeType === 11) { diff --git a/lib/commons/dom/get-composed-parent.js b/lib/commons/dom/get-composed-parent.js new file mode 100644 index 0000000000..c5cfc74339 --- /dev/null +++ b/lib/commons/dom/get-composed-parent.js @@ -0,0 +1,19 @@ +/*global dom */ +/** + * Get an element's parent in the composed tree + * @param DOMNode Element + * @return DOMNode Parent element + */ +dom.getComposedParent = function getComposedParent (element) { + if (element.assignedSlot) { + return element.assignedSlot; // content of a shadow DOM slot + } else if (element.parentNode) { + var parentNode = element.parentNode; + if (parentNode.nodeType === 1) { + return parentNode; // Regular node + } else if (parentNode.host) { + return parentNode.host; // Shadow root + } + } + return null; // Root node +}; diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index 60add9d77a..433fdc25b3 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -64,8 +64,7 @@ dom.isVisible = function (el, screenReader, recursed) { return false; } - parent = (el.assignedSlot) ? el.assignedSlot : el.parentNode; - + parent = dom.getComposedParent(el); if (parent) { return dom.isVisible(parent, screenReader, true); } diff --git a/test/commons/dom/get-composed-parent.js b/test/commons/dom/get-composed-parent.js new file mode 100644 index 0000000000..4dfd41a230 --- /dev/null +++ b/test/commons/dom/get-composed-parent.js @@ -0,0 +1,72 @@ +/* global xit */ +describe('dom.getComposedParent', function () { + 'use strict'; + var getComposedParent = axe.commons.dom.getComposedParent; + var fixture = document.getElementById('fixture'); + var shadowSupport = document.body && typeof document.body.attachShadow === 'function'; + + afterEach(function () { + fixture.innerHTML = ''; + }); + + it('returns the parentNode normally', function () { + fixture.innerHTML = '
'; + + var actual = getComposedParent(document.getElementById('target')); + assert.instanceOf(actual, Node); + assert.equal(actual, document.getElementById('parent')); + }); + + it('returns null from the documentElement', function () { + assert.isNull( + getComposedParent(document.documentElement) + ); + }); + + (shadowSupport ? it : xit)('returns the slot node for slotted content', function () { + fixture.innerHTML = '
'; + var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '
' + + '' + + '
'; + + var actual = getComposedParent(fixture.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#parent')); + }); + + (shadowSupport ? it : xit)('returns explicitly slotted nodes', function () { + fixture.innerHTML = '
'; + var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '
' + + '' + + '' + + '
'; + + var actual = getComposedParent(fixture.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#parent')); + }); + + (shadowSupport ? it : xit)('returns elements within a shadow tree', function () { + fixture.innerHTML = '
content
'; + var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '
' + + '' + + '
'; + + var actual = getComposedParent(shadowRoot.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, shadowRoot.querySelector('#parent')); + }); + + (shadowSupport ? it : xit)('returns the host when it reaches the shadow root', function () { + fixture.innerHTML = '
content
'; + var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '
'; + + var actual = getComposedParent(shadowRoot.querySelector('#target')); + assert.instanceOf(actual, Node); + assert.equal(actual, fixture.querySelector('#parent')); + }); +});