@astryxdesign/core 0.7.7-canary.fc68123 → 0.7.7

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 (240) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/AlertDialog/AlertDialog.d.ts +1 -4
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +6 -36
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetPanel.js +4 -23
  7. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  8. package/dist/BottomSheet/useSheetGestures.js +1 -3
  9. package/dist/Button/Button.d.ts.map +1 -1
  10. package/dist/Button/Button.js +1 -17
  11. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  12. package/dist/CheckboxInput/CheckboxInput.js +2 -13
  13. package/dist/ComplexSelector/ComplexSelector.d.ts +1 -4
  14. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  15. package/dist/ComplexSelector/ComplexSelector.js +4 -14
  16. package/dist/ContextMenu/ContextMenu.js +2 -2
  17. package/dist/Dialog/Dialog.d.ts.map +1 -1
  18. package/dist/Dialog/Dialog.js +31 -7
  19. package/dist/Field/Field.js +2 -2
  20. package/dist/HoverCard/HoverCard.d.ts +3 -4
  21. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  22. package/dist/HoverCard/HoverCard.js +2 -27
  23. package/dist/HoverCard/useHoverCard.d.ts +4 -5
  24. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  25. package/dist/HoverCard/useHoverCard.js +31 -17
  26. package/dist/Icon/Icon.d.ts +1 -5
  27. package/dist/Icon/Icon.d.ts.map +1 -1
  28. package/dist/Icon/Icon.js +2 -5
  29. package/dist/Layer/usePressPin.d.ts +5 -17
  30. package/dist/Layer/usePressPin.d.ts.map +1 -1
  31. package/dist/Layer/usePressPin.js +12 -62
  32. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  33. package/dist/Lightbox/Lightbox.js +0 -18
  34. package/dist/PowerSearch/PowerSearch.d.ts +3 -36
  35. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  36. package/dist/PowerSearch/PowerSearch.js +127 -433
  37. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +1 -32
  38. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  39. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -17
  40. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -38
  41. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  42. package/dist/PowerSearch/PowerSearchValueEditor.js +61 -170
  43. package/dist/PowerSearch/formatFilterValue.d.ts +0 -7
  44. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  45. package/dist/PowerSearch/formatFilterValue.js +1 -11
  46. package/dist/RadioList/RadioList.d.ts.map +1 -1
  47. package/dist/RadioList/RadioList.js +2 -3
  48. package/dist/RadioList/RadioListItem.d.ts +1 -1
  49. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  50. package/dist/RadioList/RadioListItem.js +3 -5
  51. package/dist/Resizable/ResizeHandle.d.ts +1 -2
  52. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  53. package/dist/Resizable/ResizeHandle.js +77 -99
  54. package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
  55. package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
  56. package/dist/Slider/sliderTouchController.js +2 -2
  57. package/dist/Spinner/Spinner.d.ts +1 -1
  58. package/dist/Spinner/Spinner.d.ts.map +1 -1
  59. package/dist/Spinner/Spinner.js +58 -60
  60. package/dist/Switch/useSwitchTouch.js +4 -4
  61. package/dist/Table/BaseTable.d.ts.map +1 -1
  62. package/dist/Table/BaseTable.js +8 -29
  63. package/dist/TextInput/TextInput.d.ts +2 -2
  64. package/dist/TextInput/TextInput.d.ts.map +1 -1
  65. package/dist/TextInput/TextInput.js +7 -10
  66. package/dist/Toast/Toast.d.ts +1 -11
  67. package/dist/Toast/Toast.d.ts.map +1 -1
  68. package/dist/Toast/Toast.js +15 -25
  69. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  70. package/dist/Toast/ToastViewport.js +34 -94
  71. package/dist/Toast/types.d.ts +3 -9
  72. package/dist/Toast/types.d.ts.map +1 -1
  73. package/dist/Tooltip/useTooltip.d.ts +1 -19
  74. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  75. package/dist/Tooltip/useTooltip.js +53 -34
  76. package/dist/Typeahead/TypeaheadItem.d.ts +1 -2
  77. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  78. package/dist/astryx.css +6 -35
  79. package/dist/gestures/PressTracker.d.ts.map +1 -1
  80. package/dist/gestures/PressTracker.js +3 -4
  81. package/dist/hooks/actionGroupTracker.d.ts +1 -4
  82. package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
  83. package/dist/hooks/actionGroupTracker.js +3 -13
  84. package/dist/hooks/index.d.ts +0 -1
  85. package/dist/hooks/index.d.ts.map +1 -1
  86. package/dist/hooks/index.js +0 -1
  87. package/dist/hooks/useAttachment.d.ts +1 -1
  88. package/dist/hooks/useAttachment.js +1 -1
  89. package/dist/hooks/useInputStatusIcon.d.ts +1 -1
  90. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  91. package/dist/hooks/useInputStatusIcon.js +2 -2
  92. package/dist/hooks/useLongPress.d.ts +2 -6
  93. package/dist/hooks/useLongPress.d.ts.map +1 -1
  94. package/dist/hooks/useLongPress.js +15 -95
  95. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
  96. package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +0 -6
  97. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  98. package/dist/i18n/generated-locales/en.generated.js +0 -30
  99. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  100. package/dist/i18n/generated-locales/pseudo.generated.js +0 -30
  101. package/dist/index.d.ts +0 -1
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +0 -4
  104. package/dist/motion/animatedLayout.d.ts.map +1 -1
  105. package/dist/motion/animatedLayout.js +6 -5
  106. package/dist/motion/animator.d.ts +14 -14
  107. package/dist/motion/animator.d.ts.map +1 -1
  108. package/dist/motion/animator.js +18 -41
  109. package/dist/utils/dateParser.d.ts.map +1 -1
  110. package/dist/utils/dateParser.js +0 -12
  111. package/dist/utils/focusReturn.d.ts +0 -15
  112. package/dist/utils/focusReturn.d.ts.map +1 -1
  113. package/dist/utils/focusReturn.js +0 -28
  114. package/dist/utils/interactionOverlay.stylex.d.ts +2 -3
  115. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  116. package/dist/utils/interactionOverlay.stylex.js +2 -3
  117. package/dist/utils/pressFeedback.d.ts.map +1 -1
  118. package/dist/utils/pressFeedback.js +6 -19
  119. package/locales/en.json +0 -120
  120. package/locales/pseudo.json +0 -90
  121. package/package.json +5 -10
  122. package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
  123. package/src/AlertDialog/AlertDialog.tsx +5 -37
  124. package/src/BottomSheet/BottomSheetPanel.tsx +3 -37
  125. package/src/BottomSheet/useSheetGestures.ts +1 -3
  126. package/src/Button/Button.tsx +4 -26
  127. package/src/CheckboxInput/CheckboxInput.doc.mjs +0 -18
  128. package/src/CheckboxInput/CheckboxInput.tsx +9 -36
  129. package/src/ComplexSelector/ComplexSelector.tsx +5 -20
  130. package/src/ContextMenu/ContextMenu.tsx +2 -2
  131. package/src/Dialog/Dialog.tsx +31 -10
  132. package/src/Field/Field.tsx +2 -2
  133. package/src/HoverCard/HoverCard.doc.mjs +2 -4
  134. package/src/HoverCard/HoverCard.tsx +4 -35
  135. package/src/HoverCard/useHoverCard.doc.mjs +36 -178
  136. package/src/HoverCard/useHoverCard.tsx +52 -46
  137. package/src/Icon/Icon.doc.mjs +8 -49
  138. package/src/Icon/Icon.tsx +7 -15
  139. package/src/Layer/usePressPin.ts +15 -81
  140. package/src/Lightbox/Lightbox.tsx +0 -18
  141. package/src/PowerSearch/PowerSearch.doc.mjs +5 -54
  142. package/src/PowerSearch/PowerSearch.tsx +155 -609
  143. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -28
  144. package/src/PowerSearch/PowerSearchValueEditor.tsx +38 -203
  145. package/src/PowerSearch/formatFilterValue.ts +1 -18
  146. package/src/RadioList/RadioList.tsx +2 -3
  147. package/src/RadioList/RadioListItem.tsx +3 -5
  148. package/src/Resizable/Resizable.doc.mjs +4 -5
  149. package/src/Resizable/ResizeHandle.tsx +109 -167
  150. package/src/SegmentedControl/useSegmentedSelectionMotion.ts +3 -6
  151. package/src/Slider/sliderTouchController.ts +2 -2
  152. package/src/Spinner/Spinner.tsx +63 -77
  153. package/src/Switch/useSwitchTouch.ts +4 -4
  154. package/src/Table/BaseTable.tsx +12 -46
  155. package/src/TextInput/TextInput.tsx +10 -11
  156. package/src/Toast/Toast.doc.mjs +3 -4
  157. package/src/Toast/Toast.tsx +32 -52
  158. package/src/Toast/ToastViewport.tsx +35 -121
  159. package/src/Toast/types.ts +3 -9
  160. package/src/Tooltip/useTooltip.doc.mjs +0 -24
  161. package/src/Tooltip/useTooltip.tsx +56 -61
  162. package/src/Typeahead/TypeaheadItem.doc.mjs +3 -6
  163. package/src/Typeahead/TypeaheadItem.tsx +1 -2
  164. package/src/gestures/PressTracker.ts +3 -4
  165. package/src/gestures/README.md +1 -7
  166. package/src/hooks/actionGroupTracker.ts +5 -20
  167. package/src/hooks/index.ts +0 -2
  168. package/src/hooks/useAttachment.ts +1 -1
  169. package/src/hooks/useInputStatusIcon.tsx +2 -3
  170. package/src/hooks/useLongPress.doc.mjs +2 -2
  171. package/src/hooks/useLongPress.ts +15 -136
  172. package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +0 -6
  173. package/src/i18n/generated-locales/en.generated.ts +0 -41
  174. package/src/i18n/generated-locales/pseudo.generated.ts +0 -42
  175. package/src/index.ts +0 -13
  176. package/src/motion/README.md +5 -9
  177. package/src/motion/animatedLayout.ts +4 -5
  178. package/src/motion/animator.ts +29 -61
  179. package/src/utils/dateParser.ts +0 -16
  180. package/src/utils/focusReturn.ts +0 -30
  181. package/src/utils/interactionOverlay.stylex.ts +2 -3
  182. package/src/utils/pressFeedback.ts +6 -19
  183. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +0 -100
  184. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +0 -1
  185. package/dist/PowerSearch/PowerSearchBottomSheet.js +0 -1913
  186. package/dist/PowerSearch/formatTokenValue.d.ts +0 -28
  187. package/dist/PowerSearch/formatTokenValue.d.ts.map +0 -1
  188. package/dist/PowerSearch/formatTokenValue.js +0 -160
  189. package/dist/PowerSearch/powerSearchSheetUtils.d.ts +0 -65
  190. package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +0 -1
  191. package/dist/PowerSearch/powerSearchSheetUtils.js +0 -168
  192. package/dist/animator/createAnimator.d.ts +0 -98
  193. package/dist/animator/createAnimator.d.ts.map +0 -1
  194. package/dist/animator/createAnimator.js +0 -259
  195. package/dist/hooks/pressGroupContext.d.ts +0 -6
  196. package/dist/hooks/pressGroupContext.d.ts.map +0 -1
  197. package/dist/hooks/pressGroupContext.js +0 -20
  198. package/dist/hooks/useActionGroupTracking.d.ts +0 -53
  199. package/dist/hooks/useActionGroupTracking.d.ts.map +0 -1
  200. package/dist/hooks/useActionGroupTracking.js +0 -174
  201. package/dist/hooks/useAnimator.d.ts +0 -41
  202. package/dist/hooks/useAnimator.d.ts.map +0 -1
  203. package/dist/hooks/useAnimator.js +0 -70
  204. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +0 -8
  205. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +0 -1
  206. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +0 -42
  207. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +0 -8
  208. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +0 -1
  209. package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +0 -14
  210. package/dist/internal/indicatorPress.stylex.d.ts +0 -20
  211. package/dist/internal/indicatorPress.stylex.d.ts.map +0 -1
  212. package/dist/internal/indicatorPress.stylex.js +0 -26
  213. package/dist/motion/progress.d.ts +0 -88
  214. package/dist/motion/progress.d.ts.map +0 -1
  215. package/dist/motion/progress.js +0 -128
  216. package/dist/motion/uikit/alert.d.ts +0 -41
  217. package/dist/motion/uikit/alert.d.ts.map +0 -1
  218. package/dist/motion/uikit/alert.js +0 -43
  219. package/dist/privateAttributes/pressGroupItem.stylex.d.ts +0 -15
  220. package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +0 -1
  221. package/dist/privateAttributes/pressGroupItem.stylex.js +0 -23
  222. package/dist/utils/triggerFocus.d.ts +0 -36
  223. package/dist/utils/triggerFocus.d.ts.map +0 -1
  224. package/dist/utils/triggerFocus.js +0 -65
  225. package/src/PowerSearch/PowerSearchBottomSheet.tsx +0 -2515
  226. package/src/PowerSearch/formatTokenValue.ts +0 -215
  227. package/src/PowerSearch/powerSearchSheetUtils.ts +0 -224
  228. package/src/animator/createAnimator.doc.mjs +0 -153
  229. package/src/animator/createAnimator.ts +0 -324
  230. package/src/hooks/pressGroupContext.ts +0 -21
  231. package/src/hooks/useActionGroupTracking.ts +0 -224
  232. package/src/hooks/useAnimator.doc.mjs +0 -109
  233. package/src/hooks/useAnimator.ts +0 -67
  234. package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +0 -43
  235. package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +0 -15
  236. package/src/internal/indicatorPress.stylex.ts +0 -24
  237. package/src/motion/progress.ts +0 -155
  238. package/src/motion/uikit/alert.ts +0 -43
  239. package/src/privateAttributes/pressGroupItem.stylex.ts +0 -23
  240. package/src/utils/triggerFocus.ts +0 -80
@@ -46,7 +46,6 @@ import {useTooltip} from '../Tooltip/useTooltip';
46
46
  import {mergeProps} from '../utils/mergeProps';
47
47
  import {rtlStyles} from '../utils/rtlStyles';
48
48
  import {indicatorScope} from '../Indicator/indicator.markers.stylex';
49
- import {indicatorPressScope} from '../internal/indicatorPress.stylex';
50
49
  import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
51
50
  import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
52
51
  import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
@@ -64,11 +63,6 @@ import english from '../i18n/generated-english/CheckboxInput/CheckboxInput.gener
64
63
 
65
64
  registerEnglishFallback(english);
66
65
 
67
- /** Written by the touch press controller while the row is pressed. */
68
- const PRESSED_ON = '[data-astryx-press="on"]';
69
- /** Written by the touch press controller for the press's release. */
70
- const PRESSED_FADING = '[data-astryx-press="fading"]';
71
-
72
66
  // The touch press's paint, declared by the shared overlay styles on the
73
67
  // element the controller writes to (`pressedAlpha`) at the press's strength,
74
68
  // 1 while on and 1 → 0 over the release, and inherited resolved by the layer
@@ -84,20 +78,6 @@ const styles = stylex.create({
84
78
  containerLabelHidden: {
85
79
  gap: 0,
86
80
  },
87
- // An indicator in another package may not name this row's private press
88
- // attribute. So the row hands its touch press image down to an indicator
89
- // that paints its own press, but only in its own press states. A pressed
90
- // ancestor row reaches it no more than it reaches the built-in box. The
91
- // property has no default: a layered stylesheet ranks a default above the
92
- // arms. Only this row declares it. A menu item paints its own press, so a
93
- // mapped indicator there paints none.
94
- indicatorPressImage: {
95
- '--_indicator-press-image': {
96
- default: null,
97
- [PRESSED_ON]: 'var(--_press-paint-image)',
98
- [PRESSED_FADING]: 'var(--_press-paint-image)',
99
- },
100
- },
101
81
  checkboxWrapper: {
102
82
  position: 'relative',
103
83
  display: 'flex',
@@ -106,22 +86,15 @@ const styles = stylex.create({
106
86
  flexShrink: 0,
107
87
  isolation: 'isolate',
108
88
  },
109
- // The owner paints this layer over the resolved indicator, so a plain theme
110
- // replacement keeps the pressed feedback. An indicator with
111
- // `indicatorPressScope` paints its own press from what the row hands down,
112
- // so this layer stays off over it.
89
+ // The owner paints this layer over the resolved indicator, so a theme
90
+ // replacement cannot accidentally drop the component's pressed contract.
113
91
  // Two pointers, two press models (see interactionOverlay.stylex.ts): a
114
92
  // mouse keeps `:active`; under a coarse pointer that arm is dropped and the
115
93
  // touch press controller writes `data-astryx-press` on the row, which this layer
116
94
  // reads off the same scope marker as the hover tint.
117
95
  indicatorPressOverlay: {
118
96
  '::after': {
119
- // `when.descendant` takes a pseudo-class; `:is(*)` matches any marked
120
- // indicator.
121
- content: {
122
- default: '""',
123
- [stylex.when.descendant(':is(*)', indicatorPressScope)]: 'none',
124
- },
97
+ content: '""',
125
98
  position: 'absolute',
126
99
  inset: 0,
127
100
  borderRadius: radiusVars['--radius-inner'],
@@ -139,14 +112,15 @@ const styles = stylex.create({
139
112
  // what moves. Coarse pointers only, like the drop above.
140
113
  backgroundImage: {
141
114
  default: null,
142
- [stylex.when.ancestor(PRESSED_ON, indicatorScope)]: {
143
- default: null,
144
- '@media (pointer: coarse)': pressedOverlayImage,
145
- },
146
- [stylex.when.ancestor(PRESSED_FADING, indicatorScope)]: {
115
+ [stylex.when.ancestor('[data-astryx-press="on"]', indicatorScope)]: {
147
116
  default: null,
148
117
  '@media (pointer: coarse)': pressedOverlayImage,
149
118
  },
119
+ [stylex.when.ancestor('[data-astryx-press="fading"]', indicatorScope)]:
120
+ {
121
+ default: null,
122
+ '@media (pointer: coarse)': pressedOverlayImage,
123
+ },
150
124
  },
151
125
  },
152
126
  },
@@ -476,7 +450,6 @@ export function CheckboxInput({
476
450
  // Hover and focus reach the checkbox visual through this ancestor
477
451
  // marker rather than props, so the whole row drives it.
478
452
  !isDisabled && indicatorScope,
479
- !isDisabled && styles.indicatorPressImage,
480
453
  // The touch press's strength and release live on the row the
481
454
  // controller writes to; the layer over the checkbox box inherits
482
455
  // and paints it.
@@ -35,7 +35,6 @@ import {
35
35
  } from '../Field/fieldParts';
36
36
  import {useDevWarning} from '../hooks/useDevWarning';
37
37
  import {mergeRefs} from '../utils/mergeRefs';
38
- import {isFocusLost} from '../utils/focusReturn';
39
38
  import {useMergedRefs} from '../hooks/useMergedRefs';
40
39
  import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
41
40
  import {Field, inputWrapperStyles} from '../Field';
@@ -348,10 +347,7 @@ export interface ComplexSelectorRenderState {
348
347
  export interface ComplexSelectorHandle {
349
348
  /** Open the selector surface. No-op when disabled or already open. */
350
349
  open(): void;
351
- /**
352
- * Close the selector surface. Focus returns to the trigger only when it
353
- * would otherwise be lost; a caller-rendered trigger always gets it back.
354
- */
350
+ /** Close the selector surface. Restores focus to the trigger. */
355
351
  close(): void;
356
352
  /** Toggle the selector surface open or closed. */
357
353
  toggle(): void;
@@ -634,22 +630,12 @@ export function ComplexSelector<Value>({
634
630
  onOpenChange?.(true);
635
631
  }, [onOpenChange]);
636
632
 
637
- // The popup's content element, read when it closes.
638
- const popupContentRef = useRef<HTMLElement | null>(null);
639
- const hasCallerTrigger = renderTrigger != null;
640
633
  const handlePopoverHide = useCallback(() => {
641
- const trigger = triggerRef.current;
642
- // A press that moved focus to another control keeps it there. A caller's
643
- // control always gets focus back. Focus moves before the callback, so a
644
- // consumer that moves it from the callback wins.
645
- if (
646
- hasCallerTrigger ||
647
- isFocusLost(popupContentRef.current, trigger?.ownerDocument)
648
- ) {
649
- trigger?.focus();
650
- }
634
+ // Focus is restored first so a consumer that moves focus elsewhere from
635
+ // the callback wins, instead of being overwritten a line later.
636
+ triggerRef.current?.focus();
651
637
  onOpenChange?.(false);
652
- }, [hasCallerTrigger, onOpenChange]);
638
+ }, [onOpenChange]);
653
639
 
654
640
  const popover = usePopover({
655
641
  dialogLabel: label,
@@ -659,7 +645,6 @@ export function ComplexSelector<Value>({
659
645
  onShow: handlePopoverShow,
660
646
  onHide: handlePopoverHide,
661
647
  });
662
- popupContentRef.current = popover.contentRef.current;
663
648
 
664
649
  const isOpen = popover.isOpen;
665
650
 
@@ -712,14 +712,14 @@ export function ContextMenu({
712
712
  );
713
713
  const handleTriggerTouchEnd = useCallback(
714
714
  (event: React.TouchEvent<HTMLElement>) => {
715
- handleLongPressTouchEnd(event);
715
+ handleLongPressTouchEnd();
716
716
  onTouchEndProp?.(event);
717
717
  },
718
718
  [handleLongPressTouchEnd, onTouchEndProp],
719
719
  );
720
720
  const handleTriggerTouchCancel = useCallback(
721
721
  (event: React.TouchEvent<HTMLElement>) => {
722
- handleLongPressTouchCancel(event);
722
+ handleLongPressTouchCancel();
723
723
  onTouchCancelProp?.(event);
724
724
  },
725
725
  [handleLongPressTouchCancel, onTouchCancelProp],
@@ -81,10 +81,7 @@ import {themeProps} from '../utils/themeProps';
81
81
  import type {DialogVariantMap} from './index';
82
82
  import {focusOutlineProps} from '../utils/focusOutline.stylex';
83
83
  import {useInteractionModalityTracking} from '../utils/interactionModality';
84
- import {
85
- focusForLastInput,
86
- prepareFocusReturnOnRemoval,
87
- } from '../utils/focusReturn';
84
+ import {focusForLastInput, isFocusLost} from '../utils/focusReturn';
88
85
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
89
86
 
90
87
  import {useMergedRefs} from '../hooks/useMergedRefs';
@@ -656,15 +653,39 @@ export function Dialog({
656
653
  }, [isOpen, isInline]);
657
654
 
658
655
  // A dialog that unmounts while open (rendered only while open, or removed
659
- // with the rest of a view) never runs the close branch above. Focus the
660
- // removal lost goes back to the opener.
656
+ // with the rest of a view) never runs the close branch above, and removing a
657
+ // <dialog> does not run the browser's own focus return. Focus left inside
658
+ // it, or dropped to the body by the removal, goes back to the opener.
661
659
  useEffect(() => {
662
- const dialog = dialogRef.current;
663
- if (isInline || dialog == null) {
660
+ if (isInline) {
664
661
  return;
665
662
  }
666
- const returnFocusOnRemoval = prepareFocusReturnOnRemoval(dialog);
667
- return () => returnFocusOnRemoval(triggerElementRef.current);
663
+ const dialog = dialogRef.current;
664
+ // Read now: a removed iframe's document has no window, so its frame
665
+ // element can no longer be found once the dialog is gone.
666
+ const host = dialog?.ownerDocument.defaultView?.frameElement ?? null;
667
+ return () => {
668
+ const trigger = triggerElementRef.current;
669
+ if (dialog == null || trigger == null) {
670
+ return;
671
+ }
672
+ // Checked once the commit has finished: StrictMode's simulated unmount
673
+ // leaves the dialog in the document, and only a real removal returns
674
+ // focus.
675
+ queueMicrotask(() => {
676
+ if (dialog.isConnected || !trigger.isConnected) {
677
+ return;
678
+ }
679
+ // isFocusLost reads the dialog's own document. For a dialog in an
680
+ // iframe, the document that hosts the frame must also have lost focus
681
+ // or keep it on that frame, wherever the opener is: a control the
682
+ // user pressed outside the frame to remove the dialog, or the frame
683
+ // itself, keeps its focus.
684
+ if (isFocusLost(dialog) && (host == null || isFocusLost(host))) {
685
+ focusForLastInput(trigger);
686
+ }
687
+ });
688
+ };
668
689
  }, [isInline]);
669
690
 
670
691
  // Host app-global surfaces (toasts) inside this modal while it is open, so
@@ -25,11 +25,11 @@ import {FieldStatus} from '../FieldStatus/FieldStatus';
25
25
  import {useFieldStatusAnnouncement} from '../FieldStatus/useFieldStatusAnnouncement';
26
26
  import {spacingVars, borderVars, sizeVars} from '../theme/tokens.stylex';
27
27
  import type {IconType} from '../Icon';
28
- import {mergeProps} from '../utils/mergeProps';
28
+ import {mergeProps} from '../utils';
29
29
  import {useDevWarning} from '../hooks/useDevWarning';
30
30
  import {FormLayoutContext} from '../FormLayout/FormLayoutContext';
31
31
  import {Text} from '../Text/Text';
32
- import {VisuallyHidden} from '../VisuallyHidden/VisuallyHidden';
32
+ import {VisuallyHidden} from '../VisuallyHidden';
33
33
  import {themeProps} from '../utils/themeProps';
34
34
  import type {FieldStatusPlacement} from './fieldStatusPlacement';
35
35
 
@@ -98,8 +98,7 @@ export const docs = {
98
98
  {
99
99
  name: 'focusTrigger',
100
100
  type: "'auto' | 'always' | 'never'",
101
- description:
102
- 'When focus should open the hover card. auto opens when the trigger itself takes focus, or focus lands inside a trigger that is focusable at that moment, so a roving-tabindex member opens when the arrow keys reach it.',
101
+ description: 'Controls when focus events trigger the hover card.',
103
102
  default: "'auto'",
104
103
  },
105
104
  {
@@ -304,8 +303,7 @@ export const docsZh = {
304
303
  {
305
304
  name: 'focusTrigger',
306
305
  type: "'auto' | 'always' | 'never'",
307
- description:
308
- '焦点何时打开悬浮卡片。auto 在触发元素自身获得焦点,或焦点落在当时可聚焦的触发元素内部时打开,因此方向键到达漫游 tabindex 成员时也会打开。',
306
+ description: '控制焦点事件何时触发悬浮卡片。',
309
307
  default: "'auto'",
310
308
  },
311
309
  {
@@ -16,14 +16,7 @@
16
16
  * - /packages/cli/assets/templates/blocks/components/HoverCard/ (showcase blocks)
17
17
  */
18
18
 
19
- import {
20
- useCallback,
21
- useRef,
22
- useState,
23
- type ReactElement,
24
- type ReactNode,
25
- } from 'react';
26
- import {observeMutations} from '../utils/sharedMutationObserver';
19
+ import {useCallback, useRef, type ReactElement, type ReactNode} from 'react';
27
20
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
28
21
  import * as stylex from '@stylexjs/stylex';
29
22
  import {
@@ -155,10 +148,9 @@ export interface HoverCardProps extends Pick<
155
148
 
156
149
  /**
157
150
  * When to trigger on focus:
158
- * - `auto`: focus on the trigger itself, or within a trigger that is
159
- * focusable when focus arrives
160
- * - `always`: focus on the trigger or anything inside it
161
- * - `never`: never on focus (for composite widgets)
151
+ * - `auto`: Only if element is naturally focusable
152
+ * - `always`: Always attach focus listeners
153
+ * - `never`: Never attach focus listeners (for composite widgets)
162
154
  *
163
155
  * @default 'auto'
164
156
  */
@@ -311,28 +303,6 @@ export function HoverCard({
311
303
  onHide: handleHide,
312
304
  });
313
305
 
314
- // The wrapper's first element is the trigger, and it can be replaced after
315
- // mount: by new children, or by a child component that swaps its own root
316
- // without a render here. Syncs with the DOM: on a replacement the wiring
317
- // below re-runs, so the old element is released before the new one is wired.
318
- const [triggerReplacements, setTriggerReplacements] = useState(0);
319
- const observedTriggerRef = useRef<Element | null>(null);
320
- useIsomorphicLayoutEffect(() => {
321
- const wrapper = wrapperRef.current;
322
- if (textOnly || wrapper == null) {
323
- return;
324
- }
325
- observedTriggerRef.current = wrapper.firstElementChild;
326
- return observeMutations(wrapper, {childList: true}, () => {
327
- const trigger = wrapper.firstElementChild;
328
- if (trigger === observedTriggerRef.current) {
329
- return;
330
- }
331
- observedTriggerRef.current = trigger;
332
- setTriggerReplacements(count => count + 1);
333
- });
334
- }, [textOnly]);
335
-
336
306
  // For element children with display:contents, attach ref to first child
337
307
  useIsomorphicLayoutEffect(() => {
338
308
  if (textOnly) {
@@ -437,7 +407,6 @@ export function HoverCard({
437
407
  hoverCard.id,
438
408
  hoverCard.isOpen,
439
409
  hoverCard.describedBy,
440
- triggerReplacements,
441
410
  ]);
442
411
 
443
412
  // While closed, useLayer leaves only an inert <template> marker at this JSX
@@ -5,159 +5,37 @@ export const docs = {
5
5
  name: 'useHoverCard',
6
6
  displayName: 'useHoverCard',
7
7
  group: 'HoverCard',
8
- keywords: [
9
- 'hovercard',
10
- 'hover',
11
- 'preview',
12
- 'card',
13
- 'tooltip',
14
- 'popup',
15
- 'floating',
16
- 'anchor',
17
- ],
8
+ keywords: ['hovercard', 'hover', 'preview', 'card', 'tooltip', 'popup', 'floating', 'anchor'],
18
9
  params: [
19
- {
20
- name: 'placement',
21
- type: "'above' | 'below' | 'start' | 'end'",
22
- description:
23
- "Position relative to the trigger. Logical: start/end resolve against the popover\'s own inherited direction (RTL mirrors in pure CSS).",
24
- default: "'above'",
25
- },
26
- {
27
- name: 'alignment',
28
- type: "'start' | 'center' | 'end'",
29
- description:
30
- "Alignment along the placement axis. Logical: start/end resolve against the popover\'s own inherited direction (RTL mirrors in pure CSS).",
31
- default: "'center'",
32
- },
33
- {
34
- name: 'delay',
35
- type: 'number',
36
- description:
37
- 'Delay before showing the hover card on hover, in milliseconds.',
38
- default: '300',
39
- },
40
- {
41
- name: 'hideDelay',
42
- type: 'number',
43
- description:
44
- 'Delay before hiding after mouse or focus leaves, in milliseconds.',
45
- default: '200',
46
- },
47
- {
48
- name: 'focusTrigger',
49
- type: "'auto' | 'always' | 'never'",
50
- description:
51
- 'When focus should open the hover card. auto opens when the trigger itself takes focus, or focus lands inside a trigger that is focusable at that moment, so a roving-tabindex member opens when the arrow keys reach it.',
52
- default: "'auto'",
53
- },
54
- {
55
- name: 'touchTrigger',
56
- type: "'auto' | 'tap' | 'none'",
57
- description:
58
- 'What a tap does where there is no hover. auto opens on tap unless the trigger performs an action of its own (an aria-disabled control performs none); tap always opens; none never opens on touch.',
59
- default: "'auto'",
60
- },
61
- {
62
- name: 'isEnabled',
63
- type: 'boolean',
64
- description: 'Whether hover and focus triggers are enabled.',
65
- default: 'true',
66
- },
67
- {
68
- name: 'label',
69
- type: 'string',
70
- description:
71
- 'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).',
72
- },
73
- {
74
- name: 'isOpen',
75
- type: 'boolean',
76
- description:
77
- 'Controlled open state. true force-shows, false force-hides, undefined lets hover/focus manage visibility.',
78
- },
79
- {
80
- name: 'isDefaultOpen',
81
- type: 'boolean',
82
- description: 'Whether the hover card should be shown on mount.',
83
- default: 'false',
84
- },
85
- {
86
- name: 'onShow',
87
- type: '() => void',
88
- description: 'Callback fired when the hover card becomes visible.',
89
- },
90
- {
91
- name: 'onHide',
92
- type: '() => void',
93
- description: 'Callback fired when the hover card is hidden.',
94
- },
10
+ {name: 'placement', type: "'above' | 'below' | 'start' | 'end'", description: "Position relative to the trigger. Logical: start/end resolve against the popover\'s own inherited direction (RTL mirrors in pure CSS).", default: "'above'"},
11
+ {name: 'alignment', type: "'start' | 'center' | 'end'", description: "Alignment along the placement axis. Logical: start/end resolve against the popover\'s own inherited direction (RTL mirrors in pure CSS).", default: "'center'"},
12
+ {name: 'delay', type: 'number', description: 'Delay before showing the hover card on hover, in milliseconds.', default: '300'},
13
+ {name: 'hideDelay', type: 'number', description: 'Delay before hiding after mouse or focus leaves, in milliseconds.', default: '200'},
14
+ {name: 'focusTrigger', type: "'auto' | 'always' | 'never'", description: 'When focus should open the hover card. auto only attaches focus listeners to naturally focusable elements.', default: "'auto'"},
15
+ {name: 'touchTrigger', type: "'auto' | 'tap' | 'none'", description: 'What a tap does where there is no hover. auto opens on tap unless the trigger performs an action of its own (an aria-disabled control performs none); tap always opens; none never opens on touch.', default: "'auto'"},
16
+ {name: 'isEnabled', type: 'boolean', description: 'Whether hover and focus triggers are enabled.', default: 'true'},
17
+ {name: 'label', type: 'string', description: 'Accessible name for the hover card popup. When provided, the popup is exposed as a named role="dialog"; when omitted, it falls back to role="group" (a group may validly be unnamed).'},
18
+ {name: 'isOpen', type: 'boolean', description: 'Controlled open state. true force-shows, false force-hides, undefined lets hover/focus manage visibility.'},
19
+ {name: 'isDefaultOpen', type: 'boolean', description: 'Whether the hover card should be shown on mount.', default: 'false'},
20
+ {name: 'onShow', type: '() => void', description: 'Callback fired when the hover card becomes visible.'},
21
+ {name: 'onHide', type: '() => void', description: 'Callback fired when the hover card is hidden.'},
95
22
  ],
96
23
  returns: [
97
- {
98
- name: 'ref',
99
- type: 'RefCallback<HTMLElement>',
100
- description:
101
- 'Combined ref that sets both position and interaction on the same trigger element.',
102
- },
103
- {
104
- name: 'positionRef',
105
- type: 'RefCallback<HTMLElement>',
106
- description:
107
- 'Ref for the positioning anchor element. Use when position and interaction live on different elements.',
108
- },
109
- {
110
- name: 'interactionRef',
111
- type: 'RefCallback<HTMLElement>',
112
- description:
113
- 'Ref for the hover/focus interaction element. Use with positionRef for split trigger patterns.',
114
- },
115
- {
116
- name: 'anchorId',
117
- type: 'string',
118
- description: 'CSS anchor name for advanced positioning cases.',
119
- },
120
- {
121
- name: 'describedBy',
122
- type: 'string',
123
- description: 'ID to compose into aria-describedby on the trigger.',
124
- },
125
- {
126
- name: 'renderHoverCard',
127
- type: "(children: ReactNode, props?: Omit<ContextRenderProps, 'positioning'>) => ReactNode",
128
- description:
129
- 'Render function for the anchor-positioned hover card content. The positioning opt-out is excluded: the hover card always derives its position from placement/alignment.',
130
- },
131
- {
132
- name: 'show',
133
- type: '() => void',
134
- description: 'Imperatively show the hover card immediately.',
135
- },
136
- {
137
- name: 'hide',
138
- type: '() => void',
139
- description: 'Imperatively hide the hover card immediately.',
140
- },
24
+ {name: 'ref', type: 'RefCallback<HTMLElement>', description: 'Combined ref that sets both position and interaction on the same trigger element.'},
25
+ {name: 'positionRef', type: 'RefCallback<HTMLElement>', description: 'Ref for the positioning anchor element. Use when position and interaction live on different elements.'},
26
+ {name: 'interactionRef', type: 'RefCallback<HTMLElement>', description: 'Ref for the hover/focus interaction element. Use with positionRef for split trigger patterns.'},
27
+ {name: 'anchorId', type: 'string', description: 'CSS anchor name for advanced positioning cases.'},
28
+ {name: 'describedBy', type: 'string', description: 'ID to compose into aria-describedby on the trigger.'},
29
+ {name: 'renderHoverCard', type: "(children: ReactNode, props?: Omit<ContextRenderProps, 'positioning'>) => ReactNode", description: 'Render function for the anchor-positioned hover card content. The positioning opt-out is excluded: the hover card always derives its position from placement/alignment.'},
30
+ {name: 'show', type: '() => void', description: 'Imperatively show the hover card immediately.'},
31
+ {name: 'hide', type: '() => void', description: 'Imperatively hide the hover card immediately.'},
141
32
  ],
142
33
  usage: {
143
- description:
144
- 'Headless hook for hover-triggered floating cards. Builds on useLayer with hover/focus intent detection, configurable delays, safe hover behavior, and accessible aria-describedby linking. Use for rich previews on hover when you need full control over the trigger or rendered content.',
34
+ description: 'Headless hook for hover-triggered floating cards. Builds on useLayer with hover/focus intent detection, configurable delays, safe hover behavior, and accessible aria-describedby linking. Use for rich previews on hover when you need full control over the trigger or rendered content.',
145
35
  bestPractices: [
146
- {
147
- guidance: true,
148
- description:
149
- 'Use for rich content previews such as user profiles, entity summaries, and link previews.',
150
- },
151
- {
152
- guidance: true,
153
- description:
154
- 'Prefer the HoverCard component for standard trigger-content pairs; use the hook for custom trigger patterns.',
155
- },
156
- {
157
- guidance: false,
158
- description:
159
- 'Use for simple text hints: use Tooltip or useTooltip instead.',
160
- },
36
+ {guidance: true, description: 'Use for rich content previews such as user profiles, entity summaries, and link previews.'},
37
+ {guidance: true, description: 'Prefer the HoverCard component for standard trigger-content pairs; use the hook for custom trigger patterns.'},
38
+ {guidance: false, description: 'Use for simple text hints: use Tooltip or useTooltip instead.'},
161
39
  ],
162
40
  },
163
41
  relatedComponents: ['HoverCard', 'Tooltip', 'Popover'],
@@ -168,24 +46,17 @@ export const docs = {
168
46
 
169
47
  /** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
170
48
  export const docsDense = {
171
- description:
172
- 'Headless hover-triggered floating cards. Builds on useLayer w/ hover/focus intent, delays, safe hover behavior, aria-describedby. Use for rich previews when trigger/content rendering needs full control.',
49
+ description: 'Headless hover-triggered floating cards. Builds on useLayer w/ hover/focus intent, delays, safe hover behavior, aria-describedby. Use for rich previews when trigger/content rendering needs full control.',
173
50
  paramDescriptions: {
174
- placement:
175
- "position relative to trigger. logical: start/end follow the popover's inherited direction (RTL mirrors).",
176
- alignment:
177
- "alignment along placement axis. logical: start/end follow the popover's inherited direction (RTL mirrors).",
51
+ placement: 'position relative to trigger. logical: start/end follow the popover\'s inherited direction (RTL mirrors).',
52
+ alignment: 'alignment along placement axis. logical: start/end follow the popover\'s inherited direction (RTL mirrors).',
178
53
  delay: 'show delay in ms.',
179
54
  hideDelay: 'hide delay after mouse/focus leave in ms.',
180
- focusTrigger:
181
- 'when focus opens hover card; auto = focus on the trigger, or inside a trigger focusable when focus arrives.',
182
- touchTrigger:
183
- 'tap behavior with no hover; auto = tap unless trigger acts, tap = always, none = never.',
55
+ focusTrigger: 'when focus opens hover card; auto = naturally focusable elements only.',
56
+ touchTrigger: 'tap behavior with no hover; auto = tap unless trigger acts, tap = always, none = never.',
184
57
  isEnabled: 'whether hover/focus triggers are enabled.',
185
- label:
186
- 'accessible name for popup. with label: named role="dialog"; without: role="group".',
187
- isOpen:
188
- 'controlled open state: true force-show, false force-hide, undefined unmanaged.',
58
+ label: 'accessible name for popup. with label: named role="dialog"; without: role="group".',
59
+ isOpen: 'controlled open state: true force-show, false force-hide, undefined unmanaged.',
189
60
  isDefaultOpen: 'show on mount.',
190
61
  onShow: 'fires when hover card becomes visible.',
191
62
  onHide: 'fires when hover card hides.',
@@ -201,24 +72,11 @@ export const docsDense = {
201
72
  hide: 'hide immediately.',
202
73
  },
203
74
  usage: {
204
- description:
205
- 'Headless hover-triggered floating cards. Builds on useLayer w/ hover/focus intent, delays, safe hover behavior, aria-describedby. Use for rich previews when trigger/content rendering needs full control.',
75
+ description: 'Headless hover-triggered floating cards. Builds on useLayer w/ hover/focus intent, delays, safe hover behavior, aria-describedby. Use for rich previews when trigger/content rendering needs full control.',
206
76
  bestPractices: [
207
- {
208
- guidance: true,
209
- description:
210
- 'Use for rich previews: profiles, entity summaries, link previews.',
211
- },
212
- {
213
- guidance: true,
214
- description:
215
- 'Prefer HoverCard for standard trigger-content pairs; use hook for custom trigger patterns.',
216
- },
217
- {
218
- guidance: false,
219
- description:
220
- 'Use for simple text hints: use Tooltip / useTooltip instead.',
221
- },
77
+ {guidance: true, description: 'Use for rich previews: profiles, entity summaries, link previews.'},
78
+ {guidance: true, description: 'Prefer HoverCard for standard trigger-content pairs; use hook for custom trigger patterns.'},
79
+ {guidance: false, description: 'Use for simple text hints: use Tooltip / useTooltip instead.'},
222
80
  ],
223
81
  },
224
82
  };