diff --git a/plugins/wporg-5ftf/assets/js/frontend.js b/plugins/wporg-5ftf/assets/js/frontend.js new file mode 100644 index 0000000..b8f4054 --- /dev/null +++ b/plugins/wporg-5ftf/assets/js/frontend.js @@ -0,0 +1,136 @@ +/* global FiveForTheFuture, jQuery */ +jQuery( document ).ready( function( $ ) { + const button = document.getElementById( 'toggle-management-link-form' ); + const template = wp.template( '5ftf-send-link-dialog' ); + + if ( ! template && ! button ) { + // No modal on this page. + return; + } + + $( document.body ).prepend( template() ); + const modal = document.getElementById( 'send-link-dialog' ); + const modalBg = document.getElementById( 'send-link-dialog-bg' ); + const children = document.querySelectorAll( 'body > *:not([role="dialog"])' ); + + /** + * Get the top/left position for the modal, based on the button location. + */ + function getModalPosition() { + const offsetTop = ( 'number' === typeof window.scrollY ) ? window.scrollY : window.pageYOffset; + const offsetLeft = ( 'number' === typeof window.scrollX ) ? window.scrollX : window.pageXOffset; + const bounds = button.getBoundingClientRect(); + const modalWidth = 300; // Modal width is hardcoded, because it's not visible yet. + + return { + top: bounds.y + offsetTop + bounds.height, + left: bounds.x + offsetLeft + bounds.width - modalWidth, + }; + } + + /** + * Open the modal. + */ + function openModal() { + const position = getModalPosition(); + // Hide other content on this page while modal is open. + for ( let i = 0; i < children.length; i++ ) { + if ( children[ i ].hasAttribute( 'data-no-inert' ) ) { + continue; + } + if ( children[ i ].getAttribute( 'inert' ) ) { + children[ i ].setAttribute( 'data-keep-inert', '' ); + } else { + children[ i ].setAttribute( 'inert', 'true' ); + } + } + + modal.removeAttribute( 'hidden' ); + modalBg.removeAttribute( 'hidden' ); + modal.style.top = position.top + 'px'; + modal.style.left = position.left + 'px'; + modal.focus(); + } + + /** + * Close the modal. + */ + function closeModal() { + // Reveal content again. + for ( let i = 0; i < children.length; i++ ) { + if ( ! children[ i ].hasAttribute( 'data-keep-inert' ) ) { + children[ i ].removeAttribute( 'inert' ); + } + + children[ i ].removeAttribute( 'data-keep-inert' ); + } + + modal.hidden = true; + modalBg.hidden = true; + + // Wait a tick before setting focus. See https://github.com/WICG/inert#performance-and-gotchas + setTimeout( function() { + if ( button ) { + button.focus(); + } else { + document.body.focus(); + } + }, 0 ); + } + + function sendRequest( event ) { + event.preventDefault(); + const email = $( event.target.querySelector( 'input[type="email"]' ) ).val(); + $( event.target.querySelector( '.message' ) ).html( '' ); + $.ajax( { + type: 'POST', + url: FiveForTheFuture.ajaxurl, + data: { + action: 'send-manage-email', + pledge_id: FiveForTheFuture.pledgeId, + email, + _ajax_nonce: FiveForTheFuture.ajaxNonce, + }, + success( response ) { + if ( response.message ) { + // Say the message for screen reader users. + wp.a11y.speak( response.message ); + + if ( response.success ) { + closeModal(); + $( button ).after( $( '
' ).html( '' + response.message + '' ) );
+ } else {
+ const $message = $( ' ' ).html( response.message ) );
+
+ $( event.target.querySelector( '.message' ) ).html( $message );
+ }
+ }
+ },
+ dataType: 'json',
+ } );
+ }
+
+ // Initialize.
+ $( button ).on( 'click', function( event ) {
+ event.preventDefault();
+
+ if ( !! modal.hidden ) {
+ openModal( event );
+ } else {
+ closeModal( event );
+ }
+ } );
+
+ $( modalBg ).on( 'click', closeModal );
+ $( modal ).on( 'click', '.pledge-dialog__close', closeModal );
+ $( document ).on( 'keydown', function( event ) {
+ if ( 27 === event.which ) { // Esc
+ closeModal( event );
+ }
+ } );
+
+ $( modal.querySelector( 'form' ) ).submit( sendRequest );
+} );
diff --git a/plugins/wporg-5ftf/assets/js/inert.js b/plugins/wporg-5ftf/assets/js/inert.js
new file mode 100644
index 0000000..498e6d9
--- /dev/null
+++ b/plugins/wporg-5ftf/assets/js/inert.js
@@ -0,0 +1,833 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
+ typeof define === 'function' && define.amd ? define('inert', factory) :
+ (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).
+ */
+
+ // 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;
+
+ /** @type {string} */
+ var _focusableElementsString = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '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.
+ */
+
+ var InertRoot = function () {
+ /**
+ * @param {!Element} rootElement The Element 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 {!Element} */
+ this._rootElement = rootElement;
+
+ /**
+ * @type {!Set}
+ * 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')) {
+ /** @type {?string} */
+ this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden');
+ } else {
+ 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);
+
+ // 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 });
+ }
+
+ /**
+ * 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');
+ }
+ }
+
+ this._managedNodes.forEach(function (inertNode) {
+ this._unmanageNode(inertNode.node);
+ }, this);
+
+ // 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} 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;
+ }
+ 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();
+ }
+ }
+ }
+
+ /**
+ * @param {!Node} node
+ */
+
+ }, {
+ key: '_visitNode',
+ value: function _visitNode(node) {
+ if (node.nodeType !== Node.ELEMENT_NODE) {
+ return;
+ }
+ 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);
+ }
+
+ if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) {
+ this._manageNode(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);
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Unregister the entire subtree starting at `startNode`.
+ * @param {!Node} startNode
+ */
+
+ }, {
+ key: '_unmanageSubtree',
+ value: function _unmanageSubtree(startNode) {
+ var _this3 = this;
+
+ composedTreeWalk(startNode, function (node) {
+ return _this3._unmanageNode(node);
+ });
+ }
+
+ /**
+ * If a descendant node is found with an `inert` attribute, adopt its managed nodes.
+ * @param {!Element} node
+ */
+
+ }, {
+ key: '_adoptInertRoot',
+ 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.
+ if (!inertSubroot) {
+ this._inertManager.setInert(node, true);
+ inertSubroot = this._inertManager.getInertRoot(node);
+ }
+
+ inertSubroot.managedNodes.forEach(function (savedInertNode) {
+ this._manageNode(savedInertNode.node);
+ }, this);
+ }
+
+ /**
+ * Callback used when mutation observer detects subtree additions, removals, or attribute changes.
+ * @param {!Array} records
+ * @param {!MutationObserver} self
+ */
+
+ }, {
+ 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);
+ }
+
+ /** @return {boolean} */
+
+ }, {
+ key: 'hasSavedAriaHidden',
+ get: function get() {
+ return this._savedAriaHidden !== null;
+ }
+
+ /** @param {?string} ariaHidden */
+
+ }, {
+ key: 'savedAriaHidden',
+ set: function set(ariaHidden) {
+ this._savedAriaHidden = ariaHidden;
+ }
+
+ /** @return {?string} */
+ ,
+ get: function get() {
+ return this._savedAriaHidden;
+ }
+ }]);
+
+ return InertRoot;
+ }();
+
+ /**
+ * `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);
+
+ /** @type {!Node} */
+ this._node = node;
+
+ /** @type {boolean} */
+ this._overrodeFocusMethod = false;
+
+ /**
+ * @type {!Set} 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();
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ // 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.
+ */
+
+ }, {
+ key: '_throwIfDestroyed',
+
+
+ /**
+ * Throw if user tries to access destroyed InertNode.
+ */
+ value: function _throwIfDestroyed() {
+ if (this.destroyed) {
+ throw new Error('Trying to access destroyed InertNode');
+ }
+ }
+
+ /** @return {boolean} */
+
+ }, {
+ 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) {
+ return;
+ }
+
+ 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
+ */
+
+ }, {
+ 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
+ */
+
+ }, {
+ 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;
+ }
+
+ /** @return {!Node} */
+
+ }, {
+ key: 'node',
+ get: function get() {
+ this._throwIfDestroyed();
+ return this._node;
+ }
+
+ /** @param {?number} tabIndex */
+
+ }, {
+ key: 'savedTabIndex',
+ set: function set(tabIndex) {
+ this._throwIfDestroyed();
+ this._savedTabIndex = tabIndex;
+ }
+
+ /** @return {?number} */
+ ,
+ get: function get() {
+ this._throwIfDestroyed();
+ return this._savedTabIndex;
+ }
+ }]);
+
+ 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
+ */
+ 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;
+
+ /**
+ * All managed nodes known to this InertManager. In a map to allow looking up by Node.
+ * @type {!Map}
+ */
+ this._managedNodes = new Map();
+
+ /**
+ * All inert roots known to this InertManager. In a map to allow looking up by Node.
+ * @type {!Map}
+ */
+ 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
+ */
+
+
+ _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);
+ }
+ 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');
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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: '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);
+ }
+
+ this._managedNodes.set(node, inertNode);
+
+ return 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.
+ */
+
+ }, {
+ key: 'deregister',
+ value: function deregister(node, inertRoot) {
+ var inertNode = this._managedNodes.get(node);
+ if (!inertNode) {
+ return null;
+ }
+
+ inertNode.removeInertRoot(inertRoot);
+ if (inertNode.destroyed) {
+ this._managedNodes['delete'](node);
+ }
+
+ return inertNode;
+ }
+
+ /**
+ * Callback used when document has finished loading.
+ */
+
+ }, {
+ 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);
+
+ // Comment this out to use programmatic API only.
+ this._observer.observe(this._document.body, { attributes: true, subtree: true, childList: true });
+ }
+
+ /**
+ * Callback used when mutation observer detects attribute changes.
+ * @param {!Array} 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) {
+ 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);
+ }
+ }]);
+
+ 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.
+ */
+
+
+ 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
-
-
-
-