@astryxdesign/core 0.7.5 → 0.7.6-canary.bb18c81

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 (353) hide show
  1. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/AlertDialog/AlertDialog.js +5 -5
  4. package/dist/Avatar/Avatar.d.ts.map +1 -1
  5. package/dist/Avatar/Avatar.js +12 -3
  6. package/dist/BottomSheet/BottomSheet.js +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +2 -1
  9. package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
  10. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  11. package/dist/BottomSheet/useSheetGestures.js +30 -3
  12. package/dist/Button/Button.js +4 -4
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  15. package/dist/DateInput/DateInput.d.ts.map +1 -1
  16. package/dist/DateInput/DateInput.js +4 -1
  17. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  18. package/dist/DateInput/MonthScroller.js +101 -15
  19. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  20. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  21. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  22. package/dist/DateTimeInput/DateTimeInput.js +4 -1
  23. package/dist/Dialog/Dialog.d.ts.map +1 -1
  24. package/dist/Dialog/Dialog.js +34 -9
  25. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  26. package/dist/Dialog/DialogHeader.js +5 -4
  27. package/dist/Divider/Divider.d.ts.map +1 -1
  28. package/dist/Divider/Divider.js +2 -1
  29. package/dist/Drawer/Drawer.d.ts.map +1 -1
  30. package/dist/Drawer/Drawer.js +5 -0
  31. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  32. package/dist/DropdownMenu/DropdownMenu.js +4 -1
  33. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
  35. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
  36. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  37. package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
  38. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  39. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  40. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  41. package/dist/EmptyState/EmptyState.js +2 -1
  42. package/dist/Field/FieldLabel.d.ts +1 -1
  43. package/dist/Field/FieldLabel.d.ts.map +1 -1
  44. package/dist/Field/FieldLabel.js +4 -4
  45. package/dist/Field/InputClearButton.js +3 -3
  46. package/dist/Field/PanelSearchInput.js +2 -2
  47. package/dist/FieldStatus/FieldStatus.js +2 -2
  48. package/dist/FileInput/FileInput.d.ts.map +1 -1
  49. package/dist/FileInput/FileInput.js +7 -1
  50. package/dist/Heading/Heading.js +1 -1
  51. package/dist/Icon/Icon.js +1 -1
  52. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  53. package/dist/Indicator/CheckIndicator.js +2 -1
  54. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  55. package/dist/Indicator/CheckboxIndicator.js +3 -1
  56. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  57. package/dist/Indicator/RadioIndicator.js +3 -1
  58. package/dist/Item/Item.d.ts.map +1 -1
  59. package/dist/Item/Item.js +2 -1
  60. package/dist/Item/ItemSwipeLayer.js +1 -1
  61. package/dist/Layout/Layout.js +1 -1
  62. package/dist/Layout/LayoutContent.js +1 -1
  63. package/dist/Layout/LayoutFooter.js +1 -1
  64. package/dist/Layout/LayoutHeader.js +1 -1
  65. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  66. package/dist/Lightbox/Lightbox.js +5 -0
  67. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
  68. package/dist/List/List.js +1 -1
  69. package/dist/List/ListItem.js +3 -3
  70. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  71. package/dist/MobileNav/MobileNav.js +7 -2
  72. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  73. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  74. package/dist/MultiSelector/MultiSelector.js +5 -2
  75. package/dist/NumberInput/NumberInput.d.ts +3 -1
  76. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  77. package/dist/NumberInput/NumberInput.js +3 -3
  78. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  79. package/dist/ProgressBar/ProgressBar.js +16 -12
  80. package/dist/Resizable/useResizable.d.ts.map +1 -1
  81. package/dist/Resizable/useResizable.js +28 -2
  82. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  83. package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
  84. package/dist/Selector/useSelectorPresentation.d.ts +8 -1
  85. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  86. package/dist/Selector/useSelectorPresentation.js +20 -4
  87. package/dist/Slider/Slider.d.ts.map +1 -1
  88. package/dist/Slider/Slider.js +7 -1
  89. package/dist/Spinner/Spinner.js +2 -2
  90. package/dist/Stack/Stack.js +1 -1
  91. package/dist/Stack/StackItem.js +1 -1
  92. package/dist/TabList/TabList.d.ts.map +1 -1
  93. package/dist/TabList/TabList.js +16 -1
  94. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  95. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  96. package/dist/Text/Text.js +1 -1
  97. package/dist/Text/useTruncation.d.ts.map +1 -1
  98. package/dist/Text/useTruncation.js +23 -5
  99. package/dist/TextArea/TextArea.d.ts +1 -1
  100. package/dist/TextArea/TextArea.d.ts.map +1 -1
  101. package/dist/TextArea/TextArea.js +13 -2
  102. package/dist/TextInput/TextInput.d.ts.map +1 -1
  103. package/dist/TextInput/TextInput.js +3 -3
  104. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  105. package/dist/Timestamp/Timestamp.js +2 -2
  106. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  107. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  108. package/dist/Toast/ToastViewport.js +82 -51
  109. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  110. package/dist/Tokenizer/Tokenizer.js +13 -2
  111. package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
  112. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  113. package/dist/Toolbar/Toolbar.js +2 -1
  114. package/dist/Typeahead/Typeahead.d.ts +1 -1
  115. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  116. package/dist/Typeahead/Typeahead.js +9 -1
  117. package/dist/astryx.css +14 -1
  118. package/dist/gestures/PinchGestureRecognizer.d.ts +30 -0
  119. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  120. package/dist/gestures/PinchGestureRecognizer.js +125 -0
  121. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +70 -0
  122. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  123. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +220 -0
  124. package/dist/gestures/SwipeGestureRecognizer.d.ts +36 -0
  125. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  126. package/dist/gestures/SwipeGestureRecognizer.js +145 -0
  127. package/dist/gestures/VelocityTracker.d.ts +6 -2
  128. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  129. package/dist/gestures/VelocityTracker.js +7 -3
  130. package/dist/gestures/types.d.ts +11 -7
  131. package/dist/gestures/types.d.ts.map +1 -1
  132. package/dist/gestures/types.js +22 -2
  133. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  134. package/dist/hooks/useAdaptivePresentation.js +4 -1
  135. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  136. package/dist/hooks/useMenuHover.js +12 -1
  137. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  138. package/dist/i18n/generated-locales/en.generated.js +3 -0
  139. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  140. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  141. package/dist/motion/animator.d.ts +7 -1
  142. package/dist/motion/animator.d.ts.map +1 -1
  143. package/dist/motion/animator.js +119 -27
  144. package/dist/theme/defineTheme.js +1 -1
  145. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  146. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  147. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  148. package/dist/theme/domainTokens/index.d.ts +5 -4
  149. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  150. package/dist/theme/domainTokens/index.js +5 -9
  151. package/dist/utils/focusReturn.d.ts +17 -1
  152. package/dist/utils/focusReturn.d.ts.map +1 -1
  153. package/dist/utils/focusReturn.js +11 -4
  154. package/dist/utils/getNodeText.d.ts +8 -0
  155. package/dist/utils/getNodeText.d.ts.map +1 -0
  156. package/dist/utils/getNodeText.js +37 -0
  157. package/dist/utils/interactionModality.d.ts +11 -2
  158. package/dist/utils/interactionModality.d.ts.map +1 -1
  159. package/dist/utils/interactionModality.js +58 -5
  160. package/dist/utils/presentation.d.ts +50 -0
  161. package/dist/utils/presentation.d.ts.map +1 -0
  162. package/dist/utils/presentation.js +125 -0
  163. package/dist/utils/touchPresentation.d.ts.map +1 -1
  164. package/dist/utils/touchPresentation.js +5 -3
  165. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  166. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  167. package/dist/utils/touchPresentation.stylex.js +33 -0
  168. package/locales/en.json +13 -1
  169. package/locales/pseudo.json +9 -0
  170. package/package.json +5 -4
  171. package/src/AlertDialog/AlertDialog.tsx +6 -5
  172. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
  173. package/src/Avatar/Avatar.spec.md +22 -22
  174. package/src/Avatar/Avatar.tsx +18 -1
  175. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
  176. package/src/BottomSheet/BottomSheet.test.tsx +46 -0
  177. package/src/BottomSheet/BottomSheet.tsx +1 -1
  178. package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
  179. package/src/BottomSheet/useSheetGestures.test.ts +52 -0
  180. package/src/BottomSheet/useSheetGestures.ts +37 -6
  181. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
  182. package/src/Button/Button.tsx +4 -4
  183. package/src/Calendar/Calendar.spec.md +87 -48
  184. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
  185. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
  186. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  187. package/src/Collapsible/Collapsible.spec.md +2 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
  189. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
  190. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
  191. package/src/ContextMenu/ContextMenu.test.tsx +35 -0
  192. package/src/DateInput/DateInput.test.tsx +53 -2
  193. package/src/DateInput/DateInput.tsx +15 -11
  194. package/src/DateInput/MonthScroller.tsx +106 -20
  195. package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
  196. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
  197. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
  198. package/src/DateRangeInput/DateRangeInput.tsx +16 -12
  199. package/src/DateTimeInput/DateTimeInput.spec.md +71 -48
  200. package/src/DateTimeInput/DateTimeInput.test.tsx +61 -0
  201. package/src/DateTimeInput/DateTimeInput.tsx +15 -11
  202. package/src/Dialog/Dialog.test.tsx +216 -2
  203. package/src/Dialog/Dialog.tsx +42 -9
  204. package/src/Dialog/DialogHeader.tsx +5 -4
  205. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
  206. package/src/Divider/Divider.tsx +2 -1
  207. package/src/Drawer/Drawer.spec.md +111 -80
  208. package/src/Drawer/Drawer.test.tsx +22 -0
  209. package/src/Drawer/Drawer.tsx +5 -0
  210. package/src/Drawer/DrawerHeader.spec.md +181 -0
  211. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
  212. package/src/DropdownMenu/DropdownMenu.tsx +7 -1
  213. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
  214. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
  215. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
  216. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
  217. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +53 -0
  218. package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
  219. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  220. package/src/EmptyState/EmptyState.tsx +2 -1
  221. package/src/Field/FieldLabel.tsx +5 -4
  222. package/src/Field/InputClearButton.tsx +3 -3
  223. package/src/Field/PanelSearchInput.tsx +2 -2
  224. package/src/FieldStatus/FieldStatus.tsx +2 -2
  225. package/src/FileInput/FileInput.test.tsx +21 -0
  226. package/src/FileInput/FileInput.tsx +4 -1
  227. package/src/FormLayout/FormLayout.spec.md +7 -8
  228. package/src/Heading/Heading.tsx +1 -1
  229. package/src/Icon/Icon.tsx +1 -1
  230. package/src/Indicator/CheckIndicator.spec.md +47 -42
  231. package/src/Indicator/CheckIndicator.tsx +2 -1
  232. package/src/Indicator/CheckboxIndicator.spec.md +40 -37
  233. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  234. package/src/Indicator/Indicator.spec.md +188 -0
  235. package/src/Indicator/RadioIndicator.tsx +3 -1
  236. package/src/Item/Item.tsx +2 -1
  237. package/src/Item/ItemSwipeLayer.tsx +1 -1
  238. package/src/Layout/Layout.spec.md +127 -131
  239. package/src/Layout/Layout.tsx +1 -1
  240. package/src/Layout/LayoutContent.spec.md +205 -0
  241. package/src/Layout/LayoutContent.tsx +1 -1
  242. package/src/Layout/LayoutFooter.spec.md +190 -0
  243. package/src/Layout/LayoutFooter.tsx +1 -1
  244. package/src/Layout/LayoutHeader.spec.md +193 -0
  245. package/src/Layout/LayoutHeader.tsx +1 -1
  246. package/src/Layout/LayoutPanel.spec.md +194 -0
  247. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
  248. package/src/Lightbox/Lightbox.test.tsx +18 -0
  249. package/src/Lightbox/Lightbox.tsx +5 -0
  250. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
  251. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
  252. package/src/List/List.tsx +1 -1
  253. package/src/List/ListItem.tsx +3 -3
  254. package/src/MobileNav/MobileNav.test.tsx +15 -0
  255. package/src/MobileNav/MobileNav.tsx +9 -1
  256. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
  257. package/src/MoreMenu/MoreMenu.test.tsx +11 -0
  258. package/src/MultiSelector/MultiSelector.spec.md +7 -4
  259. package/src/MultiSelector/MultiSelector.test.tsx +118 -1
  260. package/src/MultiSelector/MultiSelector.tsx +6 -2
  261. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
  262. package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
  263. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  264. package/src/NumberInput/NumberInput.spec.md +59 -37
  265. package/src/NumberInput/NumberInput.tsx +13 -3
  266. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
  267. package/src/ProgressBar/ProgressBar.test.tsx +108 -32
  268. package/src/ProgressBar/ProgressBar.tsx +58 -10
  269. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
  270. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
  271. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
  272. package/src/Resizable/useResizable.test.ts +43 -0
  273. package/src/Resizable/useResizable.ts +32 -9
  274. package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
  275. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
  276. package/src/Selector/Selector.test.tsx +90 -1
  277. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
  278. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
  279. package/src/Selector/useSelectorPresentation.ts +31 -3
  280. package/src/SideNav/SideNav.test.tsx +27 -0
  281. package/src/Slider/Slider.test.tsx +12 -0
  282. package/src/Slider/Slider.tsx +4 -1
  283. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
  284. package/src/Spinner/Spinner.tsx +2 -2
  285. package/src/Stack/Stack.tsx +1 -1
  286. package/src/Stack/StackItem.tsx +1 -1
  287. package/src/TabList/TabList.test.tsx +52 -1
  288. package/src/TabList/TabList.tsx +16 -1
  289. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
  290. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
  291. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
  292. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  293. package/src/Text/Text.tsx +1 -1
  294. package/src/Text/useTruncation.test.tsx +128 -1
  295. package/src/Text/useTruncation.ts +23 -5
  296. package/src/TextArea/TextArea.spec.md +10 -13
  297. package/src/TextArea/TextArea.test.tsx +60 -0
  298. package/src/TextArea/TextArea.tsx +15 -1
  299. package/src/TextInput/TextInput.test.tsx +21 -0
  300. package/src/TextInput/TextInput.tsx +3 -1
  301. package/src/Timestamp/Timestamp.spec.md +6 -12
  302. package/src/Timestamp/Timestamp.test.tsx +30 -5
  303. package/src/Timestamp/Timestamp.tsx +6 -2
  304. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  305. package/src/Toast/Toast.spec.md +7 -10
  306. package/src/Toast/ToastViewport.test.tsx +575 -1
  307. package/src/Toast/ToastViewport.tsx +100 -50
  308. package/src/Tokenizer/Tokenizer.spec.md +5 -3
  309. package/src/Tokenizer/Tokenizer.test.tsx +68 -0
  310. package/src/Tokenizer/Tokenizer.tsx +14 -2
  311. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
  312. package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
  313. package/src/Toolbar/Toolbar.spec.md +5 -6
  314. package/src/Toolbar/Toolbar.test.tsx +30 -0
  315. package/src/Toolbar/Toolbar.tsx +2 -1
  316. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
  317. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
  318. package/src/TopNav/TopNav.test.tsx +27 -0
  319. package/src/Typeahead/Typeahead.test.tsx +40 -0
  320. package/src/Typeahead/Typeahead.tsx +12 -1
  321. package/src/gestures/PinchGestureRecognizer.ts +144 -0
  322. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +252 -0
  323. package/src/gestures/SwipeGestureRecognizer.ts +193 -0
  324. package/src/gestures/VelocityTracker.ts +10 -3
  325. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
  326. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
  327. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
  328. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
  329. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +188 -0
  330. package/src/gestures/__tests__/harness.ts +21 -2
  331. package/src/gestures/types.ts +23 -9
  332. package/src/hooks/useAdaptivePresentation.ts +4 -1
  333. package/src/hooks/useMenuHover.ts +15 -1
  334. package/src/i18n/generated-locales/en.generated.ts +4 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +4 -0
  336. package/src/motion/README.md +11 -1
  337. package/src/motion/animator.test.ts +151 -3
  338. package/src/motion/animator.ts +149 -33
  339. package/src/theme/defineTheme.ts +1 -1
  340. package/src/theme/derivedVarRegistry.test.ts +4 -0
  341. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  342. package/src/theme/domainTokens/index.ts +6 -10
  343. package/src/utils/focusReturn.ts +17 -6
  344. package/src/utils/getNodeText.test.ts +24 -0
  345. package/src/utils/getNodeText.ts +37 -0
  346. package/src/utils/interactionModality.test.ts +45 -1
  347. package/src/utils/interactionModality.ts +64 -5
  348. package/src/utils/presentation.test.ts +380 -0
  349. package/src/utils/presentation.ts +180 -0
  350. package/src/utils/pressPaintPriority.test.ts +80 -0
  351. package/src/utils/touchPresentation.stylex.ts +43 -0
  352. package/src/utils/touchPresentation.test.ts +140 -1
  353. package/src/utils/touchPresentation.ts +5 -3
@@ -0,0 +1,50 @@
1
+ import type { AdaptivePresentation } from '../hooks/useAdaptivePresentation.js';
2
+ import type { CompactDefinition } from '../theme/themeAdaptations.js';
3
+ import { type TouchPresentation } from './touchPresentation.js';
4
+ /** What a component does when the caller omits `presentation`. */
5
+ export type PresentationDefault = 'popover' | 'touch';
6
+ export interface ResolvedPresentation {
7
+ /** The surface to render. */
8
+ readonly surface: 'popover' | 'bottom-sheet';
9
+ /**
10
+ * The component's touch input modes apply: a finger, and `presentation`
11
+ * not pinned to `'popover'`.
12
+ */
13
+ readonly touchModes: boolean;
14
+ }
15
+ export interface PresentationOptions {
16
+ /** What an omitted `presentation` means. @default 'popover' */
17
+ readonly whenOmitted?: PresentationDefault;
18
+ }
19
+ /**
20
+ * usePresentation's options. There is no server hint: the server and
21
+ * hydrating render never present touch modes (`spec:AST-094` FR13).
22
+ */
23
+ export type UsePresentationOptions = PresentationOptions;
24
+ export interface ReadPresentationOptions extends PresentationOptions {
25
+ /** The window to read; the global one by default. */
26
+ readonly view?: Window | null;
27
+ /**
28
+ * The active theme's compact definition (`useCompactDefinition()`, read
29
+ * while rendering), so a theme that moves `md` moves the sheet too
30
+ * (`spec:AST-012` FR9).
31
+ */
32
+ readonly compact: CompactDefinition;
33
+ }
34
+ /** The decision, from a presentation request and a touch presentation. */
35
+ export declare function resolvePresentation(presentation: AdaptivePresentation | undefined, touch: TouchPresentation, whenOmitted?: PresentationDefault): ResolvedPresentation;
36
+ /**
37
+ * The presentation now, following the pointer and the size as they change
38
+ * (a tablet docking a trackpad). SSR-safe: renders the server default (not
39
+ * touch, not compact) until hydrated (`spec:AST-094` FR13). It subscribes
40
+ * only to what the request reads: a pinned `'popover'` to neither query,
41
+ * `'bottom-sheet'` and the omitted `'touch'` default to the pointer, and
42
+ * `'adaptive'` to the pointer and the compact size.
43
+ */
44
+ export declare function usePresentation(presentation: AdaptivePresentation | undefined, { whenOmitted }?: UsePresentationOptions): ResolvedPresentation;
45
+ /**
46
+ * The presentation at this moment, for a decision taken once (a menu
47
+ * deciding how it follows the finger that opened it).
48
+ */
49
+ export declare function readPresentation(presentation: AdaptivePresentation | undefined, { whenOmitted, view, compact }: ReadPresentationOptions): ResolvedPresentation;
50
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../src/utils/presentation.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,kCAAkC,CAAC;AAC3E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAE7B,kEAAkE;AAClE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,6BAA6B;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,cAAc,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,qDAAqD;IACrD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,oBAAoB,GAAG,SAAS,EAC9C,KAAK,EAAE,iBAAiB,EACxB,WAAW,GAAE,mBAA+B,GAC3C,oBAAoB,CAStB;AA8BD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,oBAAoB,GAAG,SAAS,EAC9C,EAAC,WAAuB,EAAC,GAAE,sBAA2B,GACrD,oBAAoB,CAWtB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,oBAAoB,GAAG,SAAS,EAC9C,EAAC,WAAuB,EAAE,IAAI,EAAE,OAAO,EAAC,EAAE,uBAAuB,GAChE,oBAAoB,CAQtB"}
@@ -0,0 +1,125 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file presentation.ts
7
+ * @input A component's `presentation` prop (a caller's value, or omitted),
8
+ * what the component does when it is omitted, and the touch presentation
9
+ * (utils/touchPresentation.ts)
10
+ * @output usePresentation and readPresentation (and the pure
11
+ * resolvePresentation): the surface to render and whether the component's
12
+ * touch input modes apply
13
+ * @position Internal resolver for stable components that, under a finger,
14
+ * present a different surface or switch how an open surface follows input
15
+ * (architecture:public-component-api INV16)
16
+ *
17
+ * A caller's explicit `presentation` always wins over the touch default:
18
+ *
19
+ * - `'popover'`: the fine-pointer surface AND input modes, on every device;
20
+ * - `'bottom-sheet'`: the sheet everywhere, with touch input modes under a
21
+ * finger;
22
+ * - `'adaptive'`: the sheet on a compact touch screen and the popover
23
+ * elsewhere, with touch input modes under a finger;
24
+ * - omitted: the component's default. `whenOmitted: 'popover'` answers as
25
+ * `'popover'` (today's components). `whenOmitted: 'touch'` keeps the
26
+ * popover surface and turns the component's touch input modes on under a
27
+ * finger, as UIMenu tracks a finger in an anchored menu. Making `'touch'`
28
+ * a released component's default is a functional default change
29
+ * (`spec:AST-017` FR3) whose Changeset names `presentation="popover"`.
30
+ *
31
+ * One touch model on every touch device: the modes depend on the pointer,
32
+ * never on the platform. Touch styling (press paint, the text-size floor)
33
+ * and touch-only behavior that a component's own record defines (a row
34
+ * swipe, a slider's thumb) do not read this: they keep the touch
35
+ * presentation.
36
+ *
37
+ * CSS: a style that implements a touch surface or input mode is applied with
38
+ * `touchModes` (`stylex.props(touchModes && styles.touchMode)`), never as a
39
+ * bare media key on a base style, so a pinned `'popover'` keeps none of it.
40
+ * A rule inside it that needs the touch query uses the touch presentation's
41
+ * touch key; the iOS `@supports` gate is only for the one style iOS WebKit
42
+ * itself requires (the 16px text-size floor), never for a touch mode. On the
43
+ * server and while hydrating, `touchModes` is false and `'adaptive'` is the
44
+ * popover: a touch surface or mode opens only after hydration
45
+ * (`spec:AST-094` FR13), and no hint changes that. usePresentation listens
46
+ * only to the queries a request reads (`architecture:react-component-runtime`
47
+ * INV5): none for a pinned `'popover'`.
48
+ *
49
+ * SYNC: When modified, update presentation.test.ts.
50
+ */
51
+ import { useCallback, useMemo, useSyncExternalStore } from 'react';
52
+ import { readTouchPresentation, TOUCH_QUERY, useCompactDefinition } from "./touchPresentation.js";
53
+
54
+ /** What a component does when the caller omits `presentation`. */
55
+
56
+ /**
57
+ * usePresentation's options. There is no server hint: the server and
58
+ * hydrating render never present touch modes (`spec:AST-094` FR13).
59
+ */
60
+
61
+ /** The decision, from a presentation request and a touch presentation. */
62
+ export function resolvePresentation(presentation, touch, whenOmitted = 'popover') {
63
+ const request = presentation ?? whenOmitted;
64
+ const surface = request === 'bottom-sheet' || request === 'adaptive' && touch.isTouch && touch.isCompact ? 'bottom-sheet' : 'popover';
65
+ const touchModes = request !== 'popover' && touch.isTouch;
66
+ return {
67
+ surface,
68
+ touchModes
69
+ };
70
+ }
71
+ const noSubscription = () => () => {};
72
+ /** The server and hydrating render match no query (`spec:AST-094` FR13). */
73
+ const matchesNothing = () => false;
74
+
75
+ /**
76
+ * A media query's match, subscribed only while `query` is given
77
+ * (`architecture:react-component-runtime` INV5); false on the server and
78
+ * while hydrating (`spec:AST-094` FR13).
79
+ */
80
+ function useMatchWhileRead(query) {
81
+ const subscribe = useCallback(onStoreChange => {
82
+ if (query == null) {
83
+ return noSubscription();
84
+ }
85
+ const mql = window.matchMedia(query);
86
+ mql.addEventListener('change', onStoreChange);
87
+ return () => mql.removeEventListener('change', onStoreChange);
88
+ }, [query]);
89
+ const getSnapshot = useCallback(() => query != null && window.matchMedia(query).matches, [query]);
90
+ return useSyncExternalStore(subscribe, getSnapshot, matchesNothing);
91
+ }
92
+
93
+ /**
94
+ * The presentation now, following the pointer and the size as they change
95
+ * (a tablet docking a trackpad). SSR-safe: renders the server default (not
96
+ * touch, not compact) until hydrated (`spec:AST-094` FR13). It subscribes
97
+ * only to what the request reads: a pinned `'popover'` to neither query,
98
+ * `'bottom-sheet'` and the omitted `'touch'` default to the pointer, and
99
+ * `'adaptive'` to the pointer and the compact size.
100
+ */
101
+ export function usePresentation(presentation, {
102
+ whenOmitted = 'popover'
103
+ } = {}) {
104
+ const request = presentation ?? whenOmitted;
105
+ const compact = useCompactDefinition();
106
+ const isTouch = useMatchWhileRead(request === 'popover' ? null : TOUCH_QUERY);
107
+ const isCompact = useMatchWhileRead(request === 'adaptive' ? compact.query : null);
108
+ return useMemo(() => resolvePresentation(presentation, {
109
+ isTouch,
110
+ isCompact
111
+ }, whenOmitted), [presentation, isTouch, isCompact, whenOmitted]);
112
+ }
113
+
114
+ /**
115
+ * The presentation at this moment, for a decision taken once (a menu
116
+ * deciding how it follows the finger that opened it).
117
+ */
118
+ export function readPresentation(presentation, {
119
+ whenOmitted = 'popover',
120
+ view,
121
+ compact
122
+ }) {
123
+ const target = view === undefined ? typeof window === 'undefined' ? null : window : view;
124
+ return resolvePresentation(presentation, readTouchPresentation(target, compact), whenOmitted);
125
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"touchPresentation.d.ts","sourceRoot":"","sources":["../../src/utils/touchPresentation.ts"],"names":[],"mappings":"AA6DA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AAGnC,uDAAuD;AACvD,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QAAmC,CAAC;AAE9D,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,QACe,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,oBAAoB,kFAGvB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CAOxD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAC7C,iBAAiB,CAQnB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAOnB"}
1
+ {"version":3,"file":"touchPresentation.d.ts","sourceRoot":"","sources":["../../src/utils/touchPresentation.ts"],"names":[],"mappings":"AA+DA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AAGnC,uDAAuD;AACvD,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QAAmC,CAAC;AAE9D,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,QACe,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,oBAAoB,kFAGvB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CAOxD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAC7C,iBAAiB,CAQnB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAOnB"}
@@ -46,9 +46,11 @@
46
46
  * test keeps core's compact-touch keys identical to COMPACT_TOUCH_QUERY and
47
47
  * every touch-callout key spelled and nested as above. The iOS style gate
48
48
  * is only for a style iOS WebKit itself requires (core's 16px input floor);
49
- * it never selects a behaviour. The keys are literal because the Astryx
50
- * StyleX build does not resolve a key shared across files through
51
- * `defineConsts`. The hook is for behaviour. It answers the server default
49
+ * it never selects a behaviour. Core spells the keys literally: an imported
50
+ * `defineConsts` key compiles to a different StyleX priority than the same
51
+ * literal, which would reorder the cascade. `touchKeys`
52
+ * (touchPresentation.stylex.ts, internal to core) holds the same keys, and a
53
+ * test keeps every literal equal to them. The hook is for behaviour. It answers the server default
52
54
  * (not touch, not compact) until hydrated unless the caller passes a server
53
55
  * hint, and follows the pointer and size when they change (a tablet docking
54
56
  * a mouse).
@@ -0,0 +1,15 @@
1
+ export declare const touchKeys: {
2
+ /** The primary pointer is coarse (TOUCH_QUERY). */
3
+ touch: string;
4
+ /** The viewport is compact (COMPACT_QUERY, `spec:AST-012` FR9). */
5
+ compact: string;
6
+ /** Both: a presentation such as a sheet in place of a popover. */
7
+ compactTouch: string;
8
+ /**
9
+ * The iOS WebKit gate, inside `touch` (IOS_SUPPORTS_QUERY): only for a
10
+ * style iOS WebKit itself requires (the 16px input floor), never a
11
+ * behaviour; touch behaviour is one model on every touch device.
12
+ */
13
+ iosSupports: string;
14
+ };
15
+ //# sourceMappingURL=touchPresentation.stylex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"touchPresentation.stylex.d.ts","sourceRoot":"","sources":["../../src/utils/touchPresentation.stylex.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,SAAS;IACpB,mDAAmD;;IAEnD,mEAAmE;;IAEnE,kEAAkE;;IAGlE;;;;OAIG;;CAEH,CAAC"}
@@ -0,0 +1,33 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file touchPresentation.stylex.ts
5
+ * @input Uses StyleX defineConsts
6
+ * @output Exports touchKeys: the touch presentation resolver's queries as
7
+ * StyleX at-rule keys
8
+ * @position The reference spellings of the touch, compact and iOS-gate
9
+ * conditions. Core styles write these as literal keys wherever they need
10
+ * them (`spec:AST-094` FR13, `spec:AST-012` FR9), and a test pins each
11
+ * literal to these values; utils/touchPresentation.ts holds the same
12
+ * queries for script (matchMedia)
13
+ *
14
+ * These are the spellings to match. A style that changes with these
15
+ * conditions writes the same literal key (`'@media (pointer: coarse)'`, the
16
+ * iOS gate as that media key outside `'@supports (-webkit-touch-callout:
17
+ * none)'`), and a test pins every literal in core to these values. Do not use
18
+ * an imported key as a computed StyleX key: it compiles to a different
19
+ * priority than the literal (6000 against 3200) and reorders the cascade.
20
+ * The iOS supports gate is only for a style iOS WebKit itself requires (the
21
+ * 16px input floor), never a behaviour.
22
+ *
23
+ * SYNC: When modified, update touchPresentation.ts and
24
+ * touchPresentation.test.ts.
25
+ */
26
+
27
+ import * as stylex from '@stylexjs/stylex';
28
+ export const touchKeys = {
29
+ touch: "@media (pointer: coarse)",
30
+ compact: "@media (width < 768px), (height < 500px)",
31
+ compactTouch: "@media (width < 768px) and (pointer: coarse), (height < 500px) and (pointer: coarse)",
32
+ iosSupports: "@supports (-webkit-touch-callout: none)"
33
+ };
package/locales/en.json CHANGED
@@ -803,6 +803,10 @@
803
803
  "defaultMessage": "No data",
804
804
  "description": "Fallback empty-state text in the table body when there are zero rows. Neutral tone (not error-y); consumers commonly override with something specific like \"No results\"."
805
805
  },
806
+ "@astryx.table.columnResize.resizeColumn": {
807
+ "defaultMessage": "Resize column {label}",
808
+ "description": "Accessible name for a Table column-resize handle (a vertical separator between header cells). `label` is the column header's visible text, or the column key when the header has no text."
809
+ },
806
810
  "@astryx.table.filter.allPlaceholder": {
807
811
  "defaultMessage": "All",
808
812
  "description": "Placeholder on a per-column filter dropdown when nothing is selected, meaning \"no filter — all rows match\". Determiner form (as in \"all values\"), not the pronoun."
@@ -1567,6 +1571,10 @@
1567
1571
  "defaultMessage": "Expand all rows",
1568
1572
  "description": "Aria label for the Table tree-data header toggle when rows are collapsed and clicking it expands them."
1569
1573
  },
1574
+ "@astryx.textArea.counter": {
1575
+ "defaultMessage": "{count, number}/{limit, number}",
1576
+ "description": "Visible character counter at the bottom of a TextArea that has a character limit, for example `62/2,000`. `{count}` is the characters typed so far and `{limit}` the limit; keep both numbers and reorder or change the separator if your language needs to."
1577
+ },
1570
1578
  "@astryx.textArea.charactersRemaining": {
1571
1579
  "defaultMessage": "{count, number} {count, plural, one {character} other {characters}} remaining",
1572
1580
  "description": "Screen-reader-only announcement of how many characters the user can still type before hitting a TextArea's maxLength. Announced politely as the count nears the limit. `{count}` is the number remaining — example: `12 characters remaining`."
@@ -1643,6 +1651,10 @@
1643
1651
  "defaultMessage": "{count, number} of {total, number} selected",
1644
1652
  "description": "Polite screen-reader announcement of how many MultiSelector options are selected out of the total, spoken after each toggle. Live-region text, never shown on screen; example: `3 of 12 selected`."
1645
1653
  },
1654
+ "@astryx.multiSelector.selectedCount": {
1655
+ "defaultMessage": "{count, plural, one {# selected} other {# selected}}",
1656
+ "description": "Visible text on a closed MultiSelector's trigger when it shows a count of the chosen options (`triggerDisplay=\"count\"`), for example `2 selected`. `{count}` is the number of chosen options; use a plural form if your language needs one."
1657
+ },
1646
1658
  "@astryx.multiSelector.createOption": {
1647
1659
  "defaultMessage": "Create \"{query}\"",
1648
1660
  "description": "Label of the first row in a MultiSelector's search panel when `hasCreate` is set and the typed text matches no option. Picking it reports a creation to the caller. `{query}` is the trimmed text the person typed, shown in quotes."
@@ -1693,7 +1705,7 @@
1693
1705
  },
1694
1706
  "@astryx.tokenizer.create": {
1695
1707
  "defaultMessage": "Create \"{value}\"",
1696
- "description": "Label of the create row inside a Tokenizer's bottom sheet when `hasCreate` is set and the typed text matches no existing item. `{value}` is the typed text, e.g. `Create \"Marketing\"`."
1708
+ "description": "Label of a Tokenizer's create row, in the inline menu and the bottom sheet, when `hasCreate` is set and the typed text matches no existing item. `{value}` is the typed text, e.g. `Create \"Marketing\"`."
1697
1709
  },
1698
1710
  "@astryx.tokenizer.add": {
1699
1711
  "defaultMessage": "Add",
@@ -602,6 +602,9 @@
602
602
  "@astryx.table.noData": {
603
603
  "defaultMessage": "⟦Ñó ðàţà⟧"
604
604
  },
605
+ "@astryx.table.columnResize.resizeColumn": {
606
+ "defaultMessage": "⟦Řéšížé çółúɱñ {label}⟧"
607
+ },
605
608
  "@astryx.table.filter.allPlaceholder": {
606
609
  "defaultMessage": "⟦Àłł⟧"
607
610
  },
@@ -1175,6 +1178,9 @@
1175
1178
  "@astryx.tableTree.expandAllRows": {
1176
1179
  "defaultMessage": "⟦Éẋþàñð àłł řóŵš⟧"
1177
1180
  },
1181
+ "@astryx.textArea.counter": {
1182
+ "defaultMessage": "⟦{count, number}/{limit, number}⟧"
1183
+ },
1178
1184
  "@astryx.textArea.charactersRemaining": {
1179
1185
  "defaultMessage": "⟦{count, number} {count, plural, one {character} other {characters}} řéɱàíñíñĝ⟧"
1180
1186
  },
@@ -1232,6 +1238,9 @@
1232
1238
  "@astryx.multiSelector.selectionCount": {
1233
1239
  "defaultMessage": "⟦{count, number} óƒ {total, number} šéłéçţéð⟧"
1234
1240
  },
1241
+ "@astryx.multiSelector.selectedCount": {
1242
+ "defaultMessage": "⟦{count, plural, one {# selected} other {# selected}}⟧"
1243
+ },
1235
1244
  "@astryx.multiSelector.createOption": {
1236
1245
  "defaultMessage": "⟦Çřéàţé \"{query}\"⟧"
1237
1246
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.5",
3
+ "version": "0.7.6-canary.bb18c81",
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",
@@ -847,6 +847,9 @@
847
847
  "react-dom": ">=19.0.0"
848
848
  },
849
849
  "devDependencies": {
850
+ "@astryxdesign/a11y-spec": "0.7.6-canary.bb18c81",
851
+ "@astryxdesign/cli": "0.7.6-canary.bb18c81",
852
+ "@astryxdesign/interaction-harness": "0.7.6-canary.bb18c81",
850
853
  "@babel/cli": "^7.29.7",
851
854
  "@babel/core": "^7.29.7",
852
855
  "@babel/preset-react": "^7.29.7",
@@ -857,9 +860,7 @@
857
860
  "@testing-library/jest-dom": "^6.6.0",
858
861
  "@testing-library/react": "^16.3.2",
859
862
  "@types/babel__core": "^7.20.5",
860
- "tailwindcss": "^4.3.2",
861
- "@astryxdesign/cli": "0.7.5",
862
- "@astryxdesign/a11y-spec": "0.0.0"
863
+ "tailwindcss": "^4.3.2"
863
864
  },
864
865
  "dependencies": {
865
866
  "intl-messageformat": "^11.2.9"
@@ -18,18 +18,19 @@
18
18
 
19
19
  import React, {useId, useCallback} from 'react';
20
20
  import * as stylex from '@stylexjs/stylex';
21
- import {Dialog} from '../Dialog';
21
+ import {Dialog} from '../Dialog/Dialog';
22
22
  import {Layout} from '../Layout/Layout';
23
23
  import {LayoutContent} from '../Layout/LayoutContent';
24
24
  import {LayoutFooter} from '../Layout/LayoutFooter';
25
- import {Stack} from '../Stack';
25
+ import {Stack} from '../Stack/Stack';
26
26
  import {Heading} from '../Heading/Heading';
27
27
  import {Text} from '../Text/Text';
28
- import {Button, type ButtonVariant} from '../Button';
28
+ import {Button} from '../Button/Button';
29
+ import type {ButtonVariant} from '../Button';
29
30
  import type {BaseProps} from '../BaseProps';
30
- import {mergeProps} from '../utils';
31
+ import {mergeProps} from '../utils/mergeProps';
31
32
  import {themeProps} from '../utils/themeProps';
32
- import {useTranslator} from '../i18n';
33
+ import {useTranslator} from '../i18n/useTranslator';
33
34
  import {useMediaQuery} from '../hooks/useMediaQuery';
34
35
 
35
36
  const SMALL_SCREEN_QUERY = '(max-width: 640px)';
@@ -91,15 +91,27 @@ async function runState(
91
91
  });
92
92
  }
93
93
 
94
- test('every alert-dialog expectation is exercised by an AlertDialog state', async ({
95
- page,
96
- }) => {
97
- const cdp = await page.context().newCDPSession(page);
94
+ // Checking coverage runs every state again, which on a loaded runner nears
95
+ // the per-test timeout as one test. It runs as one serial part per state,
96
+ // sharing their results, then one check over all of them.
97
+ test.describe('every alert-dialog expectation is exercised by an AlertDialog state', () => {
98
+ test.describe.configure({mode: 'serial'});
98
99
  const results: BindingResult[] = [];
100
+
99
101
  for (const state of ALERT_DIALOG_BINDING_STATES) {
100
- results.push(await runState(page, cdp, state));
102
+ test(`runs ${state.id}`, async ({page}) => {
103
+ const cdp = await page.context().newCDPSession(page);
104
+ results.push(await runState(page, cdp, state));
105
+ });
101
106
  }
102
- expect(neverExercised(results)).toEqual([]);
107
+
108
+ test('no expectation is left unexercised', () => {
109
+ // Every part ran in this worker; this test alone proves nothing.
110
+ expect(results.map(result => result.state).sort()).toEqual(
111
+ ALERT_DIALOG_BINDING_STATES.map(state => state.id).sort(),
112
+ );
113
+ expect(neverExercised(results)).toEqual([]);
114
+ });
103
115
  });
104
116
 
105
117
  for (const state of ALERT_DIALOG_BINDING_STATES) {
@@ -122,17 +122,17 @@ Consumer migration instructions belong in consumer docs and release notes.
122
122
 
123
123
  ## Behavioral and layout contract
124
124
 
125
- | ID | Invariant | Basis |
126
- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
127
- | FR1 | The root is a relative, non-shrinking inline-flex box of the resolved size with the shape's radius; it carries the `avatar` target, which reflects `size` and `shape`. A clipped content area fills it with the same radius and suppresses text selection. | Current source and tests |
128
- | FR2 | The content area shows `src` while it has not failed, then `fallbackSrc` while it has not failed, then initials when the name yields any, then a default person icon at 60% of the box. Each image renders with empty `alt` and `object-fit: cover`. A failure is tracked per source, so a changed `src` or `fallbackSrc` is tried again. | Current source and tests |
129
- | FR3 | Initials are the first letter, digit, or emoji of the first and last words of the trimmed `name`, uppercased; leading punctuation in a word is skipped, words with none are dropped, one qualifying word yields one initial, and grapheme clusters are never split. A whitespace-only name counts as absent. Initials and the default icon paint on the `avatar-fallback` target (reflecting `size`) with `--color-neutral`, secondary text, body family, medium weight, and initials at 40% of the box. | Current source and tests |
130
- | FR4 | A truthy `status` renders in an absolutely positioned slot outside the clipped content area. On a circle it centers on the circle's edge at the bottom inline-end 45° point; on other shapes it sits at the bottom inline-end corner, offset outward by a quarter of its size. Placement mirrors in right-to-left documents. | Current source and tests |
131
- | FR5 | The resolved name is `alt`, else `name` (blank strings count as absent), composed with the status label as the translated "{name}, {status}" when both exist; a status label alone also names the avatar. The status label comes from a string `label` on the passed element, read on every render, and from a label an AvatarStatusDot reports at any wrapper depth, which wins and updates the root before paint without a second render. | Current source and tests |
132
- | FR6 | `href` renders the shared link component (or `as`) with `target` and `rel`; otherwise `onClick` renders a `button type="button"`; otherwise the root is a `div`. Link and button roots carry the `data-avatar-item` marker and the shared focus-visible outline; `onClick` attaches to the link or button. In development, an interactive avatar whose only name is a status label, or that has no name and no caller `aria-label`/`aria-labelledby`, logs a warning. | Current source and tests |
133
- | FR7 | The tooltip shows the trimmed custom string, else the meaningful `name`, placed above, on hover and keyboard focus; `tooltip={false}` or a blank result shows none. A static avatar with a tooltip and outside a group gets `tabIndex={0}` so keyboard users can reveal it. A custom string is added to `aria-describedby` after any caller value; the default name tooltip is not, because it repeats the name. Implementation gap: a grouped link or button avatar that is not the group's tab stop stops opening its tooltip on focus after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount (Current-state impact). | Current source and tests |
134
- | FR8 | Inside an AvatarGroup in the same layer (`spec:AST-038` FR7, FR8), the group's size and shape win over the avatar's own props; the root gains a 2px surface-colored ring outside its box and the group's negative inline-start overlap; a static avatar adds no tooltip tab stop, and link and button roots keep the `data-avatar-item` marker so they join the group's roving set. An avatar in a hover card, popover, or other layer opened from a group keeps its own size and shape, gets no ring or overlap, and keeps its tooltip tab stop; a link or button avatar there still carries the marker, so the group's roving focus, which selects marked elements in its DOM subtree, still includes it. | Current source and tests |
135
- | FR9 | `ref` reaches the root of every variant. Supported DOM, ARIA, data, and event props reach the root after the derived ARIA, so a caller `aria-label`, `role`, or `aria-hidden` wins (`architecture:public-component-api` INV5); `data-testid` reaches the root; `xstyle`, `className`, and `style` merge in that order after component styles. Implementation gap: on the static root the component sets `tabIndex` after caller props, so a caller `tabIndex` is replaced (Current-state impact). | Current source and tests |
125
+ | ID | Invariant | Basis |
126
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
127
+ | FR1 | The root is a relative, non-shrinking inline-flex box of the resolved size with the shape's radius; it carries the `avatar` target, which reflects `size` and `shape`. A clipped content area fills it with the same radius and suppresses text selection. | Current source and tests |
128
+ | FR2 | The content area shows `src` while it has not failed, then `fallbackSrc` while it has not failed, then initials when the name yields any, then a default person icon at 60% of the box. Each image renders with empty `alt` and `object-fit: cover`. A failure is tracked per source, so a changed `src` or `fallbackSrc` is tried again. | Current source and tests |
129
+ | FR3 | Initials are the first letter, digit, or emoji of the first and last words of the trimmed `name`, uppercased; leading punctuation in a word is skipped, words with none are dropped, one qualifying word yields one initial, and grapheme clusters are never split. A whitespace-only name counts as absent. Initials and the default icon paint on the `avatar-fallback` target (reflecting `size`) with `--color-neutral`, secondary text, body family, medium weight, and initials at 40% of the box. In forced colors, a lone Avatar's initials and default icon take the surrounding color (`currentColor`): the forced foreground by default, and the system color of a control that paints one around it (GrayText, HighlightText); inside an AvatarGroup they keep the forced foreground on the group's ring. | Current source and tests |
130
+ | FR4 | A truthy `status` renders in an absolutely positioned slot outside the clipped content area. On a circle it centers on the circle's edge at the bottom inline-end 45° point; on other shapes it sits at the bottom inline-end corner, offset outward by a quarter of its size. Placement mirrors in right-to-left documents. | Current source and tests |
131
+ | FR5 | The resolved name is `alt`, else `name` (blank strings count as absent), composed with the status label as the translated "{name}, {status}" when both exist; a status label alone also names the avatar. The status label comes from a string `label` on the passed element, read on every render, and from a label an AvatarStatusDot reports at any wrapper depth, which wins and updates the root before paint without a second render. | Current source and tests |
132
+ | FR6 | `href` renders the shared link component (or `as`) with `target` and `rel`; otherwise `onClick` renders a `button type="button"`; otherwise the root is a `div`. Link and button roots carry the `data-avatar-item` marker and the shared focus-visible outline; `onClick` attaches to the link or button. In development, an interactive avatar whose only name is a status label, or that has no name and no caller `aria-label`/`aria-labelledby`, logs a warning. | Current source and tests |
133
+ | FR7 | The tooltip shows the trimmed custom string, else the meaningful `name`, placed above, on hover and keyboard focus; `tooltip={false}` or a blank result shows none. A static avatar with a tooltip and outside a group gets `tabIndex={0}` so keyboard users can reveal it. A custom string is added to `aria-describedby` after any caller value; the default name tooltip is not, because it repeats the name. Implementation gap: a grouped link or button avatar that is not the group's tab stop stops opening its tooltip on focus after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount (Current-state impact). | Current source and tests |
134
+ | FR8 | Inside an AvatarGroup in the same layer (`spec:AST-038` FR7, FR8), the group's size and shape win over the avatar's own props; the root gains a 2px surface-colored ring outside its box and the group's negative inline-start overlap; a static avatar adds no tooltip tab stop, and link and button roots keep the `data-avatar-item` marker so they join the group's roving set. An avatar in a hover card, popover, or other layer opened from a group keeps its own size and shape, gets no ring or overlap, and keeps its tooltip tab stop; a link or button avatar there still carries the marker, so the group's roving focus, which selects marked elements in its DOM subtree, still includes it. | Current source and tests |
135
+ | FR9 | `ref` reaches the root of every variant. Supported DOM, ARIA, data, and event props reach the root after the derived ARIA, so a caller `aria-label`, `role`, or `aria-hidden` wins (`architecture:public-component-api` INV5); `data-testid` reaches the root; `xstyle`, `className`, and `style` merge in that order after component styles. Implementation gap: on the static root the component sets `tabIndex` after caller props, so a caller `tabIndex` is replaced (Current-state impact). | Current source and tests |
136
136
 
137
137
  ### Allowed variation
138
138
 
@@ -227,17 +227,17 @@ no root part, so no anatomy-theming block is declared.
227
227
 
228
228
  ## Verification map
229
229
 
230
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
231
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
232
- | FR1–FR3 | `Avatar.test.tsx` fallback, initials, retry, and target tests | Photo, failure, initials, punctuation, emoji, blank name; `lg` box | A wrong initials-or-icon fallback, split grapheme, lost retry, missing target, or wrong `lg` box size fails. Gap: no test sets `src` and `fallbackSrc` together, so the `src`-first order is unchecked, and sizes other than `lg` and numeric sizes are visible only in `Avatar.stories.tsx`. | `audit:Avatar/behavior` |
233
- | FR4 | `Avatar.test.tsx` shape and status tests; `Avatar.stories.tsx` status fixtures | Every shape with a status; RTL | A missing status slot fails. Gap: the tests assert presence only, so the slot's position, mirroring, and offset are visible only in the rendered fixtures. | `audit:Avatar/layout` |
234
- | FR5, AR1, AR2, PR1 | `Avatar.test.tsx` status-name and wrapper suites | Direct, wrapped, changing, removed labels | A lost composed name, extra render, or stale label fails. | `audit:Avatar/a11y` |
235
- | FR6, AR3 | `Avatar.test.tsx` interactivity suite | Link, button, static, dev warnings | A wrong root, a missing marker, or a missing warning fails. Gap: no test checks the focus-visible outline. | `audit:Avatar/behavior` |
236
- | FR7, AR4, AR5 | `Avatar.test.tsx` name-tooltip suite | Default, custom, disabled, blank | A missing tab stop, a duplicated description, or a lost ref fails. Gap: no test covers a grouped avatar's tooltip after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount. | `audit:Avatar/a11y` |
237
- | FR8 | `Avatar.test.tsx` group shape tests; `AvatarGroup.test.tsx` size-cascade, overlap, and roving suites; `layerScopedContext.test.tsx` AvatarGroup layer tests | Inside a group; in a layer opened from a group; outside a group | An avatar size or shape prop winning inside a group, a lost overlap, an interactive avatar missing from the roving set, or a static avatar in a layer losing its tooltip tab stop fails. Gap: no test asserts the ring, or size and shape inside a layer. | `audit:Avatar/behavior` |
238
- | FR9, AR6 | `Avatar.test.tsx` consumer-ARIA override suite | Static and interactive roots | A derived value replacing a caller `aria-label`, `role`, or `aria-hidden` fails. Gap: no test covers a caller `tabIndex`. | `audit:Avatar/a11y` |
239
- | AR1–AR3, AR5 | Accessibility-tree evidence (`spec:AST-009` FR3) | Named, decorative, status-composed, interactive | Gap: no accessibility-tree check proves the computed roles, names, or description; the unit tests query the DOM. | `audit:Avatar/a11y` |
240
- | AR7 | `Avatar.stories.tsx` interactive and size fixtures in a browser | Interactive avatars at every size | Gap: no automated target-size check; the undersized targets are the recorded gap. | `audit:Avatar/a11y` |
230
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
231
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
232
+ | FR1–FR3 | `Avatar.test.tsx` fallback, initials, retry, and target tests; `AvatarForcedColors.a11y.chromium.spec.ts` in real Chromium with forced colors emulated | Photo, failure, initials, punctuation, emoji, blank name; `lg` box; forced colors alone, inside GrayText and HighlightText, and grouped | A wrong initials-or-icon fallback, split grapheme, lost retry, missing target, wrong `lg` box size, or forced-colors initials that miss the surrounding system color (lone) or leave the forced foreground (grouped) fails. Gap: no test sets `src` and `fallbackSrc` together, so the `src`-first order is unchecked, and sizes other than `lg` and numeric sizes are visible only in `Avatar.stories.tsx`. | `audit:Avatar/behavior` |
233
+ | FR4 | `Avatar.test.tsx` shape and status tests; `Avatar.stories.tsx` status fixtures | Every shape with a status; RTL | A missing status slot fails. Gap: the tests assert presence only, so the slot's position, mirroring, and offset are visible only in the rendered fixtures. | `audit:Avatar/layout` |
234
+ | FR5, AR1, AR2, PR1 | `Avatar.test.tsx` status-name and wrapper suites | Direct, wrapped, changing, removed labels | A lost composed name, extra render, or stale label fails. | `audit:Avatar/a11y` |
235
+ | FR6, AR3 | `Avatar.test.tsx` interactivity suite | Link, button, static, dev warnings | A wrong root, a missing marker, or a missing warning fails. Gap: no test checks the focus-visible outline. | `audit:Avatar/behavior` |
236
+ | FR7, AR4, AR5 | `Avatar.test.tsx` name-tooltip suite | Default, custom, disabled, blank | A missing tab stop, a duplicated description, or a lost ref fails. Gap: no test covers a grouped avatar's tooltip after a re-render that re-attaches its root element: a new `status` element (an inline `status` prop creates one on every parent render), a changed `name`, `alt`, locale, caller ARIA, or caller `ref`, or a tooltip that turns on after mount. | `audit:Avatar/a11y` |
237
+ | FR8 | `Avatar.test.tsx` group shape tests; `AvatarGroup.test.tsx` size-cascade, overlap, and roving suites; `layerScopedContext.test.tsx` AvatarGroup layer tests | Inside a group; in a layer opened from a group; outside a group | An avatar size or shape prop winning inside a group, a lost overlap, an interactive avatar missing from the roving set, or a static avatar in a layer losing its tooltip tab stop fails. Gap: no test asserts the ring, or size and shape inside a layer. | `audit:Avatar/behavior` |
238
+ | FR9, AR6 | `Avatar.test.tsx` consumer-ARIA override suite | Static and interactive roots | A derived value replacing a caller `aria-label`, `role`, or `aria-hidden` fails. Gap: no test covers a caller `tabIndex`. | `audit:Avatar/a11y` |
239
+ | AR1–AR3, AR5 | Accessibility-tree evidence (`spec:AST-009` FR3) | Named, decorative, status-composed, interactive | Gap: no accessibility-tree check proves the computed roles, names, or description; the unit tests query the DOM. | `audit:Avatar/a11y` |
240
+ | AR7 | `Avatar.stories.tsx` interactive and size fixtures in a browser | Interactive avatars at every size | Gap: no automated target-size check; the undersized targets are the recorded gap. | `audit:Avatar/a11y` |
241
241
 
242
242
  ## Decision log
243
243
 
@@ -193,6 +193,19 @@ const styles = stylex.create({
193
193
  fontWeight: fontWeightVars['--font-weight-medium'],
194
194
  textTransform: 'uppercase',
195
195
  },
196
+ // After `fallback` on a lone Avatar: in forced colors its initials and
197
+ // default icon take the color around them, which the browser forces to
198
+ // CanvasText, so a control that paints itself disabled (GrayText) or
199
+ // selected (HighlightText) carries them with it; an
200
+ // authored color would be forced to CanvasText whatever the control shows.
201
+ // A grouped face keeps it: its ring's opaque fill is forced to Canvas, so
202
+ // only CanvasText is sure to read on it.
203
+ loneFallback: {
204
+ color: {
205
+ default: colorVars['--color-text-secondary'],
206
+ '@media (forced-colors: active)': 'currentColor',
207
+ },
208
+ },
196
209
  status: {
197
210
  position: 'absolute',
198
211
  },
@@ -784,6 +797,7 @@ export function Avatar({
784
797
  themeProps('avatar-fallback', {size: resolvedSize}),
785
798
  stylex.props(
786
799
  styles.fallback,
800
+ avatarGroup == null && styles.loneFallback,
787
801
  dynamicStyles.fontSize(numericSize),
788
802
  ),
789
803
  )}>
@@ -794,7 +808,10 @@ export function Avatar({
794
808
  <div
795
809
  {...mergeProps(
796
810
  themeProps('avatar-fallback', {size: resolvedSize}),
797
- stylex.props(styles.fallback),
811
+ stylex.props(
812
+ styles.fallback,
813
+ avatarGroup == null && styles.loneFallback,
814
+ ),
798
815
  )}>
799
816
  <DefaultIcon size={numericSize} />
800
817
  </div>