@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
@@ -16,11 +16,9 @@ import {
16
16
  useCallback,
17
17
  useEffect,
18
18
  useRef,
19
- useState,
20
19
  type ReactNode,
21
20
  type RefCallback,
22
21
  } from 'react';
23
- import {createTriggerFocusGate} from '../utils/triggerFocus';
24
22
  import * as stylex from '@stylexjs/stylex';
25
23
  import {
26
24
  useLayer,
@@ -62,7 +60,7 @@ const styles = stylex.create({
62
60
  });
63
61
 
64
62
  /**
65
- * When focus on the trigger opens the hover card
63
+ * Focus trigger behavior for hover cards
66
64
  */
67
65
  export type HoverCardFocusTrigger = 'auto' | 'always' | 'never';
68
66
 
@@ -98,10 +96,9 @@ export interface HoverCardOptions {
98
96
 
99
97
  /**
100
98
  * When to trigger on focus:
101
- * - `auto`: focus on the trigger itself, or within a trigger that is
102
- * focusable when focus arrives
103
- * - `always`: focus on the trigger or anything inside it
104
- * - `never`: never on focus (for composite widgets)
99
+ * - `auto`: Only if element is naturally focusable
100
+ * - `always`: Always attach focus listeners
101
+ * - `never`: Never attach focus listeners (for composite widgets)
105
102
  *
106
103
  * @default 'auto'
107
104
  */
@@ -238,6 +235,29 @@ export interface HoverCardReturn {
238
235
  hide: () => void;
239
236
  }
240
237
 
238
+ /**
239
+ * Check if an element is naturally focusable
240
+ */
241
+ function isFocusable(element: HTMLElement): boolean {
242
+ // Elements with explicit tabindex
243
+ if (element.hasAttribute('tabindex')) {
244
+ return element.tabIndex >= 0;
245
+ }
246
+
247
+ // Naturally focusable elements
248
+ const focusableTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
249
+ if (focusableTags.includes(element.tagName)) {
250
+ return !(element as HTMLButtonElement).disabled;
251
+ }
252
+
253
+ // Elements with contenteditable
254
+ if (element.isContentEditable) {
255
+ return true;
256
+ }
257
+
258
+ return false;
259
+ }
260
+
241
261
  /**
242
262
  * Hook for hover card behavior with hover/focus triggers.
243
263
  *
@@ -406,44 +426,27 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
406
426
  [handleTouchPointerDown],
407
427
  );
408
428
 
409
- // Under `auto`, whether focus counts is decided when it arrives, and a
410
- // focus that counted still hides on blur (utils/triggerFocus.ts).
411
- const [focusGate] = useState(createTriggerFocusGate);
412
-
413
- const handleFocusIn = useCallback(
414
- (e: Event) => {
415
- if (!focusGate.focusIn(e, focusTrigger) || !isEnabled) {
416
- return;
417
- }
418
- // A tap focuses the trigger it activates. Opening on that focus would
419
- // reinstate exactly the behavior the touch path just decided against —
420
- // and on an action trigger it covers the thing the user tapped.
421
- if (isTouchInteraction()) {
422
- return;
423
- }
424
- // Skip showing if we're in the middle of an Escape dismiss
425
- if (isEscapeDismissingRef.current) {
426
- isEscapeDismissingRef.current = false;
427
- return;
428
- }
429
- clearTimeouts();
430
- showLayer();
431
- },
432
- [
433
- focusGate,
434
- focusTrigger,
435
- isEnabled,
436
- isTouchInteraction,
437
- clearTimeouts,
438
- showLayer,
439
- ],
440
- );
429
+ const handleFocusIn = useCallback(() => {
430
+ if (!isEnabled) {
431
+ return;
432
+ }
433
+ // A tap focuses the trigger it activates. Opening on that focus would
434
+ // reinstate exactly the behavior the touch path just decided against —
435
+ // and on an action trigger it covers the thing the user tapped.
436
+ if (isTouchInteraction()) {
437
+ return;
438
+ }
439
+ // Skip showing if we're in the middle of an Escape dismiss
440
+ if (isEscapeDismissingRef.current) {
441
+ isEscapeDismissingRef.current = false;
442
+ return;
443
+ }
444
+ clearTimeouts();
445
+ showLayer();
446
+ }, [isEnabled, isTouchInteraction, clearTimeouts, showLayer]);
441
447
 
442
448
  const handleFocusOut = useCallback(
443
449
  (e: FocusEvent) => {
444
- if (!focusGate.focusOut(e, focusTrigger)) {
445
- return;
446
- }
447
450
  // Check if focus is moving to the hover card content
448
451
  const relatedTarget = e.relatedTarget as HTMLElement | null;
449
452
  const popoverElement = document.getElementById(layerId);
@@ -455,7 +458,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
455
458
 
456
459
  scheduleHide();
457
460
  },
458
- [focusGate, focusTrigger, layerId, scheduleHide],
461
+ [layerId, scheduleHide],
459
462
  );
460
463
 
461
464
  // Escape dismissal (WCAG 1.4.13) goes through the shared layer stack: a
@@ -555,9 +558,12 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
555
558
  el.addEventListener('mouseleave', handleMouseLeave);
556
559
  el.addEventListener('pointerdown', handlePointerDown);
557
560
 
558
- // Focus listeners unless focus never opens the card; `auto` decides
559
- // per event (utils/triggerFocus.ts).
560
- if (focusTrigger !== 'never') {
561
+ // Attach focus listeners based on focusTrigger option
562
+ const shouldAttachFocus =
563
+ focusTrigger === 'always' ||
564
+ (focusTrigger === 'auto' && isFocusable(el));
565
+
566
+ if (shouldAttachFocus) {
561
567
  el.addEventListener('focusin', handleFocusIn);
562
568
  el.addEventListener('focusout', handleFocusOut as EventListener);
563
569
  }
@@ -71,16 +71,15 @@ export const docs = {
71
71
  props: [
72
72
  {
73
73
  name: 'icon',
74
- type: 'IconName | NamespacedIconName | ComponentType<SVGProps>',
74
+ type: 'IconName | ComponentType<SVGProps>',
75
75
  description:
76
- 'Semantic icon name or SVG component. Valid semantic names: close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. A namespaced key such as `richtext:bold` names a glyph that one component or library registers, and a theme can replace it by key; a namespaced key that nothing registers renders nothing. For any icon not in this list, pass an SVG component directly (e.g. import from lucide-react or @heroicons/react). Note: this prop is called `icon`, not `name`.',
76
+ 'Semantic icon name or SVG component. Valid semantic names: close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. For any icon not in this list, pass an SVG component directly (e.g. import from lucide-react or @heroicons/react). Note: this prop is called `icon`, not `name`.',
77
77
  required: true,
78
78
  },
79
79
  {
80
80
  name: 'color',
81
81
  type: "'primary' | 'secondary' | 'tertiary' | 'disabled' | 'accent' | 'info' | 'success' | 'error' | 'warning' | 'inherit'",
82
- description:
83
- 'Color variant mapped to Astryx icon color tokens. Semantic values (primary through warning) follow the role, and inherit uses the surrounding text color. On the web, Icon also accepts the palette values blue, red, green, gray, cyan, teal, yellow, orange, pink, and purple, which pick a non-semantic hue.',
82
+ description: 'Color variant mapped to Astryx icon color tokens.',
84
83
  default: "'inherit'",
85
84
  },
86
85
  {
@@ -108,24 +107,6 @@ export const docs = {
108
107
  description:
109
108
  "StyleX styles for customization (color, size, opacity). Folded into the icon's own stylex.props() call so it composes with the base color/size styles. Must be a stylex.create() value, not an inline style object like style={{}}.",
110
109
  },
111
- {
112
- name: 'className',
113
- type: 'string',
114
- description:
115
- 'Class name added to the rendered glyph, beside the astryx-icon target class.',
116
- },
117
- {
118
- name: 'style',
119
- type: 'CSSProperties',
120
- description:
121
- "Inline styles merged onto the rendered glyph after Icon's own size and color styles.",
122
- },
123
- {
124
- name: 'ref',
125
- type: 'Ref<SVGSVGElement>',
126
- description:
127
- 'Ref to the rendered SVG element when icon is an icon component. A semantic or namespaced name renders inside an internal wrapper, and the ref is not attached.',
128
- },
129
110
  ],
130
111
  theming: {
131
112
  targets: [{className: 'astryx-icon', visualProps: ['color', 'size']}],
@@ -226,16 +207,15 @@ export const docsZh = {
226
207
  props: [
227
208
  {
228
209
  name: 'icon',
229
- type: 'IconName | NamespacedIconName | ComponentType<SVGProps>',
210
+ type: 'IconName | ComponentType<SVGProps>',
230
211
  description:
231
- '语义图标名称或 SVG 组件。有效语义名称:close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone。命名空间键(如 `richtext:bold`)指向某个组件或库注册的图标,主题可以按键替换它;没有注册的命名空间键不渲染任何内容。列表之外的图标请直接传入 SVG 组件。',
212
+ '语义图标名称或 SVG 组件。有效语义名称:close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone。列表之外的图标请直接传入 SVG 组件。',
232
213
  required: true,
233
214
  },
234
215
  {
235
216
  name: 'color',
236
217
  type: "'primary' | 'secondary' | 'tertiary' | 'disabled' | 'accent' | 'info' | 'success' | 'error' | 'warning' | 'inherit'",
237
- description:
238
- '映射到 Astryx 图标颜色令牌的颜色变体。语义值(primary 到 warning)跟随角色,inherit 使用周围文字的颜色。在 Web 上,Icon 还接受调色板值 blue、red、green、gray、cyan、teal、yellow、orange、pink 和 purple,它们选择非语义色相。',
218
+ description: '映射到 Astryx 图标颜色令牌的颜色变体。',
239
219
  default: "'inherit'",
240
220
  },
241
221
  {
@@ -263,23 +243,6 @@ export const docsZh = {
263
243
  description:
264
244
  '用于自定义的 StyleX 样式(颜色、尺寸、不透明度)。会并入图标自身的 stylex.props() 调用,从而与基础的颜色/尺寸样式组合。必须是 stylex.create() 的值,而不是像 style={{}} 这样的内联样式对象。',
265
245
  },
266
- {
267
- name: 'className',
268
- type: 'string',
269
- description: '添加到渲染图标上的类名,与 astryx-icon 目标类名并列。',
270
- },
271
- {
272
- name: 'style',
273
- type: 'CSSProperties',
274
- description:
275
- '合并到渲染图标上的内联样式,位于 Icon 自身的尺寸和颜色样式之后。',
276
- },
277
- {
278
- name: 'ref',
279
- type: 'Ref<SVGSVGElement>',
280
- description:
281
- 'icon 为图标组件时,指向渲染的 SVG 元素的 ref。语义名称或命名空间名称在内部包装元素中渲染,不会附加 ref。',
282
- },
283
246
  ],
284
247
  theming: {
285
248
  targets: [{className: 'astryx-icon', visualProps: ['color', 'size']}],
@@ -427,9 +390,8 @@ export const docsDense = {
427
390
  ],
428
391
  },
429
392
  propDescriptions: {
430
- icon: 'Semantic icon name or SVG component. Valid names: close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. A namespaced key (`richtext:bold`) names a glyph a component or library registers; an unregistered one renders nothing. For others, pass an SVG component.',
431
- color:
432
- 'Color variant mapped to Astryx icon color tokens; inherit = surrounding text color. Web also accepts the non-semantic palette: blue, red, green, gray, cyan, teal, yellow, orange, pink, purple.',
393
+ icon: 'Semantic icon name or SVG component. Valid names: close, chevronDown, chevronLeft, chevronRight, chevronsLeft, chevronsRight, check, success, error, warning, info, calendar, clock, externalLink, menu, moreHorizontal, search, upload, arrowUp, arrowDown, arrowsUpDown, funnel, eyeSlash, viewColumns, copy, checkDouble, wrench, stop, microphone. For others, pass an SVG component.',
394
+ color: 'Color variant mapped to Astryx icon color tokens.',
433
395
  size: "xsm/sm/md/lg = 12/16/20/24px at a 16px root, scale w/ root font size; theme sets a size's glyph dimension via width + height (px or rem) on components.icon['size:sm'] etc. Explicit Icon size; otherwise nearest owning-component default, then 'md' when no contextual default exists",
434
396
  variant:
435
397
  "Requests the active theme's alternate artwork (e.g. 'filled'); names from augmentable IconVariantMap. Reaches the entry only if the active theme's iconVariants lists it (and an inherited entry's defining theme does too); else normal artwork. Ignored by a directly passed component; never in the DOM; no theming selector.",
@@ -437,8 +399,5 @@ export const docsDense = {
437
399
  'Accessible name for a meaningful, standalone icon. Sets role="img" + aria-label and drops the default aria-hidden. Omit (default) for decorative icons (stays aria-hidden). Empty string = decorative. The accessible-name/alt-text prop for icons.',
438
400
  xstyle:
439
401
  'StyleX styles (color, size, opacity) via stylex.create(); composes with the base color/size styles.',
440
- className: 'Class added to the rendered glyph beside astryx-icon.',
441
- style: "Inline styles merged after Icon's size and color styles.",
442
- ref: 'Reaches the SVG only when icon is a component; a semantic or namespaced name renders in a wrapper and gets no ref.',
443
402
  },
444
403
  };
package/src/Icon/Icon.tsx CHANGED
@@ -176,11 +176,7 @@ export interface IconProps extends Omit<
176
176
  SVGProps<SVGSVGElement>,
177
177
  'ref' | 'color'
178
178
  > {
179
- /**
180
- * Ref to the rendered SVG element when `icon` is an icon component. A
181
- * semantic or namespaced name renders inside an internal wrapper, and the
182
- * ref is not attached.
183
- */
179
+ /** Ref forwarded to the root element */
184
180
  ref?: React.Ref<SVGSVGElement>;
185
181
  /**
186
182
  * Icon to render. Can be:
@@ -271,26 +267,22 @@ export interface IconProps extends Omit<
271
267
  xstyle?: StyleXStyles;
272
268
  }
273
269
 
274
- type IconA11yProps =
275
- | {role: 'img'; 'aria-label': string; 'aria-hidden': undefined}
276
- | {'aria-hidden': 'true'};
277
-
278
270
  /**
279
271
  * Derives the ARIA attributes for an icon from its `label` prop.
280
272
  *
281
273
  * - Non-empty `label` → meaningful image: `role="img"` + `aria-label`, and no
282
274
  * `aria-hidden` (an `aria-hidden` element is removed from the accessibility
283
- * tree, so its accessible name would be ignored). Icon passes
284
- * `aria-hidden: undefined`. Many icon components, such as Heroicons, write
285
- * `aria-hidden="true"` unless a prop replaces it.
275
+ * tree, so its accessible name would be ignored).
286
276
  * - Omitted or empty `label` → decorative default: `aria-hidden="true"`.
287
277
  *
288
278
  * The result is spread BEFORE `{...props}` in both render modes so an explicit
289
279
  * `aria-hidden` / `role` / `aria-label` from the consumer always wins.
290
280
  */
291
- function getIconA11yProps(label: string | undefined): IconA11yProps {
281
+ function getIconA11yProps(
282
+ label: string | undefined,
283
+ ): {role: 'img'; 'aria-label': string} | {'aria-hidden': 'true'} {
292
284
  return label != null && label !== ''
293
- ? {role: 'img', 'aria-label': label, 'aria-hidden': undefined}
285
+ ? {role: 'img', 'aria-label': label}
294
286
  : {'aria-hidden': 'true'};
295
287
  }
296
288
 
@@ -400,7 +392,7 @@ function IconFromRegistry({
400
392
  color: IconColor;
401
393
  size: IconSize;
402
394
  variant: IconVariant | undefined;
403
- a11yProps: IconA11yProps;
395
+ a11yProps: {role: 'img'; 'aria-label': string} | {'aria-hidden': 'true'};
404
396
  className?: string;
405
397
  style?: React.CSSProperties;
406
398
  xstyle?: StyleXStyles;
@@ -6,9 +6,8 @@
6
6
  * @file usePressPin.ts
7
7
  * @input A hover layer's trigger, open state, show and hide, and its
8
8
  * `triggerBehavior` and `touchTrigger` options
9
- * @output usePressPin, the press toggle that pins a hover layer open, and
10
- * the app hold behind useTooltip's show() and hide(); isPressPinTrigger;
11
- * stopPressPinClick; the LayerTriggerBehavior type
9
+ * @output usePressPin, the press toggle that pins a hover layer open;
10
+ * isPressPinTrigger; stopPressPinClick; the LayerTriggerBehavior type
12
11
  * @position Core-internal: one call per hover layer, useTooltip's. It owns
13
12
  * the layer's touch trigger, so the host makes no second touch-trigger call.
14
13
  *
@@ -104,18 +103,8 @@ export interface UsePressPinReturn {
104
103
  isHeldOpen: () => boolean;
105
104
  /** Wires the trigger element, or unwires it with `null`. */
106
105
  attach: (element: HTMLElement | null) => void;
107
- /** Drops the pin and the app hold: Escape, or a hide from elsewhere. */
106
+ /** Drops the pin: Escape, or a hide that came from elsewhere. */
108
107
  unpin: () => void;
109
- /**
110
- * The app's `show()`: shows the layer and holds it open, as a pin does,
111
- * until `release`, Escape, a press outside, or focus leaving the trigger
112
- * and the layer. A controlled layer reports the request instead.
113
- */
114
- hold: () => void;
115
- /** The app's `hide()`: drops the hold and hides the layer. */
116
- release: () => void;
117
- /** Whether the app's `show()` holds the layer open now. */
118
- isAppHeld: () => boolean;
119
108
  /**
120
109
  * While pinned, focus moving onto an element outside the trigger and the
121
110
  * layer closes it. Returns whether the pin decided. The pin calls it for its
@@ -123,9 +112,8 @@ export interface UsePressPinReturn {
123
112
  */
124
113
  handleFocusLeave: (event: FocusEvent) => boolean;
125
114
  /**
126
- * Whether the layer shows pinned on a wired trigger, or held by the app's
127
- * `show()`. The host then announces its rendered text, unless the layer has
128
- * a `label`.
115
+ * Whether the layer shows pinned on a wired trigger. The host then announces
116
+ * its rendered text, unless the layer has a `label`.
129
117
  */
130
118
  isPinShown: boolean;
131
119
  /** The layer's touch trigger (useTouchTriggerInternal), wired to the pin. */
@@ -187,19 +175,8 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
187
175
  setIsPinnedState(next);
188
176
  }, []);
189
177
 
190
- // The app's show() holds the layer on any trigger, in any mode.
191
- const isAppHeldRef = useRef(false);
192
- const [isAppHeldState, setIsAppHeldState] = useState(false);
193
- const setAppHeld = useCallback((next: boolean) => {
194
- isAppHeldRef.current = next;
195
- setIsAppHeldState(next);
196
- }, []);
197
-
198
178
  // The last offered element lets a `triggerBehavior` change rewire it.
199
179
  const triggerRef = useRef<HTMLElement | null>(null);
200
- // Every offered trigger, in any mode, so focus leaving ends a pin or an
201
- // app hold.
202
- const focusTargetRef = useRef<HTMLElement | null>(null);
203
180
  const offeredRef = useRef<HTMLElement | null>(null);
204
181
  // A primitive: re-attaching the same trigger renders nothing.
205
182
  const [offeredKind, setOfferedKind] = useState<'none' | 'other' | 'button'>(
@@ -215,13 +192,6 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
215
192
  // a pinned layer through the pin, so the pin drops with it.
216
193
  const hideFromTouch = useCallback(() => {
217
194
  const current = optionsRef.current;
218
- if (isAppHeldRef.current) {
219
- setAppHeld(false);
220
- if (current.isOpen !== undefined) {
221
- current.report(false);
222
- return;
223
- }
224
- }
225
195
  if (current.triggerBehavior === 'toggle' && triggerRef.current != null) {
226
196
  if (current.isOpen !== undefined) {
227
197
  current.report(false);
@@ -232,7 +202,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
232
202
  }
233
203
  }
234
204
  current.hide();
235
- }, [setPinned, setAppHeld]);
205
+ }, [setPinned]);
236
206
 
237
207
  const {
238
208
  isTouchPointerRef,
@@ -263,14 +233,13 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
263
233
  const close = useCallback(() => {
264
234
  const current = optionsRef.current;
265
235
  clearTapOpen();
266
- setAppHeld(false);
267
236
  if (current.isOpen !== undefined) {
268
237
  current.report(false);
269
238
  return;
270
239
  }
271
240
  setPinned(false);
272
241
  current.hide();
273
- }, [setPinned, setAppHeld, clearTapOpen]);
242
+ }, [setPinned, clearTapOpen]);
274
243
 
275
244
  const announceName = useCallback(() => {
276
245
  const current = optionsRef.current;
@@ -379,9 +348,6 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
379
348
 
380
349
  const isHeldOpen = useCallback(() => {
381
350
  const current = optionsRef.current;
382
- if (isAppHeldRef.current && current.isLayerOpen) {
383
- return current.isOpen !== false;
384
- }
385
351
  if (current.triggerBehavior !== 'toggle' || triggerRef.current == null) {
386
352
  return false;
387
353
  }
@@ -398,7 +364,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
398
364
  return false;
399
365
  }
400
366
  const next = event.relatedTarget;
401
- const trigger = focusTargetRef.current;
367
+ const trigger = triggerRef.current;
402
368
  // Focus that leaves for no element is a press's to decide. The trigger
403
369
  // can sit in another document, so the check uses its own window.
404
370
  const view = trigger?.ownerDocument.defaultView;
@@ -421,8 +387,6 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
421
387
  );
422
388
 
423
389
  const unwire = useCallback(() => {
424
- focusTargetRef.current?.removeEventListener('focusout', handleFocusLeave);
425
- focusTargetRef.current = null;
426
390
  const previous = triggerRef.current;
427
391
  if (previous == null) {
428
392
  return;
@@ -430,6 +394,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
430
394
  previous.removeEventListener('click', handlePress);
431
395
  previous.removeEventListener('keydown', handleKeyDown);
432
396
  previous.removeEventListener('keyup', handleKeyUp);
397
+ previous.removeEventListener('focusout', handleFocusLeave);
433
398
  const own = ownStateRef.current;
434
399
  if (own != null) {
435
400
  restoreAttribute(previous, 'aria-expanded', own.expanded);
@@ -458,14 +423,10 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
458
423
  }
459
424
  settledRef.current = {element, mode: current.triggerBehavior};
460
425
  unwire();
461
- if (element == null) {
426
+ if (element == null || current.triggerBehavior !== 'toggle') {
462
427
  return;
463
428
  }
464
- // Its own focus-leave listener, in any mode: `focusTrigger="never"`
465
- // attaches none, and an app hold needs it on any trigger.
466
- element.addEventListener('focusout', handleFocusLeave);
467
- focusTargetRef.current = element;
468
- if (current.triggerBehavior !== 'toggle' || !isPressPinTrigger(element)) {
429
+ if (!isPressPinTrigger(element)) {
469
430
  return;
470
431
  }
471
432
  element.addEventListener('click', handlePress);
@@ -473,6 +434,8 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
473
434
  element.addEventListener('keydown', handleKeyDown);
474
435
  element.addEventListener('keyup', handleKeyUp);
475
436
  }
437
+ // Its own focus-leave listener: `focusTrigger="never"` attaches none.
438
+ element.addEventListener('focusout', handleFocusLeave);
476
439
  ownStateRef.current = {
477
440
  expanded: element.getAttribute('aria-expanded'),
478
441
  controls: element.getAttribute('aria-controls'),
@@ -510,31 +473,7 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
510
473
  if (isPinnedRef.current) {
511
474
  setPinned(false);
512
475
  }
513
- if (isAppHeldRef.current) {
514
- setAppHeld(false);
515
- }
516
- }, [setPinned, setAppHeld]);
517
-
518
- const hold = useCallback(() => {
519
- const current = optionsRef.current;
520
- if (!current.isEnabled) {
521
- return;
522
- }
523
- setAppHeld(true);
524
- armOutsideDismiss();
525
- announceName();
526
- if (current.isOpen !== undefined) {
527
- current.report(true);
528
- return;
529
- }
530
- current.show();
531
- }, [setAppHeld, armOutsideDismiss, announceName]);
532
-
533
- const release = useCallback(() => {
534
- close();
535
- }, [close]);
536
-
537
- const isAppHeld = useCallback(() => isAppHeldRef.current, []);
476
+ }, [setPinned]);
538
477
 
539
478
  // Each path that hides a pinned layer drops the pin itself, so a layer that
540
479
  // does not show is never reported expanded.
@@ -551,18 +490,13 @@ export function usePressPin(options: UsePressPinOptions): UsePressPinReturn {
551
490
  writeAttribute(trigger, 'aria-controls', isLayerOpen ? layerId : null);
552
491
  });
553
492
 
554
- const isPinShown =
555
- (isToggleMode && isWired && isExpanded) ||
556
- (isAppHeldState && isLayerOpen && isOpen !== false);
493
+ const isPinShown = isToggleMode && isWired && isExpanded;
557
494
 
558
495
  return {
559
496
  isToggleTrigger,
560
497
  isHeldOpen,
561
498
  attach,
562
499
  unpin,
563
- hold,
564
- release,
565
- isAppHeld,
566
500
  handleFocusLeave,
567
501
  isPinShown,
568
502
  touch: {
@@ -55,7 +55,6 @@ import {
55
55
  } from '../i18n/useComponentTranslator';
56
56
  import english from '../i18n/generated-english/Lightbox/Lightbox.generated';
57
57
  import {isRenderable} from '../utils/isRenderable';
58
- import {prepareFocusReturnOnRemoval} from '../utils/focusReturn';
59
58
 
60
59
  registerEnglishFallback(english);
61
60
  /**
@@ -446,23 +445,6 @@ export function Lightbox({
446
445
  }
447
446
  }, [isOpen, hasItem]);
448
447
 
449
- // A viewer that unmounts while open (rendered only while open, or removed
450
- // with the rest of a view) never runs the close branch above. Focus the
451
- // removal lost goes back to the opener while the opener is still on the
452
- // page.
453
- useEffect(() => {
454
- const dialog = dialogRef.current;
455
- if (dialog == null) {
456
- return;
457
- }
458
- const returnFocusOnRemoval = prepareFocusReturnOnRemoval(dialog);
459
- return () => {
460
- if (dialog.open) {
461
- returnFocusOnRemoval(triggerElementRef.current);
462
- }
463
- };
464
- }, [hasItem]);
465
-
466
448
  const handleClose = useCallback(() => {
467
449
  onOpenChange(false);
468
450
  }, [onOpenChange]);