wicg-inert
Advanced tools
Comparing version 3.0.2 to 3.0.3
1331
dist/inert.js
@@ -16,819 +16,826 @@ (function (global, factory) { | ||
// Convenience function for converting NodeLists. | ||
/** @type {typeof Array.prototype.slice} */ | ||
var slice = Array.prototype.slice; | ||
(function () { | ||
// Return early if we're not running inside of the browser. | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
/** | ||
* IE has a non-standard name for "matches". | ||
* @type {typeof Element.prototype.matches} | ||
*/ | ||
var matches = Element.prototype.matches || Element.prototype.msMatchesSelector; | ||
// Convenience function for converting NodeLists. | ||
/** @type {typeof Array.prototype.slice} */ | ||
var slice = Array.prototype.slice; | ||
/** @type {string} */ | ||
var _focusableElementsString = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'details', 'summary', 'iframe', 'object', 'embed', '[contenteditable]'].join(','); | ||
/** | ||
* IE has a non-standard name for "matches". | ||
* @type {typeof Element.prototype.matches} | ||
*/ | ||
var matches = Element.prototype.matches || Element.prototype.msMatchesSelector; | ||
/** | ||
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert` | ||
* attribute. | ||
* | ||
* Its main functions are: | ||
* | ||
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the | ||
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering | ||
* each focusable node in the subtree with the singleton `InertManager` which manages all known | ||
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode` | ||
* instance exists for each focusable node which has at least one inert root as an ancestor. | ||
* | ||
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert` | ||
* attribute is removed from the root node). This is handled in the destructor, which calls the | ||
* `deregister` method on `InertManager` for each managed inert node. | ||
*/ | ||
/** @type {string} */ | ||
var _focusableElementsString = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'details', 'summary', 'iframe', 'object', 'embed', '[contenteditable]'].join(','); | ||
var InertRoot = function () { | ||
/** | ||
* @param {!Element} rootElement The Element at the root of the inert subtree. | ||
* @param {!InertManager} inertManager The global singleton InertManager object. | ||
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert` | ||
* attribute. | ||
* | ||
* Its main functions are: | ||
* | ||
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the | ||
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering | ||
* each focusable node in the subtree with the singleton `InertManager` which manages all known | ||
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode` | ||
* instance exists for each focusable node which has at least one inert root as an ancestor. | ||
* | ||
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert` | ||
* attribute is removed from the root node). This is handled in the destructor, which calls the | ||
* `deregister` method on `InertManager` for each managed inert node. | ||
*/ | ||
function InertRoot(rootElement, inertManager) { | ||
_classCallCheck(this, InertRoot); | ||
/** @type {!InertManager} */ | ||
this._inertManager = inertManager; | ||
/** @type {!Element} */ | ||
this._rootElement = rootElement; | ||
var InertRoot = function () { | ||
/** | ||
* @type {!Set<!InertNode>} | ||
* All managed focusable nodes in this InertRoot's subtree. | ||
* @param {!Element} rootElement The Element at the root of the inert subtree. | ||
* @param {!InertManager} inertManager The global singleton InertManager object. | ||
*/ | ||
this._managedNodes = new Set(); | ||
function InertRoot(rootElement, inertManager) { | ||
_classCallCheck(this, InertRoot); | ||
// Make the subtree hidden from assistive technology | ||
if (this._rootElement.hasAttribute('aria-hidden')) { | ||
/** @type {?string} */ | ||
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden'); | ||
} else { | ||
this._savedAriaHidden = null; | ||
} | ||
this._rootElement.setAttribute('aria-hidden', 'true'); | ||
/** @type {!InertManager} */ | ||
this._inertManager = inertManager; | ||
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes | ||
this._makeSubtreeUnfocusable(this._rootElement); | ||
/** @type {!Element} */ | ||
this._rootElement = rootElement; | ||
// Watch for: | ||
// - any additions in the subtree: make them unfocusable too | ||
// - any removals from the subtree: remove them from this inert root's managed nodes | ||
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable | ||
// element, make that node a managed node. | ||
this._observer = new MutationObserver(this._onMutation.bind(this)); | ||
this._observer.observe(this._rootElement, { attributes: true, childList: true, subtree: true }); | ||
} | ||
/** | ||
* @type {!Set<!InertNode>} | ||
* All managed focusable nodes in this InertRoot's subtree. | ||
*/ | ||
this._managedNodes = new Set(); | ||
/** | ||
* Call this whenever this object is about to become obsolete. This unwinds all of the state | ||
* stored in this object and updates the state of all of the managed nodes. | ||
*/ | ||
_createClass(InertRoot, [{ | ||
key: 'destructor', | ||
value: function destructor() { | ||
this._observer.disconnect(); | ||
if (this._rootElement) { | ||
if (this._savedAriaHidden !== null) { | ||
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden); | ||
} else { | ||
this._rootElement.removeAttribute('aria-hidden'); | ||
} | ||
// Make the subtree hidden from assistive technology | ||
if (this._rootElement.hasAttribute('aria-hidden')) { | ||
/** @type {?string} */ | ||
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden'); | ||
} else { | ||
this._savedAriaHidden = null; | ||
} | ||
this._rootElement.setAttribute('aria-hidden', 'true'); | ||
this._managedNodes.forEach(function (inertNode) { | ||
this._unmanageNode(inertNode.node); | ||
}, this); | ||
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes | ||
this._makeSubtreeUnfocusable(this._rootElement); | ||
// Note we cast the nulls to the ANY type here because: | ||
// 1) We want the class properties to be declared as non-null, or else we | ||
// need even more casts throughout this code. All bets are off if an | ||
// instance has been destroyed and a method is called. | ||
// 2) We don't want to cast "this", because we want type-aware optimizations | ||
// to know which properties we're setting. | ||
this._observer = /** @type {?} */null; | ||
this._rootElement = /** @type {?} */null; | ||
this._managedNodes = /** @type {?} */null; | ||
this._inertManager = /** @type {?} */null; | ||
// Watch for: | ||
// - any additions in the subtree: make them unfocusable too | ||
// - any removals from the subtree: remove them from this inert root's managed nodes | ||
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable | ||
// element, make that node a managed node. | ||
this._observer = new MutationObserver(this._onMutation.bind(this)); | ||
this._observer.observe(this._rootElement, { attributes: true, childList: true, subtree: true }); | ||
} | ||
/** | ||
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set. | ||
* Call this whenever this object is about to become obsolete. This unwinds all of the state | ||
* stored in this object and updates the state of all of the managed nodes. | ||
*/ | ||
}, { | ||
key: '_makeSubtreeUnfocusable', | ||
_createClass(InertRoot, [{ | ||
key: 'destructor', | ||
value: function destructor() { | ||
this._observer.disconnect(); | ||
/** | ||
* @param {!Node} startNode | ||
*/ | ||
value: function _makeSubtreeUnfocusable(startNode) { | ||
var _this2 = this; | ||
if (this._rootElement) { | ||
if (this._savedAriaHidden !== null) { | ||
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden); | ||
} else { | ||
this._rootElement.removeAttribute('aria-hidden'); | ||
} | ||
} | ||
composedTreeWalk(startNode, function (node) { | ||
return _this2._visitNode(node); | ||
}); | ||
this._managedNodes.forEach(function (inertNode) { | ||
this._unmanageNode(inertNode.node); | ||
}, this); | ||
var activeElement = document.activeElement; | ||
// Note we cast the nulls to the ANY type here because: | ||
// 1) We want the class properties to be declared as non-null, or else we | ||
// need even more casts throughout this code. All bets are off if an | ||
// instance has been destroyed and a method is called. | ||
// 2) We don't want to cast "this", because we want type-aware optimizations | ||
// to know which properties we're setting. | ||
this._observer = /** @type {?} */null; | ||
this._rootElement = /** @type {?} */null; | ||
this._managedNodes = /** @type {?} */null; | ||
this._inertManager = /** @type {?} */null; | ||
} | ||
if (!document.body.contains(startNode)) { | ||
// startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement. | ||
var node = startNode; | ||
/** @type {!ShadowRoot|undefined} */ | ||
var root = undefined; | ||
while (node) { | ||
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | ||
root = /** @type {!ShadowRoot} */node; | ||
break; | ||
/** | ||
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set. | ||
*/ | ||
}, { | ||
key: '_makeSubtreeUnfocusable', | ||
/** | ||
* @param {!Node} startNode | ||
*/ | ||
value: function _makeSubtreeUnfocusable(startNode) { | ||
var _this2 = this; | ||
composedTreeWalk(startNode, function (node) { | ||
return _this2._visitNode(node); | ||
}); | ||
var activeElement = document.activeElement; | ||
if (!document.body.contains(startNode)) { | ||
// startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement. | ||
var node = startNode; | ||
/** @type {!ShadowRoot|undefined} */ | ||
var root = undefined; | ||
while (node) { | ||
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | ||
root = /** @type {!ShadowRoot} */node; | ||
break; | ||
} | ||
node = node.parentNode; | ||
} | ||
node = node.parentNode; | ||
if (root) { | ||
activeElement = root.activeElement; | ||
} | ||
} | ||
if (root) { | ||
activeElement = root.activeElement; | ||
if (startNode.contains(activeElement)) { | ||
activeElement.blur(); | ||
// In IE11, if an element is already focused, and then set to tabindex=-1 | ||
// calling blur() will not actually move the focus. | ||
// To work around this we call focus() on the body instead. | ||
if (activeElement === document.activeElement) { | ||
document.body.focus(); | ||
} | ||
} | ||
} | ||
if (startNode.contains(activeElement)) { | ||
activeElement.blur(); | ||
// In IE11, if an element is already focused, and then set to tabindex=-1 | ||
// calling blur() will not actually move the focus. | ||
// To work around this we call focus() on the body instead. | ||
if (activeElement === document.activeElement) { | ||
document.body.focus(); | ||
/** | ||
* @param {!Node} node | ||
*/ | ||
}, { | ||
key: '_visitNode', | ||
value: function _visitNode(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
} | ||
} | ||
var element = /** @type {!Element} */node; | ||
/** | ||
* @param {!Node} node | ||
*/ | ||
// If a descendant inert root becomes un-inert, its descendants will still be inert because of | ||
// this inert root, so all of its managed nodes need to be adopted by this InertRoot. | ||
if (element !== this._rootElement && element.hasAttribute('inert')) { | ||
this._adoptInertRoot(element); | ||
} | ||
}, { | ||
key: '_visitNode', | ||
value: function _visitNode(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) { | ||
this._manageNode(element); | ||
} | ||
} | ||
var element = /** @type {!Element} */node; | ||
// If a descendant inert root becomes un-inert, its descendants will still be inert because of | ||
// this inert root, so all of its managed nodes need to be adopted by this InertRoot. | ||
if (element !== this._rootElement && element.hasAttribute('inert')) { | ||
this._adoptInertRoot(element); | ||
/** | ||
* Register the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
}, { | ||
key: '_manageNode', | ||
value: function _manageNode(node) { | ||
var inertNode = this._inertManager.register(node, this); | ||
this._managedNodes.add(inertNode); | ||
} | ||
if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) { | ||
this._manageNode(element); | ||
/** | ||
* Unregister the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
}, { | ||
key: '_unmanageNode', | ||
value: function _unmanageNode(node) { | ||
var inertNode = this._inertManager.deregister(node, this); | ||
if (inertNode) { | ||
this._managedNodes['delete'](inertNode); | ||
} | ||
} | ||
} | ||
/** | ||
* Register the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
/** | ||
* Unregister the entire subtree starting at `startNode`. | ||
* @param {!Node} startNode | ||
*/ | ||
}, { | ||
key: '_manageNode', | ||
value: function _manageNode(node) { | ||
var inertNode = this._inertManager.register(node, this); | ||
this._managedNodes.add(inertNode); | ||
} | ||
}, { | ||
key: '_unmanageSubtree', | ||
value: function _unmanageSubtree(startNode) { | ||
var _this3 = this; | ||
/** | ||
* Unregister the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
}, { | ||
key: '_unmanageNode', | ||
value: function _unmanageNode(node) { | ||
var inertNode = this._inertManager.deregister(node, this); | ||
if (inertNode) { | ||
this._managedNodes['delete'](inertNode); | ||
composedTreeWalk(startNode, function (node) { | ||
return _this3._unmanageNode(node); | ||
}); | ||
} | ||
} | ||
/** | ||
* Unregister the entire subtree starting at `startNode`. | ||
* @param {!Node} startNode | ||
*/ | ||
/** | ||
* If a descendant node is found with an `inert` attribute, adopt its managed nodes. | ||
* @param {!Element} node | ||
*/ | ||
}, { | ||
key: '_unmanageSubtree', | ||
value: function _unmanageSubtree(startNode) { | ||
var _this3 = this; | ||
}, { | ||
key: '_adoptInertRoot', | ||
value: function _adoptInertRoot(node) { | ||
var inertSubroot = this._inertManager.getInertRoot(node); | ||
composedTreeWalk(startNode, function (node) { | ||
return _this3._unmanageNode(node); | ||
}); | ||
} | ||
// During initialisation this inert root may not have been registered yet, | ||
// so register it now if need be. | ||
if (!inertSubroot) { | ||
this._inertManager.setInert(node, true); | ||
inertSubroot = this._inertManager.getInertRoot(node); | ||
} | ||
/** | ||
* If a descendant node is found with an `inert` attribute, adopt its managed nodes. | ||
* @param {!Element} node | ||
*/ | ||
inertSubroot.managedNodes.forEach(function (savedInertNode) { | ||
this._manageNode(savedInertNode.node); | ||
}, this); | ||
} | ||
}, { | ||
key: '_adoptInertRoot', | ||
value: function _adoptInertRoot(node) { | ||
var inertSubroot = this._inertManager.getInertRoot(node); | ||
/** | ||
* Callback used when mutation observer detects subtree additions, removals, or attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
// During initialisation this inert root may not have been registered yet, | ||
// so register it now if need be. | ||
if (!inertSubroot) { | ||
this._inertManager.setInert(node, true); | ||
inertSubroot = this._inertManager.getInertRoot(node); | ||
}, { | ||
key: '_onMutation', | ||
value: function _onMutation(records, self) { | ||
records.forEach(function (record) { | ||
var target = /** @type {!Element} */record.target; | ||
if (record.type === 'childList') { | ||
// Manage added nodes | ||
slice.call(record.addedNodes).forEach(function (node) { | ||
this._makeSubtreeUnfocusable(node); | ||
}, this); | ||
// Un-manage removed nodes | ||
slice.call(record.removedNodes).forEach(function (node) { | ||
this._unmanageSubtree(node); | ||
}, this); | ||
} else if (record.type === 'attributes') { | ||
if (record.attributeName === 'tabindex') { | ||
// Re-initialise inert node if tabindex changes | ||
this._manageNode(target); | ||
} else if (target !== this._rootElement && record.attributeName === 'inert' && target.hasAttribute('inert')) { | ||
// If a new inert root is added, adopt its managed nodes and make sure it knows about the | ||
// already managed nodes from this inert subroot. | ||
this._adoptInertRoot(target); | ||
var inertSubroot = this._inertManager.getInertRoot(target); | ||
this._managedNodes.forEach(function (managedNode) { | ||
if (target.contains(managedNode.node)) { | ||
inertSubroot._manageNode(managedNode.node); | ||
} | ||
}); | ||
} | ||
} | ||
}, this); | ||
} | ||
}, { | ||
key: 'managedNodes', | ||
get: function get() { | ||
return new Set(this._managedNodes); | ||
} | ||
inertSubroot.managedNodes.forEach(function (savedInertNode) { | ||
this._manageNode(savedInertNode.node); | ||
}, this); | ||
} | ||
/** @return {boolean} */ | ||
/** | ||
* Callback used when mutation observer detects subtree additions, removals, or attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
}, { | ||
key: 'hasSavedAriaHidden', | ||
get: function get() { | ||
return this._savedAriaHidden !== null; | ||
} | ||
}, { | ||
key: '_onMutation', | ||
value: function _onMutation(records, self) { | ||
records.forEach(function (record) { | ||
var target = /** @type {!Element} */record.target; | ||
if (record.type === 'childList') { | ||
// Manage added nodes | ||
slice.call(record.addedNodes).forEach(function (node) { | ||
this._makeSubtreeUnfocusable(node); | ||
}, this); | ||
/** @param {?string} ariaHidden */ | ||
// Un-manage removed nodes | ||
slice.call(record.removedNodes).forEach(function (node) { | ||
this._unmanageSubtree(node); | ||
}, this); | ||
} else if (record.type === 'attributes') { | ||
if (record.attributeName === 'tabindex') { | ||
// Re-initialise inert node if tabindex changes | ||
this._manageNode(target); | ||
} else if (target !== this._rootElement && record.attributeName === 'inert' && target.hasAttribute('inert')) { | ||
// If a new inert root is added, adopt its managed nodes and make sure it knows about the | ||
// already managed nodes from this inert subroot. | ||
this._adoptInertRoot(target); | ||
var inertSubroot = this._inertManager.getInertRoot(target); | ||
this._managedNodes.forEach(function (managedNode) { | ||
if (target.contains(managedNode.node)) { | ||
inertSubroot._manageNode(managedNode.node); | ||
} | ||
}); | ||
} | ||
} | ||
}, this); | ||
} | ||
}, { | ||
key: 'managedNodes', | ||
get: function get() { | ||
return new Set(this._managedNodes); | ||
} | ||
}, { | ||
key: 'savedAriaHidden', | ||
set: function set(ariaHidden) { | ||
this._savedAriaHidden = ariaHidden; | ||
} | ||
/** @return {boolean} */ | ||
/** @return {?string} */ | ||
, | ||
get: function get() { | ||
return this._savedAriaHidden; | ||
} | ||
}]); | ||
}, { | ||
key: 'hasSavedAriaHidden', | ||
get: function get() { | ||
return this._savedAriaHidden !== null; | ||
} | ||
return InertRoot; | ||
}(); | ||
/** @param {?string} ariaHidden */ | ||
/** | ||
* `InertNode` initialises and manages a single inert node. | ||
* A node is inert if it is a descendant of one or more inert root elements. | ||
* | ||
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and | ||
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element | ||
* is intrinsically focusable or not. | ||
* | ||
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an | ||
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the | ||
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s | ||
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists, | ||
* or removes the `tabindex` attribute if the element is intrinsically focusable. | ||
*/ | ||
}, { | ||
key: 'savedAriaHidden', | ||
set: function set(ariaHidden) { | ||
this._savedAriaHidden = ariaHidden; | ||
} | ||
/** @return {?string} */ | ||
, | ||
get: function get() { | ||
return this._savedAriaHidden; | ||
} | ||
}]); | ||
var InertNode = function () { | ||
/** | ||
* @param {!Node} node A focusable element to be made inert. | ||
* @param {!InertRoot} inertRoot The inert root element associated with this inert node. | ||
*/ | ||
function InertNode(node, inertRoot) { | ||
_classCallCheck(this, InertNode); | ||
return InertRoot; | ||
}(); | ||
/** @type {!Node} */ | ||
this._node = node; | ||
/** | ||
* `InertNode` initialises and manages a single inert node. | ||
* A node is inert if it is a descendant of one or more inert root elements. | ||
* | ||
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and | ||
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element | ||
* is intrinsically focusable or not. | ||
* | ||
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an | ||
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the | ||
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s | ||
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists, | ||
* or removes the `tabindex` attribute if the element is intrinsically focusable. | ||
*/ | ||
/** @type {boolean} */ | ||
this._overrodeFocusMethod = false; | ||
/** | ||
* @type {!Set<!InertRoot>} The set of descendant inert roots. | ||
* If and only if this set becomes empty, this node is no longer inert. | ||
*/ | ||
this._inertRoots = new Set([inertRoot]); | ||
var InertNode = function () { | ||
/** | ||
* @param {!Node} node A focusable element to be made inert. | ||
* @param {!InertRoot} inertRoot The inert root element associated with this inert node. | ||
*/ | ||
function InertNode(node, inertRoot) { | ||
_classCallCheck(this, InertNode); | ||
/** @type {?number} */ | ||
this._savedTabIndex = null; | ||
/** @type {!Node} */ | ||
this._node = node; | ||
/** @type {boolean} */ | ||
this._destroyed = false; | ||
/** @type {boolean} */ | ||
this._overrodeFocusMethod = false; | ||
// Save any prior tabindex info and make this node untabbable | ||
this.ensureUntabbable(); | ||
} | ||
/** | ||
* @type {!Set<!InertRoot>} The set of descendant inert roots. | ||
* If and only if this set becomes empty, this node is no longer inert. | ||
* Call this whenever this object is about to become obsolete. | ||
* This makes the managed node focusable again and deletes all of the previously stored state. | ||
*/ | ||
this._inertRoots = new Set([inertRoot]); | ||
/** @type {?number} */ | ||
this._savedTabIndex = null; | ||
/** @type {boolean} */ | ||
this._destroyed = false; | ||
_createClass(InertNode, [{ | ||
key: 'destructor', | ||
value: function destructor() { | ||
this._throwIfDestroyed(); | ||
// Save any prior tabindex info and make this node untabbable | ||
this.ensureUntabbable(); | ||
} | ||
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) { | ||
var element = /** @type {!Element} */this._node; | ||
if (this._savedTabIndex !== null) { | ||
element.setAttribute('tabindex', this._savedTabIndex); | ||
} else { | ||
element.removeAttribute('tabindex'); | ||
} | ||
/** | ||
* Call this whenever this object is about to become obsolete. | ||
* This makes the managed node focusable again and deletes all of the previously stored state. | ||
*/ | ||
_createClass(InertNode, [{ | ||
key: 'destructor', | ||
value: function destructor() { | ||
this._throwIfDestroyed(); | ||
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) { | ||
var element = /** @type {!Element} */this._node; | ||
if (this._savedTabIndex !== null) { | ||
element.setAttribute('tabindex', this._savedTabIndex); | ||
} else { | ||
element.removeAttribute('tabindex'); | ||
// Use `delete` to restore native focus method. | ||
if (this._overrodeFocusMethod) { | ||
delete element.focus; | ||
} | ||
} | ||
// Use `delete` to restore native focus method. | ||
if (this._overrodeFocusMethod) { | ||
delete element.focus; | ||
} | ||
// See note in InertRoot.destructor for why we cast these nulls to ANY. | ||
this._node = /** @type {?} */null; | ||
this._inertRoots = /** @type {?} */null; | ||
this._destroyed = true; | ||
} | ||
// See note in InertRoot.destructor for why we cast these nulls to ANY. | ||
this._node = /** @type {?} */null; | ||
this._inertRoots = /** @type {?} */null; | ||
this._destroyed = true; | ||
} | ||
/** | ||
* @type {boolean} Whether this object is obsolete because the managed node is no longer inert. | ||
* If the object has been destroyed, any attempt to access it will cause an exception. | ||
*/ | ||
/** | ||
* @type {boolean} Whether this object is obsolete because the managed node is no longer inert. | ||
* If the object has been destroyed, any attempt to access it will cause an exception. | ||
*/ | ||
}, { | ||
key: '_throwIfDestroyed', | ||
}, { | ||
key: '_throwIfDestroyed', | ||
/** | ||
* Throw if user tries to access destroyed InertNode. | ||
*/ | ||
value: function _throwIfDestroyed() { | ||
if (this.destroyed) { | ||
throw new Error('Trying to access destroyed InertNode'); | ||
/** | ||
* Throw if user tries to access destroyed InertNode. | ||
*/ | ||
value: function _throwIfDestroyed() { | ||
if (this.destroyed) { | ||
throw new Error('Trying to access destroyed InertNode'); | ||
} | ||
} | ||
} | ||
/** @return {boolean} */ | ||
/** @return {boolean} */ | ||
}, { | ||
key: 'ensureUntabbable', | ||
}, { | ||
key: 'ensureUntabbable', | ||
/** Save the existing tabindex value and make the node untabbable and unfocusable */ | ||
value: function ensureUntabbable() { | ||
if (this.node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
var element = /** @type {!Element} */this.node; | ||
if (matches.call(element, _focusableElementsString)) { | ||
if ( /** @type {!HTMLElement} */element.tabIndex === -1 && this.hasSavedTabIndex) { | ||
/** Save the existing tabindex value and make the node untabbable and unfocusable */ | ||
value: function ensureUntabbable() { | ||
if (this.node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
var element = /** @type {!Element} */this.node; | ||
if (matches.call(element, _focusableElementsString)) { | ||
if ( /** @type {!HTMLElement} */element.tabIndex === -1 && this.hasSavedTabIndex) { | ||
return; | ||
} | ||
if (element.hasAttribute('tabindex')) { | ||
if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */element.tabIndex; | ||
} | ||
element.setAttribute('tabindex', '-1'); | ||
if (element.nodeType === Node.ELEMENT_NODE) { | ||
element.focus = function () {}; | ||
this._overrodeFocusMethod = true; | ||
} | ||
} else if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */element.tabIndex; | ||
element.removeAttribute('tabindex'); | ||
} | ||
element.setAttribute('tabindex', '-1'); | ||
if (element.nodeType === Node.ELEMENT_NODE) { | ||
element.focus = function () {}; | ||
this._overrodeFocusMethod = true; | ||
} | ||
} else if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */element.tabIndex; | ||
element.removeAttribute('tabindex'); | ||
} | ||
} | ||
/** | ||
* Add another inert root to this inert node's set of managing inert roots. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
/** | ||
* Add another inert root to this inert node's set of managing inert roots. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
}, { | ||
key: 'addInertRoot', | ||
value: function addInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.add(inertRoot); | ||
} | ||
}, { | ||
key: 'addInertRoot', | ||
value: function addInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.add(inertRoot); | ||
} | ||
/** | ||
* Remove the given inert root from this inert node's set of managing inert roots. | ||
* If the set of managing inert roots becomes empty, this node is no longer inert, | ||
* so the object should be destroyed. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
/** | ||
* Remove the given inert root from this inert node's set of managing inert roots. | ||
* If the set of managing inert roots becomes empty, this node is no longer inert, | ||
* so the object should be destroyed. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
}, { | ||
key: 'removeInertRoot', | ||
value: function removeInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots['delete'](inertRoot); | ||
if (this._inertRoots.size === 0) { | ||
this.destructor(); | ||
}, { | ||
key: 'removeInertRoot', | ||
value: function removeInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots['delete'](inertRoot); | ||
if (this._inertRoots.size === 0) { | ||
this.destructor(); | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'destroyed', | ||
get: function get() { | ||
return (/** @type {!InertNode} */this._destroyed | ||
); | ||
} | ||
}, { | ||
key: 'hasSavedTabIndex', | ||
get: function get() { | ||
return this._savedTabIndex !== null; | ||
} | ||
}, { | ||
key: 'destroyed', | ||
get: function get() { | ||
return (/** @type {!InertNode} */this._destroyed | ||
); | ||
} | ||
}, { | ||
key: 'hasSavedTabIndex', | ||
get: function get() { | ||
return this._savedTabIndex !== null; | ||
} | ||
/** @return {!Node} */ | ||
/** @return {!Node} */ | ||
}, { | ||
key: 'node', | ||
get: function get() { | ||
this._throwIfDestroyed(); | ||
return this._node; | ||
} | ||
}, { | ||
key: 'node', | ||
get: function get() { | ||
this._throwIfDestroyed(); | ||
return this._node; | ||
} | ||
/** @param {?number} tabIndex */ | ||
/** @param {?number} tabIndex */ | ||
}, { | ||
key: 'savedTabIndex', | ||
set: function set(tabIndex) { | ||
this._throwIfDestroyed(); | ||
this._savedTabIndex = tabIndex; | ||
} | ||
}, { | ||
key: 'savedTabIndex', | ||
set: function set(tabIndex) { | ||
this._throwIfDestroyed(); | ||
this._savedTabIndex = tabIndex; | ||
} | ||
/** @return {?number} */ | ||
, | ||
get: function get() { | ||
this._throwIfDestroyed(); | ||
return this._savedTabIndex; | ||
} | ||
}]); | ||
/** @return {?number} */ | ||
, | ||
get: function get() { | ||
this._throwIfDestroyed(); | ||
return this._savedTabIndex; | ||
} | ||
}]); | ||
return InertNode; | ||
}(); | ||
return InertNode; | ||
}(); | ||
/** | ||
* InertManager is a per-document singleton object which manages all inert roots and nodes. | ||
* | ||
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert` | ||
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element. | ||
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant | ||
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance | ||
* is created for each such node, via the `_managedNodes` map. | ||
*/ | ||
var InertManager = function () { | ||
/** | ||
* @param {!Document} document | ||
* InertManager is a per-document singleton object which manages all inert roots and nodes. | ||
* | ||
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert` | ||
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element. | ||
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant | ||
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance | ||
* is created for each such node, via the `_managedNodes` map. | ||
*/ | ||
function InertManager(document) { | ||
_classCallCheck(this, InertManager); | ||
if (!document) { | ||
throw new Error('Missing required argument; InertManager needs to wrap a document.'); | ||
} | ||
/** @type {!Document} */ | ||
this._document = document; | ||
var InertManager = function () { | ||
/** | ||
* All managed nodes known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertNode>} | ||
* @param {!Document} document | ||
*/ | ||
this._managedNodes = new Map(); | ||
function InertManager(document) { | ||
_classCallCheck(this, InertManager); | ||
/** | ||
* All inert roots known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertRoot>} | ||
*/ | ||
this._inertRoots = new Map(); | ||
if (!document) { | ||
throw new Error('Missing required argument; InertManager needs to wrap a document.'); | ||
} | ||
/** | ||
* Observer for mutations on `document.body`. | ||
* @type {!MutationObserver} | ||
*/ | ||
this._observer = new MutationObserver(this._watchForInert.bind(this)); | ||
/** @type {!Document} */ | ||
this._document = document; | ||
// Add inert style. | ||
addInertStyle(document.head || document.body || document.documentElement); | ||
/** | ||
* All managed nodes known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertNode>} | ||
*/ | ||
this._managedNodes = new Map(); | ||
// Wait for document to be loaded. | ||
if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this)); | ||
} else { | ||
this._onDocumentLoaded(); | ||
/** | ||
* All inert roots known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertRoot>} | ||
*/ | ||
this._inertRoots = new Map(); | ||
/** | ||
* Observer for mutations on `document.body`. | ||
* @type {!MutationObserver} | ||
*/ | ||
this._observer = new MutationObserver(this._watchForInert.bind(this)); | ||
// Add inert style. | ||
addInertStyle(document.head || document.body || document.documentElement); | ||
// Wait for document to be loaded. | ||
if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this)); | ||
} else { | ||
this._onDocumentLoaded(); | ||
} | ||
} | ||
} | ||
/** | ||
* Set whether the given element should be an inert root or not. | ||
* @param {!Element} root | ||
* @param {boolean} inert | ||
*/ | ||
/** | ||
* Set whether the given element should be an inert root or not. | ||
* @param {!Element} root | ||
* @param {boolean} inert | ||
*/ | ||
_createClass(InertManager, [{ | ||
key: 'setInert', | ||
value: function setInert(root, inert) { | ||
if (inert) { | ||
if (this._inertRoots.has(root)) { | ||
// element is already inert | ||
return; | ||
} | ||
_createClass(InertManager, [{ | ||
key: 'setInert', | ||
value: function setInert(root, inert) { | ||
if (inert) { | ||
if (this._inertRoots.has(root)) { | ||
// element is already inert | ||
return; | ||
} | ||
var inertRoot = new InertRoot(root, this); | ||
root.setAttribute('inert', ''); | ||
this._inertRoots.set(root, inertRoot); | ||
// If not contained in the document, it must be in a shadowRoot. | ||
// Ensure inert styles are added there. | ||
if (!this._document.body.contains(root)) { | ||
var parent = root.parentNode; | ||
while (parent) { | ||
if (parent.nodeType === 11) { | ||
addInertStyle(parent); | ||
var inertRoot = new InertRoot(root, this); | ||
root.setAttribute('inert', ''); | ||
this._inertRoots.set(root, inertRoot); | ||
// If not contained in the document, it must be in a shadowRoot. | ||
// Ensure inert styles are added there. | ||
if (!this._document.body.contains(root)) { | ||
var parent = root.parentNode; | ||
while (parent) { | ||
if (parent.nodeType === 11) { | ||
addInertStyle(parent); | ||
} | ||
parent = parent.parentNode; | ||
} | ||
parent = parent.parentNode; | ||
} | ||
} else { | ||
if (!this._inertRoots.has(root)) { | ||
// element is already non-inert | ||
return; | ||
} | ||
var _inertRoot = this._inertRoots.get(root); | ||
_inertRoot.destructor(); | ||
this._inertRoots['delete'](root); | ||
root.removeAttribute('inert'); | ||
} | ||
} else { | ||
if (!this._inertRoots.has(root)) { | ||
// element is already non-inert | ||
return; | ||
} | ||
} | ||
var _inertRoot = this._inertRoots.get(root); | ||
_inertRoot.destructor(); | ||
this._inertRoots['delete'](root); | ||
root.removeAttribute('inert'); | ||
/** | ||
* Get the InertRoot object corresponding to the given inert root element, if any. | ||
* @param {!Node} element | ||
* @return {!InertRoot|undefined} | ||
*/ | ||
}, { | ||
key: 'getInertRoot', | ||
value: function getInertRoot(element) { | ||
return this._inertRoots.get(element); | ||
} | ||
} | ||
/** | ||
* Get the InertRoot object corresponding to the given inert root element, if any. | ||
* @param {!Node} element | ||
* @return {!InertRoot|undefined} | ||
*/ | ||
/** | ||
* Register the given InertRoot as managing the given node. | ||
* In the case where the node has a previously existing inert root, this inert root will | ||
* be added to its set of inert roots. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {!InertNode} inertNode | ||
*/ | ||
}, { | ||
key: 'getInertRoot', | ||
value: function getInertRoot(element) { | ||
return this._inertRoots.get(element); | ||
} | ||
}, { | ||
key: 'register', | ||
value: function register(node, inertRoot) { | ||
var inertNode = this._managedNodes.get(node); | ||
if (inertNode !== undefined) { | ||
// node was already in an inert subtree | ||
inertNode.addInertRoot(inertRoot); | ||
} else { | ||
inertNode = new InertNode(node, inertRoot); | ||
} | ||
/** | ||
* Register the given InertRoot as managing the given node. | ||
* In the case where the node has a previously existing inert root, this inert root will | ||
* be added to its set of inert roots. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {!InertNode} inertNode | ||
*/ | ||
this._managedNodes.set(node, inertNode); | ||
}, { | ||
key: 'register', | ||
value: function register(node, inertRoot) { | ||
var inertNode = this._managedNodes.get(node); | ||
if (inertNode !== undefined) { | ||
// node was already in an inert subtree | ||
inertNode.addInertRoot(inertRoot); | ||
} else { | ||
inertNode = new InertNode(node, inertRoot); | ||
return inertNode; | ||
} | ||
this._managedNodes.set(node, inertNode); | ||
/** | ||
* De-register the given InertRoot as managing the given inert node. | ||
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert | ||
* node from the InertManager's set of managed nodes if it is destroyed. | ||
* If the node is not currently managed, this is essentially a no-op. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {?InertNode} The potentially destroyed InertNode associated with this node, if any. | ||
*/ | ||
return inertNode; | ||
} | ||
}, { | ||
key: 'deregister', | ||
value: function deregister(node, inertRoot) { | ||
var inertNode = this._managedNodes.get(node); | ||
if (!inertNode) { | ||
return null; | ||
} | ||
/** | ||
* De-register the given InertRoot as managing the given inert node. | ||
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert | ||
* node from the InertManager's set of managed nodes if it is destroyed. | ||
* If the node is not currently managed, this is essentially a no-op. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {?InertNode} The potentially destroyed InertNode associated with this node, if any. | ||
*/ | ||
inertNode.removeInertRoot(inertRoot); | ||
if (inertNode.destroyed) { | ||
this._managedNodes['delete'](node); | ||
} | ||
}, { | ||
key: 'deregister', | ||
value: function deregister(node, inertRoot) { | ||
var inertNode = this._managedNodes.get(node); | ||
if (!inertNode) { | ||
return null; | ||
return inertNode; | ||
} | ||
inertNode.removeInertRoot(inertRoot); | ||
if (inertNode.destroyed) { | ||
this._managedNodes['delete'](node); | ||
} | ||
/** | ||
* Callback used when document has finished loading. | ||
*/ | ||
return inertNode; | ||
} | ||
}, { | ||
key: '_onDocumentLoaded', | ||
value: function _onDocumentLoaded() { | ||
// Find all inert roots in document and make them actually inert. | ||
var inertElements = slice.call(this._document.querySelectorAll('[inert]')); | ||
inertElements.forEach(function (inertElement) { | ||
this.setInert(inertElement, true); | ||
}, this); | ||
/** | ||
* Callback used when document has finished loading. | ||
*/ | ||
// Comment this out to use programmatic API only. | ||
this._observer.observe(this._document.body || this._document.documentElement, { attributes: true, subtree: true, childList: true }); | ||
} | ||
}, { | ||
key: '_onDocumentLoaded', | ||
value: function _onDocumentLoaded() { | ||
// Find all inert roots in document and make them actually inert. | ||
var inertElements = slice.call(this._document.querySelectorAll('[inert]')); | ||
inertElements.forEach(function (inertElement) { | ||
this.setInert(inertElement, true); | ||
}, this); | ||
/** | ||
* Callback used when mutation observer detects attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
// Comment this out to use programmatic API only. | ||
this._observer.observe(this._document.body || this._document.documentElement, { attributes: true, subtree: true, childList: true }); | ||
} | ||
/** | ||
* Callback used when mutation observer detects attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
}, { | ||
key: '_watchForInert', | ||
value: function _watchForInert(records, self) { | ||
var _this = this; | ||
records.forEach(function (record) { | ||
switch (record.type) { | ||
case 'childList': | ||
slice.call(record.addedNodes).forEach(function (node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
}, { | ||
key: '_watchForInert', | ||
value: function _watchForInert(records, self) { | ||
var _this = this; | ||
records.forEach(function (record) { | ||
switch (record.type) { | ||
case 'childList': | ||
slice.call(record.addedNodes).forEach(function (node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
var inertElements = slice.call(node.querySelectorAll('[inert]')); | ||
if (matches.call(node, '[inert]')) { | ||
inertElements.unshift(node); | ||
} | ||
inertElements.forEach(function (inertElement) { | ||
this.setInert(inertElement, true); | ||
}, _this); | ||
}, _this); | ||
break; | ||
case 'attributes': | ||
if (record.attributeName !== 'inert') { | ||
return; | ||
} | ||
var inertElements = slice.call(node.querySelectorAll('[inert]')); | ||
if (matches.call(node, '[inert]')) { | ||
inertElements.unshift(node); | ||
} | ||
inertElements.forEach(function (inertElement) { | ||
this.setInert(inertElement, true); | ||
}, _this); | ||
}, _this); | ||
break; | ||
case 'attributes': | ||
if (record.attributeName !== 'inert') { | ||
return; | ||
} | ||
var target = /** @type {!Element} */record.target; | ||
var inert = target.hasAttribute('inert'); | ||
_this.setInert(target, inert); | ||
break; | ||
} | ||
}, this); | ||
} | ||
}]); | ||
var target = /** @type {!Element} */record.target; | ||
var inert = target.hasAttribute('inert'); | ||
_this.setInert(target, inert); | ||
break; | ||
} | ||
}, this); | ||
} | ||
}]); | ||
return InertManager; | ||
}(); | ||
return InertManager; | ||
}(); | ||
/** | ||
* Recursively walk the composed tree from |node|. | ||
* @param {!Node} node | ||
* @param {(function (!Element))=} callback Callback to be called for each element traversed, | ||
* before descending into child nodes. | ||
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any. | ||
*/ | ||
/** | ||
* Recursively walk the composed tree from |node|. | ||
* @param {!Node} node | ||
* @param {(function (!Element))=} callback Callback to be called for each element traversed, | ||
* before descending into child nodes. | ||
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any. | ||
*/ | ||
function composedTreeWalk(node, callback, shadowRootAncestor) { | ||
if (node.nodeType == Node.ELEMENT_NODE) { | ||
var element = /** @type {!Element} */node; | ||
if (callback) { | ||
callback(element); | ||
} | ||
function composedTreeWalk(node, callback, shadowRootAncestor) { | ||
if (node.nodeType == Node.ELEMENT_NODE) { | ||
var element = /** @type {!Element} */node; | ||
if (callback) { | ||
callback(element); | ||
} | ||
// Descend into node: | ||
// If it has a ShadowRoot, ignore all child elements - these will be picked | ||
// up by the <content> or <shadow> elements. Descend straight into the | ||
// ShadowRoot. | ||
var shadowRoot = /** @type {!HTMLElement} */element.shadowRoot; | ||
if (shadowRoot) { | ||
composedTreeWalk(shadowRoot, callback, shadowRoot); | ||
return; | ||
} | ||
// Descend into node: | ||
// If it has a ShadowRoot, ignore all child elements - these will be picked | ||
// up by the <content> or <shadow> elements. Descend straight into the | ||
// ShadowRoot. | ||
var shadowRoot = /** @type {!HTMLElement} */element.shadowRoot; | ||
if (shadowRoot) { | ||
composedTreeWalk(shadowRoot, callback, shadowRoot); | ||
return; | ||
} | ||
// If it is a <content> element, descend into distributed elements - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'content') { | ||
var content = /** @type {!HTMLContentElement} */element; | ||
// Verifies if ShadowDom v0 is supported. | ||
var distributedNodes = content.getDistributedNodes ? content.getDistributedNodes() : []; | ||
for (var i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
// If it is a <content> element, descend into distributed elements - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'content') { | ||
var content = /** @type {!HTMLContentElement} */element; | ||
// Verifies if ShadowDom v0 is supported. | ||
var distributedNodes = content.getDistributedNodes ? content.getDistributedNodes() : []; | ||
for (var i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
} | ||
return; | ||
} | ||
return; | ||
} | ||
// If it is a <slot> element, descend into assigned nodes - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'slot') { | ||
var slot = /** @type {!HTMLSlotElement} */element; | ||
// Verify if ShadowDom v1 is supported. | ||
var _distributedNodes = slot.assignedNodes ? slot.assignedNodes({ flatten: true }) : []; | ||
for (var _i = 0; _i < _distributedNodes.length; _i++) { | ||
composedTreeWalk(_distributedNodes[_i], callback, shadowRootAncestor); | ||
// If it is a <slot> element, descend into assigned nodes - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'slot') { | ||
var slot = /** @type {!HTMLSlotElement} */element; | ||
// Verify if ShadowDom v1 is supported. | ||
var _distributedNodes = slot.assignedNodes ? slot.assignedNodes({ flatten: true }) : []; | ||
for (var _i = 0; _i < _distributedNodes.length; _i++) { | ||
composedTreeWalk(_distributedNodes[_i], callback, shadowRootAncestor); | ||
} | ||
return; | ||
} | ||
return; | ||
} | ||
} | ||
// If it is neither the parent of a ShadowRoot, a <content> element, a <slot> | ||
// element, nor a <shadow> element recurse normally. | ||
var child = node.firstChild; | ||
while (child != null) { | ||
composedTreeWalk(child, callback, shadowRootAncestor); | ||
child = child.nextSibling; | ||
// If it is neither the parent of a ShadowRoot, a <content> element, a <slot> | ||
// element, nor a <shadow> element recurse normally. | ||
var child = node.firstChild; | ||
while (child != null) { | ||
composedTreeWalk(child, callback, shadowRootAncestor); | ||
child = child.nextSibling; | ||
} | ||
} | ||
} | ||
/** | ||
* Adds a style element to the node containing the inert specific styles | ||
* @param {!Node} node | ||
*/ | ||
function addInertStyle(node) { | ||
if (node.querySelector('style#inert-style')) { | ||
return; | ||
/** | ||
* Adds a style element to the node containing the inert specific styles | ||
* @param {!Node} node | ||
*/ | ||
function addInertStyle(node) { | ||
if (node.querySelector('style#inert-style, link#inert-style')) { | ||
return; | ||
} | ||
var style = document.createElement('style'); | ||
style.setAttribute('id', 'inert-style'); | ||
style.textContent = '\n' + '[inert] {\n' + ' pointer-events: none;\n' + ' cursor: default;\n' + '}\n' + '\n' + '[inert], [inert] * {\n' + ' -webkit-user-select: none;\n' + ' -moz-user-select: none;\n' + ' -ms-user-select: none;\n' + ' user-select: none;\n' + '}\n'; | ||
node.appendChild(style); | ||
} | ||
var style = document.createElement('style'); | ||
style.setAttribute('id', 'inert-style'); | ||
style.textContent = '\n' + '[inert] {\n' + ' pointer-events: none;\n' + ' cursor: default;\n' + '}\n' + '\n' + '[inert], [inert] * {\n' + ' user-select: none;\n' + ' -webkit-user-select: none;\n' + ' -moz-user-select: none;\n' + ' -ms-user-select: none;\n' + '}\n'; | ||
node.appendChild(style); | ||
} | ||
/** @type {!InertManager} */ | ||
var inertManager = new InertManager(document); | ||
/** @type {!InertManager} */ | ||
var inertManager = new InertManager(document); | ||
if (!Element.prototype.hasOwnProperty('inert')) { | ||
Object.defineProperty(Element.prototype, 'inert', { | ||
enumerable: true, | ||
/** @this {!Element} */ | ||
get: function get() { | ||
return this.hasAttribute('inert'); | ||
}, | ||
/** @this {!Element} */ | ||
set: function set(inert) { | ||
inertManager.setInert(this, inert); | ||
} | ||
}); | ||
} | ||
if (!Element.prototype.hasOwnProperty('inert')) { | ||
Object.defineProperty(Element.prototype, 'inert', { | ||
enumerable: true, | ||
/** @this {!Element} */ | ||
get: function get() { | ||
return this.hasAttribute('inert'); | ||
}, | ||
/** @this {!Element} */ | ||
set: function set(inert) { | ||
inertManager.setInert(this, inert); | ||
} | ||
}); | ||
} | ||
})(); | ||
}))); |
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define("inert",t):t()}(0,function(){"use strict";var e=function(){function i(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(e,t,n){return t&&i(e.prototype,t),n&&i(e,n),e}}();function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=Array.prototype.slice,r=Element.prototype.matches||Element.prototype.msMatchesSelector,s=["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])","details","summary","iframe","object","embed","[contenteditable]"].join(","),a=function(){function n(e,t){i(this,n),this._inertManager=t,this._rootElement=e,this._managedNodes=new Set,this._rootElement.hasAttribute("aria-hidden")?this._savedAriaHidden=this._rootElement.getAttribute("aria-hidden"):this._savedAriaHidden=null,this._rootElement.setAttribute("aria-hidden","true"),this._makeSubtreeUnfocusable(this._rootElement),this._observer=new MutationObserver(this._onMutation.bind(this)),this._observer.observe(this._rootElement,{attributes:!0,childList:!0,subtree:!0})}return e(n,[{key:"destructor",value:function(){this._observer.disconnect(),this._rootElement&&(null!==this._savedAriaHidden?this._rootElement.setAttribute("aria-hidden",this._savedAriaHidden):this._rootElement.removeAttribute("aria-hidden")),this._managedNodes.forEach(function(e){this._unmanageNode(e.node)},this),this._observer=null,this._rootElement=null,this._managedNodes=null,this._inertManager=null}},{key:"_makeSubtreeUnfocusable",value:function(e){var t=this;l(e,function(e){return t._visitNode(e)});var n=document.activeElement;if(!document.body.contains(e)){for(var i=e,o=void 0;i;){if(i.nodeType===Node.DOCUMENT_FRAGMENT_NODE){o=i;break}i=i.parentNode}o&&(n=o.activeElement)}e.contains(n)&&(n.blur(),n===document.activeElement&&document.body.focus())}},{key:"_visitNode",value:function(e){if(e.nodeType===Node.ELEMENT_NODE){var t=e;t!==this._rootElement&&t.hasAttribute("inert")&&this._adoptInertRoot(t),(r.call(t,s)||t.hasAttribute("tabindex"))&&this._manageNode(t)}}},{key:"_manageNode",value:function(e){var t=this._inertManager.register(e,this);this._managedNodes.add(t)}},{key:"_unmanageNode",value:function(e){var t=this._inertManager.deregister(e,this);t&&this._managedNodes.delete(t)}},{key:"_unmanageSubtree",value:function(e){var t=this;l(e,function(e){return t._unmanageNode(e)})}},{key:"_adoptInertRoot",value:function(e){var t=this._inertManager.getInertRoot(e);t||(this._inertManager.setInert(e,!0),t=this._inertManager.getInertRoot(e)),t.managedNodes.forEach(function(e){this._manageNode(e.node)},this)}},{key:"_onMutation",value:function(e,t){e.forEach(function(e){var t=e.target;if("childList"===e.type)o.call(e.addedNodes).forEach(function(e){this._makeSubtreeUnfocusable(e)},this),o.call(e.removedNodes).forEach(function(e){this._unmanageSubtree(e)},this);else if("attributes"===e.type)if("tabindex"===e.attributeName)this._manageNode(t);else if(t!==this._rootElement&&"inert"===e.attributeName&&t.hasAttribute("inert")){this._adoptInertRoot(t);var n=this._inertManager.getInertRoot(t);this._managedNodes.forEach(function(e){t.contains(e.node)&&n._manageNode(e.node)})}},this)}},{key:"managedNodes",get:function(){return new Set(this._managedNodes)}},{key:"hasSavedAriaHidden",get:function(){return null!==this._savedAriaHidden}},{key:"savedAriaHidden",set:function(e){this._savedAriaHidden=e},get:function(){return this._savedAriaHidden}}]),n}(),d=function(){function n(e,t){i(this,n),this._node=e,this._overrodeFocusMethod=!1,this._inertRoots=new Set([t]),this._savedTabIndex=null,this._destroyed=!1,this.ensureUntabbable()}return e(n,[{key:"destructor",value:function(){if(this._throwIfDestroyed(),this._node&&this._node.nodeType===Node.ELEMENT_NODE){var e=this._node;null!==this._savedTabIndex?e.setAttribute("tabindex",this._savedTabIndex):e.removeAttribute("tabindex"),this._overrodeFocusMethod&&delete e.focus}this._node=null,this._inertRoots=null,this._destroyed=!0}},{key:"_throwIfDestroyed",value:function(){if(this.destroyed)throw new Error("Trying to access destroyed InertNode")}},{key:"ensureUntabbable",value:function(){if(this.node.nodeType===Node.ELEMENT_NODE){var e=this.node;if(r.call(e,s)){if(-1===e.tabIndex&&this.hasSavedTabIndex)return;e.hasAttribute("tabindex")&&(this._savedTabIndex=e.tabIndex),e.setAttribute("tabindex","-1"),e.nodeType===Node.ELEMENT_NODE&&(e.focus=function(){},this._overrodeFocusMethod=!0)}else e.hasAttribute("tabindex")&&(this._savedTabIndex=e.tabIndex,e.removeAttribute("tabindex"))}}},{key:"addInertRoot",value:function(e){this._throwIfDestroyed(),this._inertRoots.add(e)}},{key:"removeInertRoot",value:function(e){this._throwIfDestroyed(),this._inertRoots.delete(e),0===this._inertRoots.size&&this.destructor()}},{key:"destroyed",get:function(){return this._destroyed}},{key:"hasSavedTabIndex",get:function(){return null!==this._savedTabIndex}},{key:"node",get:function(){return this._throwIfDestroyed(),this._node}},{key:"savedTabIndex",set:function(e){this._throwIfDestroyed(),this._savedTabIndex=e},get:function(){return this._throwIfDestroyed(),this._savedTabIndex}}]),n}();function l(e,t,n){if(e.nodeType==Node.ELEMENT_NODE){var i=e;t&&t(i);var o=i.shadowRoot;if(o)return void l(o,t,o);if("content"==i.localName){for(var r=i,s=r.getDistributedNodes?r.getDistributedNodes():[],a=0;a<s.length;a++)l(s[a],t,n);return}if("slot"==i.localName){for(var d=i,u=d.assignedNodes?d.assignedNodes({flatten:!0}):[],h=0;h<u.length;h++)l(u[h],t,n);return}}for(var c=e.firstChild;null!=c;)l(c,t,n),c=c.nextSibling}function u(e){if(!e.querySelector("style#inert-style")){var t=document.createElement("style");t.setAttribute("id","inert-style"),t.textContent="\n[inert] {\n pointer-events: none;\n cursor: default;\n}\n\n[inert], [inert] * {\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n}\n",e.appendChild(t)}}var t=new(function(){function t(e){if(i(this,t),!e)throw new Error("Missing required argument; InertManager needs to wrap a document.");this._document=e,this._managedNodes=new Map,this._inertRoots=new Map,this._observer=new MutationObserver(this._watchForInert.bind(this)),u(e.head||e.body||e.documentElement),"loading"===e.readyState?e.addEventListener("DOMContentLoaded",this._onDocumentLoaded.bind(this)):this._onDocumentLoaded()}return e(t,[{key:"setInert",value:function(e,t){if(t){if(this._inertRoots.has(e))return;var n=new a(e,this);if(e.setAttribute("inert",""),this._inertRoots.set(e,n),!this._document.body.contains(e))for(var i=e.parentNode;i;)11===i.nodeType&&u(i),i=i.parentNode}else{if(!this._inertRoots.has(e))return;this._inertRoots.get(e).destructor(),this._inertRoots.delete(e),e.removeAttribute("inert")}}},{key:"getInertRoot",value:function(e){return this._inertRoots.get(e)}},{key:"register",value:function(e,t){var n=this._managedNodes.get(e);return void 0!==n?n.addInertRoot(t):n=new d(e,t),this._managedNodes.set(e,n),n}},{key:"deregister",value:function(e,t){var n=this._managedNodes.get(e);return n?(n.removeInertRoot(t),n.destroyed&&this._managedNodes.delete(e),n):null}},{key:"_onDocumentLoaded",value:function(){o.call(this._document.querySelectorAll("[inert]")).forEach(function(e){this.setInert(e,!0)},this),this._observer.observe(this._document.body||this._document.documentElement,{attributes:!0,subtree:!0,childList:!0})}},{key:"_watchForInert",value:function(e,t){var i=this;e.forEach(function(e){switch(e.type){case"childList":o.call(e.addedNodes).forEach(function(e){if(e.nodeType===Node.ELEMENT_NODE){var t=o.call(e.querySelectorAll("[inert]"));r.call(e,"[inert]")&&t.unshift(e),t.forEach(function(e){this.setInert(e,!0)},i)}},i);break;case"attributes":if("inert"!==e.attributeName)return;var t=e.target,n=t.hasAttribute("inert");i.setInert(t,n)}},this)}}]),t}())(document);Element.prototype.hasOwnProperty("inert")||Object.defineProperty(Element.prototype,"inert",{enumerable:!0,get:function(){return this.hasAttribute("inert")},set:function(e){t.setInert(this,e)}})}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define("inert",t):t()}(0,function(){"use strict";var e=function(){function i(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(e,t,n){return t&&i(e.prototype,t),n&&i(e,n),e}}();function u(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}!function(){if("undefined"!=typeof window){var o=Array.prototype.slice,r=Element.prototype.matches||Element.prototype.msMatchesSelector,i=["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])","details","summary","iframe","object","embed","[contenteditable]"].join(","),s=function(){function n(e,t){u(this,n),this._inertManager=t,this._rootElement=e,this._managedNodes=new Set,this._rootElement.hasAttribute("aria-hidden")?this._savedAriaHidden=this._rootElement.getAttribute("aria-hidden"):this._savedAriaHidden=null,this._rootElement.setAttribute("aria-hidden","true"),this._makeSubtreeUnfocusable(this._rootElement),this._observer=new MutationObserver(this._onMutation.bind(this)),this._observer.observe(this._rootElement,{attributes:!0,childList:!0,subtree:!0})}return e(n,[{key:"destructor",value:function(){this._observer.disconnect(),this._rootElement&&(null!==this._savedAriaHidden?this._rootElement.setAttribute("aria-hidden",this._savedAriaHidden):this._rootElement.removeAttribute("aria-hidden")),this._managedNodes.forEach(function(e){this._unmanageNode(e.node)},this),this._observer=null,this._rootElement=null,this._managedNodes=null,this._inertManager=null}},{key:"_makeSubtreeUnfocusable",value:function(e){var t=this;l(e,function(e){return t._visitNode(e)});var n=document.activeElement;if(!document.body.contains(e)){for(var i=e,o=void 0;i;){if(i.nodeType===Node.DOCUMENT_FRAGMENT_NODE){o=i;break}i=i.parentNode}o&&(n=o.activeElement)}e.contains(n)&&(n.blur(),n===document.activeElement&&document.body.focus())}},{key:"_visitNode",value:function(e){if(e.nodeType===Node.ELEMENT_NODE){var t=e;t!==this._rootElement&&t.hasAttribute("inert")&&this._adoptInertRoot(t),(r.call(t,i)||t.hasAttribute("tabindex"))&&this._manageNode(t)}}},{key:"_manageNode",value:function(e){var t=this._inertManager.register(e,this);this._managedNodes.add(t)}},{key:"_unmanageNode",value:function(e){var t=this._inertManager.deregister(e,this);t&&this._managedNodes.delete(t)}},{key:"_unmanageSubtree",value:function(e){var t=this;l(e,function(e){return t._unmanageNode(e)})}},{key:"_adoptInertRoot",value:function(e){var t=this._inertManager.getInertRoot(e);t||(this._inertManager.setInert(e,!0),t=this._inertManager.getInertRoot(e)),t.managedNodes.forEach(function(e){this._manageNode(e.node)},this)}},{key:"_onMutation",value:function(e,t){e.forEach(function(e){var t=e.target;if("childList"===e.type)o.call(e.addedNodes).forEach(function(e){this._makeSubtreeUnfocusable(e)},this),o.call(e.removedNodes).forEach(function(e){this._unmanageSubtree(e)},this);else if("attributes"===e.type)if("tabindex"===e.attributeName)this._manageNode(t);else if(t!==this._rootElement&&"inert"===e.attributeName&&t.hasAttribute("inert")){this._adoptInertRoot(t);var n=this._inertManager.getInertRoot(t);this._managedNodes.forEach(function(e){t.contains(e.node)&&n._manageNode(e.node)})}},this)}},{key:"managedNodes",get:function(){return new Set(this._managedNodes)}},{key:"hasSavedAriaHidden",get:function(){return null!==this._savedAriaHidden}},{key:"savedAriaHidden",set:function(e){this._savedAriaHidden=e},get:function(){return this._savedAriaHidden}}]),n}(),a=function(){function n(e,t){u(this,n),this._node=e,this._overrodeFocusMethod=!1,this._inertRoots=new Set([t]),this._savedTabIndex=null,this._destroyed=!1,this.ensureUntabbable()}return e(n,[{key:"destructor",value:function(){if(this._throwIfDestroyed(),this._node&&this._node.nodeType===Node.ELEMENT_NODE){var e=this._node;null!==this._savedTabIndex?e.setAttribute("tabindex",this._savedTabIndex):e.removeAttribute("tabindex"),this._overrodeFocusMethod&&delete e.focus}this._node=null,this._inertRoots=null,this._destroyed=!0}},{key:"_throwIfDestroyed",value:function(){if(this.destroyed)throw new Error("Trying to access destroyed InertNode")}},{key:"ensureUntabbable",value:function(){if(this.node.nodeType===Node.ELEMENT_NODE){var e=this.node;if(r.call(e,i)){if(-1===e.tabIndex&&this.hasSavedTabIndex)return;e.hasAttribute("tabindex")&&(this._savedTabIndex=e.tabIndex),e.setAttribute("tabindex","-1"),e.nodeType===Node.ELEMENT_NODE&&(e.focus=function(){},this._overrodeFocusMethod=!0)}else e.hasAttribute("tabindex")&&(this._savedTabIndex=e.tabIndex,e.removeAttribute("tabindex"))}}},{key:"addInertRoot",value:function(e){this._throwIfDestroyed(),this._inertRoots.add(e)}},{key:"removeInertRoot",value:function(e){this._throwIfDestroyed(),this._inertRoots.delete(e),0===this._inertRoots.size&&this.destructor()}},{key:"destroyed",get:function(){return this._destroyed}},{key:"hasSavedTabIndex",get:function(){return null!==this._savedTabIndex}},{key:"node",get:function(){return this._throwIfDestroyed(),this._node}},{key:"savedTabIndex",set:function(e){this._throwIfDestroyed(),this._savedTabIndex=e},get:function(){return this._throwIfDestroyed(),this._savedTabIndex}}]),n}(),t=new(function(){function t(e){if(u(this,t),!e)throw new Error("Missing required argument; InertManager needs to wrap a document.");this._document=e,this._managedNodes=new Map,this._inertRoots=new Map,this._observer=new MutationObserver(this._watchForInert.bind(this)),d(e.head||e.body||e.documentElement),"loading"===e.readyState?e.addEventListener("DOMContentLoaded",this._onDocumentLoaded.bind(this)):this._onDocumentLoaded()}return e(t,[{key:"setInert",value:function(e,t){if(t){if(this._inertRoots.has(e))return;var n=new s(e,this);if(e.setAttribute("inert",""),this._inertRoots.set(e,n),!this._document.body.contains(e))for(var i=e.parentNode;i;)11===i.nodeType&&d(i),i=i.parentNode}else{if(!this._inertRoots.has(e))return;this._inertRoots.get(e).destructor(),this._inertRoots.delete(e),e.removeAttribute("inert")}}},{key:"getInertRoot",value:function(e){return this._inertRoots.get(e)}},{key:"register",value:function(e,t){var n=this._managedNodes.get(e);return void 0!==n?n.addInertRoot(t):n=new a(e,t),this._managedNodes.set(e,n),n}},{key:"deregister",value:function(e,t){var n=this._managedNodes.get(e);return n?(n.removeInertRoot(t),n.destroyed&&this._managedNodes.delete(e),n):null}},{key:"_onDocumentLoaded",value:function(){o.call(this._document.querySelectorAll("[inert]")).forEach(function(e){this.setInert(e,!0)},this),this._observer.observe(this._document.body||this._document.documentElement,{attributes:!0,subtree:!0,childList:!0})}},{key:"_watchForInert",value:function(e,t){var i=this;e.forEach(function(e){switch(e.type){case"childList":o.call(e.addedNodes).forEach(function(e){if(e.nodeType===Node.ELEMENT_NODE){var t=o.call(e.querySelectorAll("[inert]"));r.call(e,"[inert]")&&t.unshift(e),t.forEach(function(e){this.setInert(e,!0)},i)}},i);break;case"attributes":if("inert"!==e.attributeName)return;var t=e.target,n=t.hasAttribute("inert");i.setInert(t,n)}},this)}}]),t}())(document);Element.prototype.hasOwnProperty("inert")||Object.defineProperty(Element.prototype,"inert",{enumerable:!0,get:function(){return this.hasAttribute("inert")},set:function(e){t.setInert(this,e)}})}function l(e,t,n){if(e.nodeType==Node.ELEMENT_NODE){var i=e;t&&t(i);var o=i.shadowRoot;if(o)return void l(o,t,o);if("content"==i.localName){for(var r=i,s=r.getDistributedNodes?r.getDistributedNodes():[],a=0;a<s.length;a++)l(s[a],t,n);return}if("slot"==i.localName){for(var d=i,u=d.assignedNodes?d.assignedNodes({flatten:!0}):[],h=0;h<u.length;h++)l(u[h],t,n);return}}for(var c=e.firstChild;null!=c;)l(c,t,n),c=c.nextSibling}function d(e){if(!e.querySelector("style#inert-style, link#inert-style")){var t=document.createElement("style");t.setAttribute("id","inert-style"),t.textContent="\n[inert] {\n pointer-events: none;\n cursor: default;\n}\n\n[inert], [inert] * {\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n",e.appendChild(t)}}}()}); | ||
//# sourceMappingURL=inert.min.js.map |
{ | ||
"name": "wicg-inert", | ||
"version": "3.0.2", | ||
"version": "3.0.3", | ||
"description": "A polyfill for the proposed inert API", | ||
@@ -9,3 +9,2 @@ "main": "dist/inert.js", | ||
"build": "rollup -c", | ||
"prepublishOnly": "SAUCE=true npm run test", | ||
"test": "npm run build && karma start" | ||
@@ -12,0 +11,0 @@ }, |
@@ -85,2 +85,31 @@ [![Build Status](https://travis-ci.org/WICG/inert.svg?branch=master)](https://travis-ci.org/WICG/inert) | ||
### Strict Content Security Policy | ||
By default, this polyfill will dynamically insert some CSS, which requires the | ||
style-src rule of your Content Security Policy to allow 'unsafe-inline'. | ||
It is possible avoid doing so by including those rules from a CSS stylesheet, as | ||
follows (the `id` property is used by the polyfill to know if it needs to | ||
dynamically add the missing rules): | ||
```html | ||
<link rel="stylesheet" type="text/css" href="inert.css" id="inert-style" /> | ||
``` | ||
The stylesheet should include the following rules: | ||
```css | ||
[inert] { | ||
pointer-events: none; | ||
cursor: default; | ||
} | ||
[inert], [inert] * { | ||
user-select: none; | ||
-webkit-user-select: none; | ||
-moz-user-select: none; | ||
-ms-user-select: none; | ||
} | ||
``` | ||
### Performance and gotchas | ||
@@ -87,0 +116,0 @@ |
1219
src/inert.js
@@ -6,728 +6,735 @@ /** | ||
// Convenience function for converting NodeLists. | ||
/** @type {typeof Array.prototype.slice} */ | ||
const slice = Array.prototype.slice; | ||
(function() { | ||
// Return early if we're not running inside of the browser. | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
/** | ||
* IE has a non-standard name for "matches". | ||
* @type {typeof Element.prototype.matches} | ||
*/ | ||
const matches = | ||
Element.prototype.matches || Element.prototype.msMatchesSelector; | ||
// Convenience function for converting NodeLists. | ||
/** @type {typeof Array.prototype.slice} */ | ||
const slice = Array.prototype.slice; | ||
/** @type {string} */ | ||
const _focusableElementsString = ['a[href]', | ||
'area[href]', | ||
'input:not([disabled])', | ||
'select:not([disabled])', | ||
'textarea:not([disabled])', | ||
'button:not([disabled])', | ||
'details', | ||
'summary', | ||
'iframe', | ||
'object', | ||
'embed', | ||
'[contenteditable]'].join(','); | ||
/** | ||
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert` | ||
* attribute. | ||
* | ||
* Its main functions are: | ||
* | ||
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the | ||
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering | ||
* each focusable node in the subtree with the singleton `InertManager` which manages all known | ||
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode` | ||
* instance exists for each focusable node which has at least one inert root as an ancestor. | ||
* | ||
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert` | ||
* attribute is removed from the root node). This is handled in the destructor, which calls the | ||
* `deregister` method on `InertManager` for each managed inert node. | ||
*/ | ||
class InertRoot { | ||
/** | ||
* @param {!Element} rootElement The Element at the root of the inert subtree. | ||
* @param {!InertManager} inertManager The global singleton InertManager object. | ||
* IE has a non-standard name for "matches". | ||
* @type {typeof Element.prototype.matches} | ||
*/ | ||
constructor(rootElement, inertManager) { | ||
/** @type {!InertManager} */ | ||
this._inertManager = inertManager; | ||
const matches = | ||
Element.prototype.matches || Element.prototype.msMatchesSelector; | ||
/** @type {!Element} */ | ||
this._rootElement = rootElement; | ||
/** @type {string} */ | ||
const _focusableElementsString = ['a[href]', | ||
'area[href]', | ||
'input:not([disabled])', | ||
'select:not([disabled])', | ||
'textarea:not([disabled])', | ||
'button:not([disabled])', | ||
'details', | ||
'summary', | ||
'iframe', | ||
'object', | ||
'embed', | ||
'[contenteditable]'].join(','); | ||
/** | ||
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert` | ||
* attribute. | ||
* | ||
* Its main functions are: | ||
* | ||
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the | ||
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering | ||
* each focusable node in the subtree with the singleton `InertManager` which manages all known | ||
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode` | ||
* instance exists for each focusable node which has at least one inert root as an ancestor. | ||
* | ||
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert` | ||
* attribute is removed from the root node). This is handled in the destructor, which calls the | ||
* `deregister` method on `InertManager` for each managed inert node. | ||
*/ | ||
class InertRoot { | ||
/** | ||
* @type {!Set<!InertNode>} | ||
* All managed focusable nodes in this InertRoot's subtree. | ||
* @param {!Element} rootElement The Element at the root of the inert subtree. | ||
* @param {!InertManager} inertManager The global singleton InertManager object. | ||
*/ | ||
this._managedNodes = new Set(); | ||
constructor(rootElement, inertManager) { | ||
/** @type {!InertManager} */ | ||
this._inertManager = inertManager; | ||
// Make the subtree hidden from assistive technology | ||
if (this._rootElement.hasAttribute('aria-hidden')) { | ||
/** @type {?string} */ | ||
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden'); | ||
} else { | ||
this._savedAriaHidden = null; | ||
} | ||
this._rootElement.setAttribute('aria-hidden', 'true'); | ||
/** @type {!Element} */ | ||
this._rootElement = rootElement; | ||
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes | ||
this._makeSubtreeUnfocusable(this._rootElement); | ||
/** | ||
* @type {!Set<!InertNode>} | ||
* All managed focusable nodes in this InertRoot's subtree. | ||
*/ | ||
this._managedNodes = new Set(); | ||
// Watch for: | ||
// - any additions in the subtree: make them unfocusable too | ||
// - any removals from the subtree: remove them from this inert root's managed nodes | ||
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable | ||
// element, make that node a managed node. | ||
this._observer = new MutationObserver(this._onMutation.bind(this)); | ||
this._observer.observe(this._rootElement, {attributes: true, childList: true, subtree: true}); | ||
} | ||
// Make the subtree hidden from assistive technology | ||
if (this._rootElement.hasAttribute('aria-hidden')) { | ||
/** @type {?string} */ | ||
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden'); | ||
} else { | ||
this._savedAriaHidden = null; | ||
} | ||
this._rootElement.setAttribute('aria-hidden', 'true'); | ||
/** | ||
* Call this whenever this object is about to become obsolete. This unwinds all of the state | ||
* stored in this object and updates the state of all of the managed nodes. | ||
*/ | ||
destructor() { | ||
this._observer.disconnect(); | ||
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes | ||
this._makeSubtreeUnfocusable(this._rootElement); | ||
if (this._rootElement) { | ||
if (this._savedAriaHidden !== null) { | ||
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden); | ||
} else { | ||
this._rootElement.removeAttribute('aria-hidden'); | ||
} | ||
// Watch for: | ||
// - any additions in the subtree: make them unfocusable too | ||
// - any removals from the subtree: remove them from this inert root's managed nodes | ||
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable | ||
// element, make that node a managed node. | ||
this._observer = new MutationObserver(this._onMutation.bind(this)); | ||
this._observer.observe(this._rootElement, {attributes: true, childList: true, subtree: true}); | ||
} | ||
this._managedNodes.forEach(function(inertNode) { | ||
this._unmanageNode(inertNode.node); | ||
}, this); | ||
/** | ||
* Call this whenever this object is about to become obsolete. This unwinds all of the state | ||
* stored in this object and updates the state of all of the managed nodes. | ||
*/ | ||
destructor() { | ||
this._observer.disconnect(); | ||
// Note we cast the nulls to the ANY type here because: | ||
// 1) We want the class properties to be declared as non-null, or else we | ||
// need even more casts throughout this code. All bets are off if an | ||
// instance has been destroyed and a method is called. | ||
// 2) We don't want to cast "this", because we want type-aware optimizations | ||
// to know which properties we're setting. | ||
this._observer = /** @type {?} */ (null); | ||
this._rootElement = /** @type {?} */ (null); | ||
this._managedNodes = /** @type {?} */ (null); | ||
this._inertManager = /** @type {?} */ (null); | ||
} | ||
if (this._rootElement) { | ||
if (this._savedAriaHidden !== null) { | ||
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden); | ||
} else { | ||
this._rootElement.removeAttribute('aria-hidden'); | ||
} | ||
} | ||
/** | ||
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set. | ||
*/ | ||
get managedNodes() { | ||
return new Set(this._managedNodes); | ||
} | ||
this._managedNodes.forEach(function(inertNode) { | ||
this._unmanageNode(inertNode.node); | ||
}, this); | ||
/** @return {boolean} */ | ||
get hasSavedAriaHidden() { | ||
return this._savedAriaHidden !== null; | ||
} | ||
// Note we cast the nulls to the ANY type here because: | ||
// 1) We want the class properties to be declared as non-null, or else we | ||
// need even more casts throughout this code. All bets are off if an | ||
// instance has been destroyed and a method is called. | ||
// 2) We don't want to cast "this", because we want type-aware optimizations | ||
// to know which properties we're setting. | ||
this._observer = /** @type {?} */ (null); | ||
this._rootElement = /** @type {?} */ (null); | ||
this._managedNodes = /** @type {?} */ (null); | ||
this._inertManager = /** @type {?} */ (null); | ||
} | ||
/** @param {?string} ariaHidden */ | ||
set savedAriaHidden(ariaHidden) { | ||
this._savedAriaHidden = ariaHidden; | ||
} | ||
/** | ||
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set. | ||
*/ | ||
get managedNodes() { | ||
return new Set(this._managedNodes); | ||
} | ||
/** @return {?string} */ | ||
get savedAriaHidden() { | ||
return this._savedAriaHidden; | ||
} | ||
/** @return {boolean} */ | ||
get hasSavedAriaHidden() { | ||
return this._savedAriaHidden !== null; | ||
} | ||
/** | ||
* @param {!Node} startNode | ||
*/ | ||
_makeSubtreeUnfocusable(startNode) { | ||
composedTreeWalk(startNode, (node) => this._visitNode(node)); | ||
/** @param {?string} ariaHidden */ | ||
set savedAriaHidden(ariaHidden) { | ||
this._savedAriaHidden = ariaHidden; | ||
} | ||
let activeElement = document.activeElement; | ||
/** @return {?string} */ | ||
get savedAriaHidden() { | ||
return this._savedAriaHidden; | ||
} | ||
if (!document.body.contains(startNode)) { | ||
// startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement. | ||
let node = startNode; | ||
/** @type {!ShadowRoot|undefined} */ | ||
let root = undefined; | ||
while (node) { | ||
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | ||
root = /** @type {!ShadowRoot} */ (node); | ||
break; | ||
/** | ||
* @param {!Node} startNode | ||
*/ | ||
_makeSubtreeUnfocusable(startNode) { | ||
composedTreeWalk(startNode, (node) => this._visitNode(node)); | ||
let activeElement = document.activeElement; | ||
if (!document.body.contains(startNode)) { | ||
// startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement. | ||
let node = startNode; | ||
/** @type {!ShadowRoot|undefined} */ | ||
let root = undefined; | ||
while (node) { | ||
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { | ||
root = /** @type {!ShadowRoot} */ (node); | ||
break; | ||
} | ||
node = node.parentNode; | ||
} | ||
node = node.parentNode; | ||
if (root) { | ||
activeElement = root.activeElement; | ||
} | ||
} | ||
if (root) { | ||
activeElement = root.activeElement; | ||
if (startNode.contains(activeElement)) { | ||
activeElement.blur(); | ||
// In IE11, if an element is already focused, and then set to tabindex=-1 | ||
// calling blur() will not actually move the focus. | ||
// To work around this we call focus() on the body instead. | ||
if (activeElement === document.activeElement) { | ||
document.body.focus(); | ||
} | ||
} | ||
} | ||
if (startNode.contains(activeElement)) { | ||
activeElement.blur(); | ||
// In IE11, if an element is already focused, and then set to tabindex=-1 | ||
// calling blur() will not actually move the focus. | ||
// To work around this we call focus() on the body instead. | ||
if (activeElement === document.activeElement) { | ||
document.body.focus(); | ||
/** | ||
* @param {!Node} node | ||
*/ | ||
_visitNode(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
const element = /** @type {!Element} */ (node); | ||
// If a descendant inert root becomes un-inert, its descendants will still be inert because of | ||
// this inert root, so all of its managed nodes need to be adopted by this InertRoot. | ||
if (element !== this._rootElement && element.hasAttribute('inert')) { | ||
this._adoptInertRoot(element); | ||
} | ||
if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) { | ||
this._manageNode(element); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!Node} node | ||
*/ | ||
_visitNode(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
/** | ||
* Register the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
_manageNode(node) { | ||
const inertNode = this._inertManager.register(node, this); | ||
this._managedNodes.add(inertNode); | ||
} | ||
const element = /** @type {!Element} */ (node); | ||
// If a descendant inert root becomes un-inert, its descendants will still be inert because of | ||
// this inert root, so all of its managed nodes need to be adopted by this InertRoot. | ||
if (element !== this._rootElement && element.hasAttribute('inert')) { | ||
this._adoptInertRoot(element); | ||
/** | ||
* Unregister the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
_unmanageNode(node) { | ||
const inertNode = this._inertManager.deregister(node, this); | ||
if (inertNode) { | ||
this._managedNodes.delete(inertNode); | ||
} | ||
} | ||
if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) { | ||
this._manageNode(element); | ||
/** | ||
* Unregister the entire subtree starting at `startNode`. | ||
* @param {!Node} startNode | ||
*/ | ||
_unmanageSubtree(startNode) { | ||
composedTreeWalk(startNode, (node) => this._unmanageNode(node)); | ||
} | ||
} | ||
/** | ||
* Register the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
_manageNode(node) { | ||
const inertNode = this._inertManager.register(node, this); | ||
this._managedNodes.add(inertNode); | ||
} | ||
/** | ||
* If a descendant node is found with an `inert` attribute, adopt its managed nodes. | ||
* @param {!Element} node | ||
*/ | ||
_adoptInertRoot(node) { | ||
let inertSubroot = this._inertManager.getInertRoot(node); | ||
/** | ||
* Unregister the given node with this InertRoot and with InertManager. | ||
* @param {!Node} node | ||
*/ | ||
_unmanageNode(node) { | ||
const inertNode = this._inertManager.deregister(node, this); | ||
if (inertNode) { | ||
this._managedNodes.delete(inertNode); | ||
// During initialisation this inert root may not have been registered yet, | ||
// so register it now if need be. | ||
if (!inertSubroot) { | ||
this._inertManager.setInert(node, true); | ||
inertSubroot = this._inertManager.getInertRoot(node); | ||
} | ||
inertSubroot.managedNodes.forEach(function(savedInertNode) { | ||
this._manageNode(savedInertNode.node); | ||
}, this); | ||
} | ||
} | ||
/** | ||
* Unregister the entire subtree starting at `startNode`. | ||
* @param {!Node} startNode | ||
*/ | ||
_unmanageSubtree(startNode) { | ||
composedTreeWalk(startNode, (node) => this._unmanageNode(node)); | ||
/** | ||
* Callback used when mutation observer detects subtree additions, removals, or attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
_onMutation(records, self) { | ||
records.forEach(function(record) { | ||
const target = /** @type {!Element} */ (record.target); | ||
if (record.type === 'childList') { | ||
// Manage added nodes | ||
slice.call(record.addedNodes).forEach(function(node) { | ||
this._makeSubtreeUnfocusable(node); | ||
}, this); | ||
// Un-manage removed nodes | ||
slice.call(record.removedNodes).forEach(function(node) { | ||
this._unmanageSubtree(node); | ||
}, this); | ||
} else if (record.type === 'attributes') { | ||
if (record.attributeName === 'tabindex') { | ||
// Re-initialise inert node if tabindex changes | ||
this._manageNode(target); | ||
} else if (target !== this._rootElement && | ||
record.attributeName === 'inert' && | ||
target.hasAttribute('inert')) { | ||
// If a new inert root is added, adopt its managed nodes and make sure it knows about the | ||
// already managed nodes from this inert subroot. | ||
this._adoptInertRoot(target); | ||
const inertSubroot = this._inertManager.getInertRoot(target); | ||
this._managedNodes.forEach(function(managedNode) { | ||
if (target.contains(managedNode.node)) { | ||
inertSubroot._manageNode(managedNode.node); | ||
} | ||
}); | ||
} | ||
} | ||
}, this); | ||
} | ||
} | ||
/** | ||
* If a descendant node is found with an `inert` attribute, adopt its managed nodes. | ||
* @param {!Element} node | ||
* `InertNode` initialises and manages a single inert node. | ||
* A node is inert if it is a descendant of one or more inert root elements. | ||
* | ||
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and | ||
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element | ||
* is intrinsically focusable or not. | ||
* | ||
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an | ||
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the | ||
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s | ||
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists, | ||
* or removes the `tabindex` attribute if the element is intrinsically focusable. | ||
*/ | ||
_adoptInertRoot(node) { | ||
let inertSubroot = this._inertManager.getInertRoot(node); | ||
class InertNode { | ||
/** | ||
* @param {!Node} node A focusable element to be made inert. | ||
* @param {!InertRoot} inertRoot The inert root element associated with this inert node. | ||
*/ | ||
constructor(node, inertRoot) { | ||
/** @type {!Node} */ | ||
this._node = node; | ||
// During initialisation this inert root may not have been registered yet, | ||
// so register it now if need be. | ||
if (!inertSubroot) { | ||
this._inertManager.setInert(node, true); | ||
inertSubroot = this._inertManager.getInertRoot(node); | ||
/** @type {boolean} */ | ||
this._overrodeFocusMethod = false; | ||
/** | ||
* @type {!Set<!InertRoot>} The set of descendant inert roots. | ||
* If and only if this set becomes empty, this node is no longer inert. | ||
*/ | ||
this._inertRoots = new Set([inertRoot]); | ||
/** @type {?number} */ | ||
this._savedTabIndex = null; | ||
/** @type {boolean} */ | ||
this._destroyed = false; | ||
// Save any prior tabindex info and make this node untabbable | ||
this.ensureUntabbable(); | ||
} | ||
inertSubroot.managedNodes.forEach(function(savedInertNode) { | ||
this._manageNode(savedInertNode.node); | ||
}, this); | ||
} | ||
/** | ||
* Call this whenever this object is about to become obsolete. | ||
* This makes the managed node focusable again and deletes all of the previously stored state. | ||
*/ | ||
destructor() { | ||
this._throwIfDestroyed(); | ||
/** | ||
* Callback used when mutation observer detects subtree additions, removals, or attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
_onMutation(records, self) { | ||
records.forEach(function(record) { | ||
const target = /** @type {!Element} */ (record.target); | ||
if (record.type === 'childList') { | ||
// Manage added nodes | ||
slice.call(record.addedNodes).forEach(function(node) { | ||
this._makeSubtreeUnfocusable(node); | ||
}, this); | ||
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) { | ||
const element = /** @type {!Element} */ (this._node); | ||
if (this._savedTabIndex !== null) { | ||
element.setAttribute('tabindex', this._savedTabIndex); | ||
} else { | ||
element.removeAttribute('tabindex'); | ||
} | ||
// Un-manage removed nodes | ||
slice.call(record.removedNodes).forEach(function(node) { | ||
this._unmanageSubtree(node); | ||
}, this); | ||
} else if (record.type === 'attributes') { | ||
if (record.attributeName === 'tabindex') { | ||
// Re-initialise inert node if tabindex changes | ||
this._manageNode(target); | ||
} else if (target !== this._rootElement && | ||
record.attributeName === 'inert' && | ||
target.hasAttribute('inert')) { | ||
// If a new inert root is added, adopt its managed nodes and make sure it knows about the | ||
// already managed nodes from this inert subroot. | ||
this._adoptInertRoot(target); | ||
const inertSubroot = this._inertManager.getInertRoot(target); | ||
this._managedNodes.forEach(function(managedNode) { | ||
if (target.contains(managedNode.node)) { | ||
inertSubroot._manageNode(managedNode.node); | ||
} | ||
}); | ||
// Use `delete` to restore native focus method. | ||
if (this._overrodeFocusMethod) { | ||
delete element.focus; | ||
} | ||
} | ||
}, this); | ||
} | ||
} | ||
/** | ||
* `InertNode` initialises and manages a single inert node. | ||
* A node is inert if it is a descendant of one or more inert root elements. | ||
* | ||
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and | ||
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element | ||
* is intrinsically focusable or not. | ||
* | ||
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an | ||
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the | ||
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s | ||
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists, | ||
* or removes the `tabindex` attribute if the element is intrinsically focusable. | ||
*/ | ||
class InertNode { | ||
/** | ||
* @param {!Node} node A focusable element to be made inert. | ||
* @param {!InertRoot} inertRoot The inert root element associated with this inert node. | ||
*/ | ||
constructor(node, inertRoot) { | ||
/** @type {!Node} */ | ||
this._node = node; | ||
// See note in InertRoot.destructor for why we cast these nulls to ANY. | ||
this._node = /** @type {?} */ (null); | ||
this._inertRoots = /** @type {?} */ (null); | ||
this._destroyed = true; | ||
} | ||
/** @type {boolean} */ | ||
this._overrodeFocusMethod = false; | ||
/** | ||
* @type {boolean} Whether this object is obsolete because the managed node is no longer inert. | ||
* If the object has been destroyed, any attempt to access it will cause an exception. | ||
*/ | ||
get destroyed() { | ||
return /** @type {!InertNode} */ (this)._destroyed; | ||
} | ||
/** | ||
* @type {!Set<!InertRoot>} The set of descendant inert roots. | ||
* If and only if this set becomes empty, this node is no longer inert. | ||
* Throw if user tries to access destroyed InertNode. | ||
*/ | ||
this._inertRoots = new Set([inertRoot]); | ||
_throwIfDestroyed() { | ||
if (this.destroyed) { | ||
throw new Error('Trying to access destroyed InertNode'); | ||
} | ||
} | ||
/** @type {?number} */ | ||
this._savedTabIndex = null; | ||
/** @return {boolean} */ | ||
get hasSavedTabIndex() { | ||
return this._savedTabIndex !== null; | ||
} | ||
/** @type {boolean} */ | ||
this._destroyed = false; | ||
/** @return {!Node} */ | ||
get node() { | ||
this._throwIfDestroyed(); | ||
return this._node; | ||
} | ||
// Save any prior tabindex info and make this node untabbable | ||
this.ensureUntabbable(); | ||
} | ||
/** @param {?number} tabIndex */ | ||
set savedTabIndex(tabIndex) { | ||
this._throwIfDestroyed(); | ||
this._savedTabIndex = tabIndex; | ||
} | ||
/** | ||
* Call this whenever this object is about to become obsolete. | ||
* This makes the managed node focusable again and deletes all of the previously stored state. | ||
*/ | ||
destructor() { | ||
this._throwIfDestroyed(); | ||
/** @return {?number} */ | ||
get savedTabIndex() { | ||
this._throwIfDestroyed(); | ||
return this._savedTabIndex; | ||
} | ||
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) { | ||
const element = /** @type {!Element} */ (this._node); | ||
if (this._savedTabIndex !== null) { | ||
element.setAttribute('tabindex', this._savedTabIndex); | ||
} else { | ||
element.removeAttribute('tabindex'); | ||
/** Save the existing tabindex value and make the node untabbable and unfocusable */ | ||
ensureUntabbable() { | ||
if (this.node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
const element = /** @type {!Element} */ (this.node); | ||
if (matches.call(element, _focusableElementsString)) { | ||
if (/** @type {!HTMLElement} */ (element).tabIndex === -1 && | ||
this.hasSavedTabIndex) { | ||
return; | ||
} | ||
// Use `delete` to restore native focus method. | ||
if (this._overrodeFocusMethod) { | ||
delete element.focus; | ||
if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex; | ||
} | ||
element.setAttribute('tabindex', '-1'); | ||
if (element.nodeType === Node.ELEMENT_NODE) { | ||
element.focus = function() {}; | ||
this._overrodeFocusMethod = true; | ||
} | ||
} else if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex; | ||
element.removeAttribute('tabindex'); | ||
} | ||
} | ||
// See note in InertRoot.destructor for why we cast these nulls to ANY. | ||
this._node = /** @type {?} */ (null); | ||
this._inertRoots = /** @type {?} */ (null); | ||
this._destroyed = true; | ||
} | ||
/** | ||
* Add another inert root to this inert node's set of managing inert roots. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
addInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.add(inertRoot); | ||
} | ||
/** | ||
* @type {boolean} Whether this object is obsolete because the managed node is no longer inert. | ||
* If the object has been destroyed, any attempt to access it will cause an exception. | ||
*/ | ||
get destroyed() { | ||
return /** @type {!InertNode} */ (this)._destroyed; | ||
/** | ||
* Remove the given inert root from this inert node's set of managing inert roots. | ||
* If the set of managing inert roots becomes empty, this node is no longer inert, | ||
* so the object should be destroyed. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
removeInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.delete(inertRoot); | ||
if (this._inertRoots.size === 0) { | ||
this.destructor(); | ||
} | ||
} | ||
} | ||
/** | ||
* Throw if user tries to access destroyed InertNode. | ||
* InertManager is a per-document singleton object which manages all inert roots and nodes. | ||
* | ||
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert` | ||
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element. | ||
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant | ||
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance | ||
* is created for each such node, via the `_managedNodes` map. | ||
*/ | ||
_throwIfDestroyed() { | ||
if (this.destroyed) { | ||
throw new Error('Trying to access destroyed InertNode'); | ||
} | ||
} | ||
class InertManager { | ||
/** | ||
* @param {!Document} document | ||
*/ | ||
constructor(document) { | ||
if (!document) { | ||
throw new Error('Missing required argument; InertManager needs to wrap a document.'); | ||
} | ||
/** @return {boolean} */ | ||
get hasSavedTabIndex() { | ||
return this._savedTabIndex !== null; | ||
} | ||
/** @type {!Document} */ | ||
this._document = document; | ||
/** @return {!Node} */ | ||
get node() { | ||
this._throwIfDestroyed(); | ||
return this._node; | ||
} | ||
/** | ||
* All managed nodes known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertNode>} | ||
*/ | ||
this._managedNodes = new Map(); | ||
/** @param {?number} tabIndex */ | ||
set savedTabIndex(tabIndex) { | ||
this._throwIfDestroyed(); | ||
this._savedTabIndex = tabIndex; | ||
} | ||
/** | ||
* All inert roots known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertRoot>} | ||
*/ | ||
this._inertRoots = new Map(); | ||
/** @return {?number} */ | ||
get savedTabIndex() { | ||
this._throwIfDestroyed(); | ||
return this._savedTabIndex; | ||
} | ||
/** | ||
* Observer for mutations on `document.body`. | ||
* @type {!MutationObserver} | ||
*/ | ||
this._observer = new MutationObserver(this._watchForInert.bind(this)); | ||
/** Save the existing tabindex value and make the node untabbable and unfocusable */ | ||
ensureUntabbable() { | ||
if (this.node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
const element = /** @type {!Element} */ (this.node); | ||
if (matches.call(element, _focusableElementsString)) { | ||
if (/** @type {!HTMLElement} */ (element).tabIndex === -1 && | ||
this.hasSavedTabIndex) { | ||
return; | ||
} | ||
// Add inert style. | ||
addInertStyle(document.head || document.body || document.documentElement); | ||
if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex; | ||
// Wait for document to be loaded. | ||
if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this)); | ||
} else { | ||
this._onDocumentLoaded(); | ||
} | ||
element.setAttribute('tabindex', '-1'); | ||
if (element.nodeType === Node.ELEMENT_NODE) { | ||
element.focus = function() {}; | ||
this._overrodeFocusMethod = true; | ||
} | ||
} else if (element.hasAttribute('tabindex')) { | ||
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex; | ||
element.removeAttribute('tabindex'); | ||
} | ||
} | ||
/** | ||
* Add another inert root to this inert node's set of managing inert roots. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
addInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.add(inertRoot); | ||
} | ||
/** | ||
* Set whether the given element should be an inert root or not. | ||
* @param {!Element} root | ||
* @param {boolean} inert | ||
*/ | ||
setInert(root, inert) { | ||
if (inert) { | ||
if (this._inertRoots.has(root)) { // element is already inert | ||
return; | ||
} | ||
/** | ||
* Remove the given inert root from this inert node's set of managing inert roots. | ||
* If the set of managing inert roots becomes empty, this node is no longer inert, | ||
* so the object should be destroyed. | ||
* @param {!InertRoot} inertRoot | ||
*/ | ||
removeInertRoot(inertRoot) { | ||
this._throwIfDestroyed(); | ||
this._inertRoots.delete(inertRoot); | ||
if (this._inertRoots.size === 0) { | ||
this.destructor(); | ||
} | ||
} | ||
} | ||
const inertRoot = new InertRoot(root, this); | ||
root.setAttribute('inert', ''); | ||
this._inertRoots.set(root, inertRoot); | ||
// If not contained in the document, it must be in a shadowRoot. | ||
// Ensure inert styles are added there. | ||
if (!this._document.body.contains(root)) { | ||
let parent = root.parentNode; | ||
while (parent) { | ||
if (parent.nodeType === 11) { | ||
addInertStyle(parent); | ||
} | ||
parent = parent.parentNode; | ||
} | ||
} | ||
} else { | ||
if (!this._inertRoots.has(root)) { // element is already non-inert | ||
return; | ||
} | ||
/** | ||
* InertManager is a per-document singleton object which manages all inert roots and nodes. | ||
* | ||
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert` | ||
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element. | ||
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant | ||
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance | ||
* is created for each such node, via the `_managedNodes` map. | ||
*/ | ||
class InertManager { | ||
/** | ||
* @param {!Document} document | ||
*/ | ||
constructor(document) { | ||
if (!document) { | ||
throw new Error('Missing required argument; InertManager needs to wrap a document.'); | ||
const inertRoot = this._inertRoots.get(root); | ||
inertRoot.destructor(); | ||
this._inertRoots.delete(root); | ||
root.removeAttribute('inert'); | ||
} | ||
} | ||
/** @type {!Document} */ | ||
this._document = document; | ||
/** | ||
* All managed nodes known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertNode>} | ||
* Get the InertRoot object corresponding to the given inert root element, if any. | ||
* @param {!Node} element | ||
* @return {!InertRoot|undefined} | ||
*/ | ||
this._managedNodes = new Map(); | ||
getInertRoot(element) { | ||
return this._inertRoots.get(element); | ||
} | ||
/** | ||
* All inert roots known to this InertManager. In a map to allow looking up by Node. | ||
* @type {!Map<!Node, !InertRoot>} | ||
* Register the given InertRoot as managing the given node. | ||
* In the case where the node has a previously existing inert root, this inert root will | ||
* be added to its set of inert roots. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {!InertNode} inertNode | ||
*/ | ||
this._inertRoots = new Map(); | ||
register(node, inertRoot) { | ||
let inertNode = this._managedNodes.get(node); | ||
if (inertNode !== undefined) { // node was already in an inert subtree | ||
inertNode.addInertRoot(inertRoot); | ||
} else { | ||
inertNode = new InertNode(node, inertRoot); | ||
} | ||
/** | ||
* Observer for mutations on `document.body`. | ||
* @type {!MutationObserver} | ||
*/ | ||
this._observer = new MutationObserver(this._watchForInert.bind(this)); | ||
this._managedNodes.set(node, inertNode); | ||
// Add inert style. | ||
addInertStyle(document.head || document.body || document.documentElement); | ||
// Wait for document to be loaded. | ||
if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this)); | ||
} else { | ||
this._onDocumentLoaded(); | ||
return inertNode; | ||
} | ||
} | ||
/** | ||
* Set whether the given element should be an inert root or not. | ||
* @param {!Element} root | ||
* @param {boolean} inert | ||
*/ | ||
setInert(root, inert) { | ||
if (inert) { | ||
if (this._inertRoots.has(root)) { // element is already inert | ||
return; | ||
/** | ||
* De-register the given InertRoot as managing the given inert node. | ||
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert | ||
* node from the InertManager's set of managed nodes if it is destroyed. | ||
* If the node is not currently managed, this is essentially a no-op. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {?InertNode} The potentially destroyed InertNode associated with this node, if any. | ||
*/ | ||
deregister(node, inertRoot) { | ||
const inertNode = this._managedNodes.get(node); | ||
if (!inertNode) { | ||
return null; | ||
} | ||
const inertRoot = new InertRoot(root, this); | ||
root.setAttribute('inert', ''); | ||
this._inertRoots.set(root, inertRoot); | ||
// If not contained in the document, it must be in a shadowRoot. | ||
// Ensure inert styles are added there. | ||
if (!this._document.body.contains(root)) { | ||
let parent = root.parentNode; | ||
while (parent) { | ||
if (parent.nodeType === 11) { | ||
addInertStyle(parent); | ||
} | ||
parent = parent.parentNode; | ||
} | ||
inertNode.removeInertRoot(inertRoot); | ||
if (inertNode.destroyed) { | ||
this._managedNodes.delete(node); | ||
} | ||
} else { | ||
if (!this._inertRoots.has(root)) { // element is already non-inert | ||
return; | ||
} | ||
const inertRoot = this._inertRoots.get(root); | ||
inertRoot.destructor(); | ||
this._inertRoots.delete(root); | ||
root.removeAttribute('inert'); | ||
return inertNode; | ||
} | ||
} | ||
/** | ||
* Get the InertRoot object corresponding to the given inert root element, if any. | ||
* @param {!Node} element | ||
* @return {!InertRoot|undefined} | ||
*/ | ||
getInertRoot(element) { | ||
return this._inertRoots.get(element); | ||
} | ||
/** | ||
* Callback used when document has finished loading. | ||
*/ | ||
_onDocumentLoaded() { | ||
// Find all inert roots in document and make them actually inert. | ||
const inertElements = slice.call(this._document.querySelectorAll('[inert]')); | ||
inertElements.forEach(function(inertElement) { | ||
this.setInert(inertElement, true); | ||
}, this); | ||
/** | ||
* Register the given InertRoot as managing the given node. | ||
* In the case where the node has a previously existing inert root, this inert root will | ||
* be added to its set of inert roots. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {!InertNode} inertNode | ||
*/ | ||
register(node, inertRoot) { | ||
let inertNode = this._managedNodes.get(node); | ||
if (inertNode !== undefined) { // node was already in an inert subtree | ||
inertNode.addInertRoot(inertRoot); | ||
} else { | ||
inertNode = new InertNode(node, inertRoot); | ||
// Comment this out to use programmatic API only. | ||
this._observer.observe(this._document.body || this._document.documentElement, {attributes: true, subtree: true, childList: true}); | ||
} | ||
this._managedNodes.set(node, inertNode); | ||
return inertNode; | ||
/** | ||
* Callback used when mutation observer detects attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
_watchForInert(records, self) { | ||
const _this = this; | ||
records.forEach(function(record) { | ||
switch (record.type) { | ||
case 'childList': | ||
slice.call(record.addedNodes).forEach(function(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
const inertElements = slice.call(node.querySelectorAll('[inert]')); | ||
if (matches.call(node, '[inert]')) { | ||
inertElements.unshift(node); | ||
} | ||
inertElements.forEach(function(inertElement) { | ||
this.setInert(inertElement, true); | ||
}, _this); | ||
}, _this); | ||
break; | ||
case 'attributes': | ||
if (record.attributeName !== 'inert') { | ||
return; | ||
} | ||
const target = /** @type {!Element} */ (record.target); | ||
const inert = target.hasAttribute('inert'); | ||
_this.setInert(target, inert); | ||
break; | ||
} | ||
}, this); | ||
} | ||
} | ||
/** | ||
* De-register the given InertRoot as managing the given inert node. | ||
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert | ||
* node from the InertManager's set of managed nodes if it is destroyed. | ||
* If the node is not currently managed, this is essentially a no-op. | ||
* Recursively walk the composed tree from |node|. | ||
* @param {!Node} node | ||
* @param {!InertRoot} inertRoot | ||
* @return {?InertNode} The potentially destroyed InertNode associated with this node, if any. | ||
* @param {(function (!Element))=} callback Callback to be called for each element traversed, | ||
* before descending into child nodes. | ||
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any. | ||
*/ | ||
deregister(node, inertRoot) { | ||
const inertNode = this._managedNodes.get(node); | ||
if (!inertNode) { | ||
return null; | ||
} | ||
function composedTreeWalk(node, callback, shadowRootAncestor) { | ||
if (node.nodeType == Node.ELEMENT_NODE) { | ||
const element = /** @type {!Element} */ (node); | ||
if (callback) { | ||
callback(element); | ||
} | ||
inertNode.removeInertRoot(inertRoot); | ||
if (inertNode.destroyed) { | ||
this._managedNodes.delete(node); | ||
} | ||
// Descend into node: | ||
// If it has a ShadowRoot, ignore all child elements - these will be picked | ||
// up by the <content> or <shadow> elements. Descend straight into the | ||
// ShadowRoot. | ||
const shadowRoot = /** @type {!HTMLElement} */ (element).shadowRoot; | ||
if (shadowRoot) { | ||
composedTreeWalk(shadowRoot, callback, shadowRoot); | ||
return; | ||
} | ||
return inertNode; | ||
} | ||
// If it is a <content> element, descend into distributed elements - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'content') { | ||
const content = /** @type {!HTMLContentElement} */ (element); | ||
// Verifies if ShadowDom v0 is supported. | ||
const distributedNodes = content.getDistributedNodes ? | ||
content.getDistributedNodes() : []; | ||
for (let i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
} | ||
return; | ||
} | ||
/** | ||
* Callback used when document has finished loading. | ||
*/ | ||
_onDocumentLoaded() { | ||
// Find all inert roots in document and make them actually inert. | ||
const inertElements = slice.call(this._document.querySelectorAll('[inert]')); | ||
inertElements.forEach(function(inertElement) { | ||
this.setInert(inertElement, true); | ||
}, this); | ||
// Comment this out to use programmatic API only. | ||
this._observer.observe(this._document.body || this._document.documentElement, {attributes: true, subtree: true, childList: true}); | ||
} | ||
/** | ||
* Callback used when mutation observer detects attribute changes. | ||
* @param {!Array<!MutationRecord>} records | ||
* @param {!MutationObserver} self | ||
*/ | ||
_watchForInert(records, self) { | ||
const _this = this; | ||
records.forEach(function(record) { | ||
switch (record.type) { | ||
case 'childList': | ||
slice.call(record.addedNodes).forEach(function(node) { | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
return; | ||
} | ||
const inertElements = slice.call(node.querySelectorAll('[inert]')); | ||
if (matches.call(node, '[inert]')) { | ||
inertElements.unshift(node); | ||
} | ||
inertElements.forEach(function(inertElement) { | ||
this.setInert(inertElement, true); | ||
}, _this); | ||
}, _this); | ||
break; | ||
case 'attributes': | ||
if (record.attributeName !== 'inert') { | ||
return; | ||
// If it is a <slot> element, descend into assigned nodes - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'slot') { | ||
const slot = /** @type {!HTMLSlotElement} */ (element); | ||
// Verify if ShadowDom v1 is supported. | ||
const distributedNodes = slot.assignedNodes ? | ||
slot.assignedNodes({flatten: true}) : []; | ||
for (let i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
} | ||
const target = /** @type {!Element} */ (record.target); | ||
const inert = target.hasAttribute('inert'); | ||
_this.setInert(target, inert); | ||
break; | ||
return; | ||
} | ||
}, this); | ||
} | ||
} | ||
/** | ||
* Recursively walk the composed tree from |node|. | ||
* @param {!Node} node | ||
* @param {(function (!Element))=} callback Callback to be called for each element traversed, | ||
* before descending into child nodes. | ||
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any. | ||
*/ | ||
function composedTreeWalk(node, callback, shadowRootAncestor) { | ||
if (node.nodeType == Node.ELEMENT_NODE) { | ||
const element = /** @type {!Element} */ (node); | ||
if (callback) { | ||
callback(element); | ||
} | ||
// Descend into node: | ||
// If it has a ShadowRoot, ignore all child elements - these will be picked | ||
// up by the <content> or <shadow> elements. Descend straight into the | ||
// ShadowRoot. | ||
const shadowRoot = /** @type {!HTMLElement} */ (element).shadowRoot; | ||
if (shadowRoot) { | ||
composedTreeWalk(shadowRoot, callback, shadowRoot); | ||
return; | ||
// If it is neither the parent of a ShadowRoot, a <content> element, a <slot> | ||
// element, nor a <shadow> element recurse normally. | ||
let child = node.firstChild; | ||
while (child != null) { | ||
composedTreeWalk(child, callback, shadowRootAncestor); | ||
child = child.nextSibling; | ||
} | ||
} | ||
// If it is a <content> element, descend into distributed elements - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'content') { | ||
const content = /** @type {!HTMLContentElement} */ (element); | ||
// Verifies if ShadowDom v0 is supported. | ||
const distributedNodes = content.getDistributedNodes ? | ||
content.getDistributedNodes() : []; | ||
for (let i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
} | ||
/** | ||
* Adds a style element to the node containing the inert specific styles | ||
* @param {!Node} node | ||
*/ | ||
function addInertStyle(node) { | ||
if (node.querySelector('style#inert-style, link#inert-style')) { | ||
return; | ||
} | ||
// If it is a <slot> element, descend into assigned nodes - these | ||
// are elements from outside the shadow root which are rendered inside the | ||
// shadow DOM. | ||
if (element.localName == 'slot') { | ||
const slot = /** @type {!HTMLSlotElement} */ (element); | ||
// Verify if ShadowDom v1 is supported. | ||
const distributedNodes = slot.assignedNodes ? | ||
slot.assignedNodes({flatten: true}) : []; | ||
for (let i = 0; i < distributedNodes.length; i++) { | ||
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor); | ||
} | ||
return; | ||
} | ||
const style = document.createElement('style'); | ||
style.setAttribute('id', 'inert-style'); | ||
style.textContent = '\n'+ | ||
'[inert] {\n' + | ||
' pointer-events: none;\n' + | ||
' cursor: default;\n' + | ||
'}\n' + | ||
'\n' + | ||
'[inert], [inert] * {\n' + | ||
' -webkit-user-select: none;\n' + | ||
' -moz-user-select: none;\n' + | ||
' -ms-user-select: none;\n' + | ||
' user-select: none;\n' + | ||
'}\n'; | ||
node.appendChild(style); | ||
} | ||
// If it is neither the parent of a ShadowRoot, a <content> element, a <slot> | ||
// element, nor a <shadow> element recurse normally. | ||
let child = node.firstChild; | ||
while (child != null) { | ||
composedTreeWalk(child, callback, shadowRootAncestor); | ||
child = child.nextSibling; | ||
} | ||
} | ||
/** @type {!InertManager} */ | ||
const inertManager = new InertManager(document); | ||
/** | ||
* Adds a style element to the node containing the inert specific styles | ||
* @param {!Node} node | ||
*/ | ||
function addInertStyle(node) { | ||
if (node.querySelector('style#inert-style')) { | ||
return; | ||
if (!Element.prototype.hasOwnProperty('inert')) { | ||
Object.defineProperty(Element.prototype, 'inert', { | ||
enumerable: true, | ||
/** @this {!Element} */ | ||
get: function() { | ||
return this.hasAttribute('inert'); | ||
}, | ||
/** @this {!Element} */ | ||
set: function(inert) { | ||
inertManager.setInert(this, inert); | ||
}, | ||
}); | ||
} | ||
const style = document.createElement('style'); | ||
style.setAttribute('id', 'inert-style'); | ||
style.textContent = '\n'+ | ||
'[inert] {\n' + | ||
' pointer-events: none;\n' + | ||
' cursor: default;\n' + | ||
'}\n' + | ||
'\n' + | ||
'[inert], [inert] * {\n' + | ||
' user-select: none;\n' + | ||
' -webkit-user-select: none;\n' + | ||
' -moz-user-select: none;\n' + | ||
' -ms-user-select: none;\n' + | ||
'}\n'; | ||
node.appendChild(style); | ||
} | ||
/** @type {!InertManager} */ | ||
const inertManager = new InertManager(document); | ||
if (!Element.prototype.hasOwnProperty('inert')) { | ||
Object.defineProperty(Element.prototype, 'inert', { | ||
enumerable: true, | ||
/** @this {!Element} */ | ||
get: function() { | ||
return this.hasAttribute('inert'); | ||
}, | ||
/** @this {!Element} */ | ||
set: function(inert) { | ||
inertManager.setInert(this, inert); | ||
}, | ||
}); | ||
} | ||
})(); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
151105
2053
163