wicg-inert 3.0.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/inert.esm.js +832 -0
- package/dist/inert.js +682 -675
- package/dist/inert.min.js +1 -1
- package/dist/inert.min.js.map +1 -1
- package/package.json +2 -2
- package/rollup.config.js +12 -0
- package/src/inert.js +627 -620
- package/w3c.json +1 -1
package/src/inert.js
CHANGED
@@ -3,730 +3,737 @@
|
|
3
3
|
* (http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document).
|
4
4
|
*/
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
(function() {
|
7
|
+
// Return early if we're not running inside of the browser.
|
8
|
+
if (typeof window === 'undefined') {
|
9
|
+
return;
|
10
|
+
}
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
*/
|
14
|
-
const matches =
|
15
|
-
Element.prototype.matches || Element.prototype.msMatchesSelector;
|
16
|
-
|
17
|
-
/** @type {string} */
|
18
|
-
const _focusableElementsString = ['a[href]',
|
19
|
-
'area[href]',
|
20
|
-
'input:not([disabled])',
|
21
|
-
'select:not([disabled])',
|
22
|
-
'textarea:not([disabled])',
|
23
|
-
'button:not([disabled])',
|
24
|
-
'details',
|
25
|
-
'summary',
|
26
|
-
'iframe',
|
27
|
-
'object',
|
28
|
-
'embed',
|
29
|
-
'[contenteditable]'].join(',');
|
12
|
+
// Convenience function for converting NodeLists.
|
13
|
+
/** @type {typeof Array.prototype.slice} */
|
14
|
+
const slice = Array.prototype.slice;
|
30
15
|
|
31
|
-
/**
|
32
|
-
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert`
|
33
|
-
* attribute.
|
34
|
-
*
|
35
|
-
* Its main functions are:
|
36
|
-
*
|
37
|
-
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the
|
38
|
-
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering
|
39
|
-
* each focusable node in the subtree with the singleton `InertManager` which manages all known
|
40
|
-
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode`
|
41
|
-
* instance exists for each focusable node which has at least one inert root as an ancestor.
|
42
|
-
*
|
43
|
-
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert`
|
44
|
-
* attribute is removed from the root node). This is handled in the destructor, which calls the
|
45
|
-
* `deregister` method on `InertManager` for each managed inert node.
|
46
|
-
*/
|
47
|
-
class InertRoot {
|
48
16
|
/**
|
49
|
-
*
|
50
|
-
* @
|
17
|
+
* IE has a non-standard name for "matches".
|
18
|
+
* @type {typeof Element.prototype.matches}
|
51
19
|
*/
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden');
|
69
|
-
} else {
|
70
|
-
this._savedAriaHidden = null;
|
71
|
-
}
|
72
|
-
this._rootElement.setAttribute('aria-hidden', 'true');
|
73
|
-
|
74
|
-
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes
|
75
|
-
this._makeSubtreeUnfocusable(this._rootElement);
|
76
|
-
|
77
|
-
// Watch for:
|
78
|
-
// - any additions in the subtree: make them unfocusable too
|
79
|
-
// - any removals from the subtree: remove them from this inert root's managed nodes
|
80
|
-
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable
|
81
|
-
// element, make that node a managed node.
|
82
|
-
this._observer = new MutationObserver(this._onMutation.bind(this));
|
83
|
-
this._observer.observe(this._rootElement, {attributes: true, childList: true, subtree: true});
|
84
|
-
}
|
20
|
+
const matches =
|
21
|
+
Element.prototype.matches || Element.prototype.msMatchesSelector;
|
22
|
+
|
23
|
+
/** @type {string} */
|
24
|
+
const _focusableElementsString = ['a[href]',
|
25
|
+
'area[href]',
|
26
|
+
'input:not([disabled])',
|
27
|
+
'select:not([disabled])',
|
28
|
+
'textarea:not([disabled])',
|
29
|
+
'button:not([disabled])',
|
30
|
+
'details',
|
31
|
+
'summary',
|
32
|
+
'iframe',
|
33
|
+
'object',
|
34
|
+
'embed',
|
35
|
+
'[contenteditable]'].join(',');
|
85
36
|
|
86
37
|
/**
|
87
|
-
*
|
88
|
-
*
|
38
|
+
* `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert`
|
39
|
+
* attribute.
|
40
|
+
*
|
41
|
+
* Its main functions are:
|
42
|
+
*
|
43
|
+
* - to create and maintain a set of managed `InertNode`s, including when mutations occur in the
|
44
|
+
* subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering
|
45
|
+
* each focusable node in the subtree with the singleton `InertManager` which manages all known
|
46
|
+
* focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode`
|
47
|
+
* instance exists for each focusable node which has at least one inert root as an ancestor.
|
48
|
+
*
|
49
|
+
* - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert`
|
50
|
+
* attribute is removed from the root node). This is handled in the destructor, which calls the
|
51
|
+
* `deregister` method on `InertManager` for each managed inert node.
|
89
52
|
*/
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
53
|
+
class InertRoot {
|
54
|
+
/**
|
55
|
+
* @param {!Element} rootElement The Element at the root of the inert subtree.
|
56
|
+
* @param {!InertManager} inertManager The global singleton InertManager object.
|
57
|
+
*/
|
58
|
+
constructor(rootElement, inertManager) {
|
59
|
+
/** @type {!InertManager} */
|
60
|
+
this._inertManager = inertManager;
|
61
|
+
|
62
|
+
/** @type {!Element} */
|
63
|
+
this._rootElement = rootElement;
|
64
|
+
|
65
|
+
/**
|
66
|
+
* @type {!Set<!InertNode>}
|
67
|
+
* All managed focusable nodes in this InertRoot's subtree.
|
68
|
+
*/
|
69
|
+
this._managedNodes = new Set();
|
70
|
+
|
71
|
+
// Make the subtree hidden from assistive technology
|
72
|
+
if (this._rootElement.hasAttribute('aria-hidden')) {
|
73
|
+
/** @type {?string} */
|
74
|
+
this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden');
|
96
75
|
} else {
|
97
|
-
this.
|
76
|
+
this._savedAriaHidden = null;
|
98
77
|
}
|
78
|
+
this._rootElement.setAttribute('aria-hidden', 'true');
|
79
|
+
|
80
|
+
// Make all focusable elements in the subtree unfocusable and add them to _managedNodes
|
81
|
+
this._makeSubtreeUnfocusable(this._rootElement);
|
82
|
+
|
83
|
+
// Watch for:
|
84
|
+
// - any additions in the subtree: make them unfocusable too
|
85
|
+
// - any removals from the subtree: remove them from this inert root's managed nodes
|
86
|
+
// - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable
|
87
|
+
// element, make that node a managed node.
|
88
|
+
this._observer = new MutationObserver(this._onMutation.bind(this));
|
89
|
+
this._observer.observe(this._rootElement, {attributes: true, childList: true, subtree: true});
|
99
90
|
}
|
100
91
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
}
|
92
|
+
/**
|
93
|
+
* Call this whenever this object is about to become obsolete. This unwinds all of the state
|
94
|
+
* stored in this object and updates the state of all of the managed nodes.
|
95
|
+
*/
|
96
|
+
destructor() {
|
97
|
+
this._observer.disconnect();
|
98
|
+
|
99
|
+
if (this._rootElement) {
|
100
|
+
if (this._savedAriaHidden !== null) {
|
101
|
+
this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden);
|
102
|
+
} else {
|
103
|
+
this._rootElement.removeAttribute('aria-hidden');
|
104
|
+
}
|
105
|
+
}
|
116
106
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
107
|
+
this._managedNodes.forEach(function(inertNode) {
|
108
|
+
this._unmanageNode(inertNode.node);
|
109
|
+
}, this);
|
110
|
+
|
111
|
+
// Note we cast the nulls to the ANY type here because:
|
112
|
+
// 1) We want the class properties to be declared as non-null, or else we
|
113
|
+
// need even more casts throughout this code. All bets are off if an
|
114
|
+
// instance has been destroyed and a method is called.
|
115
|
+
// 2) We don't want to cast "this", because we want type-aware optimizations
|
116
|
+
// to know which properties we're setting.
|
117
|
+
this._observer = /** @type {?} */ (null);
|
118
|
+
this._rootElement = /** @type {?} */ (null);
|
119
|
+
this._managedNodes = /** @type {?} */ (null);
|
120
|
+
this._inertManager = /** @type {?} */ (null);
|
121
|
+
}
|
123
122
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
123
|
+
/**
|
124
|
+
* @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set.
|
125
|
+
*/
|
126
|
+
get managedNodes() {
|
127
|
+
return new Set(this._managedNodes);
|
128
|
+
}
|
128
129
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
130
|
+
/** @return {boolean} */
|
131
|
+
get hasSavedAriaHidden() {
|
132
|
+
return this._savedAriaHidden !== null;
|
133
|
+
}
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
/** @param {?string} ariaHidden */
|
136
|
+
set savedAriaHidden(ariaHidden) {
|
137
|
+
this._savedAriaHidden = ariaHidden;
|
138
|
+
}
|
138
139
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
140
|
+
/** @return {?string} */
|
141
|
+
get savedAriaHidden() {
|
142
|
+
return this._savedAriaHidden;
|
143
|
+
}
|
144
|
+
|
145
|
+
/**
|
146
|
+
* @param {!Node} startNode
|
147
|
+
*/
|
148
|
+
_makeSubtreeUnfocusable(startNode) {
|
149
|
+
composedTreeWalk(startNode, (node) => this._visitNode(node));
|
150
|
+
|
151
|
+
let activeElement = document.activeElement;
|
152
|
+
|
153
|
+
if (!document.body.contains(startNode)) {
|
154
|
+
// startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement.
|
155
|
+
let node = startNode;
|
156
|
+
/** @type {!ShadowRoot|undefined} */
|
157
|
+
let root = undefined;
|
158
|
+
while (node) {
|
159
|
+
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
160
|
+
root = /** @type {!ShadowRoot} */ (node);
|
161
|
+
break;
|
162
|
+
}
|
163
|
+
node = node.parentNode;
|
164
|
+
}
|
165
|
+
if (root) {
|
166
|
+
activeElement = root.activeElement;
|
156
167
|
}
|
157
|
-
node = node.parentNode;
|
158
168
|
}
|
159
|
-
if (
|
160
|
-
activeElement
|
169
|
+
if (startNode.contains(activeElement)) {
|
170
|
+
activeElement.blur();
|
171
|
+
// In IE11, if an element is already focused, and then set to tabindex=-1
|
172
|
+
// calling blur() will not actually move the focus.
|
173
|
+
// To work around this we call focus() on the body instead.
|
174
|
+
if (activeElement === document.activeElement) {
|
175
|
+
document.body.focus();
|
176
|
+
}
|
161
177
|
}
|
162
178
|
}
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if (
|
169
|
-
|
179
|
+
|
180
|
+
/**
|
181
|
+
* @param {!Node} node
|
182
|
+
*/
|
183
|
+
_visitNode(node) {
|
184
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
185
|
+
return;
|
170
186
|
}
|
171
|
-
|
172
|
-
}
|
187
|
+
const element = /** @type {!Element} */ (node);
|
173
188
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
return;
|
180
|
-
}
|
181
|
-
const element = /** @type {!Element} */ (node);
|
189
|
+
// If a descendant inert root becomes un-inert, its descendants will still be inert because of
|
190
|
+
// this inert root, so all of its managed nodes need to be adopted by this InertRoot.
|
191
|
+
if (element !== this._rootElement && element.hasAttribute('inert')) {
|
192
|
+
this._adoptInertRoot(element);
|
193
|
+
}
|
182
194
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
this._adoptInertRoot(element);
|
195
|
+
if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) {
|
196
|
+
this._manageNode(element);
|
197
|
+
}
|
187
198
|
}
|
188
199
|
|
189
|
-
|
190
|
-
|
200
|
+
/**
|
201
|
+
* Register the given node with this InertRoot and with InertManager.
|
202
|
+
* @param {!Node} node
|
203
|
+
*/
|
204
|
+
_manageNode(node) {
|
205
|
+
const inertNode = this._inertManager.register(node, this);
|
206
|
+
this._managedNodes.add(inertNode);
|
191
207
|
}
|
192
|
-
}
|
193
|
-
|
194
|
-
/**
|
195
|
-
* Register the given node with this InertRoot and with InertManager.
|
196
|
-
* @param {!Node} node
|
197
|
-
*/
|
198
|
-
_manageNode(node) {
|
199
|
-
const inertNode = this._inertManager.register(node, this);
|
200
|
-
this._managedNodes.add(inertNode);
|
201
|
-
}
|
202
208
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
209
|
+
/**
|
210
|
+
* Unregister the given node with this InertRoot and with InertManager.
|
211
|
+
* @param {!Node} node
|
212
|
+
*/
|
213
|
+
_unmanageNode(node) {
|
214
|
+
const inertNode = this._inertManager.deregister(node, this);
|
215
|
+
if (inertNode) {
|
216
|
+
this._managedNodes.delete(inertNode);
|
217
|
+
}
|
211
218
|
}
|
212
|
-
}
|
213
219
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
220
|
+
/**
|
221
|
+
* Unregister the entire subtree starting at `startNode`.
|
222
|
+
* @param {!Node} startNode
|
223
|
+
*/
|
224
|
+
_unmanageSubtree(startNode) {
|
225
|
+
composedTreeWalk(startNode, (node) => this._unmanageNode(node));
|
226
|
+
}
|
221
227
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
+
/**
|
229
|
+
* If a descendant node is found with an `inert` attribute, adopt its managed nodes.
|
230
|
+
* @param {!Element} node
|
231
|
+
*/
|
232
|
+
_adoptInertRoot(node) {
|
233
|
+
let inertSubroot = this._inertManager.getInertRoot(node);
|
234
|
+
|
235
|
+
// During initialisation this inert root may not have been registered yet,
|
236
|
+
// so register it now if need be.
|
237
|
+
if (!inertSubroot) {
|
238
|
+
this._inertManager.setInert(node, true);
|
239
|
+
inertSubroot = this._inertManager.getInertRoot(node);
|
240
|
+
}
|
228
241
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
this._inertManager.setInert(node, true);
|
233
|
-
inertSubroot = this._inertManager.getInertRoot(node);
|
242
|
+
inertSubroot.managedNodes.forEach(function(savedInertNode) {
|
243
|
+
this._manageNode(savedInertNode.node);
|
244
|
+
}, this);
|
234
245
|
}
|
235
246
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
if (target.contains(managedNode.node)) {
|
272
|
-
inertSubroot._manageNode(managedNode.node);
|
273
|
-
}
|
274
|
-
});
|
247
|
+
/**
|
248
|
+
* Callback used when mutation observer detects subtree additions, removals, or attribute changes.
|
249
|
+
* @param {!Array<!MutationRecord>} records
|
250
|
+
* @param {!MutationObserver} self
|
251
|
+
*/
|
252
|
+
_onMutation(records, self) {
|
253
|
+
records.forEach(function(record) {
|
254
|
+
const target = /** @type {!Element} */ (record.target);
|
255
|
+
if (record.type === 'childList') {
|
256
|
+
// Manage added nodes
|
257
|
+
slice.call(record.addedNodes).forEach(function(node) {
|
258
|
+
this._makeSubtreeUnfocusable(node);
|
259
|
+
}, this);
|
260
|
+
|
261
|
+
// Un-manage removed nodes
|
262
|
+
slice.call(record.removedNodes).forEach(function(node) {
|
263
|
+
this._unmanageSubtree(node);
|
264
|
+
}, this);
|
265
|
+
} else if (record.type === 'attributes') {
|
266
|
+
if (record.attributeName === 'tabindex') {
|
267
|
+
// Re-initialise inert node if tabindex changes
|
268
|
+
this._manageNode(target);
|
269
|
+
} else if (target !== this._rootElement &&
|
270
|
+
record.attributeName === 'inert' &&
|
271
|
+
target.hasAttribute('inert')) {
|
272
|
+
// If a new inert root is added, adopt its managed nodes and make sure it knows about the
|
273
|
+
// already managed nodes from this inert subroot.
|
274
|
+
this._adoptInertRoot(target);
|
275
|
+
const inertSubroot = this._inertManager.getInertRoot(target);
|
276
|
+
this._managedNodes.forEach(function(managedNode) {
|
277
|
+
if (target.contains(managedNode.node)) {
|
278
|
+
inertSubroot._manageNode(managedNode.node);
|
279
|
+
}
|
280
|
+
});
|
281
|
+
}
|
275
282
|
}
|
276
|
-
}
|
277
|
-
}
|
283
|
+
}, this);
|
284
|
+
}
|
278
285
|
}
|
279
|
-
}
|
280
286
|
|
281
|
-
/**
|
282
|
-
* `InertNode` initialises and manages a single inert node.
|
283
|
-
* A node is inert if it is a descendant of one or more inert root elements.
|
284
|
-
*
|
285
|
-
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and
|
286
|
-
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element
|
287
|
-
* is intrinsically focusable or not.
|
288
|
-
*
|
289
|
-
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an
|
290
|
-
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the
|
291
|
-
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s
|
292
|
-
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists,
|
293
|
-
* or removes the `tabindex` attribute if the element is intrinsically focusable.
|
294
|
-
*/
|
295
|
-
class InertNode {
|
296
287
|
/**
|
297
|
-
*
|
298
|
-
*
|
288
|
+
* `InertNode` initialises and manages a single inert node.
|
289
|
+
* A node is inert if it is a descendant of one or more inert root elements.
|
290
|
+
*
|
291
|
+
* On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and
|
292
|
+
* either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element
|
293
|
+
* is intrinsically focusable or not.
|
294
|
+
*
|
295
|
+
* `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an
|
296
|
+
* `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the
|
297
|
+
* `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s
|
298
|
+
* remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists,
|
299
|
+
* or removes the `tabindex` attribute if the element is intrinsically focusable.
|
299
300
|
*/
|
300
|
-
|
301
|
-
/** @type {!Node} */
|
302
|
-
this._node = node;
|
303
|
-
|
304
|
-
/** @type {boolean} */
|
305
|
-
this._overrodeFocusMethod = false;
|
306
|
-
|
301
|
+
class InertNode {
|
307
302
|
/**
|
308
|
-
* @
|
309
|
-
*
|
303
|
+
* @param {!Node} node A focusable element to be made inert.
|
304
|
+
* @param {!InertRoot} inertRoot The inert root element associated with this inert node.
|
310
305
|
*/
|
311
|
-
|
306
|
+
constructor(node, inertRoot) {
|
307
|
+
/** @type {!Node} */
|
308
|
+
this._node = node;
|
312
309
|
|
313
|
-
|
314
|
-
|
310
|
+
/** @type {boolean} */
|
311
|
+
this._overrodeFocusMethod = false;
|
315
312
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
}
|
313
|
+
/**
|
314
|
+
* @type {!Set<!InertRoot>} The set of descendant inert roots.
|
315
|
+
* If and only if this set becomes empty, this node is no longer inert.
|
316
|
+
*/
|
317
|
+
this._inertRoots = new Set([inertRoot]);
|
322
318
|
|
323
|
-
|
324
|
-
|
325
|
-
* This makes the managed node focusable again and deletes all of the previously stored state.
|
326
|
-
*/
|
327
|
-
destructor() {
|
328
|
-
this._throwIfDestroyed();
|
319
|
+
/** @type {?number} */
|
320
|
+
this._savedTabIndex = null;
|
329
321
|
|
330
|
-
|
331
|
-
|
332
|
-
if (this._savedTabIndex !== null) {
|
333
|
-
element.setAttribute('tabindex', this._savedTabIndex);
|
334
|
-
} else {
|
335
|
-
element.removeAttribute('tabindex');
|
336
|
-
}
|
322
|
+
/** @type {boolean} */
|
323
|
+
this._destroyed = false;
|
337
324
|
|
338
|
-
//
|
339
|
-
|
340
|
-
delete element.focus;
|
341
|
-
}
|
325
|
+
// Save any prior tabindex info and make this node untabbable
|
326
|
+
this.ensureUntabbable();
|
342
327
|
}
|
343
328
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
329
|
+
/**
|
330
|
+
* Call this whenever this object is about to become obsolete.
|
331
|
+
* This makes the managed node focusable again and deletes all of the previously stored state.
|
332
|
+
*/
|
333
|
+
destructor() {
|
334
|
+
this._throwIfDestroyed();
|
335
|
+
|
336
|
+
if (this._node && this._node.nodeType === Node.ELEMENT_NODE) {
|
337
|
+
const element = /** @type {!Element} */ (this._node);
|
338
|
+
if (this._savedTabIndex !== null) {
|
339
|
+
element.setAttribute('tabindex', this._savedTabIndex);
|
340
|
+
} else {
|
341
|
+
element.removeAttribute('tabindex');
|
342
|
+
}
|
349
343
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
return /** @type {!InertNode} */ (this)._destroyed;
|
356
|
-
}
|
344
|
+
// Use `delete` to restore native focus method.
|
345
|
+
if (this._overrodeFocusMethod) {
|
346
|
+
delete element.focus;
|
347
|
+
}
|
348
|
+
}
|
357
349
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
if (this.destroyed) {
|
363
|
-
throw new Error('Trying to access destroyed InertNode');
|
350
|
+
// See note in InertRoot.destructor for why we cast these nulls to ANY.
|
351
|
+
this._node = /** @type {?} */ (null);
|
352
|
+
this._inertRoots = /** @type {?} */ (null);
|
353
|
+
this._destroyed = true;
|
364
354
|
}
|
365
|
-
}
|
366
355
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
356
|
+
/**
|
357
|
+
* @type {boolean} Whether this object is obsolete because the managed node is no longer inert.
|
358
|
+
* If the object has been destroyed, any attempt to access it will cause an exception.
|
359
|
+
*/
|
360
|
+
get destroyed() {
|
361
|
+
return /** @type {!InertNode} */ (this)._destroyed;
|
362
|
+
}
|
371
363
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
364
|
+
/**
|
365
|
+
* Throw if user tries to access destroyed InertNode.
|
366
|
+
*/
|
367
|
+
_throwIfDestroyed() {
|
368
|
+
if (this.destroyed) {
|
369
|
+
throw new Error('Trying to access destroyed InertNode');
|
370
|
+
}
|
371
|
+
}
|
377
372
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
}
|
373
|
+
/** @return {boolean} */
|
374
|
+
get hasSavedTabIndex() {
|
375
|
+
return this._savedTabIndex !== null;
|
376
|
+
}
|
383
377
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
378
|
+
/** @return {!Node} */
|
379
|
+
get node() {
|
380
|
+
this._throwIfDestroyed();
|
381
|
+
return this._node;
|
382
|
+
}
|
389
383
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
384
|
+
/** @param {?number} tabIndex */
|
385
|
+
set savedTabIndex(tabIndex) {
|
386
|
+
this._throwIfDestroyed();
|
387
|
+
this._savedTabIndex = tabIndex;
|
388
|
+
}
|
389
|
+
|
390
|
+
/** @return {?number} */
|
391
|
+
get savedTabIndex() {
|
392
|
+
this._throwIfDestroyed();
|
393
|
+
return this._savedTabIndex;
|
394
394
|
}
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
395
|
+
|
396
|
+
/** Save the existing tabindex value and make the node untabbable and unfocusable */
|
397
|
+
ensureUntabbable() {
|
398
|
+
if (this.node.nodeType !== Node.ELEMENT_NODE) {
|
399
399
|
return;
|
400
400
|
}
|
401
|
+
const element = /** @type {!Element} */ (this.node);
|
402
|
+
if (matches.call(element, _focusableElementsString)) {
|
403
|
+
if (/** @type {!HTMLElement} */ (element).tabIndex === -1 &&
|
404
|
+
this.hasSavedTabIndex) {
|
405
|
+
return;
|
406
|
+
}
|
401
407
|
|
402
|
-
|
408
|
+
if (element.hasAttribute('tabindex')) {
|
409
|
+
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex;
|
410
|
+
}
|
411
|
+
element.setAttribute('tabindex', '-1');
|
412
|
+
if (element.nodeType === Node.ELEMENT_NODE) {
|
413
|
+
element.focus = function() {};
|
414
|
+
this._overrodeFocusMethod = true;
|
415
|
+
}
|
416
|
+
} else if (element.hasAttribute('tabindex')) {
|
403
417
|
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex;
|
418
|
+
element.removeAttribute('tabindex');
|
404
419
|
}
|
405
|
-
element.setAttribute('tabindex', '-1');
|
406
|
-
if (element.nodeType === Node.ELEMENT_NODE) {
|
407
|
-
element.focus = function() {};
|
408
|
-
this._overrodeFocusMethod = true;
|
409
|
-
}
|
410
|
-
} else if (element.hasAttribute('tabindex')) {
|
411
|
-
this._savedTabIndex = /** @type {!HTMLElement} */ (element).tabIndex;
|
412
|
-
element.removeAttribute('tabindex');
|
413
420
|
}
|
414
|
-
}
|
415
421
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
422
|
+
/**
|
423
|
+
* Add another inert root to this inert node's set of managing inert roots.
|
424
|
+
* @param {!InertRoot} inertRoot
|
425
|
+
*/
|
426
|
+
addInertRoot(inertRoot) {
|
427
|
+
this._throwIfDestroyed();
|
428
|
+
this._inertRoots.add(inertRoot);
|
429
|
+
}
|
424
430
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
431
|
+
/**
|
432
|
+
* Remove the given inert root from this inert node's set of managing inert roots.
|
433
|
+
* If the set of managing inert roots becomes empty, this node is no longer inert,
|
434
|
+
* so the object should be destroyed.
|
435
|
+
* @param {!InertRoot} inertRoot
|
436
|
+
*/
|
437
|
+
removeInertRoot(inertRoot) {
|
438
|
+
this._throwIfDestroyed();
|
439
|
+
this._inertRoots.delete(inertRoot);
|
440
|
+
if (this._inertRoots.size === 0) {
|
441
|
+
this.destructor();
|
442
|
+
}
|
436
443
|
}
|
437
444
|
}
|
438
|
-
}
|
439
445
|
|
440
|
-
/**
|
441
|
-
* InertManager is a per-document singleton object which manages all inert roots and nodes.
|
442
|
-
*
|
443
|
-
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert`
|
444
|
-
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element.
|
445
|
-
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant
|
446
|
-
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance
|
447
|
-
* is created for each such node, via the `_managedNodes` map.
|
448
|
-
*/
|
449
|
-
class InertManager {
|
450
446
|
/**
|
451
|
-
*
|
447
|
+
* InertManager is a per-document singleton object which manages all inert roots and nodes.
|
448
|
+
*
|
449
|
+
* When an element becomes an inert root by having an `inert` attribute set and/or its `inert`
|
450
|
+
* property set to `true`, the `setInert` method creates an `InertRoot` object for the element.
|
451
|
+
* The `InertRoot` in turn registers itself as managing all of the element's focusable descendant
|
452
|
+
* nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance
|
453
|
+
* is created for each such node, via the `_managedNodes` map.
|
452
454
|
*/
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
455
|
+
class InertManager {
|
456
|
+
/**
|
457
|
+
* @param {!Document} document
|
458
|
+
*/
|
459
|
+
constructor(document) {
|
460
|
+
if (!document) {
|
461
|
+
throw new Error('Missing required argument; InertManager needs to wrap a document.');
|
462
|
+
}
|
457
463
|
|
458
|
-
|
459
|
-
|
464
|
+
/** @type {!Document} */
|
465
|
+
this._document = document;
|
466
|
+
|
467
|
+
/**
|
468
|
+
* All managed nodes known to this InertManager. In a map to allow looking up by Node.
|
469
|
+
* @type {!Map<!Node, !InertNode>}
|
470
|
+
*/
|
471
|
+
this._managedNodes = new Map();
|
472
|
+
|
473
|
+
/**
|
474
|
+
* All inert roots known to this InertManager. In a map to allow looking up by Node.
|
475
|
+
* @type {!Map<!Node, !InertRoot>}
|
476
|
+
*/
|
477
|
+
this._inertRoots = new Map();
|
478
|
+
|
479
|
+
/**
|
480
|
+
* Observer for mutations on `document.body`.
|
481
|
+
* @type {!MutationObserver}
|
482
|
+
*/
|
483
|
+
this._observer = new MutationObserver(this._watchForInert.bind(this));
|
484
|
+
|
485
|
+
// Add inert style.
|
486
|
+
addInertStyle(document.head || document.body || document.documentElement);
|
487
|
+
|
488
|
+
// Wait for document to be loaded.
|
489
|
+
if (document.readyState === 'loading') {
|
490
|
+
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this));
|
491
|
+
} else {
|
492
|
+
this._onDocumentLoaded();
|
493
|
+
}
|
494
|
+
}
|
460
495
|
|
461
496
|
/**
|
462
|
-
*
|
463
|
-
* @
|
497
|
+
* Set whether the given element should be an inert root or not.
|
498
|
+
* @param {!Element} root
|
499
|
+
* @param {boolean} inert
|
464
500
|
*/
|
465
|
-
|
501
|
+
setInert(root, inert) {
|
502
|
+
if (inert) {
|
503
|
+
if (this._inertRoots.has(root)) { // element is already inert
|
504
|
+
return;
|
505
|
+
}
|
506
|
+
|
507
|
+
const inertRoot = new InertRoot(root, this);
|
508
|
+
root.setAttribute('inert', '');
|
509
|
+
this._inertRoots.set(root, inertRoot);
|
510
|
+
// If not contained in the document, it must be in a shadowRoot.
|
511
|
+
// Ensure inert styles are added there.
|
512
|
+
if (!this._document.body.contains(root)) {
|
513
|
+
let parent = root.parentNode;
|
514
|
+
while (parent) {
|
515
|
+
if (parent.nodeType === 11) {
|
516
|
+
addInertStyle(parent);
|
517
|
+
}
|
518
|
+
parent = parent.parentNode;
|
519
|
+
}
|
520
|
+
}
|
521
|
+
} else {
|
522
|
+
if (!this._inertRoots.has(root)) { // element is already non-inert
|
523
|
+
return;
|
524
|
+
}
|
525
|
+
|
526
|
+
const inertRoot = this._inertRoots.get(root);
|
527
|
+
inertRoot.destructor();
|
528
|
+
this._inertRoots.delete(root);
|
529
|
+
root.removeAttribute('inert');
|
530
|
+
}
|
531
|
+
}
|
466
532
|
|
467
533
|
/**
|
468
|
-
*
|
469
|
-
* @
|
534
|
+
* Get the InertRoot object corresponding to the given inert root element, if any.
|
535
|
+
* @param {!Node} element
|
536
|
+
* @return {!InertRoot|undefined}
|
470
537
|
*/
|
471
|
-
|
538
|
+
getInertRoot(element) {
|
539
|
+
return this._inertRoots.get(element);
|
540
|
+
}
|
472
541
|
|
473
542
|
/**
|
474
|
-
*
|
475
|
-
*
|
543
|
+
* Register the given InertRoot as managing the given node.
|
544
|
+
* In the case where the node has a previously existing inert root, this inert root will
|
545
|
+
* be added to its set of inert roots.
|
546
|
+
* @param {!Node} node
|
547
|
+
* @param {!InertRoot} inertRoot
|
548
|
+
* @return {!InertNode} inertNode
|
476
549
|
*/
|
477
|
-
|
550
|
+
register(node, inertRoot) {
|
551
|
+
let inertNode = this._managedNodes.get(node);
|
552
|
+
if (inertNode !== undefined) { // node was already in an inert subtree
|
553
|
+
inertNode.addInertRoot(inertRoot);
|
554
|
+
} else {
|
555
|
+
inertNode = new InertNode(node, inertRoot);
|
556
|
+
}
|
478
557
|
|
479
|
-
|
480
|
-
addInertStyle(document.head || document.body || document.documentElement);
|
558
|
+
this._managedNodes.set(node, inertNode);
|
481
559
|
|
482
|
-
|
483
|
-
if (document.readyState === 'loading') {
|
484
|
-
document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this));
|
485
|
-
} else {
|
486
|
-
this._onDocumentLoaded();
|
560
|
+
return inertNode;
|
487
561
|
}
|
488
|
-
}
|
489
562
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
563
|
+
/**
|
564
|
+
* De-register the given InertRoot as managing the given inert node.
|
565
|
+
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert
|
566
|
+
* node from the InertManager's set of managed nodes if it is destroyed.
|
567
|
+
* If the node is not currently managed, this is essentially a no-op.
|
568
|
+
* @param {!Node} node
|
569
|
+
* @param {!InertRoot} inertRoot
|
570
|
+
* @return {?InertNode} The potentially destroyed InertNode associated with this node, if any.
|
571
|
+
*/
|
572
|
+
deregister(node, inertRoot) {
|
573
|
+
const inertNode = this._managedNodes.get(node);
|
574
|
+
if (!inertNode) {
|
575
|
+
return null;
|
499
576
|
}
|
500
577
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
// If not contained in the document, it must be in a shadowRoot.
|
505
|
-
// Ensure inert styles are added there.
|
506
|
-
if (!this._document.body.contains(root)) {
|
507
|
-
let parent = root.parentNode;
|
508
|
-
while (parent) {
|
509
|
-
if (parent.nodeType === 11) {
|
510
|
-
addInertStyle(parent);
|
511
|
-
}
|
512
|
-
parent = parent.parentNode;
|
513
|
-
}
|
514
|
-
}
|
515
|
-
} else {
|
516
|
-
if (!this._inertRoots.has(root)) { // element is already non-inert
|
517
|
-
return;
|
578
|
+
inertNode.removeInertRoot(inertRoot);
|
579
|
+
if (inertNode.destroyed) {
|
580
|
+
this._managedNodes.delete(node);
|
518
581
|
}
|
519
582
|
|
520
|
-
|
521
|
-
inertRoot.destructor();
|
522
|
-
this._inertRoots.delete(root);
|
523
|
-
root.removeAttribute('inert');
|
583
|
+
return inertNode;
|
524
584
|
}
|
525
|
-
}
|
526
585
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
* be added to its set of inert roots.
|
540
|
-
* @param {!Node} node
|
541
|
-
* @param {!InertRoot} inertRoot
|
542
|
-
* @return {!InertNode} inertNode
|
543
|
-
*/
|
544
|
-
register(node, inertRoot) {
|
545
|
-
let inertNode = this._managedNodes.get(node);
|
546
|
-
if (inertNode !== undefined) { // node was already in an inert subtree
|
547
|
-
inertNode.addInertRoot(inertRoot);
|
548
|
-
} else {
|
549
|
-
inertNode = new InertNode(node, inertRoot);
|
586
|
+
/**
|
587
|
+
* Callback used when document has finished loading.
|
588
|
+
*/
|
589
|
+
_onDocumentLoaded() {
|
590
|
+
// Find all inert roots in document and make them actually inert.
|
591
|
+
const inertElements = slice.call(this._document.querySelectorAll('[inert]'));
|
592
|
+
inertElements.forEach(function(inertElement) {
|
593
|
+
this.setInert(inertElement, true);
|
594
|
+
}, this);
|
595
|
+
|
596
|
+
// Comment this out to use programmatic API only.
|
597
|
+
this._observer.observe(this._document.body || this._document.documentElement, {attributes: true, subtree: true, childList: true});
|
550
598
|
}
|
551
599
|
|
552
|
-
|
553
|
-
|
554
|
-
|
600
|
+
/**
|
601
|
+
* Callback used when mutation observer detects attribute changes.
|
602
|
+
* @param {!Array<!MutationRecord>} records
|
603
|
+
* @param {!MutationObserver} self
|
604
|
+
*/
|
605
|
+
_watchForInert(records, self) {
|
606
|
+
const _this = this;
|
607
|
+
records.forEach(function(record) {
|
608
|
+
switch (record.type) {
|
609
|
+
case 'childList':
|
610
|
+
slice.call(record.addedNodes).forEach(function(node) {
|
611
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
612
|
+
return;
|
613
|
+
}
|
614
|
+
const inertElements = slice.call(node.querySelectorAll('[inert]'));
|
615
|
+
if (matches.call(node, '[inert]')) {
|
616
|
+
inertElements.unshift(node);
|
617
|
+
}
|
618
|
+
inertElements.forEach(function(inertElement) {
|
619
|
+
this.setInert(inertElement, true);
|
620
|
+
}, _this);
|
621
|
+
}, _this);
|
622
|
+
break;
|
623
|
+
case 'attributes':
|
624
|
+
if (record.attributeName !== 'inert') {
|
625
|
+
return;
|
626
|
+
}
|
627
|
+
const target = /** @type {!Element} */ (record.target);
|
628
|
+
const inert = target.hasAttribute('inert');
|
629
|
+
_this.setInert(target, inert);
|
630
|
+
break;
|
631
|
+
}
|
632
|
+
}, this);
|
633
|
+
}
|
555
634
|
}
|
556
635
|
|
557
636
|
/**
|
558
|
-
*
|
559
|
-
* Removes the inert root from the InertNode's set of managing inert roots, and remove the inert
|
560
|
-
* node from the InertManager's set of managed nodes if it is destroyed.
|
561
|
-
* If the node is not currently managed, this is essentially a no-op.
|
637
|
+
* Recursively walk the composed tree from |node|.
|
562
638
|
* @param {!Node} node
|
563
|
-
* @param {!
|
564
|
-
*
|
639
|
+
* @param {(function (!Element))=} callback Callback to be called for each element traversed,
|
640
|
+
* before descending into child nodes.
|
641
|
+
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any.
|
565
642
|
*/
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
inertNode.removeInertRoot(inertRoot);
|
573
|
-
if (inertNode.destroyed) {
|
574
|
-
this._managedNodes.delete(node);
|
575
|
-
}
|
576
|
-
|
577
|
-
return inertNode;
|
578
|
-
}
|
643
|
+
function composedTreeWalk(node, callback, shadowRootAncestor) {
|
644
|
+
if (node.nodeType == Node.ELEMENT_NODE) {
|
645
|
+
const element = /** @type {!Element} */ (node);
|
646
|
+
if (callback) {
|
647
|
+
callback(element);
|
648
|
+
}
|
579
649
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
// Comment this out to use programmatic API only.
|
591
|
-
this._observer.observe(this._document.body, {attributes: true, subtree: true, childList: true});
|
592
|
-
}
|
650
|
+
// Descend into node:
|
651
|
+
// If it has a ShadowRoot, ignore all child elements - these will be picked
|
652
|
+
// up by the <content> or <shadow> elements. Descend straight into the
|
653
|
+
// ShadowRoot.
|
654
|
+
const shadowRoot = /** @type {!HTMLElement} */ (element).shadowRoot;
|
655
|
+
if (shadowRoot) {
|
656
|
+
composedTreeWalk(shadowRoot, callback, shadowRoot);
|
657
|
+
return;
|
658
|
+
}
|
593
659
|
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
slice.call(record.addedNodes).forEach(function(node) {
|
605
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
606
|
-
return;
|
607
|
-
}
|
608
|
-
const inertElements = slice.call(node.querySelectorAll('[inert]'));
|
609
|
-
if (matches.call(node, '[inert]')) {
|
610
|
-
inertElements.unshift(node);
|
611
|
-
}
|
612
|
-
inertElements.forEach(function(inertElement) {
|
613
|
-
this.setInert(inertElement, true);
|
614
|
-
}, _this);
|
615
|
-
}, _this);
|
616
|
-
break;
|
617
|
-
case 'attributes':
|
618
|
-
if (record.attributeName !== 'inert') {
|
619
|
-
return;
|
660
|
+
// If it is a <content> element, descend into distributed elements - these
|
661
|
+
// are elements from outside the shadow root which are rendered inside the
|
662
|
+
// shadow DOM.
|
663
|
+
if (element.localName == 'content') {
|
664
|
+
const content = /** @type {!HTMLContentElement} */ (element);
|
665
|
+
// Verifies if ShadowDom v0 is supported.
|
666
|
+
const distributedNodes = content.getDistributedNodes ?
|
667
|
+
content.getDistributedNodes() : [];
|
668
|
+
for (let i = 0; i < distributedNodes.length; i++) {
|
669
|
+
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor);
|
620
670
|
}
|
621
|
-
|
622
|
-
const inert = target.hasAttribute('inert');
|
623
|
-
_this.setInert(target, inert);
|
624
|
-
break;
|
671
|
+
return;
|
625
672
|
}
|
626
|
-
}, this);
|
627
|
-
}
|
628
|
-
}
|
629
|
-
|
630
|
-
/**
|
631
|
-
* Recursively walk the composed tree from |node|.
|
632
|
-
* @param {!Node} node
|
633
|
-
* @param {(function (!Element))=} callback Callback to be called for each element traversed,
|
634
|
-
* before descending into child nodes.
|
635
|
-
* @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any.
|
636
|
-
*/
|
637
|
-
function composedTreeWalk(node, callback, shadowRootAncestor) {
|
638
|
-
if (node.nodeType == Node.ELEMENT_NODE) {
|
639
|
-
const element = /** @type {!Element} */ (node);
|
640
|
-
if (callback) {
|
641
|
-
callback(element);
|
642
|
-
}
|
643
|
-
|
644
|
-
// Descend into node:
|
645
|
-
// If it has a ShadowRoot, ignore all child elements - these will be picked
|
646
|
-
// up by the <content> or <shadow> elements. Descend straight into the
|
647
|
-
// ShadowRoot.
|
648
|
-
const shadowRoot = /** @type {!HTMLElement} */ (element).shadowRoot;
|
649
|
-
if (shadowRoot) {
|
650
|
-
composedTreeWalk(shadowRoot, callback, shadowRoot);
|
651
|
-
return;
|
652
|
-
}
|
653
673
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
674
|
+
// If it is a <slot> element, descend into assigned nodes - these
|
675
|
+
// are elements from outside the shadow root which are rendered inside the
|
676
|
+
// shadow DOM.
|
677
|
+
if (element.localName == 'slot') {
|
678
|
+
const slot = /** @type {!HTMLSlotElement} */ (element);
|
679
|
+
// Verify if ShadowDom v1 is supported.
|
680
|
+
const distributedNodes = slot.assignedNodes ?
|
681
|
+
slot.assignedNodes({flatten: true}) : [];
|
682
|
+
for (let i = 0; i < distributedNodes.length; i++) {
|
683
|
+
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor);
|
684
|
+
}
|
685
|
+
return;
|
664
686
|
}
|
665
|
-
return;
|
666
687
|
}
|
667
688
|
|
668
|
-
// If it is a <
|
669
|
-
//
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
const distributedNodes = slot.assignedNodes ?
|
675
|
-
slot.assignedNodes({flatten: true}) : [];
|
676
|
-
for (let i = 0; i < distributedNodes.length; i++) {
|
677
|
-
composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor);
|
678
|
-
}
|
679
|
-
return;
|
689
|
+
// If it is neither the parent of a ShadowRoot, a <content> element, a <slot>
|
690
|
+
// element, nor a <shadow> element recurse normally.
|
691
|
+
let child = node.firstChild;
|
692
|
+
while (child != null) {
|
693
|
+
composedTreeWalk(child, callback, shadowRootAncestor);
|
694
|
+
child = child.nextSibling;
|
680
695
|
}
|
681
696
|
}
|
682
697
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
}
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
return this.hasAttribute('inert');
|
726
|
-
},
|
727
|
-
/** @this {!Element} */
|
728
|
-
set: function(inert) {
|
729
|
-
inertManager.setInert(this, inert);
|
730
|
-
},
|
731
|
-
});
|
732
|
-
}
|
698
|
+
/**
|
699
|
+
* Adds a style element to the node containing the inert specific styles
|
700
|
+
* @param {!Node} node
|
701
|
+
*/
|
702
|
+
function addInertStyle(node) {
|
703
|
+
if (node.querySelector('style#inert-style, link#inert-style')) {
|
704
|
+
return;
|
705
|
+
}
|
706
|
+
const style = document.createElement('style');
|
707
|
+
style.setAttribute('id', 'inert-style');
|
708
|
+
style.textContent = '\n'+
|
709
|
+
'[inert] {\n' +
|
710
|
+
' pointer-events: none;\n' +
|
711
|
+
' cursor: default;\n' +
|
712
|
+
'}\n' +
|
713
|
+
'\n' +
|
714
|
+
'[inert], [inert] * {\n' +
|
715
|
+
' -webkit-user-select: none;\n' +
|
716
|
+
' -moz-user-select: none;\n' +
|
717
|
+
' -ms-user-select: none;\n' +
|
718
|
+
' user-select: none;\n' +
|
719
|
+
'}\n';
|
720
|
+
node.appendChild(style);
|
721
|
+
}
|
722
|
+
|
723
|
+
if (!Element.prototype.hasOwnProperty('inert')) {
|
724
|
+
/** @type {!InertManager} */
|
725
|
+
const inertManager = new InertManager(document);
|
726
|
+
|
727
|
+
Object.defineProperty(Element.prototype, 'inert', {
|
728
|
+
enumerable: true,
|
729
|
+
/** @this {!Element} */
|
730
|
+
get: function() {
|
731
|
+
return this.hasAttribute('inert');
|
732
|
+
},
|
733
|
+
/** @this {!Element} */
|
734
|
+
set: function(inert) {
|
735
|
+
inertManager.setInert(this, inert);
|
736
|
+
},
|
737
|
+
});
|
738
|
+
}
|
739
|
+
})();
|