react-magma-dom 4.13.0-next.6 → 4.13.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.
package/dist/esm/index.js CHANGED
@@ -11263,25 +11263,141 @@ var CalendarHeader = function CalendarHeader(props) {
11263
11263
  })));
11264
11264
  };
11265
11265
 
11266
+ var useDeviceDetect = function useDeviceDetect() {
11267
+ var userAgent = useMemo(function () {
11268
+ return typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : '';
11269
+ }, []);
11270
+ var isSafari = useMemo(function () {
11271
+ return /^((?!chrome|android).)*safari/i.test(userAgent);
11272
+ }, [userAgent]);
11273
+ var isChrome = useMemo(function () {
11274
+ return /chrome|crios/i.test(userAgent) && !/edge|edg/i.test(userAgent);
11275
+ }, [userAgent]);
11276
+ var isFirefox = useMemo(function () {
11277
+ return /firefox|fxios/i.test(userAgent);
11278
+ }, [userAgent]);
11279
+ var isEdge = useMemo(function () {
11280
+ return /edge|edg/i.test(userAgent);
11281
+ }, [userAgent]);
11282
+ var isMobile = useMemo(function () {
11283
+ return /mobi|android|touch|mini/i.test(userAgent);
11284
+ }, [userAgent]);
11285
+ var isWindows = useMemo(function () {
11286
+ return /windows nt/.test(userAgent);
11287
+ }, [userAgent]);
11288
+ var isMacOS = useMemo(function () {
11289
+ return /macintosh/.test(userAgent);
11290
+ }, [userAgent]);
11291
+ var isAndroid = useMemo(function () {
11292
+ return /android/.test(userAgent);
11293
+ }, [userAgent]);
11294
+ var isIOS = useMemo(function () {
11295
+ return /iphone|ipad|ipod/.test(userAgent);
11296
+ }, [userAgent]);
11297
+ var isLinux = useMemo(function () {
11298
+ return /linux/.test(userAgent) && !isAndroid;
11299
+ }, [userAgent, isAndroid]);
11300
+ return {
11301
+ isSafari: isSafari,
11302
+ isChrome: isChrome,
11303
+ isFirefox: isFirefox,
11304
+ isEdge: isEdge,
11305
+ isMobile: isMobile,
11306
+ isWindows: isWindows,
11307
+ isMacOS: isMacOS,
11308
+ isLinux: isLinux,
11309
+ isAndroid: isAndroid,
11310
+ isIOS: isIOS
11311
+ };
11312
+ };
11313
+
11314
+ /**
11315
+ * Returns a grouping key if the element belongs to a group
11316
+ * that shares a single tab stop (e.g. named radio groups).
11317
+ * Returns null if the element is independently tabbable.
11318
+ *
11319
+ * Extend this function to support new grouping patterns.
11320
+ */
11321
+ function getTabStopGroupKey(el) {
11322
+ // Native radio buttons with the same name share a single tab stop.
11323
+ // Scope by form so identically-named groups in different forms stay separate.
11324
+ if (el instanceof HTMLInputElement && el.type === 'radio' && el.name) {
11325
+ var formScope = el.form ? "f" + (el.form.id || '') : 'noform';
11326
+ return "radio:" + formScope + ":" + el.name;
11327
+ }
11328
+ return null;
11329
+ }
11330
+ /**
11331
+ * Within a shared-tab-stop group, decides whether `candidate`
11332
+ * should replace `current` as the representative tab stop.
11333
+ * For radio buttons the checked element wins; otherwise the
11334
+ * first element in DOM order is kept (it was set when the
11335
+ * group was first encountered).
11336
+ */
11337
+ function isPreferredTabStop(candidate, current) {
11338
+ if (candidate instanceof HTMLInputElement && candidate.type === 'radio' && current instanceof HTMLInputElement) {
11339
+ return candidate.checked && !current.checked;
11340
+ }
11341
+ return false;
11342
+ }
11343
+ /**
11344
+ * From a flat list of focusable elements, removes duplicates
11345
+ * that share a single tab stop, keeping only the representative
11346
+ * element for each group (the checked radio, or the first one
11347
+ * in DOM order when nothing is checked, etc.).
11348
+ */
11349
+ function deduplicateTabStops(allFocusable) {
11350
+ var groupRepresentatives = new Map();
11351
+ for (var _iterator = _createForOfIteratorHelperLoose(allFocusable), _step; !(_step = _iterator()).done;) {
11352
+ var el = _step.value;
11353
+ var key = getTabStopGroupKey(el);
11354
+ if (key) {
11355
+ var current = groupRepresentatives.get(key);
11356
+ if (!current || isPreferredTabStop(el, current)) {
11357
+ groupRepresentatives.set(key, el);
11358
+ }
11359
+ }
11360
+ }
11361
+ return allFocusable.filter(function (el) {
11362
+ var key = getTabStopGroupKey(el);
11363
+ if (key) {
11364
+ return groupRepresentatives.get(key) === el;
11365
+ }
11366
+ return true;
11367
+ });
11368
+ }
11369
+ /**
11370
+ * Module-level registry of currently active focus-lock root elements.
11371
+ * Used in Safari to prevent an outer lock from handling Tab for elements
11372
+ * that belong to an inner (descendant) lock.
11373
+ */
11374
+ var activeFocusLockRoots = /*#__PURE__*/new Set();
11266
11375
  function useFocusLock(active, header, body) {
11267
11376
  var rootNode = useRef(null);
11268
11377
  var focusableItems = useRef([]);
11378
+ var _useDeviceDetect = useDeviceDetect(),
11379
+ isSafari = _useDeviceDetect.isSafari;
11269
11380
  // The filter is necessary for the proper functioning of focus in drawer-navigation or similar cases
11270
11381
  var updateFocusableItems = function updateFocusableItems() {
11271
11382
  var _rootNode$current;
11272
- focusableItems.current = Array.from(((_rootNode$current = rootNode.current) == null ? void 0 : _rootNode$current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), video')) || []).filter(function (element) {
11383
+ var allFocusable = Array.from(((_rootNode$current = rootNode.current) == null ? void 0 : _rootNode$current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), video')) || []).filter(function (element) {
11273
11384
  var style = window.getComputedStyle(element);
11274
- return element instanceof HTMLElement && style.display !== 'none' && style.visibility !== 'hidden' && !element.hasAttribute('disabled');
11385
+ return element instanceof HTMLElement && style.display !== 'none' && style.visibility !== 'hidden' && !element.hasAttribute('disabled') && element.tabIndex >= 0;
11275
11386
  });
11387
+ focusableItems.current = deduplicateTabStops(allFocusable);
11276
11388
  };
11277
11389
  useEffect(function () {
11278
11390
  if (active) {
11391
+ var root = rootNode.current;
11392
+ if (root) {
11393
+ activeFocusLockRoots.add(root);
11394
+ }
11279
11395
  updateFocusableItems();
11280
11396
  var observer = new MutationObserver(function () {
11281
11397
  updateFocusableItems();
11282
11398
  });
11283
- if (rootNode.current) {
11284
- observer.observe(rootNode.current, {
11399
+ if (root) {
11400
+ observer.observe(root, {
11285
11401
  childList: true,
11286
11402
  subtree: true
11287
11403
  });
@@ -11296,6 +11412,9 @@ function useFocusLock(active, header, body) {
11296
11412
  body.current.firstChild.focus();
11297
11413
  }
11298
11414
  return function () {
11415
+ if (root) {
11416
+ activeFocusLockRoots["delete"](root);
11417
+ }
11299
11418
  observer.disconnect();
11300
11419
  };
11301
11420
  }
@@ -11305,12 +11424,44 @@ function useFocusLock(active, header, body) {
11305
11424
  if (!focusableItems.current) return;
11306
11425
  var key = event.key,
11307
11426
  shiftKey = event.shiftKey;
11308
- var _focusableItems$curre = focusableItems.current,
11309
- length = _focusableItems$curre.length,
11310
- firstItem = _focusableItems$curre[0],
11311
- lastItem = _focusableItems$curre[length - 1];
11312
11427
  if (active && key === 'Tab') {
11313
- // If no focusable items are
11428
+ updateFocusableItems();
11429
+ var length = focusableItems.current.length;
11430
+ var firstItem = focusableItems.current[0];
11431
+ var lastItem = focusableItems.current[length - 1];
11432
+ var activeElement = document.activeElement;
11433
+ var eventTarget = event.target;
11434
+ var isEventInsideCurrentLock = !!rootNode.current && !!eventTarget && rootNode.current.contains(eventTarget);
11435
+ var isActiveElementTracked = !!activeElement && (activeElement === (header == null ? void 0 : header.current) || focusableItems.current.includes(activeElement));
11436
+ var isActiveElementInsideCurrentLock = !!rootNode.current && !!activeElement && rootNode.current.contains(activeElement);
11437
+ /**
11438
+ * Safari + VoiceOver can place screen reader focus on non-interactive content.
11439
+ * In that case, on next Tab the DOM focus may no longer be on one of the tracked
11440
+ * interactive elements, and the default browser tabbing can escape the lock.
11441
+ *
11442
+ * We only handle this as a fallback:
11443
+ * - lock is active
11444
+ * - Tab was pressed
11445
+ * - event still belongs to the current lock
11446
+ * - but DOM focus is no longer on a tracked interactive element
11447
+ *
11448
+ * This keeps the default logic intact and avoids breaking nested focus locks.
11449
+ */
11450
+ // Check whether the active element sits inside a nested active lock.
11451
+ // If so, that lock should handle Tab — not this (outer) one.
11452
+ var isInsideNestedLock = !!activeElement && Array.from(activeFocusLockRoots).some(function (lockRoot) {
11453
+ return lockRoot !== rootNode.current && rootNode.current.contains(lockRoot) && lockRoot.contains(activeElement);
11454
+ });
11455
+ if (length > 0 && (isEventInsideCurrentLock || isSafari) && !isActiveElementTracked && !isInsideNestedLock && (isActiveElementInsideCurrentLock || activeElement === document.body)) {
11456
+ event.preventDefault();
11457
+ if (shiftKey) {
11458
+ lastItem.focus();
11459
+ } else {
11460
+ firstItem.focus();
11461
+ }
11462
+ return;
11463
+ }
11464
+ // If no focusable items
11314
11465
  if (length === 0) {
11315
11466
  event.preventDefault();
11316
11467
  return;
@@ -11323,6 +11474,56 @@ function useFocusLock(active, header, body) {
11323
11474
  }
11324
11475
  return;
11325
11476
  }
11477
+ // If focused on header then focus on first/last item
11478
+ if (document.activeElement === (header == null ? void 0 : header.current)) {
11479
+ event.preventDefault();
11480
+ (shiftKey ? lastItem : firstItem).focus();
11481
+ return;
11482
+ }
11483
+ /**
11484
+ * Safari does not keep Tab navigation inside React portals,
11485
+ * so we manually move focus to the next/prev item for every Tab.
11486
+ * Other browsers handle intermediate navigation natively and
11487
+ * only need the boundary guards below.
11488
+ */
11489
+ if (isSafari) {
11490
+ // In Safari we manually manage every Tab, so we must exclude:
11491
+ // 1. Elements hidden by an ancestor (e.g. closed DatePicker calendar)
11492
+ // 2. Elements inside a nested active focus lock (they have their own handler)
11493
+ var nestedLockRoots = Array.from(activeFocusLockRoots).filter(function (lockRoot) {
11494
+ return lockRoot !== rootNode.current && rootNode.current.contains(lockRoot);
11495
+ });
11496
+ var visibleItems = focusableItems.current.filter(function (el) {
11497
+ if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) {
11498
+ return false;
11499
+ }
11500
+ // Skip elements managed by a nested lock
11501
+ if (nestedLockRoots.length > 0 && nestedLockRoots.some(function (lockRoot) {
11502
+ return lockRoot.contains(el);
11503
+ })) {
11504
+ return false;
11505
+ }
11506
+ return true;
11507
+ });
11508
+ var visibleLength = visibleItems.length;
11509
+ if (visibleLength === 0) {
11510
+ event.preventDefault();
11511
+ return;
11512
+ }
11513
+ var idx = visibleItems.indexOf(activeElement);
11514
+ // Active element is not in this lock's own elements
11515
+ // (it must be inside a nested lock) — let that lock handle it.
11516
+ if (idx === -1) {
11517
+ return;
11518
+ }
11519
+ event.preventDefault();
11520
+ if (shiftKey) {
11521
+ visibleItems[idx <= 0 ? visibleLength - 1 : idx - 1].focus();
11522
+ } else {
11523
+ visibleItems[idx >= visibleLength - 1 ? 0 : idx + 1].focus();
11524
+ }
11525
+ return;
11526
+ }
11326
11527
  // If focused on last item then focus on first item when tab is pressed
11327
11528
  if (!shiftKey && document.activeElement === lastItem) {
11328
11529
  event.preventDefault();
@@ -11330,7 +11531,7 @@ function useFocusLock(active, header, body) {
11330
11531
  return;
11331
11532
  }
11332
11533
  // If focused on first item then focus on last item when shift + tab is pressed
11333
- if (shiftKey && (document.activeElement === firstItem || document.activeElement === (header == null ? void 0 : header.current))) {
11534
+ if (shiftKey && document.activeElement === firstItem) {
11334
11535
  event.preventDefault();
11335
11536
  lastItem.focus();
11336
11537
  return;
@@ -14908,54 +15109,6 @@ var DropdownMenuItem = /*#__PURE__*/forwardRef(function (props, forwardedRef) {
14908
15109
  });
14909
15110
  DropdownMenuItem.displayName = 'DropdownMenuItem';
14910
15111
 
14911
- var useDeviceDetect = function useDeviceDetect() {
14912
- var userAgent = useMemo(function () {
14913
- return navigator.userAgent.toLowerCase();
14914
- }, []);
14915
- var isSafari = useMemo(function () {
14916
- return /^((?!chrome|android).)*safari/i.test(userAgent);
14917
- }, [userAgent]);
14918
- var isChrome = useMemo(function () {
14919
- return /chrome|crios/i.test(userAgent) && !/edge|edg/i.test(userAgent);
14920
- }, [userAgent]);
14921
- var isFirefox = useMemo(function () {
14922
- return /firefox|fxios/i.test(userAgent);
14923
- }, [userAgent]);
14924
- var isEdge = useMemo(function () {
14925
- return /edge|edg/i.test(userAgent);
14926
- }, [userAgent]);
14927
- var isMobile = useMemo(function () {
14928
- return /mobi|android|touch|mini/i.test(userAgent);
14929
- }, [userAgent]);
14930
- var isWindows = useMemo(function () {
14931
- return /windows nt/.test(userAgent);
14932
- }, [userAgent]);
14933
- var isMacOS = useMemo(function () {
14934
- return /macintosh/.test(userAgent);
14935
- }, [userAgent]);
14936
- var isAndroid = useMemo(function () {
14937
- return /android/.test(userAgent);
14938
- }, [userAgent]);
14939
- var isIOS = useMemo(function () {
14940
- return /iphone|ipad|ipod/.test(userAgent);
14941
- }, [userAgent]);
14942
- var isLinux = useMemo(function () {
14943
- return /linux/.test(userAgent) && !isAndroid;
14944
- }, [userAgent, isAndroid]);
14945
- return {
14946
- isSafari: isSafari,
14947
- isChrome: isChrome,
14948
- isFirefox: isFirefox,
14949
- isEdge: isEdge,
14950
- isMobile: isMobile,
14951
- isWindows: isWindows,
14952
- isMacOS: isMacOS,
14953
- isLinux: isLinux,
14954
- isAndroid: isAndroid,
14955
- isIOS: isIOS
14956
- };
14957
- };
14958
-
14959
15112
  var _excluded$T = ["children", "icon", "to"];
14960
15113
  var StyledItem$3 = /*#__PURE__*/_styled("a", {
14961
15114
  target: "e1b21r9q0",