@astryxdesign/core 0.7.0 → 0.7.1-canary.2b6d129

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.
Files changed (51) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheet.js +12 -2
  4. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  5. package/dist/CodeBlock/CodeBlock.js +25 -16
  6. package/dist/ContextMenu/ContextMenu.d.ts +1 -1
  7. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  8. package/dist/ContextMenu/ContextMenu.js +22 -8
  9. package/dist/Dialog/Dialog.d.ts.map +1 -1
  10. package/dist/Dialog/Dialog.js +3 -26
  11. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  12. package/dist/DropdownMenu/DropdownMenu.js +10 -3
  13. package/dist/Typeahead/BaseTypeahead.d.ts +1 -1
  14. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  15. package/dist/Typeahead/BaseTypeahead.js +12 -3
  16. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  17. package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +1 -1
  18. package/dist/utils/focusReturn.d.ts +12 -8
  19. package/dist/utils/focusReturn.d.ts.map +1 -1
  20. package/dist/utils/focusReturn.js +44 -3
  21. package/package.json +4 -4
  22. package/src/BottomSheet/BottomSheet.test.tsx +114 -0
  23. package/src/BottomSheet/BottomSheet.tsx +12 -2
  24. package/src/BottomSheet/__tests__/BottomSheetFocusReturn.a11y.browser.spec.ts +96 -0
  25. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +5 -6
  26. package/src/CodeBlock/CodeBlock.spec.md +55 -45
  27. package/src/CodeBlock/CodeBlock.test.tsx +11 -9
  28. package/src/CodeBlock/CodeBlock.tsx +29 -18
  29. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  30. package/src/ContextMenu/ContextMenu.spec.md +1 -1
  31. package/src/ContextMenu/ContextMenu.test.tsx +36 -3
  32. package/src/ContextMenu/ContextMenu.tsx +27 -11
  33. package/src/ContextMenu/__tests__/ContextMenuCursorAnchor.a11y.browser.spec.ts +153 -0
  34. package/src/Dialog/Dialog.tsx +2 -30
  35. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  36. package/src/DropdownMenu/DropdownMenu.test.tsx +97 -1
  37. package/src/DropdownMenu/DropdownMenu.tsx +10 -3
  38. package/src/DropdownMenu/__tests__/DropdownMenuFocusReturn.a11y.browser.spec.ts +73 -0
  39. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  40. package/src/Markdown/Markdown.spec.md +37 -23
  41. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  42. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  43. package/src/PowerSearch/PowerSearch.test.tsx +45 -0
  44. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  45. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  46. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  47. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  48. package/src/Typeahead/BaseTypeahead.test.tsx +99 -1
  49. package/src/Typeahead/BaseTypeahead.tsx +17 -3
  50. package/src/utils/focusReturn.test.ts +79 -2
  51. package/src/utils/focusReturn.ts +48 -3
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file BottomSheet.tsx
3
- * @input Uses React, StyleX, core hooks, shared layer dismissal, named BottomSheetPanel, BottomSheetSwitcherContext
3
+ * @input Uses React, StyleX, core hooks, shared layer dismissal, the shared interaction modality, named BottomSheetPanel, BottomSheetSwitcherContext
4
4
  * @output Exports BottomSheet component and BottomSheetProps
5
5
  * @position Public BottomSheet router plus private standalone/switcher hosts
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAiBhD,YAAY,EAAC,iBAAiB,EAAE,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAC,iBAAiB,EAAE,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AA0EhF,UAAU,sBAAuB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAChE,qDAAqD;IACrD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB,wGAAwG;IACxG,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAEjD;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,UAAU,0BAA2B,SAAQ,sBAAsB;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,wBAAyB,SAAQ,sBAAsB;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAC1B,0BAA0B,GAAG,wBAAwB,CAAC;AA8XxD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BA6BlD;yBA7Be,WAAW"}
1
+ {"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAmBhD,YAAY,EAAC,iBAAiB,EAAE,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAC,iBAAiB,EAAE,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AA0EhF,UAAU,sBAAuB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAChE,qDAAqD;IACrD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB,wGAAwG;IACxG,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAEjD;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,UAAU,0BAA2B,SAAQ,sBAAsB;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,wBAAyB,SAAQ,sBAAsB;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAC1B,0BAA0B,GAAG,wBAAwB,CAAC;AAsYxD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BA6BlD;yBA7Be,WAAW"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file BottomSheet.tsx
7
- * @input Uses React, StyleX, core hooks, shared layer dismissal, named BottomSheetPanel, BottomSheetSwitcherContext
7
+ * @input Uses React, StyleX, core hooks, shared layer dismissal, the shared interaction modality, named BottomSheetPanel, BottomSheetSwitcherContext
8
8
  * @output Exports BottomSheet component and BottomSheetProps
9
9
  * @position Public BottomSheet router plus private standalone/switcher hosts
10
10
  *
@@ -34,6 +34,8 @@ import { colorVars, durationVars, easeVars } from "../theme/tokens.stylex.js";
34
34
  import { useDevWarning, useScrollLock } from "../hooks/index.js";
35
35
  import { LayerDepthProvider } from "../Layer/LayerDepthContext.js";
36
36
  import { useLayerDismissal } from "../Layer/useLayerDismissal.js";
37
+ import { focusForLastInput } from "../utils/focusReturn.js";
38
+ import { useInteractionModalityTracking } from "../utils/interactionModality.js";
37
39
  import { BottomSheetPanel } from "./BottomSheetPanel.js";
38
40
  import { BottomSheetEdgeTint } from "./BottomSheetEdgeTint.js";
39
41
  import { BottomSheetSwitcherContext } from "./BottomSheetSwitcherContext.js";
@@ -168,9 +170,17 @@ function StandaloneBottomSheet({
168
170
  dialog.close();
169
171
  }
170
172
  setIsPresented(false);
171
- (finalFocusRef?.current ?? triggerRef.current)?.focus();
173
+ // The returned focus shows the focus-visible ring only after keyboard
174
+ // input (`architecture:interaction-modality` INV1). The native close
175
+ // may already have restored focus to the target with a ring; the helper
176
+ // clears it first.
177
+ const returnTarget = finalFocusRef?.current ?? triggerRef.current;
178
+ if (returnTarget != null) {
179
+ focusForLastInput(returnTarget);
180
+ }
172
181
  triggerRef.current = null;
173
182
  }, [finalFocusRef, isOpen]);
183
+ useInteractionModalityTracking();
174
184
  useScrollLock(shouldPresent && hasScrim);
175
185
  useDevWarning('BottomSheet', 'requires a non-empty `label` for an accessible name; the open sheet ' + 'has no built-in heading to derive one from.', isOpen && !label);
176
186
 
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/CodeBlock/CodeBlock.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA6B5C,OAAO,EAAc,KAAK,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AA4WxE,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC/D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,CACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,KACb;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAClD,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAqPD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,QAAsB,EACtB,KAAK,EACL,gBAAuB,EACvB,cAAsB,EACtB,cAAc,EACd,aAAoB,EACpB,MAAM,EACN,SAAiB,EACjB,SAAS,EACT,aAAqB,EACrB,oBAAyB,EACzB,IAAW,EACX,KAAK,EAAE,SAAyB,EAChC,SAAkB,EAClB,SAAS,EAAE,eAAe,EAC1B,aAAsB,EACtB,WAAW,EACX,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,cAAc,qBAmPhB;yBA3Qe,SAAS"}
1
+ {"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/CodeBlock/CodeBlock.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA8B5C,OAAO,EAAc,KAAK,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AA+WxE,MAAM,WAAW,cAAe,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC/D,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,CACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,KACb;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IAClD,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAqPD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,QAAsB,EACtB,KAAK,EACL,gBAAuB,EACvB,cAAsB,EACtB,cAAc,EACd,aAAoB,EACpB,MAAM,EACN,SAAiB,EACjB,SAAS,EACT,aAAqB,EACrB,oBAAyB,EACzB,IAAW,EACX,KAAK,EAAE,SAAyB,EAChC,SAAkB,EAClB,SAAS,EAAE,eAAe,EAC1B,aAAsB,EACtB,WAAW,EACX,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,cAAc,qBA0PhB;yBAlRe,SAAS"}
@@ -15,6 +15,7 @@ import "../theme/tokens.stylex.js";
15
15
  import { colorVars, spacingVars, radiusVars, typographyVars, fontWeightVars, typeScaleVars, borderVars, durationVars, easeVars } from "../theme/tokens.stylex.js";
16
16
  import { mergeProps } from "../utils/index.js";
17
17
  import { useClipboard } from "../hooks/useClipboard.js";
18
+ import { useScrollableArea } from "../hooks/useScrollableArea.js";
18
19
  import { Icon } from "../Icon/index.js";
19
20
  import { IconButton } from "../IconButton/index.js";
20
21
  import { tokenize, tokenizeAsync, flatTokensToLines, SYNC_TOKENIZE_THRESHOLD } from "./tokenizer.js";
@@ -494,7 +495,23 @@ export function CodeBlock({
494
495
  // pattern). The region stays mounted when collapsed (CSS grid animation), so
495
496
  // this is always a resolvable reference — aria-controls can be unconditional.
496
497
  const regionId = useId();
497
- const scrollContainerRef = useRef(null);
498
+
499
+ // The code viewport enters the Tab order only while its content actually
500
+ // overflows (AST-025 FR12–FR13): the shared scroll capability measures
501
+ // both axes, keeps focus where it is when overflow goes away, and owns the
502
+ // overflow declarations. role="group" (not "region") so several code
503
+ // blocks on a page don't create duplicate same-named landmarks.
504
+ const {
505
+ getViewportProps,
506
+ getContentProps
507
+ } = useScrollableArea({
508
+ axis: 'both',
509
+ keyboardAccess: {
510
+ owner: 'viewport',
511
+ role: 'group',
512
+ label: languageLabel ?? t('@astryx.codeBlock.code')
513
+ }
514
+ });
498
515
  const scrollStyle = maxHeight != null ? {
499
516
  maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
500
517
  } : undefined;
@@ -570,29 +587,21 @@ export function CodeBlock({
570
587
  }), copyButtonEl]
571
588
  }) : null;
572
589
  const codeBody = /*#__PURE__*/_jsx("div", {
573
- ref: scrollContainerRef
574
- // The scroll container is keyboard-focusable so keyboard users can
575
- // scroll long or wide code that overflows the viewport. Uses
576
- // role="group" (not "region") so multiple code blocks on a page don't
577
- // create duplicate same-named landmarks (axe: landmark-unique).
578
- ,
579
- tabIndex: 0,
580
- role: "group",
581
- "aria-label": languageLabel ?? t('@astryx.codeBlock.code'),
582
- ...mergeProps({
583
- className: "xw2csxc x1odjw0f"
584
- }, {
590
+ ...getViewportProps({
591
+ ...{
592
+ className: "x2lwn1j"
593
+ },
585
594
  style: scrollStyle
586
595
  }),
587
596
  children: /*#__PURE__*/_jsx("div", {
588
- ...{
597
+ ...getContentProps({
589
598
  0: {
590
599
  className: "x78zum5 x1pshirs"
591
600
  },
592
601
  1: {
593
602
  className: "x78zum5 x1pshirs x1ajqlyv"
594
603
  }
595
- }[!!(showHeader && !hasLineNumbers) << 0],
604
+ }[!!(showHeader && !hasLineNumbers) << 0]),
596
605
  children: useSpans ? /*#__PURE__*/_jsx(SpanCodeContent, {
597
606
  lines: lines,
598
607
  tokenLines: tokenLines,
@@ -625,7 +634,7 @@ export function CodeBlock({
625
634
  // While collapsed, the region is only hidden visually (0fr grid
626
635
  // row); inert also removes it from the tab order and accessibility
627
636
  // tree so keyboard users cannot Tab into the invisible scroll
628
- // container (tabIndex=0). aria-controls pointing at an inert
637
+ // container (a tab stop while it overflows). aria-controls pointing at an inert
629
638
  // element remains a valid, resolvable reference.
630
639
  ,
631
640
  inert: isCollapsed ? true : undefined,
@@ -6,7 +6,7 @@
6
6
  * @position Core implementation; consumed by index.ts
7
7
  *
8
8
  * Right-click context menu positioned at the cursor. The cursor point is
9
- * captured as an offset *inside the trigger* and materialized as a zero-size
9
+ * captured as an offset *inside the trigger* and materialized as a 1px
10
10
  * anchor element, so the menu is positioned relative to the trigger's context
11
11
  * (via CSS anchor positioning) rather than the viewport. It therefore follows
12
12
  * the content on scroll and auto-flips at viewport edges, while still appearing
@@ -1 +1 @@
1
- {"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAkCrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,kCAAkC,CAAC;AAgG1C,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,YAAwB,EACxB,SAAiB,EACjB,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,gBAAgB,qBA0clB;yBA1de,WAAW"}
1
+ {"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../src/ContextMenu/ContextMenu.tsx"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAkCrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,kCAAkC,CAAC;AA6G1C,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAMnD,UAAU,oBAAqB,SAAQ,SAAS;IAC9C,oDAAoD;IACpD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,oBAAqB,SAAQ,oBAAoB;IACzD,gDAAgD;IAChD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAED,UAAU,wBAAyB,SAAQ,oBAAoB;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,SAAS,CAAC;CACxB;AAED,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AAM/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,IAAW,EACX,KAAK,EAAE,cAAc,EACrB,UAAkB,EAClB,YAAY,EACZ,YAAwB,EACxB,SAAiB,EACjB,GAAG,EACH,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EACb,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,gBAAgB,qBA6clB;yBA7de,WAAW"}
@@ -9,7 +9,7 @@
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
11
11
  * Right-click context menu positioned at the cursor. The cursor point is
12
- * captured as an offset *inside the trigger* and materialized as a zero-size
12
+ * captured as an offset *inside the trigger* and materialized as a 1px
13
13
  * anchor element, so the menu is positioned relative to the trigger's context
14
14
  * (via CSS anchor positioning) rather than the viewport. It therefore follows
15
15
  * the content on scroll and auto-flips at viewport edges, while still appearing
@@ -134,6 +134,17 @@ const styles = {
134
134
  }
135
135
  };
136
136
 
137
+ /**
138
+ * Keeps the 1px cursor anchor inside the trigger along one axis. A point in
139
+ * the trigger's last pixel (Shift+F10 opens on its bottom edge) steps back by
140
+ * that pixel: past the edge the anchor would add scrollable overflow, and a
141
+ * scroller whose edge meets the trigger's would clip it and hide the menu.
142
+ * Any other point, inside the trigger or not, stays where it is.
143
+ */
144
+ function keepAnchorInside(offset, size) {
145
+ return offset > size - 1 && offset <= size ? Math.max(size - 1, 0) : offset;
146
+ }
147
+
137
148
  // =============================================================================
138
149
  // Types
139
150
  // =============================================================================
@@ -203,7 +214,7 @@ export function ContextMenu({
203
214
  const menuContent = menuContentProp;
204
215
  const menuId = useId();
205
216
  // Cursor point in the trigger's local coordinate space (offset from the
206
- // trigger's top-left, in-flow). Stored here and written to the zero-size
217
+ // trigger's top-left, in-flow). Stored here and written to the cursor
207
218
  // anchor element so the menu is positioned relative to the trigger context
208
219
  // — it scrolls with the content instead of sitting at a fixed viewport point.
209
220
  const positionRef = useRef({
@@ -380,20 +391,23 @@ export function ContextMenu({
380
391
  listNavKeyDown(e);
381
392
  }, [listNavKeyDown, closeMenu, typeahead, ownsEvent]);
382
393
 
383
- // Place the zero-size cursor anchor at a point in the trigger's local
394
+ // Place the 1px cursor anchor at a point in the trigger's local
384
395
  // coordinate space and open the menu. Positioning the anchor inside the
385
396
  // trigger (rather than storing viewport coordinates on the menu itself) is
386
397
  // what makes the menu context-relative: it scrolls with the content and the
387
398
  // browser auto-flips it against the viewport edges via CSS anchor positioning.
388
399
  const openAtLocalPoint = useCallback((localX, localY, focusEl) => {
400
+ const rect = triggerRef.current?.getBoundingClientRect();
401
+ const x = rect ? keepAnchorInside(localX, rect.width) : localX;
402
+ const y = rect ? keepAnchorInside(localY, rect.height) : localY;
389
403
  positionRef.current = {
390
- x: localX,
391
- y: localY
404
+ x,
405
+ y
392
406
  };
393
407
  const anchorEl = cursorAnchorRef.current;
394
408
  if (anchorEl) {
395
- anchorEl.style.left = `${localX}px`;
396
- anchorEl.style.top = `${localY}px`;
409
+ anchorEl.style.left = `${x}px`;
410
+ anchorEl.style.top = `${y}px`;
397
411
  }
398
412
  // Remember the element focused before opening so we can restore it on
399
413
  // close (Escape or outside-click), instead of dropping focus to <body>.
@@ -534,7 +548,7 @@ export function ContextMenu({
534
548
  ref: useMergedRefs(cursorAnchorRef, layer.ref),
535
549
  "aria-hidden": "true",
536
550
  ...mergeProps({
537
- className: "x10l6tqk xnalus7 xqtp20y x47corl"
551
+ className: "x10l6tqk x1i1rx1s xjm9jq1 x47corl"
538
552
  }, {
539
553
  style: {
540
554
  left: `${positionRef.current.x}px`,
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA2B5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAqD9C;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD,4EAA4E;AAC5E,UAAU,mBAAmB;IAC3B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,mBAAmB;IACzD,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAuBD,2FAA2F;AAC3F,eAAO,MAAM,uCAAuC;;;;;;;;;;;CAW1C,CAAC;AAoJX;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc;;;;;EAUpE;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;OAOG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAEpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,QAAgB,EAChB,YAAY,EACZ,KAAW,EACX,SAAmB,EACnB,QAAQ,EACR,OAAoB,EACpB,OAAgB,EAChB,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,sCAiUb;yBAjVe,MAAM"}
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA2B5C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gBAAgB,CAAC;AAKhD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAyB9C;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD,4EAA4E;AAC5E,UAAU,mBAAmB;IAC3B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,mBAAmB;IACzD,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAuBD,2FAA2F;AAC3F,eAAO,MAAM,uCAAuC;;;;;;;;;;;CAW1C,CAAC;AAoJX;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc;;;;;EAUpE;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;OAOG;IACH,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IAEpC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,QAAgB,EAChB,YAAY,EACZ,KAAW,EACX,SAAmB,EACnB,QAAQ,EACR,OAAoB,EACpB,OAAgB,EAChB,OAAO,EACP,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,sCAiUb;yBAjVe,MAAM"}
@@ -45,38 +45,15 @@ import { devWarn } from "../utils/devWarning.js";
45
45
  import { DialogContext } from "./DialogContext.js";
46
46
  import { themeProps } from "../utils/themeProps.js";
47
47
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
48
- import { getInteractionModality, useInteractionModalityTracking } from "../utils/interactionModality.js";
48
+ import { useInteractionModalityTracking } from "../utils/interactionModality.js";
49
+ import { focusForLastInput } from "../utils/focusReturn.js";
49
50
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
50
- /**
51
- * Move focus programmatically so its ring follows the last input. Dialog does
52
- * not render the element that opened it or the consumer's autofocus target, so
53
- * the browser's focus option is the one way to reach them.
54
- */
55
- import { jsx as _jsx } from "react/jsx-runtime";
56
- function focusForLastInput(element) {
57
- // A text-entry field paints its own focus treatment to show where typing
58
- // goes, and an extra blur there can run validation or mark it touched.
59
- const isTextEntry = element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element.isContentEditable;
60
- if (isTextEntry || getInteractionModality() !== 'pointer') {
61
- element.focus();
62
- return;
63
- }
64
- // Focusing the element that already has focus does nothing, so a ring the
65
- // browser already drew would stay. WebKit draws one when showModal() or its
66
- // native close moves focus itself, so clear that focus first.
67
- if (element.matches(':focus-visible')) {
68
- element.blur();
69
- }
70
- element.focus({
71
- focusVisible: false
72
- });
73
- }
74
-
75
51
  /**
76
52
  * Calculate a directional translate offset for dialog entry animation.
77
53
  * Returns a normalized vector from the trigger element toward the viewport
78
54
  * center, scaled to the given distance.
79
55
  */
56
+ import { jsx as _jsx } from "react/jsx-runtime";
80
57
  function getDialogDirection(triggerEl, distance = 16) {
81
58
  const rect = triggerEl.getBoundingClientRect();
82
59
  const dx = rect.left + rect.width / 2 - window.innerWidth / 2;
@@ -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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"}
@@ -48,6 +48,7 @@ import { useListFocus } from "../hooks/useListFocus.js";
48
48
  import { useMenuPress } from "../hooks/useMenuPress.js";
49
49
  import { useTypeahead } from "../hooks/useTypeahead.js";
50
50
  import { useFocusReturnVisibility } from "../hooks/useFocusReturnVisibility.js";
51
+ import { focusForLastInput } from "../utils/focusReturn.js";
51
52
  import { useMenuOverflow } from "./useMenuOverflow.js";
52
53
  import { MenuRegionsContext } from "./menuRegionsContext.js";
53
54
  import { getFocusableElements } from "../hooks/useFocusTrap.js";
@@ -654,7 +655,12 @@ function DropdownMenuPopover({
654
655
  return;
655
656
  }
656
657
  prepareFocusReturn();
657
- trigger.focus({
658
+ // After pointer input the trigger takes focus without the focus-visible
659
+ // ring (`component:DropdownMenu/DEC-1`). The style the helper above adds
660
+ // reaches only the design system's own Button; the browser's focus option
661
+ // also reaches a control the caller renders, which the browser would
662
+ // otherwise ring (Chromium after a mouse pick, WebKit after a touch).
663
+ focusForLastInput(trigger, {
658
664
  preventScroll: true
659
665
  });
660
666
  }, [isControlled, onOpenChange, prepareFocusReturn]);
@@ -1072,8 +1078,9 @@ function DropdownMenuPopover({
1072
1078
  onPointerDown: menuPress.triggerProps.onPointerDown,
1073
1079
  onContextMenu: menuPress.triggerProps.onContextMenu,
1074
1080
  onKeyDown: handleButtonKeyDown,
1075
- // The popover's focus-return hook is a separate change; until it lands
1076
- // the control's focus needs no bookkeeping here.
1081
+ // The caller's control paints its own ring, and the menu's focus return
1082
+ // keeps the browser from drawing one after pointer input (see
1083
+ // handleLayerHide), so its focus needs no bookkeeping here.
1077
1084
  onFocus: () => {},
1078
1085
  'aria-haspopup': 'menu',
1079
1086
  'aria-expanded': isOpen,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file BaseTypeahead.tsx
3
3
  * @input Uses React, StyleX, usePopover, TypeaheadItem
4
- * @output Exports BaseTypeahead with focused-input menu reactivation
4
+ * @output Exports BaseTypeahead; focus bootstraps a replaced search source
5
5
  * @position Core implementation; used by Typeahead and Tokenizer
6
6
  *
7
7
  * Pure combobox engine: input, search, keyboard navigation, dropdown.
@@ -1 +1 @@
1
- {"version":3,"file":"BaseTypeahead.d.ts","sourceRoot":"","sources":["../../src/Typeahead/BaseTypeahead.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AA2BnD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AAW1D,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,IAAI,CACxE,SAAS,CAAC,WAAW,CAAC,EACtB,UAAU,CACX;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAE9B;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAEhB;;OAEG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAEnC;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAEpC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;OASG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IAEtD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,YAAY,CAAC;IAE3B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE1C;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAE/D;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAoKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAs0BrB,CAAC,CAAC,SAAS,cAAc,EAC5B,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KACzB,KAAK,CAAC,YAAY,CAAC"}
1
+ {"version":3,"file":"BaseTypeahead.d.ts","sourceRoot":"","sources":["../../src/Typeahead/BaseTypeahead.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;AA2BnD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AAW1D,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,cAAc,CAAE,SAAQ,IAAI,CACxE,SAAS,CAAC,WAAW,CAAC,EACtB,UAAU,CACX;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAE9B;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAEhB;;OAEG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAEnC;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;IAEpC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;;;OASG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IAEtD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,YAAY,CAAC;IAE3B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE1C;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAE/D;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAoKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAo1BrB,CAAC,CAAC,SAAS,cAAc,EAC5B,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,KACzB,KAAK,CAAC,YAAY,CAAC"}
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @file BaseTypeahead.tsx
7
7
  * @input Uses React, StyleX, usePopover, TypeaheadItem
8
- * @output Exports BaseTypeahead with focused-input menu reactivation
8
+ * @output Exports BaseTypeahead; focus bootstraps a replaced search source
9
9
  * @position Core implementation; used by Typeahead and Tokenizer
10
10
  *
11
11
  * Pure combobox engine: input, search, keyboard navigation, dropdown.
@@ -326,6 +326,10 @@ export const BaseTypeahead = function BaseTypeahead({
326
326
  // in the closure are stale (a selection cleared them) and shouldn't
327
327
  // be re-shown.
328
328
  const resultsGenRef = useRef(0);
329
+ // The generation at which the current searchSource took over. Results
330
+ // populated before it (resultsGenRef below this) came from a replaced
331
+ // source: they can never be re-shown, so focus bootstraps the new source.
332
+ const sourceGenRef = useRef(0);
329
333
 
330
334
  // Results still arriving from a replaced source must not land in the new
331
335
  // one's menu.
@@ -333,7 +337,7 @@ export const BaseTypeahead = function BaseTypeahead({
333
337
  if (prevSearchSourceRef.current !== searchSource) {
334
338
  prevSearchSourceRef.current.cancel?.();
335
339
  prevSearchSourceRef.current = searchSource;
336
- searchGenRef.current++;
340
+ sourceGenRef.current = ++searchGenRef.current;
337
341
  }
338
342
 
339
343
  // Layer for dropdown
@@ -551,11 +555,16 @@ export const BaseTypeahead = function BaseTypeahead({
551
555
  // Shared by handleFocus and handleClick: bootstrap entries if none are
552
556
  // loaded yet, or re-show cached results that haven't been invalidated by
553
557
  // a selection since (comparing the two generation refs catches that).
558
+ // Results cached from a replaced search source count as none, so focusing
559
+ // an empty field bootstraps the new source instead of opening nothing.
560
+ // Results a selection invalidated still keep the menu closed when the
561
+ // selection returns focus to the input.
554
562
  const openIfEligible = useCallback(() => {
555
563
  if (isDisabled) {
556
564
  return;
557
565
  }
558
- if (hasEntriesOnFocus && results.length === 0 && query.length === 0) {
566
+ const hasResultsFromCurrentSource = results.length > 0 && resultsGenRef.current >= sourceGenRef.current;
567
+ if (hasEntriesOnFocus && !hasResultsFromCurrentSource && query.length === 0) {
559
568
  void performBootstrap();
560
569
  } else if (results.length > 0 && (query.length > 0 || hasEntriesOnFocus) && resultsGenRef.current === searchGenRef.current) {
561
570
  showLayer();
@@ -11,6 +11,6 @@
11
11
  * identical — neither hook fires during server rendering — but React only
12
12
  * warns about useLayoutEffect.
13
13
  */
14
- import { useLayoutEffect } from 'react';
15
- export declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
14
+ import { useEffect } from 'react';
15
+ export declare const useIsomorphicLayoutEffect: typeof useEffect;
16
16
  //# sourceMappingURL=useIsomorphicLayoutEffect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useIsomorphicLayoutEffect.d.ts","sourceRoot":"","sources":["../../src/hooks/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAY,eAAe,EAAC,MAAM,OAAO,CAAC;AAEjD,eAAO,MAAM,yBAAyB,wBACuB,CAAC"}
1
+ {"version":3,"file":"useIsomorphicLayoutEffect.d.ts","sourceRoot":"","sources":["../../src/hooks/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,SAAS,EAAkB,MAAM,OAAO,CAAC;AAEjD,eAAO,MAAM,yBAAyB,kBACuB,CAAC"}
@@ -1,11 +1,3 @@
1
- /**
2
- * @file focusReturn.ts
3
- * @input The document's current active element.
4
- * @output Whether focus is currently "detached" — on nothing in particular.
5
- * @position Utility; used by overlay triggers (e.g. DateInput, DateTimeInput)
6
- * that return focus to their control when the overlay closes, but only when
7
- * the dismiss did not already move focus somewhere the user chose.
8
- */
9
1
  /**
10
2
  * True when focus rests on nothing in particular — no active element, or the
11
3
  * document body / root element.
@@ -21,4 +13,16 @@
21
13
  * when the trigger should reclaim it.
22
14
  */
23
15
  export declare function isFocusDetached(doc?: Document): boolean;
16
+ /**
17
+ * Move focus programmatically so its ring follows the last input
18
+ * (`architecture:interaction-modality` INV1). After pointer input the browser's
19
+ * focus option keeps the element from matching `:focus-visible`, which is the
20
+ * one way to reach an element the overlay does not render: the consumer's
21
+ * autofocus target, the control that opened a Dialog, or a menu trigger the
22
+ * caller renders. Pass `options` (for example `preventScroll`) as to `focus()`.
23
+ *
24
+ * Make sure modality tracking is active (`useInteractionModalityTracking()`)
25
+ * in the component that calls this.
26
+ */
27
+ export declare function focusForLastInput(element: HTMLElement, options?: FocusOptions): void;
24
28
  //# sourceMappingURL=focusReturn.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"focusReturn.d.ts","sourceRoot":"","sources":["../../src/utils/focusReturn.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,QAAmB,GAAG,OAAO,CAKjE"}
1
+ {"version":3,"file":"focusReturn.d.ts","sourceRoot":"","sources":["../../src/utils/focusReturn.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,QAAmB,GAAG,OAAO,CAKjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,YAAY,GACrB,IAAI,CAwBN"}
@@ -2,13 +2,18 @@
2
2
 
3
3
  /**
4
4
  * @file focusReturn.ts
5
- * @input The document's current active element.
6
- * @output Whether focus is currently "detached" — on nothing in particular.
5
+ * @input The document's current active element and the shared interaction
6
+ * modality.
7
+ * @output Whether focus is currently "detached" — on nothing in particular —
8
+ * and a focus move whose ring follows the last input.
7
9
  * @position Utility; used by overlay triggers (e.g. DateInput, DateTimeInput)
8
10
  * that return focus to their control when the overlay closes, but only when
9
- * the dismiss did not already move focus somewhere the user chose.
11
+ * the dismiss did not already move focus somewhere the user chose, and by
12
+ * overlays (Dialog, DropdownMenu) that move focus programmatically.
10
13
  */
11
14
 
15
+ import { getInteractionModality } from "./interactionModality.js";
16
+
12
17
  /**
13
18
  * True when focus rests on nothing in particular — no active element, or the
14
19
  * document body / root element.
@@ -26,4 +31,40 @@
26
31
  export function isFocusDetached(doc = document) {
27
32
  const active = doc.activeElement;
28
33
  return active == null || active === doc.body || active === doc.documentElement;
34
+ }
35
+
36
+ /**
37
+ * Move focus programmatically so its ring follows the last input
38
+ * (`architecture:interaction-modality` INV1). After pointer input the browser's
39
+ * focus option keeps the element from matching `:focus-visible`, which is the
40
+ * one way to reach an element the overlay does not render: the consumer's
41
+ * autofocus target, the control that opened a Dialog, or a menu trigger the
42
+ * caller renders. Pass `options` (for example `preventScroll`) as to `focus()`.
43
+ *
44
+ * Make sure modality tracking is active (`useInteractionModalityTracking()`)
45
+ * in the component that calls this.
46
+ */
47
+ export function focusForLastInput(element, options) {
48
+ // A text-entry field paints its own focus treatment to show where typing
49
+ // goes, and an extra blur there can run validation or mark it touched.
50
+ const isTextEntry = element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element.isContentEditable;
51
+ if (isTextEntry || getInteractionModality() !== 'pointer') {
52
+ if (options === undefined) {
53
+ element.focus();
54
+ } else {
55
+ element.focus(options);
56
+ }
57
+ return;
58
+ }
59
+ // Focusing the element that already has focus does nothing, so a ring the
60
+ // browser already drew would stay. WebKit draws one when its own focus
61
+ // restoration (a dialog's close, a popover's hide) moves focus itself, so
62
+ // clear that focus first.
63
+ if (element.matches(':focus-visible')) {
64
+ element.blur();
65
+ }
66
+ element.focus({
67
+ ...options,
68
+ focusVisible: false
69
+ });
29
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.1-canary.2b6d129",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -787,6 +787,8 @@
787
787
  "react-dom": ">=19.0.0"
788
788
  },
789
789
  "devDependencies": {
790
+ "@astryxdesign/a11y-spec": "0.7.1-canary.2b6d129",
791
+ "@astryxdesign/cli": "0.7.1-canary.2b6d129",
790
792
  "@babel/cli": "^7.29.7",
791
793
  "@babel/core": "^7.29.7",
792
794
  "@babel/preset-react": "^7.29.7",
@@ -797,9 +799,7 @@
797
799
  "@testing-library/jest-dom": "^6.6.0",
798
800
  "@testing-library/react": "^16.3.2",
799
801
  "@types/babel__core": "^7.20.5",
800
- "tailwindcss": "^4.3.2",
801
- "@astryxdesign/a11y-spec": "0.0.0",
802
- "@astryxdesign/cli": "0.7.0"
802
+ "tailwindcss": "^4.3.2"
803
803
  },
804
804
  "dependencies": {
805
805
  "intl-messageformat": "^11.2.9"