@primer/behaviors 0.0.0-20251214175049 → 0.0.0-20251215031129

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.
@@ -23,9 +23,10 @@ function observeFocusTrap(container, sentinels) {
23
23
  const observer = new MutationObserver(mutations => {
24
24
  for (const mutation of mutations) {
25
25
  if (mutation.type === 'childList' && mutation.addedNodes.length) {
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;
26
+ for (const node of mutation.addedNodes) {
27
+ if (node instanceof HTMLElement && node.tagName === 'SPAN' && node.classList.contains('sentinel')) {
28
+ return;
29
+ }
29
30
  }
30
31
  const firstChild = container.firstElementChild;
31
32
  const lastChild = container.lastElementChild;
@@ -5,6 +5,7 @@ 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');
8
9
 
9
10
  eventListenerSignal.polyfill();
10
11
  exports.FocusKeys = void 0;
@@ -89,17 +90,18 @@ function getDirection(keyboardEvent) {
89
90
  }
90
91
  function shouldIgnoreFocusHandling(keyboardEvent, activeElement) {
91
92
  const key = keyboardEvent.key;
92
- const keyLength = [...key].length;
93
+ const isSingleChar = key.length === 1 || (key.length === 2 && key.charCodeAt(0) >= 0xd800 && key.charCodeAt(0) <= 0xdbff);
93
94
  const isEditable = isEditableElement.isEditableElement(activeElement);
94
95
  const isSelect = activeElement instanceof HTMLSelectElement;
95
- if (isEditable && (keyLength === 1 || key === 'Home' || key === 'End')) {
96
+ if (isEditable && (isSingleChar || key === 'Home' || key === 'End')) {
96
97
  return true;
97
98
  }
98
99
  if (isSelect) {
99
- if (key === 'ArrowDown' && userAgent.isMacOS() && !keyboardEvent.metaKey) {
100
+ const isMac = getIsMac();
101
+ if (key === 'ArrowDown' && isMac && !keyboardEvent.metaKey) {
100
102
  return true;
101
103
  }
102
- if (key === 'ArrowDown' && !userAgent.isMacOS() && keyboardEvent.altKey) {
104
+ if (key === 'ArrowDown' && !isMac && keyboardEvent.altKey) {
103
105
  return true;
104
106
  }
105
107
  return false;
@@ -137,7 +139,7 @@ const activeDescendantActivatedIndirectly = 'activated-indirectly';
137
139
  const hasActiveDescendantAttribute = 'data-has-active-descendant';
138
140
  function focusZone(container, settings) {
139
141
  var _a, _b, _c, _d, _e, _f;
140
- const focusableElements = [];
142
+ const focusableElements = new indexedSet.IndexedSet();
141
143
  const savedTabIndex = new WeakMap();
142
144
  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;
143
145
  const focusOutBehavior = (_b = settings === null || settings === void 0 ? void 0 : settings.focusOutBehavior) !== null && _b !== void 0 ? _b : 'stop';
@@ -149,7 +151,7 @@ function focusZone(container, settings) {
149
151
  const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
150
152
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
151
153
  function getFirstFocusableElement() {
152
- return focusableElements[0];
154
+ return focusableElements.get(0);
153
155
  }
154
156
  function isActiveDescendantInputFocused() {
155
157
  return document.activeElement === activeDescendantControl;
@@ -205,7 +207,7 @@ function focusZone(container, settings) {
205
207
  if (filteredElements.length === 0) {
206
208
  return;
207
209
  }
208
- focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
210
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
209
211
  for (const element of filteredElements) {
210
212
  if (!savedTabIndex.has(element)) {
211
213
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -218,13 +220,13 @@ function focusZone(container, settings) {
218
220
  }
219
221
  function findInsertionIndex(elementsToInsert) {
220
222
  const firstElementToInsert = elementsToInsert[0];
221
- if (focusableElements.length === 0)
223
+ if (focusableElements.size === 0)
222
224
  return 0;
223
225
  let iMin = 0;
224
- let iMax = focusableElements.length - 1;
226
+ let iMax = focusableElements.size - 1;
225
227
  while (iMin <= iMax) {
226
228
  const i = Math.floor((iMin + iMax) / 2);
227
- const element = focusableElements[i];
229
+ const element = focusableElements.get(i);
228
230
  if (followsInDocument(firstElementToInsert, element)) {
229
231
  iMax = i - 1;
230
232
  }
@@ -239,10 +241,7 @@ function focusZone(container, settings) {
239
241
  }
240
242
  function endFocusManagement(...elements) {
241
243
  for (const element of elements) {
242
- const focusableElementIndex = focusableElements.indexOf(element);
243
- if (focusableElementIndex >= 0) {
244
- focusableElements.splice(focusableElementIndex, 1);
245
- }
244
+ focusableElements.delete(element);
246
245
  const savedIndex = savedTabIndex.get(element);
247
246
  if (savedIndex !== undefined) {
248
247
  if (savedIndex === null) {
@@ -298,7 +297,12 @@ function focusZone(container, settings) {
298
297
  }
299
298
  }
300
299
  if (elementsToRemove.size > 0) {
301
- const toRemove = [...elementsToRemove].flatMap(node => [...iterateFocusableElements.iterateFocusableElements(node)]);
300
+ const toRemove = [];
301
+ for (const node of elementsToRemove) {
302
+ for (const el of iterateFocusableElements.iterateFocusableElements(node)) {
303
+ toRemove.push(el);
304
+ }
305
+ }
302
306
  if (toRemove.length > 0) {
303
307
  endFocusManagement(...toRemove);
304
308
  }
@@ -307,9 +311,12 @@ function focusZone(container, settings) {
307
311
  endFocusManagement(...attributeRemovals);
308
312
  }
309
313
  if (elementsToAdd.size > 0) {
310
- const toAdd = [...elementsToAdd].flatMap(node => [
311
- ...iterateFocusableElements.iterateFocusableElements(node, iterateFocusableElementsOptions),
312
- ]);
314
+ const toAdd = [];
315
+ for (const node of elementsToAdd) {
316
+ for (const el of iterateFocusableElements.iterateFocusableElements(node, iterateFocusableElementsOptions)) {
317
+ toAdd.push(el);
318
+ }
319
+ }
313
320
  if (toAdd.length > 0) {
314
321
  beginFocusManagement(...toAdd);
315
322
  }
@@ -338,7 +345,7 @@ function focusZone(container, settings) {
338
345
  }, { signal });
339
346
  if (activeDescendantControl) {
340
347
  container.addEventListener('focusin', event => {
341
- if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
348
+ if (event.target instanceof HTMLElement && focusableElements.has(event.target)) {
342
349
  activeDescendantControl.focus({ preventScroll });
343
350
  updateFocusedElement(event.target);
344
351
  }
@@ -348,6 +355,10 @@ function focusZone(container, settings) {
348
355
  if (!(target instanceof Node)) {
349
356
  return;
350
357
  }
358
+ if (target instanceof HTMLElement && focusableElements.has(target)) {
359
+ updateFocusedElement(target);
360
+ return;
361
+ }
351
362
  const focusableElement = focusableElements.find(element => element.contains(target));
352
363
  if (focusableElement) {
353
364
  updateFocusedElement(focusableElement);
@@ -372,8 +383,9 @@ function focusZone(container, settings) {
372
383
  if (event.target instanceof HTMLElement) {
373
384
  if (elementIndexFocusedByClick !== undefined) {
374
385
  if (elementIndexFocusedByClick >= 0) {
375
- if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
376
- updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
386
+ const clickedElement = focusableElements.get(elementIndexFocusedByClick);
387
+ if (clickedElement && clickedElement !== currentFocusedElement) {
388
+ updateFocusedElement(clickedElement);
377
389
  }
378
390
  }
379
391
  elementIndexFocusedByClick = undefined;
@@ -384,8 +396,8 @@ function focusZone(container, settings) {
384
396
  }
385
397
  else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
386
398
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
387
- const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
388
- const targetElement = focusableElements[targetElementIndex];
399
+ const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.size - 1 : 0;
400
+ const targetElement = focusableElements.get(targetElementIndex);
389
401
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
390
402
  return;
391
403
  }
@@ -396,8 +408,7 @@ function focusZone(container, settings) {
396
408
  else if (typeof focusInStrategy === 'function') {
397
409
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
398
410
  const elementToFocus = focusInStrategy(event.relatedTarget);
399
- const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
400
- if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
411
+ if (elementToFocus && focusableElements.has(elementToFocus)) {
401
412
  elementToFocus.focus({ preventScroll });
402
413
  return;
403
414
  }
@@ -456,26 +467,26 @@ function focusZone(container, settings) {
456
467
  nextFocusedIndex += 1;
457
468
  }
458
469
  else {
459
- nextFocusedIndex = focusableElements.length - 1;
470
+ nextFocusedIndex = focusableElements.size - 1;
460
471
  }
461
472
  if (nextFocusedIndex < 0) {
462
473
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
463
- nextFocusedIndex = focusableElements.length - 1;
474
+ nextFocusedIndex = focusableElements.size - 1;
464
475
  }
465
476
  else {
466
477
  nextFocusedIndex = 0;
467
478
  }
468
479
  }
469
- if (nextFocusedIndex >= focusableElements.length) {
480
+ if (nextFocusedIndex >= focusableElements.size) {
470
481
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
471
482
  nextFocusedIndex = 0;
472
483
  }
473
484
  else {
474
- nextFocusedIndex = focusableElements.length - 1;
485
+ nextFocusedIndex = focusableElements.size - 1;
475
486
  }
476
487
  }
477
488
  if (lastFocusedIndex !== nextFocusedIndex) {
478
- nextElementToFocus = focusableElements[nextFocusedIndex];
489
+ nextElementToFocus = focusableElements.get(nextFocusedIndex);
479
490
  }
480
491
  }
481
492
  if (activeDescendantControl) {
@@ -0,0 +1,12 @@
1
+ export declare class IndexedSet<T> {
2
+ #private;
3
+ insertAt(index: number, ...elements: T[]): void;
4
+ delete(element: T): boolean;
5
+ has(element: T): boolean;
6
+ indexOf(element: T): number;
7
+ get(index: number): T | undefined;
8
+ get size(): number;
9
+ [Symbol.iterator](): Iterator<T>;
10
+ clear(): void;
11
+ find(predicate: (element: T) => boolean): T | undefined;
12
+ }
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var tslib = require('tslib');
4
+
5
+ var _IndexedSet_items, _IndexedSet_itemSet;
6
+ class IndexedSet {
7
+ constructor() {
8
+ _IndexedSet_items.set(this, []);
9
+ _IndexedSet_itemSet.set(this, new Set());
10
+ }
11
+ insertAt(index, ...elements) {
12
+ const newElements = elements.filter(e => !tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(e));
13
+ if (newElements.length === 0)
14
+ return;
15
+ tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").splice(index, 0, ...newElements);
16
+ for (const element of newElements) {
17
+ tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").add(element);
18
+ }
19
+ }
20
+ delete(element) {
21
+ if (!tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element))
22
+ return false;
23
+ const index = tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").indexOf(element);
24
+ if (index >= 0) {
25
+ tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").splice(index, 1);
26
+ }
27
+ tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").delete(element);
28
+ return true;
29
+ }
30
+ has(element) {
31
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element);
32
+ }
33
+ indexOf(element) {
34
+ if (!tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element))
35
+ return -1;
36
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").indexOf(element);
37
+ }
38
+ get(index) {
39
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f")[index];
40
+ }
41
+ get size() {
42
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").length;
43
+ }
44
+ [(_IndexedSet_items = new WeakMap(), _IndexedSet_itemSet = new WeakMap(), Symbol.iterator)]() {
45
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f")[Symbol.iterator]();
46
+ }
47
+ clear() {
48
+ tslib.__classPrivateFieldSet(this, _IndexedSet_items, [], "f");
49
+ tslib.__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").clear();
50
+ }
51
+ find(predicate) {
52
+ return tslib.__classPrivateFieldGet(this, _IndexedSet_items, "f").find(predicate);
53
+ }
54
+ }
55
+
56
+ exports.IndexedSet = IndexedSet;
@@ -21,9 +21,10 @@ function observeFocusTrap(container, sentinels) {
21
21
  const observer = new MutationObserver(mutations => {
22
22
  for (const mutation of mutations) {
23
23
  if (mutation.type === 'childList' && mutation.addedNodes.length) {
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;
24
+ for (const node of mutation.addedNodes) {
25
+ if (node instanceof HTMLElement && node.tagName === 'SPAN' && node.classList.contains('sentinel')) {
26
+ return;
27
+ }
27
28
  }
28
29
  const firstChild = container.firstElementChild;
29
30
  const lastChild = container.lastElementChild;
@@ -3,6 +3,7 @@ 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';
6
7
 
7
8
  polyfill();
8
9
  var FocusKeys;
@@ -87,17 +88,18 @@ function getDirection(keyboardEvent) {
87
88
  }
88
89
  function shouldIgnoreFocusHandling(keyboardEvent, activeElement) {
89
90
  const key = keyboardEvent.key;
90
- const keyLength = [...key].length;
91
+ const isSingleChar = key.length === 1 || (key.length === 2 && key.charCodeAt(0) >= 0xd800 && key.charCodeAt(0) <= 0xdbff);
91
92
  const isEditable = isEditableElement(activeElement);
92
93
  const isSelect = activeElement instanceof HTMLSelectElement;
93
- if (isEditable && (keyLength === 1 || key === 'Home' || key === 'End')) {
94
+ if (isEditable && (isSingleChar || key === 'Home' || key === 'End')) {
94
95
  return true;
95
96
  }
96
97
  if (isSelect) {
97
- if (key === 'ArrowDown' && isMacOS() && !keyboardEvent.metaKey) {
98
+ const isMac = getIsMac();
99
+ if (key === 'ArrowDown' && isMac && !keyboardEvent.metaKey) {
98
100
  return true;
99
101
  }
100
- if (key === 'ArrowDown' && !isMacOS() && keyboardEvent.altKey) {
102
+ if (key === 'ArrowDown' && !isMac && keyboardEvent.altKey) {
101
103
  return true;
102
104
  }
103
105
  return false;
@@ -135,7 +137,7 @@ const activeDescendantActivatedIndirectly = 'activated-indirectly';
135
137
  const hasActiveDescendantAttribute = 'data-has-active-descendant';
136
138
  function focusZone(container, settings) {
137
139
  var _a, _b, _c, _d, _e, _f;
138
- const focusableElements = [];
140
+ const focusableElements = new IndexedSet();
139
141
  const savedTabIndex = new WeakMap();
140
142
  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;
141
143
  const focusOutBehavior = (_b = settings === null || settings === void 0 ? void 0 : settings.focusOutBehavior) !== null && _b !== void 0 ? _b : 'stop';
@@ -147,7 +149,7 @@ function focusZone(container, settings) {
147
149
  const preventScroll = (_e = settings === null || settings === void 0 ? void 0 : settings.preventScroll) !== null && _e !== void 0 ? _e : false;
148
150
  const preventInitialFocus = focusInStrategy === 'initial' && (settings === null || settings === void 0 ? void 0 : settings.activeDescendantControl);
149
151
  function getFirstFocusableElement() {
150
- return focusableElements[0];
152
+ return focusableElements.get(0);
151
153
  }
152
154
  function isActiveDescendantInputFocused() {
153
155
  return document.activeElement === activeDescendantControl;
@@ -203,7 +205,7 @@ function focusZone(container, settings) {
203
205
  if (filteredElements.length === 0) {
204
206
  return;
205
207
  }
206
- focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
208
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
207
209
  for (const element of filteredElements) {
208
210
  if (!savedTabIndex.has(element)) {
209
211
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -216,13 +218,13 @@ function focusZone(container, settings) {
216
218
  }
217
219
  function findInsertionIndex(elementsToInsert) {
218
220
  const firstElementToInsert = elementsToInsert[0];
219
- if (focusableElements.length === 0)
221
+ if (focusableElements.size === 0)
220
222
  return 0;
221
223
  let iMin = 0;
222
- let iMax = focusableElements.length - 1;
224
+ let iMax = focusableElements.size - 1;
223
225
  while (iMin <= iMax) {
224
226
  const i = Math.floor((iMin + iMax) / 2);
225
- const element = focusableElements[i];
227
+ const element = focusableElements.get(i);
226
228
  if (followsInDocument(firstElementToInsert, element)) {
227
229
  iMax = i - 1;
228
230
  }
@@ -237,10 +239,7 @@ function focusZone(container, settings) {
237
239
  }
238
240
  function endFocusManagement(...elements) {
239
241
  for (const element of elements) {
240
- const focusableElementIndex = focusableElements.indexOf(element);
241
- if (focusableElementIndex >= 0) {
242
- focusableElements.splice(focusableElementIndex, 1);
243
- }
242
+ focusableElements.delete(element);
244
243
  const savedIndex = savedTabIndex.get(element);
245
244
  if (savedIndex !== undefined) {
246
245
  if (savedIndex === null) {
@@ -296,7 +295,12 @@ function focusZone(container, settings) {
296
295
  }
297
296
  }
298
297
  if (elementsToRemove.size > 0) {
299
- const toRemove = [...elementsToRemove].flatMap(node => [...iterateFocusableElements(node)]);
298
+ const toRemove = [];
299
+ for (const node of elementsToRemove) {
300
+ for (const el of iterateFocusableElements(node)) {
301
+ toRemove.push(el);
302
+ }
303
+ }
300
304
  if (toRemove.length > 0) {
301
305
  endFocusManagement(...toRemove);
302
306
  }
@@ -305,9 +309,12 @@ function focusZone(container, settings) {
305
309
  endFocusManagement(...attributeRemovals);
306
310
  }
307
311
  if (elementsToAdd.size > 0) {
308
- const toAdd = [...elementsToAdd].flatMap(node => [
309
- ...iterateFocusableElements(node, iterateFocusableElementsOptions),
310
- ]);
312
+ const toAdd = [];
313
+ for (const node of elementsToAdd) {
314
+ for (const el of iterateFocusableElements(node, iterateFocusableElementsOptions)) {
315
+ toAdd.push(el);
316
+ }
317
+ }
311
318
  if (toAdd.length > 0) {
312
319
  beginFocusManagement(...toAdd);
313
320
  }
@@ -336,7 +343,7 @@ function focusZone(container, settings) {
336
343
  }, { signal });
337
344
  if (activeDescendantControl) {
338
345
  container.addEventListener('focusin', event => {
339
- if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
346
+ if (event.target instanceof HTMLElement && focusableElements.has(event.target)) {
340
347
  activeDescendantControl.focus({ preventScroll });
341
348
  updateFocusedElement(event.target);
342
349
  }
@@ -346,6 +353,10 @@ function focusZone(container, settings) {
346
353
  if (!(target instanceof Node)) {
347
354
  return;
348
355
  }
356
+ if (target instanceof HTMLElement && focusableElements.has(target)) {
357
+ updateFocusedElement(target);
358
+ return;
359
+ }
349
360
  const focusableElement = focusableElements.find(element => element.contains(target));
350
361
  if (focusableElement) {
351
362
  updateFocusedElement(focusableElement);
@@ -370,8 +381,9 @@ function focusZone(container, settings) {
370
381
  if (event.target instanceof HTMLElement) {
371
382
  if (elementIndexFocusedByClick !== undefined) {
372
383
  if (elementIndexFocusedByClick >= 0) {
373
- if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
374
- updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
384
+ const clickedElement = focusableElements.get(elementIndexFocusedByClick);
385
+ if (clickedElement && clickedElement !== currentFocusedElement) {
386
+ updateFocusedElement(clickedElement);
375
387
  }
376
388
  }
377
389
  elementIndexFocusedByClick = undefined;
@@ -382,8 +394,8 @@ function focusZone(container, settings) {
382
394
  }
383
395
  else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
384
396
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
385
- const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
386
- const targetElement = focusableElements[targetElementIndex];
397
+ const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.size - 1 : 0;
398
+ const targetElement = focusableElements.get(targetElementIndex);
387
399
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
388
400
  return;
389
401
  }
@@ -394,8 +406,7 @@ function focusZone(container, settings) {
394
406
  else if (typeof focusInStrategy === 'function') {
395
407
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
396
408
  const elementToFocus = focusInStrategy(event.relatedTarget);
397
- const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
398
- if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
409
+ if (elementToFocus && focusableElements.has(elementToFocus)) {
399
410
  elementToFocus.focus({ preventScroll });
400
411
  return;
401
412
  }
@@ -454,26 +465,26 @@ function focusZone(container, settings) {
454
465
  nextFocusedIndex += 1;
455
466
  }
456
467
  else {
457
- nextFocusedIndex = focusableElements.length - 1;
468
+ nextFocusedIndex = focusableElements.size - 1;
458
469
  }
459
470
  if (nextFocusedIndex < 0) {
460
471
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
461
- nextFocusedIndex = focusableElements.length - 1;
472
+ nextFocusedIndex = focusableElements.size - 1;
462
473
  }
463
474
  else {
464
475
  nextFocusedIndex = 0;
465
476
  }
466
477
  }
467
- if (nextFocusedIndex >= focusableElements.length) {
478
+ if (nextFocusedIndex >= focusableElements.size) {
468
479
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
469
480
  nextFocusedIndex = 0;
470
481
  }
471
482
  else {
472
- nextFocusedIndex = focusableElements.length - 1;
483
+ nextFocusedIndex = focusableElements.size - 1;
473
484
  }
474
485
  }
475
486
  if (lastFocusedIndex !== nextFocusedIndex) {
476
- nextElementToFocus = focusableElements[nextFocusedIndex];
487
+ nextElementToFocus = focusableElements.get(nextFocusedIndex);
477
488
  }
478
489
  }
479
490
  if (activeDescendantControl) {
@@ -0,0 +1,12 @@
1
+ export declare class IndexedSet<T> {
2
+ #private;
3
+ insertAt(index: number, ...elements: T[]): void;
4
+ delete(element: T): boolean;
5
+ has(element: T): boolean;
6
+ indexOf(element: T): number;
7
+ get(index: number): T | undefined;
8
+ get size(): number;
9
+ [Symbol.iterator](): Iterator<T>;
10
+ clear(): void;
11
+ find(predicate: (element: T) => boolean): T | undefined;
12
+ }
@@ -0,0 +1,54 @@
1
+ import { __classPrivateFieldGet, __classPrivateFieldSet } from 'tslib';
2
+
3
+ var _IndexedSet_items, _IndexedSet_itemSet;
4
+ class IndexedSet {
5
+ constructor() {
6
+ _IndexedSet_items.set(this, []);
7
+ _IndexedSet_itemSet.set(this, new Set());
8
+ }
9
+ insertAt(index, ...elements) {
10
+ const newElements = elements.filter(e => !__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(e));
11
+ if (newElements.length === 0)
12
+ return;
13
+ __classPrivateFieldGet(this, _IndexedSet_items, "f").splice(index, 0, ...newElements);
14
+ for (const element of newElements) {
15
+ __classPrivateFieldGet(this, _IndexedSet_itemSet, "f").add(element);
16
+ }
17
+ }
18
+ delete(element) {
19
+ if (!__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element))
20
+ return false;
21
+ const index = __classPrivateFieldGet(this, _IndexedSet_items, "f").indexOf(element);
22
+ if (index >= 0) {
23
+ __classPrivateFieldGet(this, _IndexedSet_items, "f").splice(index, 1);
24
+ }
25
+ __classPrivateFieldGet(this, _IndexedSet_itemSet, "f").delete(element);
26
+ return true;
27
+ }
28
+ has(element) {
29
+ return __classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element);
30
+ }
31
+ indexOf(element) {
32
+ if (!__classPrivateFieldGet(this, _IndexedSet_itemSet, "f").has(element))
33
+ return -1;
34
+ return __classPrivateFieldGet(this, _IndexedSet_items, "f").indexOf(element);
35
+ }
36
+ get(index) {
37
+ return __classPrivateFieldGet(this, _IndexedSet_items, "f")[index];
38
+ }
39
+ get size() {
40
+ return __classPrivateFieldGet(this, _IndexedSet_items, "f").length;
41
+ }
42
+ [(_IndexedSet_items = new WeakMap(), _IndexedSet_itemSet = new WeakMap(), Symbol.iterator)]() {
43
+ return __classPrivateFieldGet(this, _IndexedSet_items, "f")[Symbol.iterator]();
44
+ }
45
+ clear() {
46
+ __classPrivateFieldSet(this, _IndexedSet_items, [], "f");
47
+ __classPrivateFieldGet(this, _IndexedSet_itemSet, "f").clear();
48
+ }
49
+ find(predicate) {
50
+ return __classPrivateFieldGet(this, _IndexedSet_items, "f").find(predicate);
51
+ }
52
+ }
53
+
54
+ export { IndexedSet };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/behaviors",
3
- "version": "0.0.0-20251214175049",
3
+ "version": "0.0.0-20251215031129",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cjs/index.js",