wicg-inert 3.0.1 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
})();
|