@astryxdesign/core 0.7.6-canary.6bc8efa → 0.7.6-canary.6dded46

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.
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"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"}
@@ -35,6 +35,7 @@
35
35
  * - /packages/cli/assets/templates/blocks/components/DropdownMenu/ (showcase blocks)
36
36
  */
37
37
  import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
38
+ import { readAccessibleName } from "../utils/accessibleName.js";
38
39
  import * as stylex from '@stylexjs/stylex';
39
40
  import { usePopover } from "../Popover/usePopover.js";
40
41
  import { Button } from "../Button/index.js";
@@ -276,64 +277,6 @@ function holdInvokerThroughPress(control, popoverId) {
276
277
  doc.addEventListener('pointerup', onPressEnd, true);
277
278
  doc.addEventListener('pointercancel', onPressEnd, true);
278
279
  }
279
-
280
- /** Text an assistive technology reads from `element`, `aria-hidden` left out. */
281
- function visibleText(element) {
282
- const copy = element.cloneNode(true);
283
- for (const hidden of copy.querySelectorAll('[aria-hidden="true"]')) {
284
- hidden.remove();
285
- }
286
- // An icon-only control is named by its image's alt, an inner aria-label, or
287
- // an svg title rather than by text, so those count as its contents too.
288
- for (const named of copy.querySelectorAll('img[alt], [aria-label]')) {
289
- const name = named.getAttribute(named.tagName === 'IMG' ? 'alt' : 'aria-label') ?? '';
290
- named.replaceWith(copy.ownerDocument.createTextNode(` ${name} `));
291
- }
292
- for (const svg of copy.querySelectorAll('svg')) {
293
- const title = svg.querySelector('title')?.textContent ?? '';
294
- svg.replaceWith(copy.ownerDocument.createTextNode(` ${title} `));
295
- }
296
- return (copy.textContent ?? '').replace(/\s+/g, ' ').trim();
297
- }
298
-
299
- /**
300
- * The name a custom trigger gives the menu it labels (`aria-labelledby` on
301
- * the menu points at it): its own `aria-labelledby` targets, its
302
- * `aria-label`, its text, or its `title`, in that order.
303
- */
304
- function readAccessibleName(element) {
305
- if (element == null) {
306
- return undefined;
307
- }
308
- const labelledBy = element.getAttribute('aria-labelledby');
309
- if (labelledBy != null && labelledBy.trim() !== '') {
310
- const text = labelledBy.split(/\s+/).map(id => element.ownerDocument.getElementById(id)).filter(node => node != null).map(visibleText).join(' ').trim();
311
- if (text !== '') {
312
- return text;
313
- }
314
- }
315
- const label = element.getAttribute('aria-label')?.trim();
316
- if (label) {
317
- return label;
318
- }
319
- // A form control is named by its own <label>s; an input button by its value.
320
- const labels = 'labels' in element ? Array.from(element.labels ?? []) : [];
321
- const labelText = labels.map(visibleText).join(' ').trim();
322
- if (labelText !== '') {
323
- return labelText;
324
- }
325
- if (element instanceof HTMLInputElement && ['button', 'submit', 'reset'].includes(element.getAttribute('type') ?? '')) {
326
- const value = element.value.trim();
327
- if (value !== '') {
328
- return value;
329
- }
330
- }
331
- const text = visibleText(element);
332
- if (text !== '') {
333
- return text;
334
- }
335
- return element.getAttribute('title')?.trim() || undefined;
336
- }
337
280
  function DropdownMenuBottomSheet({
338
281
  button: buttonFromProps,
339
282
  renderTrigger,
@@ -1 +1 @@
1
- {"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNav.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AA+P/D,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC1D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAEvD;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IAEtC;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAC;CAClD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,WAAmB,EACnB,SAAiB,EACjB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,SAAS,EACT,GAAG,KAAK,EACT,EAAE,YAAY,+BA2Vd;yBA1We,OAAO"}
1
+ {"version":3,"file":"SideNav.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNav.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AA2Q/D,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC1D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAEvD;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IAEtC;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAC;CAClD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,WAAmB,EACnB,SAAiB,EACjB,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,SAAS,EACT,GAAG,KAAK,EACT,EAAE,YAAY,+BA4dd;yBA3ee,OAAO"}
@@ -38,6 +38,11 @@ import { useDevWarning } from "../hooks/useDevWarning.js";
38
38
  import { useTranslator } from "../i18n/index.js";
39
39
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
40
40
  import { LayoutChangeSnapshot } from "../motion/LayoutChangeSnapshot.js";
41
+ import { getFocusableElements } from "../hooks/useFocusTrap.js";
42
+ import { readAccessibleName } from "../utils/accessibleName.js";
43
+ import { moveFocusLeftBehind, noteFocusWithin } from "../utils/focusReturn.js";
44
+ import { useInteractionModalityTracking } from "../utils/interactionModality.js";
45
+ import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
41
46
  import { createRailLayout, playRail, snapshotRail, SideNavShellContext } from "./railMotion.js";
42
47
  // =============================================================================
43
48
  // Constants
@@ -189,7 +194,12 @@ export function SideNav({
189
194
  // nav needs an id; a consumer's own id wins.
190
195
  const generatedNavId = useId();
191
196
  const navId = props.id ?? generatedNavId;
192
- const mergedNavRef = useMergedRefs(ref, navRef);
197
+ // The nav's own document's last input decides the moved focus's ring
198
+ // (AR3), so a SideNav in a second document tracks input there.
199
+ const {
200
+ ref: modalityRef
201
+ } = useInteractionModalityTracking();
202
+ const mergedNavRef = useMergedRefs(ref, navRef, modalityRef);
193
203
  const collapseStateRef = useRef({
194
204
  isCollapsed: false,
195
205
  toggle: () => {},
@@ -257,6 +267,97 @@ export function SideNav({
257
267
  motionRef.current?.rail.end();
258
268
  motionRef.current?.endContent();
259
269
  }, []);
270
+
271
+ // Focus on something the collapsed rail does not render would fall to the
272
+ // page when the rail collapses (`component:SideNav` AR3). On a row the rail
273
+ // keeps (its link replaced, its expand toggle or row actions gone), focus
274
+ // stays on that row's control; from a row the rail drops (no icon, or
275
+ // nested), it moves to the rail's collapse toggle, or, with none, to the
276
+ // nearest row the rail keeps. Rows and the toggle register through their
277
+ // callback refs. Rows are matched across the commit by their place among
278
+ // the rows the rail keeps, not by element: a resizable SideNav remounts its
279
+ // nav when it collapses. Focus on a header or footer control that a
280
+ // resizable collapse remounts goes back to the control with the same
281
+ // accessible name. From one the collapse removes or renames, or the resize
282
+ // handle, focus goes to the toggle, or, with none, to the first kept row.
283
+ // Focus moves only if the collapse lost it.
284
+ const rowsRef = useRef(new Map());
285
+ const togglesRef = useRef(new Set());
286
+ const focusTargets = useMemo(() => ({
287
+ registerRow: (element, isKeptInRail) => {
288
+ rowsRef.current.set(element, isKeptInRail);
289
+ return () => {
290
+ rowsRef.current.delete(element);
291
+ };
292
+ },
293
+ registerToggle: element => {
294
+ togglesRef.current.add(element);
295
+ return () => {
296
+ togglesRef.current.delete(element);
297
+ };
298
+ }
299
+ }), []);
300
+ const resizeWrapperRef = useRef(null);
301
+ const pendingFocusRef = useRef(null);
302
+ const noteRailFocus = useCallback(wasCollapsed => {
303
+ pendingFocusRef.current = null;
304
+ const nav = navRef.current;
305
+ // The resize handle sits beside the nav, in the resize wrapper, and goes
306
+ // with a collapse its Enter starts.
307
+ const focus = wasCollapsed ? null : noteFocusWithin(nav) ?? noteFocusWithin(resizeWrapperRef.current);
308
+ if (focus == null || nav == null) {
309
+ return;
310
+ }
311
+ const rows = railRows(nav, rowsRef.current);
312
+ // The innermost row holding focus, the last in document order: a nested
313
+ // row's parent holds it too.
314
+ let index = -1;
315
+ rows.forEach((row, at) => {
316
+ if (row.contains(focus.element)) {
317
+ index = at;
318
+ }
319
+ });
320
+ let outside = null;
321
+ if (index < 0) {
322
+ const controls = controlsOutsideRows(nav, rows);
323
+ const at = controls.findIndex(control => control.contains(focus.element));
324
+ outside = at < 0 ? null : {
325
+ name: controlName(controls[at])
326
+ };
327
+ }
328
+ pendingFocusRef.current = {
329
+ focus,
330
+ outside,
331
+ wasOnToggle: [...togglesRef.current].some(toggle => toggle.contains(focus.element)),
332
+ isRowKept: index >= 0 && rowsRef.current.get(rows[index]) === true,
333
+ keptBefore: rows.slice(0, Math.max(index, 0)).filter(row => rowsRef.current.get(row) === true).length
334
+ };
335
+ }, []);
336
+ useLayoutEffect(() => {
337
+ const pending = pendingFocusRef.current;
338
+ pendingFocusRef.current = null;
339
+ const nav = navRef.current;
340
+ if (pending == null || !collapsed || nav == null) {
341
+ return;
342
+ }
343
+ // Collapsed, every row the nav renders is one the rail keeps, in order.
344
+ const kept = railRows(nav, rowsRef.current);
345
+ const controlOf = row => row == null ? undefined : getFocusableElements(row)[0];
346
+ const toggle = [...togglesRef.current].find(element => nav.contains(element));
347
+ let sameOutside;
348
+ if (pending.wasOnToggle) {
349
+ sameOutside = toggle;
350
+ } else if (pending.outside != null) {
351
+ const controls = controlsOutsideRows(nav, kept);
352
+ sameOutside = controls.find(control => controlName(control) === pending.outside?.name);
353
+ }
354
+ const target = pending.isRowKept ? controlOf(kept[pending.keptBefore]) : sameOutside ?? toggle ?? controlOf(kept[pending.keptBefore - 1]) ?? controlOf(kept[pending.keptBefore]);
355
+ moveFocusLeftBehind(pending.focus, target);
356
+ }, [collapsed]);
357
+ const beforeRailChange = useCallback(wasCollapsed => {
358
+ noteRailFocus(wasCollapsed);
359
+ measureRail(wasCollapsed);
360
+ }, [noteRailFocus, measureRail]);
260
361
  const toggle = useCallback(() => {
261
362
  const next = !collapsed;
262
363
 
@@ -459,6 +560,7 @@ export function SideNav({
459
560
  // Uses ResizeHandle in overlay mode so the handle sits inside
460
561
  // the panel's overflow: clip bounds.
461
562
  const rail = showResizeHandle ? /*#__PURE__*/_jsxs("div", {
563
+ ref: resizeWrapperRef,
462
564
  ...{
463
565
  className: "x1n2onr6 x78zum5 x2lah0s x5yr21d x7giv3"
464
566
  },
@@ -479,15 +581,36 @@ export function SideNav({
479
581
  const content = /*#__PURE__*/_jsxs(_Fragment, {
480
582
  children: [/*#__PURE__*/_jsx(LayoutChangeSnapshot, {
481
583
  value: collapsed,
482
- onBeforeChange: measureRail
584
+ onBeforeChange: beforeRailChange
483
585
  }), rail]
484
586
  });
485
587
  if (isCollapsible) {
486
- return /*#__PURE__*/_jsx(SideNavCollapseContext, {
487
- value: collapseContext,
488
- children: content
588
+ return /*#__PURE__*/_jsx(SideNavFocusTargetsContext, {
589
+ value: focusTargets,
590
+ children: /*#__PURE__*/_jsx(SideNavCollapseContext, {
591
+ value: collapseContext,
592
+ children: content
593
+ })
489
594
  });
490
595
  }
491
596
  return content;
492
597
  }
598
+
599
+ /** The focusable controls in `nav` outside every row: header and footer. */
600
+ function controlsOutsideRows(nav, rows) {
601
+ return getFocusableElements(nav).filter(control => !rows.some(row => row.contains(control)));
602
+ }
603
+
604
+ /** A control's accessible name, so a re-rendered control is found again. */
605
+ function controlName(control) {
606
+ return readAccessibleName(control) ?? '';
607
+ }
608
+
609
+ /**
610
+ * The registered rows inside `nav`, in document order, leaving out rows in a
611
+ * collapsed item's flyout, which is a layer of its own.
612
+ */
613
+ function railRows(nav, rows) {
614
+ return [...rows.keys()].filter(row => nav.contains(row) && row.closest('[popover]') == null).sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
615
+ }
493
616
  SideNav.displayName = 'SideNav';
@@ -1 +1 @@
1
- {"version":3,"file":"SideNavCollapseButton.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavCollapseButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAAc,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAKzD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAGL,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAC;AAkC5D,MAAM,WAAW,0BAA2B,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC9E,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,WAAW,CAAC,EAAE,4BAA4B,CAAC;IAE3C;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;IAEpE;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,GAAG,EACH,WAAW,EACX,SAAS,EACT,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,GAAG,KAAK,EACT,EAAE,0BAA0B,4BAiD5B;yBA1De,qBAAqB"}
1
+ {"version":3,"file":"SideNavCollapseButton.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavCollapseButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAAmB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAK9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAGL,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACrC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAC;AAkC5D,MAAM,WAAW,0BAA2B,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC9E,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;;;OAIG;IACH,WAAW,CAAC,EAAE,4BAA4B,CAAC;IAE3C;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;IAEpE;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,GAAG,EACH,WAAW,EACX,SAAS,EACT,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,GAAG,KAAK,EACT,EAAE,0BAA0B,4BA+D5B;yBAxEe,qBAAqB"}
@@ -16,7 +16,7 @@
16
16
  * - /packages/core/src/SideNav/index.ts
17
17
  * - /packages/cli/assets/templates/blocks/components/SideNav/ (showcase blocks)
18
18
  */
19
- import React, { useCallback } from 'react';
19
+ import React, { use, useCallback } from 'react';
20
20
  import * as stylex from '@stylexjs/stylex';
21
21
  import "../theme/tokens.stylex.js";
22
22
  import { durationVars, easeVars } from "../theme/tokens.stylex.js";
@@ -26,6 +26,8 @@ import { composeEventHandlers, rtlStyles } from "../utils/index.js";
26
26
  import { useSideNavCollapse } from "./SideNavCollapseContext.js";
27
27
  import { useAppShellMobile } from "../AppShell/AppShellMobileContext.js";
28
28
  import { useTranslator } from "../i18n/index.js";
29
+ import { useMergedRefs } from "../hooks/useMergedRefs.js";
30
+ import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
29
31
  import { jsx as _jsx } from "react/jsx-runtime";
30
32
  // =============================================================================
31
33
  // Styles
@@ -102,6 +104,13 @@ export function SideNavCollapseButton({
102
104
  const {
103
105
  isMobile
104
106
  } = useAppShellMobile();
107
+ // A toggle inside its SideNav registers there, so a collapse that removes
108
+ // the focused row can hand focus to it (`component:SideNav` AR3). One wired
109
+ // to state outside the SideNav (`collapsible`, `handleRef`) does not.
110
+ const focusTargets = use(SideNavFocusTargetsContext);
111
+ const isInSideNav = focusTargets != null && collapsible == null && handleRef == null;
112
+ const registerToggle = useCallback(element => element == null || !isInSideNav || focusTargets == null ? undefined : focusTargets.registerToggle(element), [focusTargets, isInSideNav]);
113
+ const mergedRef = useMergedRefs(ref, registerToggle);
105
114
 
106
115
  // Hide when not collapsible, or when in mobile mode (sidenav is in
107
116
  // the mobile drawer — collapse doesn't apply there)
@@ -109,7 +118,7 @@ export function SideNavCollapseButton({
109
118
  return null;
110
119
  }
111
120
  return /*#__PURE__*/_jsx(Button, {
112
- ref: ref,
121
+ ref: mergedRef,
113
122
  label: label ?? (isCollapsed ? t('@astryx.sideNavCollapseButton.expandSidebar') : t('@astryx.sideNavCollapseButton.collapseSidebar')),
114
123
  variant: "ghost",
115
124
  size: size,
@@ -0,0 +1,11 @@
1
+ export interface SideNavFocusTargets {
2
+ /**
3
+ * Registers a row's root element, and whether the collapsed rail renders
4
+ * the row (it has an icon and is not nested); returns its unregister.
5
+ */
6
+ registerRow: (element: HTMLElement, isKeptInRail: boolean) => () => void;
7
+ /** Registers a collapse toggle rendered inside the SideNav. */
8
+ registerToggle: (element: HTMLElement) => () => void;
9
+ }
10
+ export declare const SideNavFocusTargetsContext: import("react").Context<SideNavFocusTargets | null>;
11
+ //# sourceMappingURL=SideNavFocusTargetsContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SideNavFocusTargetsContext.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavFocusTargetsContext.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,WAAW,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;IACzE,+DAA+D;IAC/D,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;CACtD;AAED,eAAO,MAAM,0BAA0B,qDACU,CAAC"}
@@ -0,0 +1,16 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file SideNavFocusTargetsContext.ts
7
+ * @input The SideNav rail's rows and its collapse toggle, registered by their
8
+ * callback refs
9
+ * @output Where focus goes when collapsing the rail removes the focused row
10
+ * @position Internal to SideNav: not exported from the SideNav entry. SideNav
11
+ * provides it; SideNavItem registers its row and SideNavCollapseButton,
12
+ * rendered inside the SideNav, registers itself (`component:SideNav` AR3).
13
+ */
14
+ import { createContext } from 'react';
15
+ export const SideNavFocusTargetsContext = /*#__PURE__*/createContext(null);
16
+ SideNavFocusTargetsContext.displayName = 'SideNavFocusTargetsContext';
@@ -1 +1 @@
1
- {"version":3,"file":"SideNavItem.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAgB,KAAK,WAAW,EAAC,MAAM,iCAAiC,CAAC;AA6ThF,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EACR,OAAO,GACP;QACE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;KACpD,CAAC;IACN;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,UAAkB,EAClB,UAAkB,EAClB,IAAI,EACJ,OAAO,EACP,UAAU,EACV,OAAO,EACP,QAAQ,EACR,WAAW,EAAE,eAAe,EAC5B,IAAW,EACX,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,MAAM,EACN,GAAG,IAAI,EACR,EAAE,gBAAgB,sCAiclB;yBAnde,WAAW"}
1
+ {"version":3,"file":"SideNavItem.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAgB,KAAK,WAAW,EAAC,MAAM,iCAAiC,CAAC;AA8ThF,MAAM,WAAW,gBAAiB,SAAQ,SAAS,CAAC,WAAW,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EACR,OAAO,GACP;QACE,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;KACpD,CAAC;IACN;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,UAAkB,EAClB,UAAkB,EAClB,IAAI,EACJ,OAAO,EACP,UAAU,EACV,OAAO,EACP,QAAQ,EACR,WAAW,EAAE,eAAe,EAC5B,IAAW,EACX,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,MAAM,EACN,GAAG,IAAI,EACR,EAAE,gBAAgB,sCAgdlB;yBAlee,WAAW"}
@@ -55,6 +55,7 @@ import { themeProps } from "../utils/themeProps.js";
55
55
  import { disabledInteractionProps } from "../utils/disabledInteraction.js";
56
56
  import { useTranslator } from "../i18n/index.js";
57
57
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
58
+ import { SideNavFocusTargetsContext } from "./SideNavFocusTargetsContext.js";
58
59
  // =============================================================================
59
60
  // Styles
60
61
  // =============================================================================
@@ -350,6 +351,15 @@ export function SideNavItem({
350
351
  });
351
352
  const railLeafRef = useMergedRefs(ref, railTooltip.ref);
352
353
 
354
+ // The row's root registers with its SideNav, so a collapse that removes the
355
+ // focused row can hand focus to the toggle or the nearest row still in the
356
+ // rail (`component:SideNav` AR3).
357
+ const focusTargets = use(SideNavFocusTargetsContext);
358
+ // The collapsed rail renders a top-level row with an icon (FR2).
359
+ const isKeptInRail = !!icon && depth === 0;
360
+ const rowRef = useCallback(element => element == null || focusTargets == null ? undefined : focusTargets.registerRow(element, isKeptInRail), [focusTargets, isKeptInRail]);
361
+ const rowTooltipRef = useMergedRefs(rowRef, railTooltip.ref);
362
+
353
363
  // Collapse state for items with children
354
364
  const itemCollapsibleConfig = useMemo(() => typeof itemCollapsible === 'object' ? itemCollapsible : {}, [itemCollapsible]);
355
365
  const isItemCollapsible = hasChildren && itemCollapsible !== false;
@@ -447,6 +457,7 @@ export function SideNavItem({
447
457
  // Items with children: popover trigger + popover
448
458
  if (hasChildren) {
449
459
  return /*#__PURE__*/_jsxs("div", {
460
+ ref: rowRef,
450
461
  ...stylex.props(styles.root, xstyle),
451
462
  children: [/*#__PURE__*/_jsx("button", {
452
463
  ref: mergedTriggerRef,
@@ -511,7 +522,7 @@ export function SideNavItem({
511
522
  children: collapsedIcon
512
523
  });
513
524
  return /*#__PURE__*/_jsxs("div", {
514
- ref: isRowTrigger ? railTooltip.ref : undefined,
525
+ ref: isRowTrigger ? rowTooltipRef : rowRef,
515
526
  ...stylex.props(styles.root, xstyle),
516
527
  children: [collapsedElement, railTooltip.renderTooltip(label)]
517
528
  });
@@ -663,6 +674,7 @@ export function SideNavItem({
663
674
  });
664
675
  }
665
676
  const item = /*#__PURE__*/_jsxs("div", {
677
+ ref: rowRef,
666
678
  ...stylex.props(styles.root, xstyle),
667
679
  children: [itemElement, hasChildren && !isCollapsed && /*#__PURE__*/_jsx("div", {
668
680
  id: `${id}-children`,
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"AA8BA;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAU7E;AA+BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,GAAG,WAAW,EAAE,CAI1E;AA8DD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE;;OAEG;IACH,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAAC,CAAC,CAAC,CA8QvB"}
1
+ {"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"AA+BA;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAU7E;AA+BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,GAAG,WAAW,EAAE,CAI1E;AA8DD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE;;OAEG;IACH,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAAC,CAAC,CAAC,CAuQvB"}
@@ -16,6 +16,7 @@
16
16
  * - /packages/core/src/hooks/index.ts
17
17
  */
18
18
  import { useCallback, useEffect, useRef } from 'react';
19
+ import { isFocusLost } from "../utils/focusReturn.js";
19
20
  import { FOCUSABLE_SELECTOR } from "./focusableSelector.js";
20
21
  import { useLayerDismissal } from "../Layer/useLayerDismissal.js";
21
22
 
@@ -291,9 +292,7 @@ export function useFocusTrap(options) {
291
292
  if (!hadFocusInside) {
292
293
  return;
293
294
  }
294
- const active = doc.activeElement;
295
- const focusWasLost = active == null || active === doc.body || active === doc.documentElement || container != null && container.contains(active);
296
- if (!focusWasLost) {
295
+ if (!isFocusLost(container, doc)) {
297
296
  return;
298
297
  }
299
298
  if (previouslyFocused != null && previouslyFocused.isConnected && typeof previouslyFocused.focus === 'function') {
@@ -1 +1 @@
1
- {"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../src/motion/animator.ts"],"names":[],"mappings":"AAsLA,OAAO,EAAwB,KAAK,iBAAiB,EAAC,MAAM,UAAU,CAAC;AAQvE;;GAEG;AACH,MAAM,MAAM,cAAc,GACxB,GAAG,GAAG,GAAG,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAClC;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,iBAAiB;IACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACnD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,gEAAgE;IAChE,KAAK,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvD,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1D;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC;IAC9D,sEAAsE;IACtE,QAAQ,CACN,EAAE,EAAE,YAAY,EAChB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,OAAO,CAAC,EAAE,cAAc,GACvB,YAAY,CAAC;IAChB,mEAAmE;IACnE,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,KAAK,GAAG,WAAW,GAAG,SAAS,CAAC;IACpC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,GAAG,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/D,8DAA8D;IAC9D,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvE;;;;OAIG;IACH,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACtD,oDAAoD;IACpD,OAAO,CACL,EAAE,EAAE,YAAY,EAChB,UAAU,EAAE,gBAAgB,EAC5B,OAAO,CAAC,EAAE,cAAc,GACvB,YAAY,CAAC;IAChB;;;;;;;;OAQG;IACH,IAAI,CACF,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,GAAG,OAAO,EAAE,WAAW,CAAC,CAAC,EAC9D,OAAO,CAAC,EAAE,cAAc,GACvB,YAAY,CAAC;IAChB;;;;;;;;;;;;OAYG;IACH,KAAK,CACH,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,WAAW,CAAC,CAAC,EAC/C,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,cAAc,CAAC,GACpD,YAAY,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC;IACzE,wEAAwE;IACxE,IAAI,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,cAAc,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;IACjE;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,cAAc,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,uCAAuC;IACvC,OAAO,IAAI,IAAI,CAAC;CACjB;AA4JD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAO3D,CAAC;AAqEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,QAAS,CAAC;AAalC,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AA0CvC;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,eAAoB,GAC5B,QAAQ,CAaV;AAyED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAMrD;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,IAAI,OAAO,CAMhD;AAWD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,GAAG,IAAI,EAC3B,OAAO,GAAE,eAAoB,GAC5B,QAAQ,CAupFV"}
1
+ {"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../src/motion/animator.ts"],"names":[],"mappings":"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"}
@@ -560,6 +560,10 @@ export function createAnimator(element, options = {}) {
560
560
  const running = new Map();
561
561
  /** Drops a finished, still-filling animation once the main thread runs. */
562
562
  const retireTimers = new Map();
563
+ /** Channels whose retire looks again at the next frame (scheduleRetire). */
564
+ const retireFrames = new Set();
565
+ /** The timer that starts those looks a frame before a curve's end. */
566
+ const retireLooks = new Map();
563
567
  const touched = new Set();
564
568
  /**
565
569
  * The stylesheet's opacity a Reduce Motion dip borrowed because no call
@@ -761,6 +765,8 @@ export function createAnimator(element, options = {}) {
761
765
  running.delete(channel);
762
766
  clearTimeout(retireTimers.get(channel));
763
767
  retireTimers.delete(channel);
768
+ clearTimeout(retireLooks.get(channel));
769
+ retireLooks.delete(channel);
764
770
  // An element's inline style takes the value on screen below, so its old
765
771
  // animation can go first. A pseudo-element has none: its old animation
766
772
  // keeps holding the value until the new one exists (same task, no frame in
@@ -929,18 +935,55 @@ export function createAnimator(element, options = {}) {
929
935
  */
930
936
  const scheduleRetire = (channel, animation, end) => {
931
937
  clearTimeout(retireTimers.get(channel));
932
- const retire = () => {
933
- retireTimers.delete(channel);
938
+ retireTimers.delete(channel);
939
+ clearTimeout(retireLooks.get(channel));
940
+ retireLooks.delete(channel);
941
+ // Looks for the end in the next frame, before it paints: the frame whose
942
+ // time reaches the curve's end retires it there, where a timer could land
943
+ // between two frames under load and leave it on the element a frame or two
944
+ // past its curve. The timers stay, for a page that draws no frames (a
945
+ // hidden tab).
946
+ const lookAtFrame = () => {
947
+ if (requestFrame == null || retireFrames.has(channel)) {
948
+ return;
949
+ }
950
+ retireFrames.add(channel);
951
+ requestFrame(() => {
952
+ retireFrames.delete(channel);
953
+ retire(true);
954
+ });
955
+ };
956
+ // `atFrame`: a look from a frame callback, which leaves a pending timer
957
+ // where it is.
958
+ const retire = (atFrame = false) => {
959
+ if (!atFrame) {
960
+ retireTimers.delete(channel);
961
+ }
934
962
  if (running.get(channel) !== animation) {
935
963
  return;
936
964
  }
937
965
  // Paused or sought (a sampler holding it): retire when its own time
938
966
  // reaches the curve's end, never from the wall clock.
939
967
  const remaining = remainingCurve(animation);
968
+ if (atFrame && remaining == null) {
969
+ // Its time cannot be read: the timer decides.
970
+ return;
971
+ }
940
972
  if (remaining != null && remaining > 0) {
941
- retireTimers.set(channel, setTimeout(retire, animation.playState === 'paused' ? RETIRE_RECHECK_MS : Math.max(remaining, END_RECHECK_MS)));
973
+ const paused = animation.playState === 'paused';
974
+ // A frame look keeps looking while up to two frames remain: the frame
975
+ // it ran in can be dated before the timer that started the looks was
976
+ // due, and the frame after it is the one that reaches the end.
977
+ if (!paused && remaining <= (atFrame ? 2 : 1) * END_RECHECK_MS) {
978
+ lookAtFrame();
979
+ }
980
+ if (!retireTimers.has(channel)) {
981
+ retireTimers.set(channel, setTimeout(retire, paused ? RETIRE_RECHECK_MS : Math.max(remaining, END_RECHECK_MS)));
982
+ }
942
983
  return;
943
984
  }
985
+ clearTimeout(retireTimers.get(channel));
986
+ retireTimers.delete(channel);
944
987
  running.delete(channel);
945
988
  // A pseudo-element holds its resting value, unless the stylesheet
946
989
  // takes it back now (a borrowed dip that has ended).
@@ -970,6 +1013,14 @@ export function createAnimator(element, options = {}) {
970
1013
  flushReleases();
971
1014
  };
972
1015
  retireTimers.set(channel, setTimeout(retire, Math.max(0, end - now())));
1016
+ if (requestFrame != null) {
1017
+ // Frames start looking a frame before the end, so the frame that
1018
+ // reaches it retires it even when the timer above runs late.
1019
+ retireLooks.set(channel, setTimeout(() => {
1020
+ retireLooks.delete(channel);
1021
+ lookAtFrame();
1022
+ }, Math.max(0, end - now() - END_RECHECK_MS)));
1023
+ }
973
1024
  };
974
1025
 
975
1026
  /** The element's own (stylesheet) opacity, read once when a dip starts. */
@@ -2303,6 +2354,11 @@ export function createAnimator(element, options = {}) {
2303
2354
  clearTimeout(timer);
2304
2355
  }
2305
2356
  retireTimers.clear();
2357
+ for (const timer of retireLooks.values()) {
2358
+ clearTimeout(timer);
2359
+ }
2360
+ retireLooks.clear();
2361
+ retireFrames.clear();
2306
2362
  demote();
2307
2363
  // demote() asks for a fresh will-change read of the node it
2308
2364
  // demotes; a detached node gets none.
@@ -2338,6 +2394,11 @@ export function createAnimator(element, options = {}) {
2338
2394
  clearTimeout(timer);
2339
2395
  }
2340
2396
  retireTimers.clear();
2397
+ for (const timer of retireLooks.values()) {
2398
+ clearTimeout(timer);
2399
+ }
2400
+ retireLooks.clear();
2401
+ retireFrames.clear();
2341
2402
  if (isOpacityBorrowed) {
2342
2403
  // A running borrowed dip (or an exit by role returning to the
2343
2404
  // stylesheet) stays in the model through any attach, so a pending
@@ -2798,6 +2859,11 @@ export function createAnimator(element, options = {}) {
2798
2859
  clearTimeout(timer);
2799
2860
  }
2800
2861
  retireTimers.clear();
2862
+ for (const timer of retireLooks.values()) {
2863
+ clearTimeout(timer);
2864
+ }
2865
+ retireLooks.clear();
2866
+ retireFrames.clear();
2801
2867
  handBackBorrow();
2802
2868
  demote();
2803
2869
  cancelSheetWillChangeRead();
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The accessible name of `element`: its own `aria-labelledby` targets, its
3
+ * `aria-label`, its labels, an input button's value, its text, or its
4
+ * `title`, in that order. A custom DropdownMenu trigger names the menu it
5
+ * labels this way.
6
+ */
7
+ export declare function readAccessibleName(element: HTMLElement | null): string | undefined;
8
+ //# sourceMappingURL=accessibleName.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibleName.d.ts","sourceRoot":"","sources":["../../src/utils/accessibleName.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,GAAG,IAAI,GAC1B,MAAM,GAAG,SAAS,CA4CpB"}