: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define('inert', factory) :
}(this, (function () { 'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
* This work is licensed under the W3C Software and Document License
* (http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document).
// Return early if we're not running inside of the browser.
if (typeof window === 'undefined') {
// Convenience function for converting NodeLists.
/** @type {typeof Array.prototype.slice} */
var slice = Array.prototype.slice;
* IE has a non-standard name for "matches".
* @type {typeof Element.prototype.matches}
var matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
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(',');
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert`
* 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.
var InertRoot = function () {
* @param {!HTMLElement} rootElement The HTMLElement at the root of the inert subtree.
* @param {!InertManager} inertManager The global singleton InertManager object.
function InertRoot(rootElement, inertManager) {
_classCallCheck(this, InertRoot);
/** @type {!InertManager} */
this._inertManager = inertManager;
/** @type {!HTMLElement} */
this._rootElement = rootElement;
* @type {!Set<!InertNode>}
* All managed focusable nodes in this InertRoot's subtree.
this._managedNodes = new Set();
// Make the subtree hidden from assistive technology
if (this._rootElement.hasAttribute('aria-hidden')) {
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden');
this._savedAriaHidden = null;
this._rootElement.setAttribute('aria-hidden', 'true');
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes
this._makeSubtreeUnfocusable(this._rootElement);
// - 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 });
* 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, [{
value: function destructor() {
this._observer.disconnect();
if (this._savedAriaHidden !== null) {
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden);
this._rootElement.removeAttribute('aria-hidden');
this._managedNodes.forEach(function (inertNode) {
this._unmanageNode(inertNode.node);
// 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;
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set.
key: '_makeSubtreeUnfocusable',
* @param {!Node} startNode
value: function _makeSubtreeUnfocusable(startNode) {
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.
/** @type {!ShadowRoot|undefined} */
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
root = /** @type {!ShadowRoot} */node;
activeElement = root.activeElement;
if (startNode.contains(activeElement)) {
// 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) {
value: function _visitNode(node) {
if (node.nodeType !== Node.ELEMENT_NODE) {
var element = /** @type {!HTMLElement} */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);
* Register the given node with this InertRoot and with InertManager.
value: function _manageNode(node) {
var inertNode = this._inertManager.register(node, this);
this._managedNodes.add(inertNode);
* Unregister the given node with this InertRoot and with InertManager.
value: function _unmanageNode(node) {
var inertNode = this._inertManager.deregister(node, this);
this._managedNodes['delete'](inertNode);
* Unregister the entire subtree starting at `startNode`.
* @param {!Node} startNode
value: function _unmanageSubtree(startNode) {
composedTreeWalk(startNode, function (node) {
return _this3._unmanageNode(node);
* If a descendant node is found with an `inert` attribute, adopt its managed nodes.
* @param {!HTMLElement} node
value: function _adoptInertRoot(node) {
var inertSubroot = this._inertManager.getInertRoot(node);
// During initialisation this inert root may not have been registered yet,
// so register it now if need be.
this._inertManager.setInert(node, true);
inertSubroot = this._inertManager.getInertRoot(node);
inertSubroot.managedNodes.forEach(function (savedInertNode) {
this._manageNode(savedInertNode.node);
* Callback used when mutation observer detects subtree additions, removals, or attribute changes.
* @param {!Array<!MutationRecord>} records
* @param {!MutationObserver} self
value: function _onMutation(records, self) {
records.forEach(function (record) {
var target = /** @type {!HTMLElement} */record.target;
if (record.type === 'childList') {
slice.call(record.addedNodes).forEach(function (node) {
this._makeSubtreeUnfocusable(node);
// Un-manage removed nodes
slice.call(record.removedNodes).forEach(function (node) {
this._unmanageSubtree(node);
} 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);
return new Set(this._managedNodes);
key: 'hasSavedAriaHidden',
return this._savedAriaHidden !== null;
/** @param {?string} ariaHidden */
set: function set(ariaHidden) {
this._savedAriaHidden = ariaHidden;
return this._savedAriaHidden;
* `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.
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);
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]);
this._savedTabIndex = null;
// Save any prior tabindex info and make this node untabbable
* 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, [{
value: function destructor() {
this._throwIfDestroyed();
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) {
var element = /** @type {!HTMLElement} */this._node;
if (this._savedTabIndex !== null) {
element.setAttribute('tabindex', this._savedTabIndex);
element.removeAttribute('tabindex');
// Use `delete` to restore native focus method.
if (this._overrodeFocusMethod) {
// See note in InertRoot.destructor for why we cast these nulls to ANY.
this._node = /** @type {?} */null;
this._inertRoots = /** @type {?} */null;
* @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',
* Throw if user tries to access destroyed InertNode.
value: function _throwIfDestroyed() {
throw new Error('Trying to access destroyed InertNode');
/** Save the existing tabindex value and make the node untabbable and unfocusable */
value: function ensureUntabbable() {
if (this.node.nodeType !== Node.ELEMENT_NODE) {
var element = /** @type {!HTMLElement} */this.node;
if (matches.call(element, _focusableElementsString)) {
if ( /** @type {!HTMLElement} */element.tabIndex === -1 && this.hasSavedTabIndex) {
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');
* Add another inert root to this inert node's set of managing inert roots.
* @param {!InertRoot} inertRoot
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
value: function removeInertRoot(inertRoot) {
this._throwIfDestroyed();
this._inertRoots['delete'](inertRoot);
if (this._inertRoots.size === 0) {
return (/** @type {!InertNode} */this._destroyed
return this._savedTabIndex !== null;