@primer/behaviors 0.0.0-20251215125128 → 0.0.0-20251215200522
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/dist/cjs/anchored-position.js +17 -17
- package/dist/cjs/focus-trap.js +21 -28
- package/dist/cjs/focus-zone.js +43 -90
- package/dist/cjs/utils/iterate-focusable-elements.js +13 -21
- package/dist/esm/anchored-position.mjs +17 -17
- package/dist/esm/focus-trap.mjs +21 -28
- package/dist/esm/focus-zone.mjs +43 -90
- package/dist/esm/utils/iterate-focusable-elements.mjs +13 -21
- package/package.json +1 -1
- package/dist/cjs/utils/indexed-set.d.ts +0 -13
- package/dist/cjs/utils/indexed-set.js +0 -53
- package/dist/esm/utils/indexed-set.d.ts +0 -13
- package/dist/esm/utils/indexed-set.mjs +0 -51
|
@@ -27,11 +27,9 @@ function getPositionedParent(element) {
|
|
|
27
27
|
if (isOnTopLayer(element))
|
|
28
28
|
return document.body;
|
|
29
29
|
let parentNode = element.parentNode;
|
|
30
|
-
while (parentNode !== null
|
|
31
|
-
if (parentNode instanceof HTMLElement) {
|
|
32
|
-
|
|
33
|
-
return parentNode;
|
|
34
|
-
}
|
|
30
|
+
while (parentNode !== null) {
|
|
31
|
+
if (parentNode instanceof HTMLElement && getComputedStyle(parentNode).position !== 'static') {
|
|
32
|
+
return parentNode;
|
|
35
33
|
}
|
|
36
34
|
parentNode = parentNode.parentNode;
|
|
37
35
|
}
|
|
@@ -53,24 +51,26 @@ function isOnTopLayer(element) {
|
|
|
53
51
|
return false;
|
|
54
52
|
}
|
|
55
53
|
function getClippingRect(element) {
|
|
56
|
-
let clippingNode = document.body;
|
|
57
54
|
let parentNode = element;
|
|
58
|
-
while (parentNode !== null
|
|
59
|
-
if (parentNode instanceof
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
while (parentNode !== null) {
|
|
56
|
+
if (!(parentNode instanceof Element)) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const parentNodeStyle = getComputedStyle(parentNode);
|
|
60
|
+
if (parentNodeStyle.overflow !== 'visible') {
|
|
61
|
+
break;
|
|
65
62
|
}
|
|
66
63
|
parentNode = parentNode.parentNode;
|
|
67
64
|
}
|
|
65
|
+
const clippingNode = parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
|
|
68
66
|
const elemRect = clippingNode.getBoundingClientRect();
|
|
69
67
|
const elemStyle = getComputedStyle(clippingNode);
|
|
70
|
-
const borderTop
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const [borderTop, borderLeft, borderRight, borderBottom] = [
|
|
69
|
+
elemStyle.borderTopWidth,
|
|
70
|
+
elemStyle.borderLeftWidth,
|
|
71
|
+
elemStyle.borderRightWidth,
|
|
72
|
+
elemStyle.borderBottomWidth,
|
|
73
|
+
].map(v => parseInt(v, 10) || 0);
|
|
74
74
|
return {
|
|
75
75
|
top: elemRect.top + borderTop,
|
|
76
76
|
left: elemRect.left + borderLeft,
|
package/dist/cjs/focus-trap.js
CHANGED
|
@@ -6,16 +6,6 @@ var eventListenerSignal = require('./polyfills/event-listener-signal.js');
|
|
|
6
6
|
eventListenerSignal.polyfill();
|
|
7
7
|
const suspendedTrapStack = [];
|
|
8
8
|
let activeTrap = undefined;
|
|
9
|
-
const SR_ONLY_STYLES = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';
|
|
10
|
-
function createSentinel({ onFocus }) {
|
|
11
|
-
const sentinel = document.createElement('span');
|
|
12
|
-
sentinel.setAttribute('class', 'sentinel');
|
|
13
|
-
sentinel.setAttribute('tabindex', '0');
|
|
14
|
-
sentinel.setAttribute('aria-hidden', 'true');
|
|
15
|
-
sentinel.style.cssText = SR_ONLY_STYLES;
|
|
16
|
-
sentinel.onfocus = onFocus;
|
|
17
|
-
return sentinel;
|
|
18
|
-
}
|
|
19
9
|
function tryReactivate() {
|
|
20
10
|
const trapToReactivate = suspendedTrapStack.pop();
|
|
21
11
|
if (trapToReactivate) {
|
|
@@ -33,10 +23,9 @@ function observeFocusTrap(container, sentinels) {
|
|
|
33
23
|
const observer = new MutationObserver(mutations => {
|
|
34
24
|
for (const mutation of mutations) {
|
|
35
25
|
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
26
|
+
const sentinelChildren = Array.from(mutation.addedNodes).filter(e => e instanceof HTMLElement && e.classList.contains('sentinel') && e.tagName === 'SPAN');
|
|
27
|
+
if (sentinelChildren.length) {
|
|
28
|
+
return;
|
|
40
29
|
}
|
|
41
30
|
const firstChild = container.firstElementChild;
|
|
42
31
|
const lastChild = container.lastElementChild;
|
|
@@ -57,20 +46,24 @@ function focusTrap(container, initialFocus, abortSignal) {
|
|
|
57
46
|
const controller = new AbortController();
|
|
58
47
|
const signal = abortSignal !== null && abortSignal !== void 0 ? abortSignal : controller.signal;
|
|
59
48
|
container.setAttribute('data-focus-trap', 'active');
|
|
60
|
-
const sentinelStart =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
49
|
+
const sentinelStart = document.createElement('span');
|
|
50
|
+
sentinelStart.setAttribute('class', 'sentinel');
|
|
51
|
+
sentinelStart.setAttribute('tabindex', '0');
|
|
52
|
+
sentinelStart.setAttribute('aria-hidden', 'true');
|
|
53
|
+
sentinelStart.onfocus = () => {
|
|
54
|
+
const lastFocusableChild = iterateFocusableElements.getFocusableChild(container, true);
|
|
55
|
+
lastFocusableChild === null || lastFocusableChild === void 0 ? void 0 : lastFocusableChild.focus();
|
|
56
|
+
};
|
|
57
|
+
const sentinelEnd = document.createElement('span');
|
|
58
|
+
sentinelEnd.setAttribute('class', 'sentinel');
|
|
59
|
+
sentinelEnd.setAttribute('tabindex', '0');
|
|
60
|
+
sentinelEnd.setAttribute('aria-hidden', 'true');
|
|
61
|
+
sentinelEnd.onfocus = () => {
|
|
62
|
+
const firstFocusableChild = iterateFocusableElements.getFocusableChild(container);
|
|
63
|
+
firstFocusableChild === null || firstFocusableChild === void 0 ? void 0 : firstFocusableChild.focus();
|
|
64
|
+
};
|
|
65
|
+
const existingSentinels = Array.from(container.children).filter(e => e.classList.contains('sentinel') && e.tagName === 'SPAN');
|
|
66
|
+
if (!existingSentinels.length) {
|
|
74
67
|
container.prepend(sentinelStart);
|
|
75
68
|
container.append(sentinelEnd);
|
|
76
69
|
}
|
package/dist/cjs/focus-zone.js
CHANGED
|
@@ -5,7 +5,6 @@ var userAgent = require('./utils/user-agent.js');
|
|
|
5
5
|
var iterateFocusableElements = require('./utils/iterate-focusable-elements.js');
|
|
6
6
|
var uniqueId = require('./utils/unique-id.js');
|
|
7
7
|
var isEditableElement = require('./utils/is-editable-element.js');
|
|
8
|
-
var indexedSet = require('./utils/indexed-set.js');
|
|
9
8
|
|
|
10
9
|
eventListenerSignal.polyfill();
|
|
11
10
|
exports.FocusKeys = void 0;
|
|
@@ -65,19 +64,12 @@ const KEY_TO_DIRECTION = {
|
|
|
65
64
|
PageDown: 'end',
|
|
66
65
|
Backspace: 'previous',
|
|
67
66
|
};
|
|
68
|
-
let cachedIsMac;
|
|
69
|
-
function getIsMac() {
|
|
70
|
-
if (cachedIsMac === undefined) {
|
|
71
|
-
cachedIsMac = userAgent.isMacOS();
|
|
72
|
-
}
|
|
73
|
-
return cachedIsMac;
|
|
74
|
-
}
|
|
75
67
|
function getDirection(keyboardEvent) {
|
|
76
68
|
const direction = KEY_TO_DIRECTION[keyboardEvent.key];
|
|
77
69
|
if (keyboardEvent.key === 'Tab' && keyboardEvent.shiftKey) {
|
|
78
70
|
return 'previous';
|
|
79
71
|
}
|
|
80
|
-
const isMac =
|
|
72
|
+
const isMac = userAgent.isMacOS();
|
|
81
73
|
if ((isMac && keyboardEvent.metaKey) || (!isMac && keyboardEvent.ctrlKey)) {
|
|
82
74
|
if (keyboardEvent.key === 'ArrowLeft' || keyboardEvent.key === 'ArrowUp') {
|
|
83
75
|
return 'start';
|
|
@@ -90,18 +82,17 @@ function getDirection(keyboardEvent) {
|
|
|
90
82
|
}
|
|
91
83
|
function shouldIgnoreFocusHandling(keyboardEvent, activeElement) {
|
|
92
84
|
const key = keyboardEvent.key;
|
|
93
|
-
const
|
|
85
|
+
const keyLength = [...key].length;
|
|
94
86
|
const isEditable = isEditableElement.isEditableElement(activeElement);
|
|
95
87
|
const isSelect = activeElement instanceof HTMLSelectElement;
|
|
96
|
-
if (isEditable && (
|
|
88
|
+
if (isEditable && (keyLength === 1 || key === 'Home' || key === 'End')) {
|
|
97
89
|
return true;
|
|
98
90
|
}
|
|
99
91
|
if (isSelect) {
|
|
100
|
-
|
|
101
|
-
if (key === 'ArrowDown' && isMac && !keyboardEvent.metaKey) {
|
|
92
|
+
if (key === 'ArrowDown' && userAgent.isMacOS() && !keyboardEvent.metaKey) {
|
|
102
93
|
return true;
|
|
103
94
|
}
|
|
104
|
-
if (key === 'ArrowDown' && !
|
|
95
|
+
if (key === 'ArrowDown' && !userAgent.isMacOS() && keyboardEvent.altKey) {
|
|
105
96
|
return true;
|
|
106
97
|
}
|
|
107
98
|
return false;
|
|
@@ -139,7 +130,7 @@ const activeDescendantActivatedIndirectly = 'activated-indirectly';
|
|
|
139
130
|
const hasActiveDescendantAttribute = 'data-has-active-descendant';
|
|
140
131
|
function focusZone(container, settings) {
|
|
141
132
|
var _a, _b, _c, _d, _e, _f;
|
|
142
|
-
const focusableElements =
|
|
133
|
+
const focusableElements = [];
|
|
143
134
|
const savedTabIndex = new WeakMap();
|
|
144
135
|
const bindKeys = (_a = settings === null || settings === void 0 ? void 0 : settings.bindKeys) !== null && _a !== void 0 ? _a : ((settings === null || settings === void 0 ? void 0 : settings.getNextFocusable) ? exports.FocusKeys.ArrowAll : exports.FocusKeys.ArrowVertical) | exports.FocusKeys.HomeAndEnd;
|
|
145
136
|
const focusOutBehavior = (_b = settings === null || settings === void 0 ? void 0 : settings.focusOutBehavior) !== null && _b !== void 0 ? _b : 'stop';
|
|
@@ -151,7 +142,7 @@ function focusZone(container, settings) {
|
|
|
151
142
|
const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
|
|
152
143
|
const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
|
|
153
144
|
function getFirstFocusableElement() {
|
|
154
|
-
return focusableElements
|
|
145
|
+
return focusableElements[0];
|
|
155
146
|
}
|
|
156
147
|
function isActiveDescendantInputFocused() {
|
|
157
148
|
return document.activeElement === activeDescendantControl;
|
|
@@ -196,20 +187,17 @@ function focusZone(container, settings) {
|
|
|
196
187
|
activeDescendantControl === null || activeDescendantControl === void 0 ? void 0 : activeDescendantControl.removeAttribute('aria-activedescendant');
|
|
197
188
|
container.removeAttribute(hasActiveDescendantAttribute);
|
|
198
189
|
previouslyActiveElement === null || previouslyActiveElement === void 0 ? void 0 : previouslyActiveElement.removeAttribute(isActiveDescendantAttribute);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
items[i].removeAttribute(isActiveDescendantAttribute);
|
|
190
|
+
for (const item of container.querySelectorAll(`[${isActiveDescendantAttribute}]`)) {
|
|
191
|
+
item === null || item === void 0 ? void 0 : item.removeAttribute(isActiveDescendantAttribute);
|
|
202
192
|
}
|
|
203
193
|
activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(undefined, previouslyActiveElement, false);
|
|
204
194
|
}
|
|
205
195
|
function beginFocusManagement(...elements) {
|
|
206
|
-
const filteredElements = (settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter)
|
|
207
|
-
? elements.filter(e => settings.focusableElementFilter(e))
|
|
208
|
-
: elements;
|
|
196
|
+
const filteredElements = elements.filter(e => { var _a, _b; return (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter) === null || _a === void 0 ? void 0 : _a.call(settings, e)) !== null && _b !== void 0 ? _b : true; });
|
|
209
197
|
if (filteredElements.length === 0) {
|
|
210
198
|
return;
|
|
211
199
|
}
|
|
212
|
-
focusableElements.
|
|
200
|
+
focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
|
|
213
201
|
for (const element of filteredElements) {
|
|
214
202
|
if (!savedTabIndex.has(element)) {
|
|
215
203
|
savedTabIndex.set(element, element.getAttribute('tabindex'));
|
|
@@ -222,13 +210,13 @@ function focusZone(container, settings) {
|
|
|
222
210
|
}
|
|
223
211
|
function findInsertionIndex(elementsToInsert) {
|
|
224
212
|
const firstElementToInsert = elementsToInsert[0];
|
|
225
|
-
if (focusableElements.
|
|
213
|
+
if (focusableElements.length === 0)
|
|
226
214
|
return 0;
|
|
227
215
|
let iMin = 0;
|
|
228
|
-
let iMax = focusableElements.
|
|
216
|
+
let iMax = focusableElements.length - 1;
|
|
229
217
|
while (iMin <= iMax) {
|
|
230
218
|
const i = Math.floor((iMin + iMax) / 2);
|
|
231
|
-
const element = focusableElements
|
|
219
|
+
const element = focusableElements[i];
|
|
232
220
|
if (followsInDocument(firstElementToInsert, element)) {
|
|
233
221
|
iMax = i - 1;
|
|
234
222
|
}
|
|
@@ -243,7 +231,10 @@ function focusZone(container, settings) {
|
|
|
243
231
|
}
|
|
244
232
|
function endFocusManagement(...elements) {
|
|
245
233
|
for (const element of elements) {
|
|
246
|
-
focusableElements.
|
|
234
|
+
const focusableElementIndex = focusableElements.indexOf(element);
|
|
235
|
+
if (focusableElementIndex >= 0) {
|
|
236
|
+
focusableElements.splice(focusableElementIndex, 1);
|
|
237
|
+
}
|
|
247
238
|
const savedIndex = savedTabIndex.get(element);
|
|
248
239
|
if (savedIndex !== undefined) {
|
|
249
240
|
if (savedIndex === null) {
|
|
@@ -270,61 +261,29 @@ function focusZone(container, settings) {
|
|
|
270
261
|
if (!preventInitialFocus)
|
|
271
262
|
updateFocusedElement(initialElement);
|
|
272
263
|
const observer = new MutationObserver(mutations => {
|
|
273
|
-
const elementsToRemove = new Set();
|
|
274
|
-
const elementsToAdd = new Set();
|
|
275
|
-
const attributeRemovals = new Set();
|
|
276
|
-
const attributeAdditions = new Set();
|
|
277
264
|
for (const mutation of mutations) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
elementsToRemove.add(removedNode);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
for (const addedNode of mutation.addedNodes) {
|
|
285
|
-
if (addedNode instanceof HTMLElement) {
|
|
286
|
-
elementsToAdd.add(addedNode);
|
|
287
|
-
}
|
|
265
|
+
for (const removedNode of mutation.removedNodes) {
|
|
266
|
+
if (removedNode instanceof HTMLElement) {
|
|
267
|
+
endFocusManagement(...iterateFocusableElements.iterateFocusableElements(removedNode));
|
|
288
268
|
}
|
|
289
269
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (hasAttribute) {
|
|
294
|
-
attributeRemovals.add(mutation.target);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
attributeAdditions.add(mutation.target);
|
|
270
|
+
if (mutation.type === 'attributes' && mutation.oldValue === null) {
|
|
271
|
+
if (mutation.target instanceof HTMLElement) {
|
|
272
|
+
endFocusManagement(mutation.target);
|
|
298
273
|
}
|
|
299
274
|
}
|
|
300
275
|
}
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
toRemove.push(el);
|
|
276
|
+
for (const mutation of mutations) {
|
|
277
|
+
for (const addedNode of mutation.addedNodes) {
|
|
278
|
+
if (addedNode instanceof HTMLElement) {
|
|
279
|
+
beginFocusManagement(...iterateFocusableElements.iterateFocusableElements(addedNode, iterateFocusableElementsOptions));
|
|
306
280
|
}
|
|
307
281
|
}
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
if (attributeRemovals.size > 0) {
|
|
313
|
-
endFocusManagement(...attributeRemovals);
|
|
314
|
-
}
|
|
315
|
-
if (elementsToAdd.size > 0) {
|
|
316
|
-
const toAdd = [];
|
|
317
|
-
for (const node of elementsToAdd) {
|
|
318
|
-
for (const el of iterateFocusableElements.iterateFocusableElements(node, iterateFocusableElementsOptions)) {
|
|
319
|
-
toAdd.push(el);
|
|
282
|
+
if (mutation.type === 'attributes' && mutation.oldValue !== null) {
|
|
283
|
+
if (mutation.target instanceof HTMLElement) {
|
|
284
|
+
beginFocusManagement(mutation.target);
|
|
320
285
|
}
|
|
321
286
|
}
|
|
322
|
-
if (toAdd.length > 0) {
|
|
323
|
-
beginFocusManagement(...toAdd);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (attributeAdditions.size > 0) {
|
|
327
|
-
beginFocusManagement(...attributeAdditions);
|
|
328
287
|
}
|
|
329
288
|
});
|
|
330
289
|
observer.observe(container, {
|
|
@@ -336,9 +295,7 @@ function focusZone(container, settings) {
|
|
|
336
295
|
const controller = new AbortController();
|
|
337
296
|
const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
|
|
338
297
|
signal.addEventListener('abort', () => {
|
|
339
|
-
observer.disconnect();
|
|
340
298
|
endFocusManagement(...focusableElements);
|
|
341
|
-
focusableElements.clear();
|
|
342
299
|
});
|
|
343
300
|
let elementIndexFocusedByClick = undefined;
|
|
344
301
|
container.addEventListener('mousedown', event => {
|
|
@@ -348,7 +305,7 @@ function focusZone(container, settings) {
|
|
|
348
305
|
}, { signal });
|
|
349
306
|
if (activeDescendantControl) {
|
|
350
307
|
container.addEventListener('focusin', event => {
|
|
351
|
-
if (event.target instanceof HTMLElement && focusableElements.
|
|
308
|
+
if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
|
|
352
309
|
activeDescendantControl.focus({ preventScroll });
|
|
353
310
|
updateFocusedElement(event.target);
|
|
354
311
|
}
|
|
@@ -358,10 +315,6 @@ function focusZone(container, settings) {
|
|
|
358
315
|
if (!(target instanceof Node)) {
|
|
359
316
|
return;
|
|
360
317
|
}
|
|
361
|
-
if (target instanceof HTMLElement && focusableElements.has(target)) {
|
|
362
|
-
updateFocusedElement(target);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
318
|
const focusableElement = focusableElements.find(element => element.contains(target));
|
|
366
319
|
if (focusableElement) {
|
|
367
320
|
updateFocusedElement(focusableElement);
|
|
@@ -386,9 +339,8 @@ function focusZone(container, settings) {
|
|
|
386
339
|
if (event.target instanceof HTMLElement) {
|
|
387
340
|
if (elementIndexFocusedByClick !== undefined) {
|
|
388
341
|
if (elementIndexFocusedByClick >= 0) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
updateFocusedElement(clickedElement);
|
|
342
|
+
if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
|
|
343
|
+
updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
|
|
392
344
|
}
|
|
393
345
|
}
|
|
394
346
|
elementIndexFocusedByClick = undefined;
|
|
@@ -399,8 +351,8 @@ function focusZone(container, settings) {
|
|
|
399
351
|
}
|
|
400
352
|
else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
|
|
401
353
|
if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
|
|
402
|
-
const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.
|
|
403
|
-
const targetElement = focusableElements
|
|
354
|
+
const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
|
|
355
|
+
const targetElement = focusableElements[targetElementIndex];
|
|
404
356
|
targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
|
|
405
357
|
return;
|
|
406
358
|
}
|
|
@@ -411,7 +363,8 @@ function focusZone(container, settings) {
|
|
|
411
363
|
else if (typeof focusInStrategy === 'function') {
|
|
412
364
|
if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
|
|
413
365
|
const elementToFocus = focusInStrategy(event.relatedTarget);
|
|
414
|
-
|
|
366
|
+
const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
|
|
367
|
+
if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
|
|
415
368
|
elementToFocus.focus({ preventScroll });
|
|
416
369
|
return;
|
|
417
370
|
}
|
|
@@ -470,26 +423,26 @@ function focusZone(container, settings) {
|
|
|
470
423
|
nextFocusedIndex += 1;
|
|
471
424
|
}
|
|
472
425
|
else {
|
|
473
|
-
nextFocusedIndex = focusableElements.
|
|
426
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
474
427
|
}
|
|
475
428
|
if (nextFocusedIndex < 0) {
|
|
476
429
|
if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
|
|
477
|
-
nextFocusedIndex = focusableElements.
|
|
430
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
478
431
|
}
|
|
479
432
|
else {
|
|
480
433
|
nextFocusedIndex = 0;
|
|
481
434
|
}
|
|
482
435
|
}
|
|
483
|
-
if (nextFocusedIndex >= focusableElements.
|
|
436
|
+
if (nextFocusedIndex >= focusableElements.length) {
|
|
484
437
|
if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
|
|
485
438
|
nextFocusedIndex = 0;
|
|
486
439
|
}
|
|
487
440
|
else {
|
|
488
|
-
nextFocusedIndex = focusableElements.
|
|
441
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
489
442
|
}
|
|
490
443
|
}
|
|
491
444
|
if (lastFocusedIndex !== nextFocusedIndex) {
|
|
492
|
-
nextElementToFocus = focusableElements
|
|
445
|
+
nextElementToFocus = focusableElements[nextFocusedIndex];
|
|
493
446
|
}
|
|
494
447
|
}
|
|
495
448
|
if (activeDescendantControl) {
|
|
@@ -33,32 +33,24 @@ function* iterateFocusableElements(container, options = {}) {
|
|
|
33
33
|
function getFocusableChild(container, lastChild = false) {
|
|
34
34
|
return iterateFocusableElements(container, { reverse: lastChild, strict: true, onlyTabbable: true }).next().value;
|
|
35
35
|
}
|
|
36
|
-
const DISABLEABLE_TAGS = new Set(['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET']);
|
|
37
36
|
function isFocusable(elem, strict = false) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (DISABLEABLE_TAGS.has(elem.tagName) && elem.disabled)
|
|
37
|
+
const disabledAttrInert = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(elem.tagName) &&
|
|
38
|
+
elem.disabled;
|
|
39
|
+
const hiddenInert = elem.hidden;
|
|
40
|
+
const hiddenInputInert = elem instanceof HTMLInputElement && elem.type === 'hidden';
|
|
41
|
+
const sentinelInert = elem.classList.contains('sentinel');
|
|
42
|
+
if (disabledAttrInert || hiddenInert || hiddenInputInert || sentinelInert) {
|
|
45
43
|
return false;
|
|
44
|
+
}
|
|
46
45
|
if (strict) {
|
|
47
|
-
const offsetWidth = elem.offsetWidth;
|
|
48
|
-
const offsetHeight = elem.offsetHeight;
|
|
49
|
-
const offsetParent = elem.offsetParent;
|
|
50
|
-
if (offsetWidth === 0 || offsetHeight === 0)
|
|
51
|
-
return false;
|
|
52
46
|
const style = getComputedStyle(elem);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!offsetParent && position !== 'fixed' && position !== 'sticky')
|
|
59
|
-
return false;
|
|
60
|
-
if (elem.getClientRects().length === 0)
|
|
47
|
+
const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0;
|
|
48
|
+
const visibilityInert = ['hidden', 'collapse'].includes(style.visibility);
|
|
49
|
+
const displayInert = style.display === 'none' || !elem.offsetParent;
|
|
50
|
+
const clientRectsInert = elem.getClientRects().length === 0;
|
|
51
|
+
if (sizeInert || visibilityInert || clientRectsInert || displayInert) {
|
|
61
52
|
return false;
|
|
53
|
+
}
|
|
62
54
|
}
|
|
63
55
|
if (elem.getAttribute('tabindex') != null) {
|
|
64
56
|
return true;
|
|
@@ -25,11 +25,9 @@ function getPositionedParent(element) {
|
|
|
25
25
|
if (isOnTopLayer(element))
|
|
26
26
|
return document.body;
|
|
27
27
|
let parentNode = element.parentNode;
|
|
28
|
-
while (parentNode !== null
|
|
29
|
-
if (parentNode instanceof HTMLElement) {
|
|
30
|
-
|
|
31
|
-
return parentNode;
|
|
32
|
-
}
|
|
28
|
+
while (parentNode !== null) {
|
|
29
|
+
if (parentNode instanceof HTMLElement && getComputedStyle(parentNode).position !== 'static') {
|
|
30
|
+
return parentNode;
|
|
33
31
|
}
|
|
34
32
|
parentNode = parentNode.parentNode;
|
|
35
33
|
}
|
|
@@ -51,24 +49,26 @@ function isOnTopLayer(element) {
|
|
|
51
49
|
return false;
|
|
52
50
|
}
|
|
53
51
|
function getClippingRect(element) {
|
|
54
|
-
let clippingNode = document.body;
|
|
55
52
|
let parentNode = element;
|
|
56
|
-
while (parentNode !== null
|
|
57
|
-
if (parentNode instanceof
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
while (parentNode !== null) {
|
|
54
|
+
if (!(parentNode instanceof Element)) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const parentNodeStyle = getComputedStyle(parentNode);
|
|
58
|
+
if (parentNodeStyle.overflow !== 'visible') {
|
|
59
|
+
break;
|
|
63
60
|
}
|
|
64
61
|
parentNode = parentNode.parentNode;
|
|
65
62
|
}
|
|
63
|
+
const clippingNode = parentNode === document.body || !(parentNode instanceof HTMLElement) ? document.body : parentNode;
|
|
66
64
|
const elemRect = clippingNode.getBoundingClientRect();
|
|
67
65
|
const elemStyle = getComputedStyle(clippingNode);
|
|
68
|
-
const borderTop
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const [borderTop, borderLeft, borderRight, borderBottom] = [
|
|
67
|
+
elemStyle.borderTopWidth,
|
|
68
|
+
elemStyle.borderLeftWidth,
|
|
69
|
+
elemStyle.borderRightWidth,
|
|
70
|
+
elemStyle.borderBottomWidth,
|
|
71
|
+
].map(v => parseInt(v, 10) || 0);
|
|
72
72
|
return {
|
|
73
73
|
top: elemRect.top + borderTop,
|
|
74
74
|
left: elemRect.left + borderLeft,
|
package/dist/esm/focus-trap.mjs
CHANGED
|
@@ -4,16 +4,6 @@ import { polyfill } from './polyfills/event-listener-signal.mjs';
|
|
|
4
4
|
polyfill();
|
|
5
5
|
const suspendedTrapStack = [];
|
|
6
6
|
let activeTrap = undefined;
|
|
7
|
-
const SR_ONLY_STYLES = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';
|
|
8
|
-
function createSentinel({ onFocus }) {
|
|
9
|
-
const sentinel = document.createElement('span');
|
|
10
|
-
sentinel.setAttribute('class', 'sentinel');
|
|
11
|
-
sentinel.setAttribute('tabindex', '0');
|
|
12
|
-
sentinel.setAttribute('aria-hidden', 'true');
|
|
13
|
-
sentinel.style.cssText = SR_ONLY_STYLES;
|
|
14
|
-
sentinel.onfocus = onFocus;
|
|
15
|
-
return sentinel;
|
|
16
|
-
}
|
|
17
7
|
function tryReactivate() {
|
|
18
8
|
const trapToReactivate = suspendedTrapStack.pop();
|
|
19
9
|
if (trapToReactivate) {
|
|
@@ -31,10 +21,9 @@ function observeFocusTrap(container, sentinels) {
|
|
|
31
21
|
const observer = new MutationObserver(mutations => {
|
|
32
22
|
for (const mutation of mutations) {
|
|
33
23
|
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
24
|
+
const sentinelChildren = Array.from(mutation.addedNodes).filter(e => e instanceof HTMLElement && e.classList.contains('sentinel') && e.tagName === 'SPAN');
|
|
25
|
+
if (sentinelChildren.length) {
|
|
26
|
+
return;
|
|
38
27
|
}
|
|
39
28
|
const firstChild = container.firstElementChild;
|
|
40
29
|
const lastChild = container.lastElementChild;
|
|
@@ -55,20 +44,24 @@ function focusTrap(container, initialFocus, abortSignal) {
|
|
|
55
44
|
const controller = new AbortController();
|
|
56
45
|
const signal = abortSignal !== null && abortSignal !== void 0 ? abortSignal : controller.signal;
|
|
57
46
|
container.setAttribute('data-focus-trap', 'active');
|
|
58
|
-
const sentinelStart =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
47
|
+
const sentinelStart = document.createElement('span');
|
|
48
|
+
sentinelStart.setAttribute('class', 'sentinel');
|
|
49
|
+
sentinelStart.setAttribute('tabindex', '0');
|
|
50
|
+
sentinelStart.setAttribute('aria-hidden', 'true');
|
|
51
|
+
sentinelStart.onfocus = () => {
|
|
52
|
+
const lastFocusableChild = getFocusableChild(container, true);
|
|
53
|
+
lastFocusableChild === null || lastFocusableChild === void 0 ? void 0 : lastFocusableChild.focus();
|
|
54
|
+
};
|
|
55
|
+
const sentinelEnd = document.createElement('span');
|
|
56
|
+
sentinelEnd.setAttribute('class', 'sentinel');
|
|
57
|
+
sentinelEnd.setAttribute('tabindex', '0');
|
|
58
|
+
sentinelEnd.setAttribute('aria-hidden', 'true');
|
|
59
|
+
sentinelEnd.onfocus = () => {
|
|
60
|
+
const firstFocusableChild = getFocusableChild(container);
|
|
61
|
+
firstFocusableChild === null || firstFocusableChild === void 0 ? void 0 : firstFocusableChild.focus();
|
|
62
|
+
};
|
|
63
|
+
const existingSentinels = Array.from(container.children).filter(e => e.classList.contains('sentinel') && e.tagName === 'SPAN');
|
|
64
|
+
if (!existingSentinels.length) {
|
|
72
65
|
container.prepend(sentinelStart);
|
|
73
66
|
container.append(sentinelEnd);
|
|
74
67
|
}
|
package/dist/esm/focus-zone.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { isMacOS } from './utils/user-agent.mjs';
|
|
|
3
3
|
import { iterateFocusableElements } from './utils/iterate-focusable-elements.mjs';
|
|
4
4
|
import { uniqueId } from './utils/unique-id.mjs';
|
|
5
5
|
import { isEditableElement } from './utils/is-editable-element.mjs';
|
|
6
|
-
import { IndexedSet } from './utils/indexed-set.mjs';
|
|
7
6
|
|
|
8
7
|
polyfill();
|
|
9
8
|
var FocusKeys;
|
|
@@ -63,19 +62,12 @@ const KEY_TO_DIRECTION = {
|
|
|
63
62
|
PageDown: 'end',
|
|
64
63
|
Backspace: 'previous',
|
|
65
64
|
};
|
|
66
|
-
let cachedIsMac;
|
|
67
|
-
function getIsMac() {
|
|
68
|
-
if (cachedIsMac === undefined) {
|
|
69
|
-
cachedIsMac = isMacOS();
|
|
70
|
-
}
|
|
71
|
-
return cachedIsMac;
|
|
72
|
-
}
|
|
73
65
|
function getDirection(keyboardEvent) {
|
|
74
66
|
const direction = KEY_TO_DIRECTION[keyboardEvent.key];
|
|
75
67
|
if (keyboardEvent.key === 'Tab' && keyboardEvent.shiftKey) {
|
|
76
68
|
return 'previous';
|
|
77
69
|
}
|
|
78
|
-
const isMac =
|
|
70
|
+
const isMac = isMacOS();
|
|
79
71
|
if ((isMac && keyboardEvent.metaKey) || (!isMac && keyboardEvent.ctrlKey)) {
|
|
80
72
|
if (keyboardEvent.key === 'ArrowLeft' || keyboardEvent.key === 'ArrowUp') {
|
|
81
73
|
return 'start';
|
|
@@ -88,18 +80,17 @@ function getDirection(keyboardEvent) {
|
|
|
88
80
|
}
|
|
89
81
|
function shouldIgnoreFocusHandling(keyboardEvent, activeElement) {
|
|
90
82
|
const key = keyboardEvent.key;
|
|
91
|
-
const
|
|
83
|
+
const keyLength = [...key].length;
|
|
92
84
|
const isEditable = isEditableElement(activeElement);
|
|
93
85
|
const isSelect = activeElement instanceof HTMLSelectElement;
|
|
94
|
-
if (isEditable && (
|
|
86
|
+
if (isEditable && (keyLength === 1 || key === 'Home' || key === 'End')) {
|
|
95
87
|
return true;
|
|
96
88
|
}
|
|
97
89
|
if (isSelect) {
|
|
98
|
-
|
|
99
|
-
if (key === 'ArrowDown' && isMac && !keyboardEvent.metaKey) {
|
|
90
|
+
if (key === 'ArrowDown' && isMacOS() && !keyboardEvent.metaKey) {
|
|
100
91
|
return true;
|
|
101
92
|
}
|
|
102
|
-
if (key === 'ArrowDown' && !
|
|
93
|
+
if (key === 'ArrowDown' && !isMacOS() && keyboardEvent.altKey) {
|
|
103
94
|
return true;
|
|
104
95
|
}
|
|
105
96
|
return false;
|
|
@@ -137,7 +128,7 @@ const activeDescendantActivatedIndirectly = 'activated-indirectly';
|
|
|
137
128
|
const hasActiveDescendantAttribute = 'data-has-active-descendant';
|
|
138
129
|
function focusZone(container, settings) {
|
|
139
130
|
var _a, _b, _c, _d, _e, _f;
|
|
140
|
-
const focusableElements =
|
|
131
|
+
const focusableElements = [];
|
|
141
132
|
const savedTabIndex = new WeakMap();
|
|
142
133
|
const bindKeys = (_a = settings === null || settings === void 0 ? void 0 : settings.bindKeys) !== null && _a !== void 0 ? _a : ((settings === null || settings === void 0 ? void 0 : settings.getNextFocusable) ? FocusKeys.ArrowAll : FocusKeys.ArrowVertical) | FocusKeys.HomeAndEnd;
|
|
143
134
|
const focusOutBehavior = (_b = settings === null || settings === void 0 ? void 0 : settings.focusOutBehavior) !== null && _b !== void 0 ? _b : 'stop';
|
|
@@ -149,7 +140,7 @@ function focusZone(container, settings) {
|
|
|
149
140
|
const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
|
|
150
141
|
const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
|
|
151
142
|
function getFirstFocusableElement() {
|
|
152
|
-
return focusableElements
|
|
143
|
+
return focusableElements[0];
|
|
153
144
|
}
|
|
154
145
|
function isActiveDescendantInputFocused() {
|
|
155
146
|
return document.activeElement === activeDescendantControl;
|
|
@@ -194,20 +185,17 @@ function focusZone(container, settings) {
|
|
|
194
185
|
activeDescendantControl === null || activeDescendantControl === void 0 ? void 0 : activeDescendantControl.removeAttribute('aria-activedescendant');
|
|
195
186
|
container.removeAttribute(hasActiveDescendantAttribute);
|
|
196
187
|
previouslyActiveElement === null || previouslyActiveElement === void 0 ? void 0 : previouslyActiveElement.removeAttribute(isActiveDescendantAttribute);
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
items[i].removeAttribute(isActiveDescendantAttribute);
|
|
188
|
+
for (const item of container.querySelectorAll(`[${isActiveDescendantAttribute}]`)) {
|
|
189
|
+
item === null || item === void 0 ? void 0 : item.removeAttribute(isActiveDescendantAttribute);
|
|
200
190
|
}
|
|
201
191
|
activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(undefined, previouslyActiveElement, false);
|
|
202
192
|
}
|
|
203
193
|
function beginFocusManagement(...elements) {
|
|
204
|
-
const filteredElements = (settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter)
|
|
205
|
-
? elements.filter(e => settings.focusableElementFilter(e))
|
|
206
|
-
: elements;
|
|
194
|
+
const filteredElements = elements.filter(e => { var _a, _b; return (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter) === null || _a === void 0 ? void 0 : _a.call(settings, e)) !== null && _b !== void 0 ? _b : true; });
|
|
207
195
|
if (filteredElements.length === 0) {
|
|
208
196
|
return;
|
|
209
197
|
}
|
|
210
|
-
focusableElements.
|
|
198
|
+
focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
|
|
211
199
|
for (const element of filteredElements) {
|
|
212
200
|
if (!savedTabIndex.has(element)) {
|
|
213
201
|
savedTabIndex.set(element, element.getAttribute('tabindex'));
|
|
@@ -220,13 +208,13 @@ function focusZone(container, settings) {
|
|
|
220
208
|
}
|
|
221
209
|
function findInsertionIndex(elementsToInsert) {
|
|
222
210
|
const firstElementToInsert = elementsToInsert[0];
|
|
223
|
-
if (focusableElements.
|
|
211
|
+
if (focusableElements.length === 0)
|
|
224
212
|
return 0;
|
|
225
213
|
let iMin = 0;
|
|
226
|
-
let iMax = focusableElements.
|
|
214
|
+
let iMax = focusableElements.length - 1;
|
|
227
215
|
while (iMin <= iMax) {
|
|
228
216
|
const i = Math.floor((iMin + iMax) / 2);
|
|
229
|
-
const element = focusableElements
|
|
217
|
+
const element = focusableElements[i];
|
|
230
218
|
if (followsInDocument(firstElementToInsert, element)) {
|
|
231
219
|
iMax = i - 1;
|
|
232
220
|
}
|
|
@@ -241,7 +229,10 @@ function focusZone(container, settings) {
|
|
|
241
229
|
}
|
|
242
230
|
function endFocusManagement(...elements) {
|
|
243
231
|
for (const element of elements) {
|
|
244
|
-
focusableElements.
|
|
232
|
+
const focusableElementIndex = focusableElements.indexOf(element);
|
|
233
|
+
if (focusableElementIndex >= 0) {
|
|
234
|
+
focusableElements.splice(focusableElementIndex, 1);
|
|
235
|
+
}
|
|
245
236
|
const savedIndex = savedTabIndex.get(element);
|
|
246
237
|
if (savedIndex !== undefined) {
|
|
247
238
|
if (savedIndex === null) {
|
|
@@ -268,61 +259,29 @@ function focusZone(container, settings) {
|
|
|
268
259
|
if (!preventInitialFocus)
|
|
269
260
|
updateFocusedElement(initialElement);
|
|
270
261
|
const observer = new MutationObserver(mutations => {
|
|
271
|
-
const elementsToRemove = new Set();
|
|
272
|
-
const elementsToAdd = new Set();
|
|
273
|
-
const attributeRemovals = new Set();
|
|
274
|
-
const attributeAdditions = new Set();
|
|
275
262
|
for (const mutation of mutations) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
elementsToRemove.add(removedNode);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
for (const addedNode of mutation.addedNodes) {
|
|
283
|
-
if (addedNode instanceof HTMLElement) {
|
|
284
|
-
elementsToAdd.add(addedNode);
|
|
285
|
-
}
|
|
263
|
+
for (const removedNode of mutation.removedNodes) {
|
|
264
|
+
if (removedNode instanceof HTMLElement) {
|
|
265
|
+
endFocusManagement(...iterateFocusableElements(removedNode));
|
|
286
266
|
}
|
|
287
267
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (hasAttribute) {
|
|
292
|
-
attributeRemovals.add(mutation.target);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
attributeAdditions.add(mutation.target);
|
|
268
|
+
if (mutation.type === 'attributes' && mutation.oldValue === null) {
|
|
269
|
+
if (mutation.target instanceof HTMLElement) {
|
|
270
|
+
endFocusManagement(mutation.target);
|
|
296
271
|
}
|
|
297
272
|
}
|
|
298
273
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
toRemove.push(el);
|
|
274
|
+
for (const mutation of mutations) {
|
|
275
|
+
for (const addedNode of mutation.addedNodes) {
|
|
276
|
+
if (addedNode instanceof HTMLElement) {
|
|
277
|
+
beginFocusManagement(...iterateFocusableElements(addedNode, iterateFocusableElementsOptions));
|
|
304
278
|
}
|
|
305
279
|
}
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
if (attributeRemovals.size > 0) {
|
|
311
|
-
endFocusManagement(...attributeRemovals);
|
|
312
|
-
}
|
|
313
|
-
if (elementsToAdd.size > 0) {
|
|
314
|
-
const toAdd = [];
|
|
315
|
-
for (const node of elementsToAdd) {
|
|
316
|
-
for (const el of iterateFocusableElements(node, iterateFocusableElementsOptions)) {
|
|
317
|
-
toAdd.push(el);
|
|
280
|
+
if (mutation.type === 'attributes' && mutation.oldValue !== null) {
|
|
281
|
+
if (mutation.target instanceof HTMLElement) {
|
|
282
|
+
beginFocusManagement(mutation.target);
|
|
318
283
|
}
|
|
319
284
|
}
|
|
320
|
-
if (toAdd.length > 0) {
|
|
321
|
-
beginFocusManagement(...toAdd);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (attributeAdditions.size > 0) {
|
|
325
|
-
beginFocusManagement(...attributeAdditions);
|
|
326
285
|
}
|
|
327
286
|
});
|
|
328
287
|
observer.observe(container, {
|
|
@@ -334,9 +293,7 @@ function focusZone(container, settings) {
|
|
|
334
293
|
const controller = new AbortController();
|
|
335
294
|
const signal = (_f = settings === null || settings === void 0 ? void 0 : settings.abortSignal) !== null && _f !== void 0 ? _f : controller.signal;
|
|
336
295
|
signal.addEventListener('abort', () => {
|
|
337
|
-
observer.disconnect();
|
|
338
296
|
endFocusManagement(...focusableElements);
|
|
339
|
-
focusableElements.clear();
|
|
340
297
|
});
|
|
341
298
|
let elementIndexFocusedByClick = undefined;
|
|
342
299
|
container.addEventListener('mousedown', event => {
|
|
@@ -346,7 +303,7 @@ function focusZone(container, settings) {
|
|
|
346
303
|
}, { signal });
|
|
347
304
|
if (activeDescendantControl) {
|
|
348
305
|
container.addEventListener('focusin', event => {
|
|
349
|
-
if (event.target instanceof HTMLElement && focusableElements.
|
|
306
|
+
if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
|
|
350
307
|
activeDescendantControl.focus({ preventScroll });
|
|
351
308
|
updateFocusedElement(event.target);
|
|
352
309
|
}
|
|
@@ -356,10 +313,6 @@ function focusZone(container, settings) {
|
|
|
356
313
|
if (!(target instanceof Node)) {
|
|
357
314
|
return;
|
|
358
315
|
}
|
|
359
|
-
if (target instanceof HTMLElement && focusableElements.has(target)) {
|
|
360
|
-
updateFocusedElement(target);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
316
|
const focusableElement = focusableElements.find(element => element.contains(target));
|
|
364
317
|
if (focusableElement) {
|
|
365
318
|
updateFocusedElement(focusableElement);
|
|
@@ -384,9 +337,8 @@ function focusZone(container, settings) {
|
|
|
384
337
|
if (event.target instanceof HTMLElement) {
|
|
385
338
|
if (elementIndexFocusedByClick !== undefined) {
|
|
386
339
|
if (elementIndexFocusedByClick >= 0) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
updateFocusedElement(clickedElement);
|
|
340
|
+
if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
|
|
341
|
+
updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
|
|
390
342
|
}
|
|
391
343
|
}
|
|
392
344
|
elementIndexFocusedByClick = undefined;
|
|
@@ -397,8 +349,8 @@ function focusZone(container, settings) {
|
|
|
397
349
|
}
|
|
398
350
|
else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
|
|
399
351
|
if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
|
|
400
|
-
const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.
|
|
401
|
-
const targetElement = focusableElements
|
|
352
|
+
const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
|
|
353
|
+
const targetElement = focusableElements[targetElementIndex];
|
|
402
354
|
targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
|
|
403
355
|
return;
|
|
404
356
|
}
|
|
@@ -409,7 +361,8 @@ function focusZone(container, settings) {
|
|
|
409
361
|
else if (typeof focusInStrategy === 'function') {
|
|
410
362
|
if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
|
|
411
363
|
const elementToFocus = focusInStrategy(event.relatedTarget);
|
|
412
|
-
|
|
364
|
+
const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
|
|
365
|
+
if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
|
|
413
366
|
elementToFocus.focus({ preventScroll });
|
|
414
367
|
return;
|
|
415
368
|
}
|
|
@@ -468,26 +421,26 @@ function focusZone(container, settings) {
|
|
|
468
421
|
nextFocusedIndex += 1;
|
|
469
422
|
}
|
|
470
423
|
else {
|
|
471
|
-
nextFocusedIndex = focusableElements.
|
|
424
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
472
425
|
}
|
|
473
426
|
if (nextFocusedIndex < 0) {
|
|
474
427
|
if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
|
|
475
|
-
nextFocusedIndex = focusableElements.
|
|
428
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
476
429
|
}
|
|
477
430
|
else {
|
|
478
431
|
nextFocusedIndex = 0;
|
|
479
432
|
}
|
|
480
433
|
}
|
|
481
|
-
if (nextFocusedIndex >= focusableElements.
|
|
434
|
+
if (nextFocusedIndex >= focusableElements.length) {
|
|
482
435
|
if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
|
|
483
436
|
nextFocusedIndex = 0;
|
|
484
437
|
}
|
|
485
438
|
else {
|
|
486
|
-
nextFocusedIndex = focusableElements.
|
|
439
|
+
nextFocusedIndex = focusableElements.length - 1;
|
|
487
440
|
}
|
|
488
441
|
}
|
|
489
442
|
if (lastFocusedIndex !== nextFocusedIndex) {
|
|
490
|
-
nextElementToFocus = focusableElements
|
|
443
|
+
nextElementToFocus = focusableElements[nextFocusedIndex];
|
|
491
444
|
}
|
|
492
445
|
}
|
|
493
446
|
if (activeDescendantControl) {
|
|
@@ -31,32 +31,24 @@ function* iterateFocusableElements(container, options = {}) {
|
|
|
31
31
|
function getFocusableChild(container, lastChild = false) {
|
|
32
32
|
return iterateFocusableElements(container, { reverse: lastChild, strict: true, onlyTabbable: true }).next().value;
|
|
33
33
|
}
|
|
34
|
-
const DISABLEABLE_TAGS = new Set(['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET']);
|
|
35
34
|
function isFocusable(elem, strict = false) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (DISABLEABLE_TAGS.has(elem.tagName) && elem.disabled)
|
|
35
|
+
const disabledAttrInert = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(elem.tagName) &&
|
|
36
|
+
elem.disabled;
|
|
37
|
+
const hiddenInert = elem.hidden;
|
|
38
|
+
const hiddenInputInert = elem instanceof HTMLInputElement && elem.type === 'hidden';
|
|
39
|
+
const sentinelInert = elem.classList.contains('sentinel');
|
|
40
|
+
if (disabledAttrInert || hiddenInert || hiddenInputInert || sentinelInert) {
|
|
43
41
|
return false;
|
|
42
|
+
}
|
|
44
43
|
if (strict) {
|
|
45
|
-
const offsetWidth = elem.offsetWidth;
|
|
46
|
-
const offsetHeight = elem.offsetHeight;
|
|
47
|
-
const offsetParent = elem.offsetParent;
|
|
48
|
-
if (offsetWidth === 0 || offsetHeight === 0)
|
|
49
|
-
return false;
|
|
50
44
|
const style = getComputedStyle(elem);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!offsetParent && position !== 'fixed' && position !== 'sticky')
|
|
57
|
-
return false;
|
|
58
|
-
if (elem.getClientRects().length === 0)
|
|
45
|
+
const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0;
|
|
46
|
+
const visibilityInert = ['hidden', 'collapse'].includes(style.visibility);
|
|
47
|
+
const displayInert = style.display === 'none' || !elem.offsetParent;
|
|
48
|
+
const clientRectsInert = elem.getClientRects().length === 0;
|
|
49
|
+
if (sizeInert || visibilityInert || clientRectsInert || displayInert) {
|
|
59
50
|
return false;
|
|
51
|
+
}
|
|
60
52
|
}
|
|
61
53
|
if (elem.getAttribute('tabindex') != null) {
|
|
62
54
|
return true;
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class IndexedSet<T> {
|
|
2
|
-
private _items;
|
|
3
|
-
private _itemSet;
|
|
4
|
-
insertAt(index: number, ...elements: T[]): void;
|
|
5
|
-
delete(element: T): boolean;
|
|
6
|
-
has(element: T): boolean;
|
|
7
|
-
indexOf(element: T): number;
|
|
8
|
-
get(index: number): T | undefined;
|
|
9
|
-
get size(): number;
|
|
10
|
-
[Symbol.iterator](): Iterator<T>;
|
|
11
|
-
clear(): void;
|
|
12
|
-
find(predicate: (element: T) => boolean): T | undefined;
|
|
13
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
class IndexedSet {
|
|
4
|
-
constructor() {
|
|
5
|
-
this._items = [];
|
|
6
|
-
this._itemSet = new Set();
|
|
7
|
-
}
|
|
8
|
-
insertAt(index, ...elements) {
|
|
9
|
-
const newElements = elements.filter(e => !this._itemSet.has(e));
|
|
10
|
-
if (newElements.length === 0)
|
|
11
|
-
return;
|
|
12
|
-
this._items.splice(index, 0, ...newElements);
|
|
13
|
-
for (const element of newElements) {
|
|
14
|
-
this._itemSet.add(element);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
delete(element) {
|
|
18
|
-
if (!this._itemSet.has(element))
|
|
19
|
-
return false;
|
|
20
|
-
const index = this._items.indexOf(element);
|
|
21
|
-
if (index >= 0) {
|
|
22
|
-
this._items.splice(index, 1);
|
|
23
|
-
}
|
|
24
|
-
this._itemSet.delete(element);
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
has(element) {
|
|
28
|
-
return this._itemSet.has(element);
|
|
29
|
-
}
|
|
30
|
-
indexOf(element) {
|
|
31
|
-
if (!this._itemSet.has(element))
|
|
32
|
-
return -1;
|
|
33
|
-
return this._items.indexOf(element);
|
|
34
|
-
}
|
|
35
|
-
get(index) {
|
|
36
|
-
return this._items[index];
|
|
37
|
-
}
|
|
38
|
-
get size() {
|
|
39
|
-
return this._items.length;
|
|
40
|
-
}
|
|
41
|
-
[Symbol.iterator]() {
|
|
42
|
-
return this._items[Symbol.iterator]();
|
|
43
|
-
}
|
|
44
|
-
clear() {
|
|
45
|
-
this._items = [];
|
|
46
|
-
this._itemSet.clear();
|
|
47
|
-
}
|
|
48
|
-
find(predicate) {
|
|
49
|
-
return this._items.find(predicate);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
exports.IndexedSet = IndexedSet;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class IndexedSet<T> {
|
|
2
|
-
private _items;
|
|
3
|
-
private _itemSet;
|
|
4
|
-
insertAt(index: number, ...elements: T[]): void;
|
|
5
|
-
delete(element: T): boolean;
|
|
6
|
-
has(element: T): boolean;
|
|
7
|
-
indexOf(element: T): number;
|
|
8
|
-
get(index: number): T | undefined;
|
|
9
|
-
get size(): number;
|
|
10
|
-
[Symbol.iterator](): Iterator<T>;
|
|
11
|
-
clear(): void;
|
|
12
|
-
find(predicate: (element: T) => boolean): T | undefined;
|
|
13
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
class IndexedSet {
|
|
2
|
-
constructor() {
|
|
3
|
-
this._items = [];
|
|
4
|
-
this._itemSet = new Set();
|
|
5
|
-
}
|
|
6
|
-
insertAt(index, ...elements) {
|
|
7
|
-
const newElements = elements.filter(e => !this._itemSet.has(e));
|
|
8
|
-
if (newElements.length === 0)
|
|
9
|
-
return;
|
|
10
|
-
this._items.splice(index, 0, ...newElements);
|
|
11
|
-
for (const element of newElements) {
|
|
12
|
-
this._itemSet.add(element);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
delete(element) {
|
|
16
|
-
if (!this._itemSet.has(element))
|
|
17
|
-
return false;
|
|
18
|
-
const index = this._items.indexOf(element);
|
|
19
|
-
if (index >= 0) {
|
|
20
|
-
this._items.splice(index, 1);
|
|
21
|
-
}
|
|
22
|
-
this._itemSet.delete(element);
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
has(element) {
|
|
26
|
-
return this._itemSet.has(element);
|
|
27
|
-
}
|
|
28
|
-
indexOf(element) {
|
|
29
|
-
if (!this._itemSet.has(element))
|
|
30
|
-
return -1;
|
|
31
|
-
return this._items.indexOf(element);
|
|
32
|
-
}
|
|
33
|
-
get(index) {
|
|
34
|
-
return this._items[index];
|
|
35
|
-
}
|
|
36
|
-
get size() {
|
|
37
|
-
return this._items.length;
|
|
38
|
-
}
|
|
39
|
-
[Symbol.iterator]() {
|
|
40
|
-
return this._items[Symbol.iterator]();
|
|
41
|
-
}
|
|
42
|
-
clear() {
|
|
43
|
-
this._items = [];
|
|
44
|
-
this._itemSet.clear();
|
|
45
|
-
}
|
|
46
|
-
find(predicate) {
|
|
47
|
-
return this._items.find(predicate);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { IndexedSet };
|