@primer/behaviors 0.0.0-20251215025613 → 0.0.0-20251215032051

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.
@@ -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;
@@ -201,11 +203,13 @@ function focusZone(container, settings) {
201
203
  activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(undefined, previouslyActiveElement, false);
202
204
  }
203
205
  function beginFocusManagement(...elements) {
204
- 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; });
206
+ const filteredElements = (settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter)
207
+ ? elements.filter(e => settings.focusableElementFilter(e))
208
+ : elements;
205
209
  if (filteredElements.length === 0) {
206
210
  return;
207
211
  }
208
- focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
212
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
209
213
  for (const element of filteredElements) {
210
214
  if (!savedTabIndex.has(element)) {
211
215
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -218,13 +222,13 @@ function focusZone(container, settings) {
218
222
  }
219
223
  function findInsertionIndex(elementsToInsert) {
220
224
  const firstElementToInsert = elementsToInsert[0];
221
- if (focusableElements.length === 0)
225
+ if (focusableElements.size === 0)
222
226
  return 0;
223
227
  let iMin = 0;
224
- let iMax = focusableElements.length - 1;
228
+ let iMax = focusableElements.size - 1;
225
229
  while (iMin <= iMax) {
226
230
  const i = Math.floor((iMin + iMax) / 2);
227
- const element = focusableElements[i];
231
+ const element = focusableElements.get(i);
228
232
  if (followsInDocument(firstElementToInsert, element)) {
229
233
  iMax = i - 1;
230
234
  }
@@ -239,10 +243,7 @@ function focusZone(container, settings) {
239
243
  }
240
244
  function endFocusManagement(...elements) {
241
245
  for (const element of elements) {
242
- const focusableElementIndex = focusableElements.indexOf(element);
243
- if (focusableElementIndex >= 0) {
244
- focusableElements.splice(focusableElementIndex, 1);
245
- }
246
+ focusableElements.delete(element);
246
247
  const savedIndex = savedTabIndex.get(element);
247
248
  if (savedIndex !== undefined) {
248
249
  if (savedIndex === null) {
@@ -298,7 +299,12 @@ function focusZone(container, settings) {
298
299
  }
299
300
  }
300
301
  if (elementsToRemove.size > 0) {
301
- const toRemove = [...elementsToRemove].flatMap(node => [...iterateFocusableElements.iterateFocusableElements(node)]);
302
+ const toRemove = [];
303
+ for (const node of elementsToRemove) {
304
+ for (const el of iterateFocusableElements.iterateFocusableElements(node)) {
305
+ toRemove.push(el);
306
+ }
307
+ }
302
308
  if (toRemove.length > 0) {
303
309
  endFocusManagement(...toRemove);
304
310
  }
@@ -307,9 +313,12 @@ function focusZone(container, settings) {
307
313
  endFocusManagement(...attributeRemovals);
308
314
  }
309
315
  if (elementsToAdd.size > 0) {
310
- const toAdd = [...elementsToAdd].flatMap(node => [
311
- ...iterateFocusableElements.iterateFocusableElements(node, iterateFocusableElementsOptions),
312
- ]);
316
+ const toAdd = [];
317
+ for (const node of elementsToAdd) {
318
+ for (const el of iterateFocusableElements.iterateFocusableElements(node, iterateFocusableElementsOptions)) {
319
+ toAdd.push(el);
320
+ }
321
+ }
313
322
  if (toAdd.length > 0) {
314
323
  beginFocusManagement(...toAdd);
315
324
  }
@@ -338,7 +347,7 @@ function focusZone(container, settings) {
338
347
  }, { signal });
339
348
  if (activeDescendantControl) {
340
349
  container.addEventListener('focusin', event => {
341
- if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
350
+ if (event.target instanceof HTMLElement && focusableElements.has(event.target)) {
342
351
  activeDescendantControl.focus({ preventScroll });
343
352
  updateFocusedElement(event.target);
344
353
  }
@@ -348,9 +357,8 @@ function focusZone(container, settings) {
348
357
  if (!(target instanceof Node)) {
349
358
  return;
350
359
  }
351
- const targetIndex = target instanceof HTMLElement ? focusableElements.indexOf(target) : -1;
352
- if (targetIndex >= 0) {
353
- updateFocusedElement(focusableElements[targetIndex]);
360
+ if (target instanceof HTMLElement && focusableElements.has(target)) {
361
+ updateFocusedElement(target);
354
362
  return;
355
363
  }
356
364
  const focusableElement = focusableElements.find(element => element.contains(target));
@@ -377,8 +385,9 @@ function focusZone(container, settings) {
377
385
  if (event.target instanceof HTMLElement) {
378
386
  if (elementIndexFocusedByClick !== undefined) {
379
387
  if (elementIndexFocusedByClick >= 0) {
380
- if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
381
- updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
388
+ const clickedElement = focusableElements.get(elementIndexFocusedByClick);
389
+ if (clickedElement && clickedElement !== currentFocusedElement) {
390
+ updateFocusedElement(clickedElement);
382
391
  }
383
392
  }
384
393
  elementIndexFocusedByClick = undefined;
@@ -389,8 +398,8 @@ function focusZone(container, settings) {
389
398
  }
390
399
  else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
391
400
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
392
- const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
393
- const targetElement = focusableElements[targetElementIndex];
401
+ const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.size - 1 : 0;
402
+ const targetElement = focusableElements.get(targetElementIndex);
394
403
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
395
404
  return;
396
405
  }
@@ -401,8 +410,7 @@ function focusZone(container, settings) {
401
410
  else if (typeof focusInStrategy === 'function') {
402
411
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
403
412
  const elementToFocus = focusInStrategy(event.relatedTarget);
404
- const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
405
- if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
413
+ if (elementToFocus && focusableElements.has(elementToFocus)) {
406
414
  elementToFocus.focus({ preventScroll });
407
415
  return;
408
416
  }
@@ -461,26 +469,26 @@ function focusZone(container, settings) {
461
469
  nextFocusedIndex += 1;
462
470
  }
463
471
  else {
464
- nextFocusedIndex = focusableElements.length - 1;
472
+ nextFocusedIndex = focusableElements.size - 1;
465
473
  }
466
474
  if (nextFocusedIndex < 0) {
467
475
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
468
- nextFocusedIndex = focusableElements.length - 1;
476
+ nextFocusedIndex = focusableElements.size - 1;
469
477
  }
470
478
  else {
471
479
  nextFocusedIndex = 0;
472
480
  }
473
481
  }
474
- if (nextFocusedIndex >= focusableElements.length) {
482
+ if (nextFocusedIndex >= focusableElements.size) {
475
483
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
476
484
  nextFocusedIndex = 0;
477
485
  }
478
486
  else {
479
- nextFocusedIndex = focusableElements.length - 1;
487
+ nextFocusedIndex = focusableElements.size - 1;
480
488
  }
481
489
  }
482
490
  if (lastFocusedIndex !== nextFocusedIndex) {
483
- nextElementToFocus = focusableElements[nextFocusedIndex];
491
+ nextElementToFocus = focusableElements.get(nextFocusedIndex);
484
492
  }
485
493
  }
486
494
  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;
@@ -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;
@@ -199,11 +201,13 @@ function focusZone(container, settings) {
199
201
  activeDescendantCallback === null || activeDescendantCallback === void 0 ? void 0 : activeDescendantCallback(undefined, previouslyActiveElement, false);
200
202
  }
201
203
  function beginFocusManagement(...elements) {
202
- 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; });
204
+ const filteredElements = (settings === null || settings === void 0 ? void 0 : settings.focusableElementFilter)
205
+ ? elements.filter(e => settings.focusableElementFilter(e))
206
+ : elements;
203
207
  if (filteredElements.length === 0) {
204
208
  return;
205
209
  }
206
- focusableElements.splice(findInsertionIndex(filteredElements), 0, ...filteredElements);
210
+ focusableElements.insertAt(findInsertionIndex(filteredElements), ...filteredElements);
207
211
  for (const element of filteredElements) {
208
212
  if (!savedTabIndex.has(element)) {
209
213
  savedTabIndex.set(element, element.getAttribute('tabindex'));
@@ -216,13 +220,13 @@ function focusZone(container, settings) {
216
220
  }
217
221
  function findInsertionIndex(elementsToInsert) {
218
222
  const firstElementToInsert = elementsToInsert[0];
219
- if (focusableElements.length === 0)
223
+ if (focusableElements.size === 0)
220
224
  return 0;
221
225
  let iMin = 0;
222
- let iMax = focusableElements.length - 1;
226
+ let iMax = focusableElements.size - 1;
223
227
  while (iMin <= iMax) {
224
228
  const i = Math.floor((iMin + iMax) / 2);
225
- const element = focusableElements[i];
229
+ const element = focusableElements.get(i);
226
230
  if (followsInDocument(firstElementToInsert, element)) {
227
231
  iMax = i - 1;
228
232
  }
@@ -237,10 +241,7 @@ function focusZone(container, settings) {
237
241
  }
238
242
  function endFocusManagement(...elements) {
239
243
  for (const element of elements) {
240
- const focusableElementIndex = focusableElements.indexOf(element);
241
- if (focusableElementIndex >= 0) {
242
- focusableElements.splice(focusableElementIndex, 1);
243
- }
244
+ focusableElements.delete(element);
244
245
  const savedIndex = savedTabIndex.get(element);
245
246
  if (savedIndex !== undefined) {
246
247
  if (savedIndex === null) {
@@ -296,7 +297,12 @@ function focusZone(container, settings) {
296
297
  }
297
298
  }
298
299
  if (elementsToRemove.size > 0) {
299
- const toRemove = [...elementsToRemove].flatMap(node => [...iterateFocusableElements(node)]);
300
+ const toRemove = [];
301
+ for (const node of elementsToRemove) {
302
+ for (const el of iterateFocusableElements(node)) {
303
+ toRemove.push(el);
304
+ }
305
+ }
300
306
  if (toRemove.length > 0) {
301
307
  endFocusManagement(...toRemove);
302
308
  }
@@ -305,9 +311,12 @@ function focusZone(container, settings) {
305
311
  endFocusManagement(...attributeRemovals);
306
312
  }
307
313
  if (elementsToAdd.size > 0) {
308
- const toAdd = [...elementsToAdd].flatMap(node => [
309
- ...iterateFocusableElements(node, iterateFocusableElementsOptions),
310
- ]);
314
+ const toAdd = [];
315
+ for (const node of elementsToAdd) {
316
+ for (const el of iterateFocusableElements(node, iterateFocusableElementsOptions)) {
317
+ toAdd.push(el);
318
+ }
319
+ }
311
320
  if (toAdd.length > 0) {
312
321
  beginFocusManagement(...toAdd);
313
322
  }
@@ -336,7 +345,7 @@ function focusZone(container, settings) {
336
345
  }, { signal });
337
346
  if (activeDescendantControl) {
338
347
  container.addEventListener('focusin', event => {
339
- if (event.target instanceof HTMLElement && focusableElements.includes(event.target)) {
348
+ if (event.target instanceof HTMLElement && focusableElements.has(event.target)) {
340
349
  activeDescendantControl.focus({ preventScroll });
341
350
  updateFocusedElement(event.target);
342
351
  }
@@ -346,9 +355,8 @@ function focusZone(container, settings) {
346
355
  if (!(target instanceof Node)) {
347
356
  return;
348
357
  }
349
- const targetIndex = target instanceof HTMLElement ? focusableElements.indexOf(target) : -1;
350
- if (targetIndex >= 0) {
351
- updateFocusedElement(focusableElements[targetIndex]);
358
+ if (target instanceof HTMLElement && focusableElements.has(target)) {
359
+ updateFocusedElement(target);
352
360
  return;
353
361
  }
354
362
  const focusableElement = focusableElements.find(element => element.contains(target));
@@ -375,8 +383,9 @@ function focusZone(container, settings) {
375
383
  if (event.target instanceof HTMLElement) {
376
384
  if (elementIndexFocusedByClick !== undefined) {
377
385
  if (elementIndexFocusedByClick >= 0) {
378
- if (focusableElements[elementIndexFocusedByClick] !== currentFocusedElement) {
379
- updateFocusedElement(focusableElements[elementIndexFocusedByClick]);
386
+ const clickedElement = focusableElements.get(elementIndexFocusedByClick);
387
+ if (clickedElement && clickedElement !== currentFocusedElement) {
388
+ updateFocusedElement(clickedElement);
380
389
  }
381
390
  }
382
391
  elementIndexFocusedByClick = undefined;
@@ -387,8 +396,8 @@ function focusZone(container, settings) {
387
396
  }
388
397
  else if (focusInStrategy === 'closest' || focusInStrategy === 'first') {
389
398
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
390
- const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.length - 1 : 0;
391
- const targetElement = focusableElements[targetElementIndex];
399
+ const targetElementIndex = lastKeyboardFocusDirection === 'previous' ? focusableElements.size - 1 : 0;
400
+ const targetElement = focusableElements.get(targetElementIndex);
392
401
  targetElement === null || targetElement === void 0 ? void 0 : targetElement.focus({ preventScroll });
393
402
  return;
394
403
  }
@@ -399,8 +408,7 @@ function focusZone(container, settings) {
399
408
  else if (typeof focusInStrategy === 'function') {
400
409
  if (event.relatedTarget instanceof Element && !container.contains(event.relatedTarget)) {
401
410
  const elementToFocus = focusInStrategy(event.relatedTarget);
402
- const requestedFocusElementIndex = elementToFocus ? focusableElements.indexOf(elementToFocus) : -1;
403
- if (requestedFocusElementIndex >= 0 && elementToFocus instanceof HTMLElement) {
411
+ if (elementToFocus && focusableElements.has(elementToFocus)) {
404
412
  elementToFocus.focus({ preventScroll });
405
413
  return;
406
414
  }
@@ -459,26 +467,26 @@ function focusZone(container, settings) {
459
467
  nextFocusedIndex += 1;
460
468
  }
461
469
  else {
462
- nextFocusedIndex = focusableElements.length - 1;
470
+ nextFocusedIndex = focusableElements.size - 1;
463
471
  }
464
472
  if (nextFocusedIndex < 0) {
465
473
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
466
- nextFocusedIndex = focusableElements.length - 1;
474
+ nextFocusedIndex = focusableElements.size - 1;
467
475
  }
468
476
  else {
469
477
  nextFocusedIndex = 0;
470
478
  }
471
479
  }
472
- if (nextFocusedIndex >= focusableElements.length) {
480
+ if (nextFocusedIndex >= focusableElements.size) {
473
481
  if (focusOutBehavior === 'wrap' && event.key !== 'Tab') {
474
482
  nextFocusedIndex = 0;
475
483
  }
476
484
  else {
477
- nextFocusedIndex = focusableElements.length - 1;
485
+ nextFocusedIndex = focusableElements.size - 1;
478
486
  }
479
487
  }
480
488
  if (lastFocusedIndex !== nextFocusedIndex) {
481
- nextElementToFocus = focusableElements[nextFocusedIndex];
489
+ nextElementToFocus = focusableElements.get(nextFocusedIndex);
482
490
  }
483
491
  }
484
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,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-20251215025613",
3
+ "version": "0.0.0-20251215032051",
4
4
  "description": "Shared behaviors for JavaScript components",
5
5
  "type": "commonjs",
6
6
  "main": "dist/cjs/index.js",