@react-aria/focus 3.17.0 → 3.18.0

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.
@@ -18,6 +18,7 @@ import $cgawC$react, {useRef as $cgawC$useRef, useContext as $cgawC$useContext,
18
18
 
19
19
 
20
20
  const $9bf71ea28793e738$var$FocusContext = /*#__PURE__*/ (0, $cgawC$react).createContext(null);
21
+ const $9bf71ea28793e738$var$RESTORE_FOCUS_EVENT = 'react-aria-focus-scope-restore';
21
22
  let $9bf71ea28793e738$var$activeScope = null;
22
23
  function $9bf71ea28793e738$export$20e40289641fbbb6(props) {
23
24
  let { children: children, contain: contain, restoreFocus: restoreFocus, autoFocus: autoFocus } = props;
@@ -59,11 +60,17 @@ function $9bf71ea28793e738$export$20e40289641fbbb6(props) {
59
60
  // Find all rendered nodes between the sentinels and add them to the scope.
60
61
  let node = (_startRef_current = startRef.current) === null || _startRef_current === void 0 ? void 0 : _startRef_current.nextSibling;
61
62
  let nodes = [];
63
+ let stopPropagation = (e)=>e.stopPropagation();
62
64
  while(node && node !== endRef.current){
63
65
  nodes.push(node);
66
+ // Stop custom restore focus event from propagating to parent focus scopes.
67
+ node.addEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
64
68
  node = node.nextSibling;
65
69
  }
66
70
  scopeRef.current = nodes;
71
+ return ()=>{
72
+ for (let node of nodes)node.removeEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
73
+ };
67
74
  }, [
68
75
  children
69
76
  ]);
@@ -192,21 +199,21 @@ function $9bf71ea28793e738$var$createFocusManagerForScope(scopeRef) {
192
199
  };
193
200
  }
194
201
  const $9bf71ea28793e738$var$focusableElements = [
195
- "input:not([disabled]):not([type=hidden])",
196
- "select:not([disabled])",
197
- "textarea:not([disabled])",
198
- "button:not([disabled])",
199
- "a[href]",
200
- "area[href]",
201
- "summary",
202
- "iframe",
203
- "object",
204
- "embed",
205
- "audio[controls]",
206
- "video[controls]",
207
- "[contenteditable]"
202
+ 'input:not([disabled]):not([type=hidden])',
203
+ 'select:not([disabled])',
204
+ 'textarea:not([disabled])',
205
+ 'button:not([disabled])',
206
+ 'a[href]',
207
+ 'area[href]',
208
+ 'summary',
209
+ 'iframe',
210
+ 'object',
211
+ 'embed',
212
+ 'audio[controls]',
213
+ 'video[controls]',
214
+ '[contenteditable]'
208
215
  ];
209
- const $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(":not([hidden]),") + ",[tabindex]:not([disabled]):not([hidden])";
216
+ const $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(':not([hidden]),') + ',[tabindex]:not([disabled]):not([hidden])';
210
217
  $9bf71ea28793e738$var$focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
211
218
  const $9bf71ea28793e738$var$TABBABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
212
219
  function $9bf71ea28793e738$export$4c063cf1350e6fed(element) {
@@ -224,8 +231,8 @@ function $9bf71ea28793e738$var$shouldContainFocus(scopeRef) {
224
231
  return true;
225
232
  }
226
233
  function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
227
- let focusedNode = (0, $cgawC$useRef)();
228
- let raf = (0, $cgawC$useRef)();
234
+ let focusedNode = (0, $cgawC$useRef)(undefined);
235
+ let raf = (0, $cgawC$useRef)(undefined);
229
236
  (0, $cgawC$useLayoutEffect)(()=>{
230
237
  let scope = scopeRef.current;
231
238
  if (!contain) {
@@ -239,7 +246,7 @@ function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
239
246
  const ownerDocument = (0, $cgawC$getOwnerDocument)(scope ? scope[0] : undefined);
240
247
  // Handle the Tab key to contain focus within the scope
241
248
  let onKeyDown = (e)=>{
242
- if (e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
249
+ if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
243
250
  let focusedElement = ownerDocument.activeElement;
244
251
  let scope = scopeRef.current;
245
252
  if (!scope || !$9bf71ea28793e738$var$isElementInScope(focusedElement, scope)) return;
@@ -285,15 +292,15 @@ function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
285
292
  }
286
293
  });
287
294
  };
288
- ownerDocument.addEventListener("keydown", onKeyDown, false);
289
- ownerDocument.addEventListener("focusin", onFocus, false);
290
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
291
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusout", onBlur, false));
295
+ ownerDocument.addEventListener('keydown', onKeyDown, false);
296
+ ownerDocument.addEventListener('focusin', onFocus, false);
297
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
298
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusout', onBlur, false));
292
299
  return ()=>{
293
- ownerDocument.removeEventListener("keydown", onKeyDown, false);
294
- ownerDocument.removeEventListener("focusin", onFocus, false);
295
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
296
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusout", onBlur, false));
300
+ ownerDocument.removeEventListener('keydown', onKeyDown, false);
301
+ ownerDocument.removeEventListener('focusin', onFocus, false);
302
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
303
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusout', onBlur, false));
297
304
  };
298
305
  }, [
299
306
  scopeRef,
@@ -319,7 +326,7 @@ function $9bf71ea28793e738$var$isElementInScope(element, scope) {
319
326
  }
320
327
  function $9bf71ea28793e738$var$isElementInChildScope(element, scope = null) {
321
328
  // If the element is within a top layer element (e.g. toasts), always allow moving focus there.
322
- if (element instanceof Element && element.closest("[data-react-aria-top-layer]")) return true;
329
+ if (element instanceof Element && element.closest('[data-react-aria-top-layer]')) return true;
323
330
  // node.contains in isElementInScope covers child scopes that are also DOM children,
324
331
  // but does not cover child scopes in portals.
325
332
  for (let { scopeRef: s } of $9bf71ea28793e738$export$d06fae2ee68b101e.traverse($9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scope))){
@@ -351,7 +358,7 @@ function $9bf71ea28793e738$var$focusElement(element, scroll = false) {
351
358
  // ignore
352
359
  }
353
360
  }
354
- function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
361
+ function $9bf71ea28793e738$var$getFirstInScope(scope, tabbable = true) {
355
362
  let sentinel = scope[0].previousElementSibling;
356
363
  let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
357
364
  let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
@@ -368,7 +375,10 @@ function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
368
375
  walker.currentNode = sentinel;
369
376
  nextNode = walker.nextNode();
370
377
  }
371
- $9bf71ea28793e738$var$focusElement(nextNode);
378
+ return nextNode;
379
+ }
380
+ function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
381
+ $9bf71ea28793e738$var$focusElement($9bf71ea28793e738$var$getFirstInScope(scope, tabbable));
372
382
  }
373
383
  function $9bf71ea28793e738$var$useAutoFocus(scopeRef, autoFocus) {
374
384
  const autoFocusRef = (0, $cgawC$react).useRef(autoFocus);
@@ -395,11 +405,11 @@ function $9bf71ea28793e738$var$useActiveScopeTracker(scopeRef, restore, contain)
395
405
  if ($9bf71ea28793e738$var$isElementInScope(target, scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
396
406
  else if (!$9bf71ea28793e738$var$isElementInAnyScope(target)) $9bf71ea28793e738$var$activeScope = null;
397
407
  };
398
- ownerDocument.addEventListener("focusin", onFocus, false);
399
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
408
+ ownerDocument.addEventListener('focusin', onFocus, false);
409
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
400
410
  return ()=>{
401
- ownerDocument.removeEventListener("focusin", onFocus, false);
402
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
411
+ ownerDocument.removeEventListener('focusin', onFocus, false);
412
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
403
413
  };
404
414
  }, [
405
415
  scopeRef,
@@ -418,7 +428,7 @@ function $9bf71ea28793e738$var$shouldRestoreFocus(scopeRef) {
418
428
  function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain) {
419
429
  // create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
420
430
  // eslint-disable-next-line no-restricted-globals
421
- const nodeToRestoreRef = (0, $cgawC$useRef)(typeof document !== "undefined" ? (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined).activeElement : null);
431
+ const nodeToRestoreRef = (0, $cgawC$useRef)(typeof document !== 'undefined' ? (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined).activeElement : null);
422
432
  // restoring scopes should all track if they are active regardless of contain, but contain already tracks it plus logic to contain the focus
423
433
  // restoring-non-containing scopes should only care if they become active so they can perform the restore
424
434
  (0, $cgawC$useLayoutEffect)(()=>{
@@ -430,11 +440,11 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
430
440
  // Moving out of the active scope to an ancestor is not allowed.
431
441
  if ((!$9bf71ea28793e738$var$activeScope || $9bf71ea28793e738$var$isAncestorScope($9bf71ea28793e738$var$activeScope, scopeRef)) && $9bf71ea28793e738$var$isElementInScope(ownerDocument.activeElement, scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
432
442
  };
433
- ownerDocument.addEventListener("focusin", onFocus, false);
434
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
443
+ ownerDocument.addEventListener('focusin', onFocus, false);
444
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
435
445
  return ()=>{
436
- ownerDocument.removeEventListener("focusin", onFocus, false);
437
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
446
+ ownerDocument.removeEventListener('focusin', onFocus, false);
447
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
438
448
  };
439
449
  // eslint-disable-next-line react-hooks/exhaustive-deps
440
450
  }, [
@@ -449,7 +459,7 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
449
459
  // using portals for overlays, so that focus goes to the expected element when
450
460
  // tabbing out of the overlay.
451
461
  let onKeyDown = (e)=>{
452
- if (e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
462
+ if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
453
463
  let focusedElement = ownerDocument.activeElement;
454
464
  if (!$9bf71ea28793e738$var$isElementInScope(focusedElement, scopeRef.current)) return;
455
465
  let treeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
@@ -483,9 +493,9 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
483
493
  else $9bf71ea28793e738$var$focusElement(nodeToRestore, true);
484
494
  }
485
495
  };
486
- if (!contain) ownerDocument.addEventListener("keydown", onKeyDown, true);
496
+ if (!contain) ownerDocument.addEventListener('keydown', onKeyDown, true);
487
497
  return ()=>{
488
- if (!contain) ownerDocument.removeEventListener("keydown", onKeyDown, true);
498
+ if (!contain) ownerDocument.removeEventListener('keydown', onKeyDown, true);
489
499
  };
490
500
  }, [
491
501
  scopeRef,
@@ -516,7 +526,7 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
516
526
  let treeNode = clonedTree.getTreeNode(scopeRef);
517
527
  while(treeNode){
518
528
  if (treeNode.nodeToRestore && treeNode.nodeToRestore.isConnected) {
519
- $9bf71ea28793e738$var$focusElement(treeNode.nodeToRestore);
529
+ $9bf71ea28793e738$var$restoreFocusToElement(treeNode.nodeToRestore);
520
530
  return;
521
531
  }
522
532
  treeNode = treeNode.parent;
@@ -526,7 +536,8 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
526
536
  treeNode = clonedTree.getTreeNode(scopeRef);
527
537
  while(treeNode){
528
538
  if (treeNode.scopeRef && treeNode.scopeRef.current && $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(treeNode.scopeRef)) {
529
- $9bf71ea28793e738$var$focusFirstInScope(treeNode.scopeRef.current, true);
539
+ let node = $9bf71ea28793e738$var$getFirstInScope(treeNode.scopeRef.current, true);
540
+ $9bf71ea28793e738$var$restoreFocusToElement(node);
530
541
  return;
531
542
  }
532
543
  treeNode = treeNode.parent;
@@ -540,6 +551,15 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
540
551
  restoreFocus
541
552
  ]);
542
553
  }
554
+ function $9bf71ea28793e738$var$restoreFocusToElement(node) {
555
+ // Dispatch a custom event that parent elements can intercept to customize focus restoration.
556
+ // For example, virtualized collection components reuse DOM elements, so the original element
557
+ // might still exist in the DOM but representing a different item.
558
+ if (node.dispatchEvent(new CustomEvent($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, {
559
+ bubbles: true,
560
+ cancelable: true
561
+ }))) $9bf71ea28793e738$var$focusElement(node);
562
+ }
543
563
  function $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, opts, scope) {
544
564
  let selector = (opts === null || opts === void 0 ? void 0 : opts.tabbable) ? $9bf71ea28793e738$var$TABBABLE_ELEMENT_SELECTOR : $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR;
545
565
  let walker = (0, $cgawC$getOwnerDocument)(root).createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
@@ -711,4 +731,4 @@ let $9bf71ea28793e738$export$d06fae2ee68b101e = new $9bf71ea28793e738$var$Tree()
711
731
 
712
732
 
713
733
  export {$9bf71ea28793e738$export$20e40289641fbbb6 as FocusScope, $9bf71ea28793e738$export$d06fae2ee68b101e as focusScopeTree, $9bf71ea28793e738$export$10c5169755ce7bd7 as useFocusManager, $9bf71ea28793e738$export$2d6ec8fc375ceafa as getFocusableTreeWalker, $9bf71ea28793e738$export$4c063cf1350e6fed as isFocusable, $9bf71ea28793e738$export$1258395f99bf9cbf as isElementInChildOfActiveScope, $9bf71ea28793e738$export$c5251b9e124bf29 as createFocusManager};
714
- //# sourceMappingURL=FocusScope.mjs.map
734
+ //# sourceMappingURL=FocusScope.module.js.map
@@ -18,6 +18,7 @@ import $cgawC$react, {useRef as $cgawC$useRef, useContext as $cgawC$useContext,
18
18
 
19
19
 
20
20
  const $9bf71ea28793e738$var$FocusContext = /*#__PURE__*/ (0, $cgawC$react).createContext(null);
21
+ const $9bf71ea28793e738$var$RESTORE_FOCUS_EVENT = 'react-aria-focus-scope-restore';
21
22
  let $9bf71ea28793e738$var$activeScope = null;
22
23
  function $9bf71ea28793e738$export$20e40289641fbbb6(props) {
23
24
  let { children: children, contain: contain, restoreFocus: restoreFocus, autoFocus: autoFocus } = props;
@@ -59,11 +60,17 @@ function $9bf71ea28793e738$export$20e40289641fbbb6(props) {
59
60
  // Find all rendered nodes between the sentinels and add them to the scope.
60
61
  let node = (_startRef_current = startRef.current) === null || _startRef_current === void 0 ? void 0 : _startRef_current.nextSibling;
61
62
  let nodes = [];
63
+ let stopPropagation = (e)=>e.stopPropagation();
62
64
  while(node && node !== endRef.current){
63
65
  nodes.push(node);
66
+ // Stop custom restore focus event from propagating to parent focus scopes.
67
+ node.addEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
64
68
  node = node.nextSibling;
65
69
  }
66
70
  scopeRef.current = nodes;
71
+ return ()=>{
72
+ for (let node of nodes)node.removeEventListener($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, stopPropagation);
73
+ };
67
74
  }, [
68
75
  children
69
76
  ]);
@@ -192,21 +199,21 @@ function $9bf71ea28793e738$var$createFocusManagerForScope(scopeRef) {
192
199
  };
193
200
  }
194
201
  const $9bf71ea28793e738$var$focusableElements = [
195
- "input:not([disabled]):not([type=hidden])",
196
- "select:not([disabled])",
197
- "textarea:not([disabled])",
198
- "button:not([disabled])",
199
- "a[href]",
200
- "area[href]",
201
- "summary",
202
- "iframe",
203
- "object",
204
- "embed",
205
- "audio[controls]",
206
- "video[controls]",
207
- "[contenteditable]"
202
+ 'input:not([disabled]):not([type=hidden])',
203
+ 'select:not([disabled])',
204
+ 'textarea:not([disabled])',
205
+ 'button:not([disabled])',
206
+ 'a[href]',
207
+ 'area[href]',
208
+ 'summary',
209
+ 'iframe',
210
+ 'object',
211
+ 'embed',
212
+ 'audio[controls]',
213
+ 'video[controls]',
214
+ '[contenteditable]'
208
215
  ];
209
- const $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(":not([hidden]),") + ",[tabindex]:not([disabled]):not([hidden])";
216
+ const $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(':not([hidden]),') + ',[tabindex]:not([disabled]):not([hidden])';
210
217
  $9bf71ea28793e738$var$focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
211
218
  const $9bf71ea28793e738$var$TABBABLE_ELEMENT_SELECTOR = $9bf71ea28793e738$var$focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
212
219
  function $9bf71ea28793e738$export$4c063cf1350e6fed(element) {
@@ -224,8 +231,8 @@ function $9bf71ea28793e738$var$shouldContainFocus(scopeRef) {
224
231
  return true;
225
232
  }
226
233
  function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
227
- let focusedNode = (0, $cgawC$useRef)();
228
- let raf = (0, $cgawC$useRef)();
234
+ let focusedNode = (0, $cgawC$useRef)(undefined);
235
+ let raf = (0, $cgawC$useRef)(undefined);
229
236
  (0, $cgawC$useLayoutEffect)(()=>{
230
237
  let scope = scopeRef.current;
231
238
  if (!contain) {
@@ -239,7 +246,7 @@ function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
239
246
  const ownerDocument = (0, $cgawC$getOwnerDocument)(scope ? scope[0] : undefined);
240
247
  // Handle the Tab key to contain focus within the scope
241
248
  let onKeyDown = (e)=>{
242
- if (e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
249
+ if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
243
250
  let focusedElement = ownerDocument.activeElement;
244
251
  let scope = scopeRef.current;
245
252
  if (!scope || !$9bf71ea28793e738$var$isElementInScope(focusedElement, scope)) return;
@@ -285,15 +292,15 @@ function $9bf71ea28793e738$var$useFocusContainment(scopeRef, contain) {
285
292
  }
286
293
  });
287
294
  };
288
- ownerDocument.addEventListener("keydown", onKeyDown, false);
289
- ownerDocument.addEventListener("focusin", onFocus, false);
290
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
291
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusout", onBlur, false));
295
+ ownerDocument.addEventListener('keydown', onKeyDown, false);
296
+ ownerDocument.addEventListener('focusin', onFocus, false);
297
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
298
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusout', onBlur, false));
292
299
  return ()=>{
293
- ownerDocument.removeEventListener("keydown", onKeyDown, false);
294
- ownerDocument.removeEventListener("focusin", onFocus, false);
295
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
296
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusout", onBlur, false));
300
+ ownerDocument.removeEventListener('keydown', onKeyDown, false);
301
+ ownerDocument.removeEventListener('focusin', onFocus, false);
302
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
303
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusout', onBlur, false));
297
304
  };
298
305
  }, [
299
306
  scopeRef,
@@ -319,7 +326,7 @@ function $9bf71ea28793e738$var$isElementInScope(element, scope) {
319
326
  }
320
327
  function $9bf71ea28793e738$var$isElementInChildScope(element, scope = null) {
321
328
  // If the element is within a top layer element (e.g. toasts), always allow moving focus there.
322
- if (element instanceof Element && element.closest("[data-react-aria-top-layer]")) return true;
329
+ if (element instanceof Element && element.closest('[data-react-aria-top-layer]')) return true;
323
330
  // node.contains in isElementInScope covers child scopes that are also DOM children,
324
331
  // but does not cover child scopes in portals.
325
332
  for (let { scopeRef: s } of $9bf71ea28793e738$export$d06fae2ee68b101e.traverse($9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scope))){
@@ -351,7 +358,7 @@ function $9bf71ea28793e738$var$focusElement(element, scroll = false) {
351
358
  // ignore
352
359
  }
353
360
  }
354
- function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
361
+ function $9bf71ea28793e738$var$getFirstInScope(scope, tabbable = true) {
355
362
  let sentinel = scope[0].previousElementSibling;
356
363
  let scopeRoot = $9bf71ea28793e738$var$getScopeRoot(scope);
357
364
  let walker = $9bf71ea28793e738$export$2d6ec8fc375ceafa(scopeRoot, {
@@ -368,7 +375,10 @@ function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
368
375
  walker.currentNode = sentinel;
369
376
  nextNode = walker.nextNode();
370
377
  }
371
- $9bf71ea28793e738$var$focusElement(nextNode);
378
+ return nextNode;
379
+ }
380
+ function $9bf71ea28793e738$var$focusFirstInScope(scope, tabbable = true) {
381
+ $9bf71ea28793e738$var$focusElement($9bf71ea28793e738$var$getFirstInScope(scope, tabbable));
372
382
  }
373
383
  function $9bf71ea28793e738$var$useAutoFocus(scopeRef, autoFocus) {
374
384
  const autoFocusRef = (0, $cgawC$react).useRef(autoFocus);
@@ -395,11 +405,11 @@ function $9bf71ea28793e738$var$useActiveScopeTracker(scopeRef, restore, contain)
395
405
  if ($9bf71ea28793e738$var$isElementInScope(target, scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
396
406
  else if (!$9bf71ea28793e738$var$isElementInAnyScope(target)) $9bf71ea28793e738$var$activeScope = null;
397
407
  };
398
- ownerDocument.addEventListener("focusin", onFocus, false);
399
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
408
+ ownerDocument.addEventListener('focusin', onFocus, false);
409
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
400
410
  return ()=>{
401
- ownerDocument.removeEventListener("focusin", onFocus, false);
402
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
411
+ ownerDocument.removeEventListener('focusin', onFocus, false);
412
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
403
413
  };
404
414
  }, [
405
415
  scopeRef,
@@ -418,7 +428,7 @@ function $9bf71ea28793e738$var$shouldRestoreFocus(scopeRef) {
418
428
  function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain) {
419
429
  // create a ref during render instead of useLayoutEffect so the active element is saved before a child with autoFocus=true mounts.
420
430
  // eslint-disable-next-line no-restricted-globals
421
- const nodeToRestoreRef = (0, $cgawC$useRef)(typeof document !== "undefined" ? (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined).activeElement : null);
431
+ const nodeToRestoreRef = (0, $cgawC$useRef)(typeof document !== 'undefined' ? (0, $cgawC$getOwnerDocument)(scopeRef.current ? scopeRef.current[0] : undefined).activeElement : null);
422
432
  // restoring scopes should all track if they are active regardless of contain, but contain already tracks it plus logic to contain the focus
423
433
  // restoring-non-containing scopes should only care if they become active so they can perform the restore
424
434
  (0, $cgawC$useLayoutEffect)(()=>{
@@ -430,11 +440,11 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
430
440
  // Moving out of the active scope to an ancestor is not allowed.
431
441
  if ((!$9bf71ea28793e738$var$activeScope || $9bf71ea28793e738$var$isAncestorScope($9bf71ea28793e738$var$activeScope, scopeRef)) && $9bf71ea28793e738$var$isElementInScope(ownerDocument.activeElement, scopeRef.current)) $9bf71ea28793e738$var$activeScope = scopeRef;
432
442
  };
433
- ownerDocument.addEventListener("focusin", onFocus, false);
434
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener("focusin", onFocus, false));
443
+ ownerDocument.addEventListener('focusin', onFocus, false);
444
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.addEventListener('focusin', onFocus, false));
435
445
  return ()=>{
436
- ownerDocument.removeEventListener("focusin", onFocus, false);
437
- scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener("focusin", onFocus, false));
446
+ ownerDocument.removeEventListener('focusin', onFocus, false);
447
+ scope === null || scope === void 0 ? void 0 : scope.forEach((element)=>element.removeEventListener('focusin', onFocus, false));
438
448
  };
439
449
  // eslint-disable-next-line react-hooks/exhaustive-deps
440
450
  }, [
@@ -449,7 +459,7 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
449
459
  // using portals for overlays, so that focus goes to the expected element when
450
460
  // tabbing out of the overlay.
451
461
  let onKeyDown = (e)=>{
452
- if (e.key !== "Tab" || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
462
+ if (e.key !== 'Tab' || e.altKey || e.ctrlKey || e.metaKey || !$9bf71ea28793e738$var$shouldContainFocus(scopeRef) || e.isComposing) return;
453
463
  let focusedElement = ownerDocument.activeElement;
454
464
  if (!$9bf71ea28793e738$var$isElementInScope(focusedElement, scopeRef.current)) return;
455
465
  let treeNode = $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(scopeRef);
@@ -483,9 +493,9 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
483
493
  else $9bf71ea28793e738$var$focusElement(nodeToRestore, true);
484
494
  }
485
495
  };
486
- if (!contain) ownerDocument.addEventListener("keydown", onKeyDown, true);
496
+ if (!contain) ownerDocument.addEventListener('keydown', onKeyDown, true);
487
497
  return ()=>{
488
- if (!contain) ownerDocument.removeEventListener("keydown", onKeyDown, true);
498
+ if (!contain) ownerDocument.removeEventListener('keydown', onKeyDown, true);
489
499
  };
490
500
  }, [
491
501
  scopeRef,
@@ -516,7 +526,7 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
516
526
  let treeNode = clonedTree.getTreeNode(scopeRef);
517
527
  while(treeNode){
518
528
  if (treeNode.nodeToRestore && treeNode.nodeToRestore.isConnected) {
519
- $9bf71ea28793e738$var$focusElement(treeNode.nodeToRestore);
529
+ $9bf71ea28793e738$var$restoreFocusToElement(treeNode.nodeToRestore);
520
530
  return;
521
531
  }
522
532
  treeNode = treeNode.parent;
@@ -526,7 +536,8 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
526
536
  treeNode = clonedTree.getTreeNode(scopeRef);
527
537
  while(treeNode){
528
538
  if (treeNode.scopeRef && treeNode.scopeRef.current && $9bf71ea28793e738$export$d06fae2ee68b101e.getTreeNode(treeNode.scopeRef)) {
529
- $9bf71ea28793e738$var$focusFirstInScope(treeNode.scopeRef.current, true);
539
+ let node = $9bf71ea28793e738$var$getFirstInScope(treeNode.scopeRef.current, true);
540
+ $9bf71ea28793e738$var$restoreFocusToElement(node);
530
541
  return;
531
542
  }
532
543
  treeNode = treeNode.parent;
@@ -540,6 +551,15 @@ function $9bf71ea28793e738$var$useRestoreFocus(scopeRef, restoreFocus, contain)
540
551
  restoreFocus
541
552
  ]);
542
553
  }
554
+ function $9bf71ea28793e738$var$restoreFocusToElement(node) {
555
+ // Dispatch a custom event that parent elements can intercept to customize focus restoration.
556
+ // For example, virtualized collection components reuse DOM elements, so the original element
557
+ // might still exist in the DOM but representing a different item.
558
+ if (node.dispatchEvent(new CustomEvent($9bf71ea28793e738$var$RESTORE_FOCUS_EVENT, {
559
+ bubbles: true,
560
+ cancelable: true
561
+ }))) $9bf71ea28793e738$var$focusElement(node);
562
+ }
543
563
  function $9bf71ea28793e738$export$2d6ec8fc375ceafa(root, opts, scope) {
544
564
  let selector = (opts === null || opts === void 0 ? void 0 : opts.tabbable) ? $9bf71ea28793e738$var$TABBABLE_ELEMENT_SELECTOR : $9bf71ea28793e738$var$FOCUSABLE_ELEMENT_SELECTOR;
545
565
  let walker = (0, $cgawC$getOwnerDocument)(root).createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {