Skip to content

Commit

Permalink
get path to body, then inert all siblings. Complex examples added
Browse files Browse the repository at this point in the history
  • Loading branch information
valdrinkoshi committed Jul 23, 2016
1 parent 1a58d42 commit 65d9f44
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 44 deletions.
136 changes: 113 additions & 23 deletions blocking-elements.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,159 @@

<body>

<template id="x-wrap-focus">
<template id="x-trap-focus">
<style>
:host {
display: inline-block;
display: block;
border: 1px solid lightgray;
padding: 5px;
margin: 5px;
}
</style>
<label>Wrap
<label>Trap
<input id="toggle" type="checkbox">
</label>
<content></content>
</template>

<template id="x-sample">
<button>x-sample</button>
<x-wrap-focus>
<template id="x-a">
<style>
:host {
display: block;
border: 1px solid orange;
padding: 5px;
margin: 5px;
}
</style>
<button>x-a</button>
<x-trap-focus class="in-x-a">
<content></content>
</x-wrap-focus>
</x-trap-focus>
</template>

<template id="x-b">
<style>
:host {
display: block;
border: 1px solid red;
padding: 5px;
margin: 5px;
}
</style>
<button>x-b</button>
<x-trap-focus class="in-x-b">
<x-a class="in-x-b">
<button>in x-b</button>
<content></content>
</x-a>
</x-trap-focus>
</template>

<script>
(function() {
'use strict';

class XWrapFocus extends HTMLElement {
class XTrapFocus extends HTMLElement {
createdCallback() {
var t = document.querySelector('#x-wrap-focus');
var t = document.querySelector('#x-trap-focus');
var clone = document.importNode(t.content, true);
clone.querySelector('#toggle').addEventListener('change', this._toggleWrap.bind(this));
clone.querySelector('#toggle').addEventListener('change', this._toggleTrap.bind(this));
this.createShadowRoot().appendChild(clone);
}

_toggleWrap(evt) {
_toggleTrap(evt) {
if (evt.target.checked) {
document.blockingElements.push(this);
} else {
document.blockingElements.remove(this);
}
}
}
document.registerElement('x-wrap-focus', XWrapFocus);
document.registerElement('x-trap-focus', XTrapFocus);

class XSample extends HTMLElement {
class XA extends HTMLElement {
createdCallback() {
var t = document.querySelector('#x-sample');
var clone = document.importNode(t.content, true);
var clone = document.importNode(document.getElementById('x-a').content, true);
this.createShadowRoot().appendChild(clone);
}
}
document.registerElement('x-sample', XSample);
document.registerElement('x-a', XA);

class XB extends HTMLElement {
createdCallback() {
var clone = document.importNode(document.getElementById('x-b').content, true);
this.createShadowRoot().appendChild(clone);
}
}
document.registerElement('x-b', XB);
})();
</script>

<h2>x-wrap-focus</h2>
<x-wrap-focus>
<input>
</x-wrap-focus>
<h2>x-trap-focus</h2>
<x-trap-focus>
<input placeholder="in body">
</x-trap-focus>

<p>
nested
</p>
<x-trap-focus>
<input placeholder="in body">
<x-trap-focus>
<input placeholder="in body">
</x-trap-focus>
</x-trap-focus>

<h2>x-sample</h2>
<x-sample>
<h2>x-a</h2>
<x-a>
<input placeholder="in body">
</x-a>

<p>
nested
</p>
<x-a>
<input placeholder="in body">
<x-a>
<input placeholder="in body">
</x-a>
</x-a>
<p>
with x-trap-focus
</p>
<x-a>
<input>
</x-sample>
<x-trap-focus>
<input placeholder="in body">
</x-trap-focus>
</x-a>

<h2>x-b</h2>
<x-b>
<input placeholder="in body">
</x-b>

<p>
nested
</p>
<x-b>
<input placeholder="in body">
<x-b id="innerXB">
<input placeholder="in body">
</x-b>
</x-b>

<p>
with x-trap-focus
</p>
<x-b>
<input placeholder="in body">
<x-trap-focus>
<input placeholder="x-trap-focus in body">
</x-trap-focus>
</x-b>




<h2>Lots of focusable elements</h2>
Expand Down
90 changes: 69 additions & 21 deletions blocking-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/**
* The blocking elements.
* @type {Array<Node>}
* @private
*/
this._blockingElements = [];
}
Expand All @@ -31,8 +32,11 @@
* the blocking elements
*/
destructor() {
for (let i = 0; i < this._blockingElements.length; i++)
inertSiblings(this._blockingElements[i], false);
for (let i = 0; i < this._blockingElements.length; i++) {
getPathToBody(this._blockingElements[i]).forEach(function(node) {
setInertToSiblingsOfNode(node, false);
});
}
delete this._blockingElements;
}

Expand All @@ -56,8 +60,9 @@
console.warn('node already added in `document.blockingElements`.');
return;
}
var oldTop = this.top;
this._blockingElements.push(node);
inertSiblings(node, true, getDistributedChildren(node.shadowRoot));
topChanged(node, oldTop);
}

/**
Expand All @@ -68,7 +73,7 @@
let i = this._blockingElements.indexOf(node);
if (i !== -1) {
this._blockingElements.splice(i, 1);
inertSiblings(node, false);
topChanged(this.top, node);
}
}

Expand All @@ -92,50 +97,93 @@
}
}

/**
* Sets `inert` to all document nodes except the top node, its parents, and its
* distributed content.
* @param {Node=} newTop
* @param {Node=} oldTop
*/
function topChanged(newTop, oldTop) {
// TODO(valdrin) optimize this as it sets values twice.
if (oldTop) {
getPathToBody(oldTop).forEach(function(node) {
setInertToSiblingsOfNode(node, false);
});
}
if (newTop) {
let nodesToSkip = newTop.shadowRoot ? getDistributedChildren(newTop.shadowRoot) : null;
getPathToBody(newTop).forEach(function(node) {
setInertToSiblingsOfNode(node, true, nodesToSkip);
});
}
}

/**
* Inerts all the siblings of the node except the node
* and the nodes to skip.
* Sets `inert` to the siblings of the node except the nodes to skip.
* @param {Node} node
* @param {boolean} inert
* @param {Set<Node>=} nodesToSkip
*/
function inertSiblings(node, inert, nodesToSkip) {
function setInertToSiblingsOfNode(node, inert, nodesToSkip) {
let sibling = node;
while ((sibling = sibling.previousElementSibling)) {
if (!nodesToSkip ||!nodesToSkip.has(sibling)) {
if (sibling.localName === 'style' || sibling.localName === 'script') {
continue;
}
if (!nodesToSkip || !nodesToSkip.has(sibling)) {
sibling.inert = inert;
}
}
sibling = node;
while ((sibling = sibling.nextElementSibling)) {
if (!nodesToSkip ||!nodesToSkip.has(sibling)) {
if (sibling.localName === 'style' || sibling.localName === 'script') {
continue;
}
if (!nodesToSkip || !nodesToSkip.has(sibling)) {
sibling.inert = inert;
}
}
let parent = node.parentNode || node.host;
if (parent && parent !== document.body) {
inertSiblings(parent, inert, nodesToSkip);
}

/**
* Returns the list of parents, shadowRoots and insertion points, starting from
* node up to `document.body` (excluded).
* @param {!Node} node
* @returns {Array<Node>}
*/
function getPathToBody(node) {
let path = [];
let current = node;
// Stop to body.
while (current && current !== document.body) {
path.push(current);
// From deepest to top insertion point.
const insertionPoints = current.getDestinationInsertionPoints ? [...current.getDestinationInsertionPoints()] : [];
if (insertionPoints.length) {
for (var i = 0; i < insertionPoints.length - 1; i++) {
path.push(insertionPoints[i]);
}
current = insertionPoints[insertionPoints.length - 1];
} else {
current = current.parentNode || current.host;
}
}
return path;
}

/**
* Returns the distributed children of a shadow root.
* @param {DocumentFragment=} root
* @returns {Set<Node>=}
* @param {!DocumentFragment} shadowRoot
* @returns {Set<Node>}
*/
function getDistributedChildren(root) {
if (!root) {
return;
}
function getDistributedChildren(shadowRoot) {
var result = [];
// TODO(valdrin) query slots.
var contents = root.querySelectorAll('content');
var contents = shadowRoot.querySelectorAll('content');
for (var i = 0; i < contents.length; i++) {
var children = contents[i].getDistributedNodes();
for (var j = 0; j < children.length; j++) {
// No #text nodes.
if (children[j].nodeType !== 3) {
if (children[j].nodeType === Node.ELEMENT_NODE) {
result.push(children[j]);
}
}
Expand Down

0 comments on commit 65d9f44

Please sign in to comment.