@astryxdesign/core 0.4.7 → 0.5.0-canary.055e75f

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 (597) hide show
  1. package/CHANGELOG.md +161 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +45 -20
  5. package/dist/AppShell/AppShell.d.ts.map +1 -1
  6. package/dist/AppShell/AppShell.js +3 -2
  7. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  8. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  9. package/dist/AspectRatio/AspectRatio.js +23 -4
  10. package/dist/Avatar/Avatar.d.ts +25 -1
  11. package/dist/Avatar/Avatar.d.ts.map +1 -1
  12. package/dist/Avatar/Avatar.js +58 -19
  13. package/dist/Avatar/index.d.ts +2 -2
  14. package/dist/Avatar/index.d.ts.map +1 -1
  15. package/dist/Avatar/index.js +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
  17. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  18. package/dist/AvatarGroup/AvatarGroup.js +8 -4
  19. package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
  20. package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
  21. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  22. package/dist/AvatarGroup/AvatarGroupOverflow.js +10 -6
  23. package/dist/Banner/Banner.d.ts +40 -15
  24. package/dist/Banner/Banner.d.ts.map +1 -1
  25. package/dist/Banner/Banner.js +51 -18
  26. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/dist/BottomSheet/BottomSheet.js +17 -4
  28. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  29. package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
  30. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  31. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  32. package/dist/Button/Button.d.ts.map +1 -1
  33. package/dist/Button/Button.js +11 -6
  34. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  35. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  36. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  37. package/dist/Calendar/Calendar.d.ts +3 -1
  38. package/dist/Calendar/Calendar.d.ts.map +1 -1
  39. package/dist/Calendar/Calendar.js +14 -15
  40. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  41. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  42. package/dist/Calendar/getInitialFocusDate.js +62 -0
  43. package/dist/Calendar/styles.d.ts +5 -14
  44. package/dist/Calendar/styles.d.ts.map +1 -1
  45. package/dist/Calendar/styles.js +15 -15
  46. package/dist/Card/Card.d.ts +6 -9
  47. package/dist/Card/Card.d.ts.map +1 -1
  48. package/dist/Card/Card.js +6 -6
  49. package/dist/Carousel/Carousel.d.ts.map +1 -1
  50. package/dist/Carousel/Carousel.js +3 -3
  51. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  52. package/dist/Chat/ChatComposerInput.js +2 -3
  53. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  54. package/dist/Chat/ChatLayout.js +3 -3
  55. package/dist/Chat/ChatMessageList.d.ts +22 -3
  56. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  57. package/dist/Chat/ChatMessageList.js +6 -3
  58. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  59. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  60. package/dist/Chat/ChatTokenizedText.js +11 -6
  61. package/dist/Chat/useChatComposerTokens.js +1 -1
  62. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  63. package/dist/CheckboxInput/CheckboxInput.js +10 -3
  64. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  65. package/dist/ClickableCard/ClickableCard.js +3 -3
  66. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  67. package/dist/CodeBlock/CodeBlock.js +15 -4
  68. package/dist/CodeBlock/highlightRanges.js +1 -1
  69. package/dist/CodeBlock/highlightStyles.js +13 -13
  70. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  71. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  72. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  73. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  74. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  75. package/dist/ContextMenu/ContextMenu.js +4 -3
  76. package/dist/DateInput/DateInput.d.ts +43 -0
  77. package/dist/DateInput/DateInput.d.ts.map +1 -1
  78. package/dist/DateInput/DateInput.js +22 -2
  79. package/dist/DateInput/NativeDateField.d.ts +10 -0
  80. package/dist/DateInput/NativeDateField.d.ts.map +1 -0
  81. package/dist/DateInput/NativeDateField.js +494 -0
  82. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  83. package/dist/DateInput/TouchDateField.js +82 -23
  84. package/dist/DateInput/index.d.ts +1 -1
  85. package/dist/DateInput/index.d.ts.map +1 -1
  86. package/dist/DateInput/nativeDateSegments.d.ts +22 -0
  87. package/dist/DateInput/nativeDateSegments.d.ts.map +1 -0
  88. package/dist/DateInput/nativeDateSegments.js +111 -0
  89. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  90. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  92. package/dist/DateTimeInput/index.d.ts +1 -1
  93. package/dist/DateTimeInput/index.d.ts.map +1 -1
  94. package/dist/Dialog/Dialog.d.ts +21 -1
  95. package/dist/Dialog/Dialog.d.ts.map +1 -1
  96. package/dist/Dialog/Dialog.js +110 -58
  97. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  98. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  99. package/dist/Field/FieldLabel.d.ts.map +1 -1
  100. package/dist/Field/FieldLabel.js +9 -1
  101. package/dist/FileInput/FileInput.d.ts.map +1 -1
  102. package/dist/FileInput/FileInput.js +3 -2
  103. package/dist/Heading/Heading.d.ts.map +1 -1
  104. package/dist/Heading/Heading.js +6 -2
  105. package/dist/HoverCard/HoverCard.d.ts +6 -0
  106. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  107. package/dist/HoverCard/HoverCard.js +104 -3
  108. package/dist/HoverCard/useHoverCard.d.ts +19 -2
  109. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  110. package/dist/HoverCard/useHoverCard.js +74 -26
  111. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  112. package/dist/Indicator/CheckboxIndicator.js +6 -2
  113. package/dist/Item/Item.d.ts.map +1 -1
  114. package/dist/Item/Item.js +4 -2
  115. package/dist/Kbd/Kbd.d.ts.map +1 -1
  116. package/dist/Kbd/Kbd.js +2 -19
  117. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  118. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  119. package/dist/Layer/LayerDepthContext.js +66 -0
  120. package/dist/Layer/index.d.ts +3 -0
  121. package/dist/Layer/index.d.ts.map +1 -1
  122. package/dist/Layer/index.js +4 -0
  123. package/dist/Layer/layerStack.d.ts +98 -0
  124. package/dist/Layer/layerStack.d.ts.map +1 -0
  125. package/dist/Layer/layerStack.js +306 -0
  126. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  127. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  128. package/dist/Layer/useLayerDismissal.js +91 -0
  129. package/dist/Lightbox/Lightbox.d.ts +1 -0
  130. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  131. package/dist/Lightbox/Lightbox.js +103 -81
  132. package/dist/Markdown/Markdown.d.ts.map +1 -1
  133. package/dist/Markdown/Markdown.js +9 -4
  134. package/dist/Markdown/index.d.ts +1 -1
  135. package/dist/Markdown/index.d.ts.map +1 -1
  136. package/dist/Markdown/parser.d.ts +38 -1
  137. package/dist/Markdown/parser.d.ts.map +1 -1
  138. package/dist/Markdown/parser.js +182 -32
  139. package/dist/MobileNav/MobileNav.d.ts +6 -1
  140. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  141. package/dist/MobileNav/MobileNav.js +61 -36
  142. package/dist/MultiSelector/MultiSelector.d.ts +32 -1
  143. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  144. package/dist/MultiSelector/MultiSelector.js +63 -11
  145. package/dist/MultiSelector/index.d.ts +1 -1
  146. package/dist/MultiSelector/index.d.ts.map +1 -1
  147. package/dist/NavIcon/NavIcon.d.ts.map +1 -1
  148. package/dist/NavIcon/NavIcon.js +5 -1
  149. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  150. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  151. package/dist/NumberInput/NumberInput.d.ts +6 -1
  152. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  153. package/dist/NumberInput/NumberInput.js +54 -104
  154. package/dist/NumberInput/numberInputCommit.d.ts +34 -0
  155. package/dist/NumberInput/numberInputCommit.d.ts.map +1 -0
  156. package/dist/NumberInput/numberInputCommit.js +71 -0
  157. package/dist/NumberInput/numberParser.d.ts +64 -0
  158. package/dist/NumberInput/numberParser.d.ts.map +1 -0
  159. package/dist/NumberInput/numberParser.js +350 -0
  160. package/dist/Outline/Outline.d.ts.map +1 -1
  161. package/dist/Outline/Outline.js +3 -2
  162. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  163. package/dist/OverflowList/OverflowList.js +3 -2
  164. package/dist/Overlay/Overlay.d.ts.map +1 -1
  165. package/dist/Overlay/Overlay.js +3 -2
  166. package/dist/Popover/usePopover.d.ts.map +1 -1
  167. package/dist/Popover/usePopover.js +20 -17
  168. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  169. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearch.js +19 -6
  171. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  172. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  173. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  174. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  175. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  176. package/dist/PowerSearch/PowerSearchValueEditor.js +110 -27
  177. package/dist/PowerSearch/types.d.ts +2 -0
  178. package/dist/PowerSearch/types.d.ts.map +1 -1
  179. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  180. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  181. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  182. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  183. package/dist/ProgressBar/ProgressBar.js +18 -5
  184. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  185. package/dist/Resizable/ResizeHandle.js +3 -2
  186. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  187. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  188. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  189. package/dist/SelectableCard/SelectableCard.js +3 -3
  190. package/dist/Selector/Selector.d.ts +17 -0
  191. package/dist/Selector/Selector.d.ts.map +1 -1
  192. package/dist/Selector/Selector.js +81 -14
  193. package/dist/SideNav/SideNav.d.ts.map +1 -1
  194. package/dist/SideNav/SideNav.js +4 -3
  195. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  196. package/dist/SideNav/SideNavHeading.js +8 -7
  197. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavItem.js +4 -3
  199. package/dist/Slider/Slider.d.ts.map +1 -1
  200. package/dist/Slider/Slider.js +37 -5
  201. package/dist/Spinner/Spinner.d.ts +3 -3
  202. package/dist/Spinner/Spinner.d.ts.map +1 -1
  203. package/dist/Spinner/Spinner.js +130 -78
  204. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  205. package/dist/StatusDot/StatusDot.js +6 -1
  206. package/dist/Stepper/Step.d.ts +140 -0
  207. package/dist/Stepper/Step.d.ts.map +1 -0
  208. package/dist/Stepper/Step.js +1045 -0
  209. package/dist/Stepper/StepStatus.d.ts +24 -0
  210. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  211. package/dist/Stepper/StepStatus.js +1 -0
  212. package/dist/Stepper/Stepper.d.ts +93 -0
  213. package/dist/Stepper/Stepper.d.ts.map +1 -0
  214. package/dist/Stepper/Stepper.js +184 -0
  215. package/dist/Stepper/StepperContext.d.ts +41 -0
  216. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  217. package/dist/Stepper/StepperContext.js +34 -0
  218. package/dist/Stepper/index.d.ts +8 -0
  219. package/dist/Stepper/index.d.ts.map +1 -0
  220. package/dist/Stepper/index.js +7 -0
  221. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  222. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  223. package/dist/Stepper/stepper.stylex.js +20 -0
  224. package/dist/Switch/Switch.d.ts.map +1 -1
  225. package/dist/Switch/Switch.js +8 -3
  226. package/dist/TabList/Tab.d.ts +15 -2
  227. package/dist/TabList/Tab.d.ts.map +1 -1
  228. package/dist/TabList/Tab.js +32 -7
  229. package/dist/TabList/TabList.d.ts +46 -7
  230. package/dist/TabList/TabList.d.ts.map +1 -1
  231. package/dist/TabList/TabList.js +353 -23
  232. package/dist/TabList/TabListContext.d.ts +11 -1
  233. package/dist/TabList/TabListContext.d.ts.map +1 -1
  234. package/dist/TabList/TabListContext.js +11 -2
  235. package/dist/TabList/TabMenu.d.ts.map +1 -1
  236. package/dist/TabList/TabMenu.js +3 -2
  237. package/dist/TabList/index.d.ts +2 -2
  238. package/dist/TabList/index.d.ts.map +1 -1
  239. package/dist/Table/BaseTable.d.ts.map +1 -1
  240. package/dist/Table/BaseTable.js +10 -2
  241. package/dist/Table/Table.d.ts.map +1 -1
  242. package/dist/Table/Table.js +3 -6
  243. package/dist/Table/TableRow.d.ts +16 -5
  244. package/dist/Table/TableRow.d.ts.map +1 -1
  245. package/dist/Table/TableRow.js +16 -5
  246. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  247. package/dist/Table/plugins/filtering/useTableFiltering.js +73 -4
  248. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  249. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  250. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  251. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  252. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  253. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  254. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  255. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  256. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  257. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  258. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  259. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  260. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  261. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
  262. package/dist/Table/types.d.ts +22 -4
  263. package/dist/Table/types.d.ts.map +1 -1
  264. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  265. package/dist/Table/useBaseTablePlugins.js +5 -0
  266. package/dist/Text/Text.d.ts.map +1 -1
  267. package/dist/Text/Text.js +6 -2
  268. package/dist/TextArea/TextArea.d.ts.map +1 -1
  269. package/dist/TextArea/TextArea.js +9 -3
  270. package/dist/TextInput/TextInput.d.ts.map +1 -1
  271. package/dist/TextInput/TextInput.js +3 -2
  272. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  273. package/dist/TimeInput/TimeInput.js +3 -2
  274. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  275. package/dist/Timestamp/Timestamp.js +5 -3
  276. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  277. package/dist/Toast/ToastViewport.js +27 -8
  278. package/dist/Token/TokenLink.d.ts.map +1 -1
  279. package/dist/Token/TokenLink.js +2 -2
  280. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  281. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  282. package/dist/Tokenizer/Tokenizer.js +2 -0
  283. package/dist/Tooltip/Tooltip.d.ts +5 -0
  284. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  285. package/dist/Tooltip/useTooltip.d.ts +5 -0
  286. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  287. package/dist/Tooltip/useTooltip.js +39 -24
  288. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  289. package/dist/TopNav/TopNavHeading.js +5 -4
  290. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  291. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  292. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  293. package/dist/TopNav/TopNavMenu.js +4 -3
  294. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  295. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  296. package/dist/Typeahead/BaseTypeahead.js +68 -29
  297. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  298. package/dist/Typeahead/Typeahead.js +3 -2
  299. package/dist/astryx.css +98 -7
  300. package/dist/hooks/index.d.ts +1 -0
  301. package/dist/hooks/index.d.ts.map +1 -1
  302. package/dist/hooks/index.js +1 -0
  303. package/dist/hooks/useFocusTrap.d.ts +7 -4
  304. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  305. package/dist/hooks/useFocusTrap.js +60 -95
  306. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  307. package/dist/hooks/useHotkeys.js +3 -18
  308. package/dist/hooks/useMergedRefs.d.ts +18 -0
  309. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  310. package/dist/hooks/useMergedRefs.js +25 -0
  311. package/dist/index.d.ts +1 -0
  312. package/dist/index.d.ts.map +1 -1
  313. package/dist/index.js +1 -0
  314. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  315. package/dist/theme/derivedVarRegistry.js +24 -4
  316. package/dist/theme/syntax/tokens.d.ts +1 -1
  317. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  318. package/dist/theme/syntax/tokens.js +5 -2
  319. package/dist/utils/groupItems.d.ts +5 -1
  320. package/dist/utils/groupItems.d.ts.map +1 -1
  321. package/dist/utils/groupItems.js +9 -9
  322. package/dist/utils/index.d.ts +1 -1
  323. package/dist/utils/index.d.ts.map +1 -1
  324. package/dist/utils/isApplePlatform.d.ts +18 -0
  325. package/dist/utils/isApplePlatform.d.ts.map +1 -0
  326. package/dist/utils/isApplePlatform.js +37 -0
  327. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  328. package/dist/utils/sharedResizeObserver.js +10 -1
  329. package/dist/utils/timeParser.d.ts +1 -1
  330. package/dist/utils/timeParser.d.ts.map +1 -1
  331. package/dist/utils/timeParser.js +26 -12
  332. package/locales/en.json +154 -10
  333. package/locales/pseudo.json +108 -0
  334. package/package.json +8 -3
  335. package/src/AlertDialog/AlertDialog.doc.mjs +114 -30
  336. package/src/AlertDialog/AlertDialog.test.tsx +211 -2
  337. package/src/AlertDialog/AlertDialog.tsx +62 -20
  338. package/src/AppShell/AppShell.tsx +3 -2
  339. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  340. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  341. package/src/AspectRatio/AspectRatio.tsx +40 -1
  342. package/src/Avatar/Avatar.doc.mjs +14 -3
  343. package/src/Avatar/Avatar.test.tsx +97 -0
  344. package/src/Avatar/Avatar.tsx +125 -52
  345. package/src/Avatar/index.ts +2 -2
  346. package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
  347. package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
  348. package/src/AvatarGroup/AvatarGroup.tsx +15 -6
  349. package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
  350. package/src/AvatarGroup/AvatarGroupOverflow.tsx +10 -4
  351. package/src/Banner/Banner.doc.mjs +13 -11
  352. package/src/Banner/Banner.test.tsx +102 -15
  353. package/src/Banner/Banner.tsx +72 -25
  354. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  355. package/src/BottomSheet/BottomSheet.tsx +17 -4
  356. package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
  357. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  358. package/src/Button/Button.doc.mjs +24 -1
  359. package/src/Button/Button.test.tsx +32 -4
  360. package/src/Button/Button.tsx +12 -5
  361. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  362. package/src/ButtonGroup/ButtonGroup.test.tsx +404 -1
  363. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  364. package/src/Calendar/Calendar.doc.mjs +4 -3
  365. package/src/Calendar/Calendar.test.tsx +103 -0
  366. package/src/Calendar/Calendar.tsx +19 -16
  367. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  368. package/src/Calendar/getInitialFocusDate.ts +93 -0
  369. package/src/Calendar/styles.ts +49 -31
  370. package/src/Card/Card.doc.mjs +10 -7
  371. package/src/Card/Card.test.tsx +44 -19
  372. package/src/Card/Card.tsx +12 -32
  373. package/src/Carousel/Carousel.tsx +3 -2
  374. package/src/Chat/ChatComposerInput.tsx +5 -1
  375. package/src/Chat/ChatLayout.tsx +3 -2
  376. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  377. package/src/Chat/ChatMessageList.test.tsx +46 -0
  378. package/src/Chat/ChatMessageList.tsx +28 -4
  379. package/src/Chat/ChatTokenizedText.tsx +17 -10
  380. package/src/Chat/useChatComposerTokens.ts +1 -1
  381. package/src/CheckboxInput/CheckboxInput.doc.mjs +2 -0
  382. package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
  383. package/src/CheckboxInput/CheckboxInput.tsx +8 -2
  384. package/src/ClickableCard/ClickableCard.tsx +3 -2
  385. package/src/CodeBlock/CodeBlock.doc.mjs +16 -4
  386. package/src/CodeBlock/CodeBlock.test.tsx +37 -0
  387. package/src/CodeBlock/CodeBlock.tsx +20 -4
  388. package/src/CodeBlock/highlightRanges.ts +1 -1
  389. package/src/CodeBlock/highlightStyles.ts +13 -13
  390. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  391. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  392. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  393. package/src/ContextMenu/ContextMenu.tsx +4 -3
  394. package/src/DateInput/DateInput.doc.mjs +16 -0
  395. package/src/DateInput/DateInput.tsx +55 -2
  396. package/src/DateInput/DateInputTouch.test.tsx +146 -32
  397. package/src/DateInput/NativeDateField.test.tsx +635 -0
  398. package/src/DateInput/NativeDateField.tsx +641 -0
  399. package/src/DateInput/TouchDateField.tsx +108 -24
  400. package/src/DateInput/index.ts +1 -0
  401. package/src/DateInput/nativeDateSegments.ts +127 -0
  402. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  403. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  404. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  405. package/src/DateTimeInput/index.ts +1 -0
  406. package/src/Dialog/Dialog.doc.mjs +8 -3
  407. package/src/Dialog/Dialog.test.tsx +429 -2
  408. package/src/Dialog/Dialog.tsx +133 -58
  409. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  410. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  411. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  412. package/src/Field/Field.doc.mjs +13 -2
  413. package/src/Field/FieldLabel.test.tsx +29 -0
  414. package/src/Field/FieldLabel.tsx +7 -0
  415. package/src/FileInput/FileInput.tsx +3 -2
  416. package/src/Heading/Heading.test.tsx +23 -1
  417. package/src/Heading/Heading.tsx +6 -2
  418. package/src/HoverCard/HoverCard.doc.mjs +13 -2
  419. package/src/HoverCard/HoverCard.test.tsx +154 -4
  420. package/src/HoverCard/HoverCard.tsx +143 -3
  421. package/src/HoverCard/useHoverCard.tsx +104 -33
  422. package/src/Indicator/CheckboxIndicator.tsx +14 -8
  423. package/src/Indicator/Indicator.doc.mjs +2 -0
  424. package/src/Item/Item.tsx +5 -4
  425. package/src/Kbd/Kbd.tsx +2 -19
  426. package/src/Layer/LayerDepthContext.tsx +62 -0
  427. package/src/Layer/index.ts +9 -0
  428. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  429. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  430. package/src/Layer/layerStack.ts +362 -0
  431. package/src/Layer/useLayer.test.tsx +62 -14
  432. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  433. package/src/Layer/useLayerDismissal.ts +173 -0
  434. package/src/Layout/Layout.doc.mjs +2 -2
  435. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  436. package/src/Lightbox/Lightbox.tsx +124 -102
  437. package/src/Link/Link.doc.mjs +39 -0
  438. package/src/Markdown/Markdown.doc.mjs +5 -0
  439. package/src/Markdown/Markdown.tsx +9 -4
  440. package/src/Markdown/incremental.test.ts +16 -2
  441. package/src/Markdown/index.ts +1 -0
  442. package/src/Markdown/parser.perf.test.ts +96 -1
  443. package/src/Markdown/parser.test.ts +178 -2
  444. package/src/Markdown/parser.ts +253 -32
  445. package/src/MobileNav/MobileNav.tsx +62 -36
  446. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  447. package/src/MultiSelector/MultiSelector.doc.mjs +49 -1
  448. package/src/MultiSelector/MultiSelector.test.tsx +277 -0
  449. package/src/MultiSelector/MultiSelector.tsx +142 -11
  450. package/src/MultiSelector/index.ts +1 -0
  451. package/src/NavIcon/NavIcon.doc.mjs +8 -2
  452. package/src/NavIcon/NavIcon.test.tsx +9 -0
  453. package/src/NavIcon/NavIcon.tsx +5 -1
  454. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  455. package/src/NumberInput/NumberInput.doc.mjs +31 -7
  456. package/src/NumberInput/NumberInput.test.tsx +436 -8
  457. package/src/NumberInput/NumberInput.tsx +62 -114
  458. package/src/NumberInput/numberInputCommit.test.ts +72 -0
  459. package/src/NumberInput/numberInputCommit.ts +103 -0
  460. package/src/NumberInput/numberParser.docblock.test.ts +234 -0
  461. package/src/NumberInput/numberParser.test.ts +222 -0
  462. package/src/NumberInput/numberParser.ts +403 -0
  463. package/src/Outline/Outline.tsx +3 -2
  464. package/src/OverflowList/OverflowList.tsx +3 -2
  465. package/src/Overlay/Overlay.tsx +3 -2
  466. package/src/Pagination/Pagination.test.tsx +3 -2
  467. package/src/Popover/usePopover.tsx +31 -28
  468. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  469. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  470. package/src/PowerSearch/PowerSearch.tsx +34 -5
  471. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +288 -0
  472. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  473. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +111 -1
  474. package/src/PowerSearch/PowerSearchValueEditor.tsx +124 -10
  475. package/src/PowerSearch/types.ts +2 -0
  476. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  477. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  478. package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
  479. package/src/ProgressBar/ProgressBar.test.tsx +24 -0
  480. package/src/ProgressBar/ProgressBar.tsx +28 -8
  481. package/src/Resizable/ResizeHandle.tsx +3 -2
  482. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  483. package/src/SelectableCard/SelectableCard.tsx +3 -2
  484. package/src/Selector/Selector.doc.mjs +26 -1
  485. package/src/Selector/Selector.test.tsx +311 -1
  486. package/src/Selector/Selector.tsx +139 -16
  487. package/src/SideNav/SideNav.doc.mjs +6 -6
  488. package/src/SideNav/SideNav.tsx +4 -2
  489. package/src/SideNav/SideNavHeading.tsx +12 -12
  490. package/src/SideNav/SideNavItem.tsx +4 -2
  491. package/src/Slider/Slider.test.tsx +106 -1
  492. package/src/Slider/Slider.tsx +55 -3
  493. package/src/Spinner/Spinner.test.tsx +147 -1
  494. package/src/Spinner/Spinner.tsx +120 -101
  495. package/src/StatusDot/StatusDot.doc.mjs +8 -2
  496. package/src/StatusDot/StatusDot.test.tsx +9 -0
  497. package/src/StatusDot/StatusDot.tsx +7 -1
  498. package/src/Stepper/Step.doc.mjs +98 -0
  499. package/src/Stepper/Step.tsx +1638 -0
  500. package/src/Stepper/StepStatus.ts +26 -0
  501. package/src/Stepper/Stepper.doc.mjs +370 -0
  502. package/src/Stepper/Stepper.test.tsx +1024 -0
  503. package/src/Stepper/Stepper.tsx +258 -0
  504. package/src/Stepper/StepperContext.ts +71 -0
  505. package/src/Stepper/index.ts +18 -0
  506. package/src/Stepper/stepper.stylex.ts +19 -0
  507. package/src/Switch/Switch.doc.mjs +2 -0
  508. package/src/Switch/Switch.test.tsx +10 -0
  509. package/src/Switch/Switch.tsx +6 -2
  510. package/src/TabList/Tab.doc.mjs +13 -1
  511. package/src/TabList/Tab.tsx +67 -7
  512. package/src/TabList/TabList.doc.mjs +22 -6
  513. package/src/TabList/TabList.test.tsx +559 -0
  514. package/src/TabList/TabList.tsx +490 -22
  515. package/src/TabList/TabListContext.ts +13 -2
  516. package/src/TabList/TabMenu.tsx +3 -2
  517. package/src/TabList/index.ts +6 -2
  518. package/src/Table/BaseTable.tsx +12 -4
  519. package/src/Table/Table.doc.mjs +9 -2
  520. package/src/Table/Table.test.tsx +132 -14
  521. package/src/Table/Table.tsx +3 -8
  522. package/src/Table/TableBody.doc.mjs +40 -0
  523. package/src/Table/TableFooter.doc.mjs +40 -0
  524. package/src/Table/TableHeader.doc.mjs +40 -0
  525. package/src/Table/TableRow.tsx +16 -5
  526. package/src/Table/plugins/filtering/useTableFiltering.test.tsx +44 -6
  527. package/src/Table/plugins/filtering/useTableFiltering.tsx +100 -4
  528. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  529. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  530. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  531. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  532. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  533. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  534. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  535. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  536. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  537. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  538. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
  539. package/src/Table/types.ts +22 -4
  540. package/src/Table/useBaseTablePlugins.ts +5 -0
  541. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  542. package/src/Table/useTablePagination.doc.mjs +7 -0
  543. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  544. package/src/Table/useTableSelection.doc.mjs +31 -0
  545. package/src/Text/Text.test.tsx +23 -1
  546. package/src/Text/Text.tsx +6 -2
  547. package/src/TextArea/TextArea.doc.mjs +8 -2
  548. package/src/TextArea/TextArea.test.tsx +11 -0
  549. package/src/TextArea/TextArea.tsx +15 -8
  550. package/src/TextInput/TextInput.doc.mjs +10 -0
  551. package/src/TextInput/TextInput.tsx +3 -2
  552. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  553. package/src/TimeInput/TimeInput.tsx +2 -2
  554. package/src/Timestamp/Timestamp.test.tsx +18 -0
  555. package/src/Timestamp/Timestamp.tsx +4 -2
  556. package/src/Toast/Toast.doc.mjs +6 -0
  557. package/src/Toast/ToastViewport.tsx +17 -1
  558. package/src/Token/TokenLink.tsx +2 -2
  559. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  560. package/src/Tokenizer/Tokenizer.tsx +4 -0
  561. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  562. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  563. package/src/Tooltip/Tooltip.test.tsx +32 -0
  564. package/src/Tooltip/Tooltip.tsx +5 -0
  565. package/src/Tooltip/useTooltip.tsx +47 -24
  566. package/src/TopNav/TopNavHeading.tsx +5 -4
  567. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  568. package/src/TopNav/TopNavMenu.tsx +4 -3
  569. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  570. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  571. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  572. package/src/Typeahead/Typeahead.test.tsx +49 -0
  573. package/src/Typeahead/Typeahead.tsx +3 -2
  574. package/src/hooks/index.ts +2 -0
  575. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  576. package/src/hooks/useFocusTrap.test.tsx +14 -0
  577. package/src/hooks/useFocusTrap.ts +62 -111
  578. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  579. package/src/hooks/useHotkeys.ts +3 -18
  580. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  581. package/src/hooks/useMergedRefs.test.tsx +62 -0
  582. package/src/hooks/useMergedRefs.ts +36 -0
  583. package/src/index.ts +1 -0
  584. package/src/raw-import.d.ts +14 -0
  585. package/src/theme/MediaTheme.doc.mjs +5 -5
  586. package/src/theme/derivedVarRegistry.test.ts +19 -3
  587. package/src/theme/derivedVarRegistry.ts +23 -4
  588. package/src/theme/generateThemeRules.test.ts +37 -0
  589. package/src/theme/syntax/tokens.ts +5 -2
  590. package/src/utils/groupItems.ts +12 -6
  591. package/src/utils/index.ts +1 -1
  592. package/src/utils/isApplePlatform.test.ts +61 -0
  593. package/src/utils/isApplePlatform.ts +37 -0
  594. package/src/utils/sharedResizeObserver.ts +10 -2
  595. package/src/utils/timeParser.test.ts +6 -1
  596. package/src/utils/timeParser.ts +35 -13
  597. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -648,6 +648,25 @@ interface SelectorPropsBase<
648
648
  */
649
649
  searchPlaceholder?: string;
650
650
 
651
+ /**
652
+ * Content shown in the panel when there are no options to show, and
653
+ * announced in a polite live region when the panel opens. Not shown while
654
+ * `isLoading` — the options have not arrived yet.
655
+ * @default 'No options'
656
+ */
657
+ emptyText?: ReactNode;
658
+
659
+ /**
660
+ * Content shown in the panel when a search query matches no options, and
661
+ * announced in a polite live region at the same time.
662
+ *
663
+ * The panel message is `role="presentation"`, so the live region is the only
664
+ * route to assistive tech: a string is announced verbatim, a richer node
665
+ * falls back to the default text since it cannot be spoken.
666
+ * @default 'No results found'
667
+ */
668
+ emptySearchText?: ReactNode;
669
+
651
670
  /**
652
671
  * Position placement relative to the trigger.
653
672
  *
@@ -794,6 +813,8 @@ export function Selector<T extends SelectorOptionType>(
794
813
  indicatorPosition = 'end',
795
814
  hasSearch = false,
796
815
  searchPlaceholder: searchPlaceholderFromProps,
816
+ emptyText: emptyTextFromProps,
817
+ emptySearchText: emptySearchTextFromProps,
797
818
  placement,
798
819
  isDefaultOpen = false,
799
820
  'data-testid': testId,
@@ -802,12 +823,16 @@ export function Selector<T extends SelectorOptionType>(
802
823
  className,
803
824
  style,
804
825
  hasClear: hasClearProp,
826
+ id,
805
827
  ...rest
806
828
  } = props as SelectorPropsClearable<T>;
807
829
  const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
808
830
  const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
809
831
  const searchPlaceholder =
810
832
  searchPlaceholderFromProps ?? t('@astryx.selector.searchPlaceholder');
833
+ const emptyText = emptyTextFromProps ?? t('@astryx.selector.empty');
834
+ const emptySearchText =
835
+ emptySearchTextFromProps ?? t('@astryx.selector.emptySearchResults');
811
836
  const hasClear = hasClearProp === true;
812
837
  const size = useSize(sizeProp, 'md');
813
838
  const effectiveStatusVariant =
@@ -817,7 +842,10 @@ export function Selector<T extends SelectorOptionType>(
817
842
 
818
843
  // Normalize null to undefined for internal use (null is the clear sentinel)
819
844
  const normalizedValue = value === null ? undefined : value;
820
- const triggerId = useId();
845
+ const generatedTriggerId = useId();
846
+ // A caller's `id` lands on the trigger either way, so the internal identity
847
+ // has to be that same value or the label and listbox point at nothing.
848
+ const triggerId = id ?? generatedTriggerId;
821
849
  const listboxId = useId();
822
850
  const descriptionId = useId();
823
851
  const statusMessageId = useId();
@@ -831,6 +859,9 @@ export function Selector<T extends SelectorOptionType>(
831
859
  const inputGroup = useInputGroup();
832
860
 
833
861
  const [searchQuery, setSearchQuery] = useState('');
862
+ // Mirrors searchQuery for the seed path, which runs before focus reaches the
863
+ // input and so cannot read the rendered state.
864
+ const searchQueryRef = useRef('');
834
865
  // A typed query shows the search row's clear (✕) button, which becomes
835
866
  // the next tab stop after the search input.
836
867
  const hasQuery = searchQuery.length > 0;
@@ -840,6 +871,17 @@ export function Selector<T extends SelectorOptionType>(
840
871
  const isBusy = isLoading || optimisticValue !== normalizedValue;
841
872
  const announce = useAnnounce();
842
873
 
874
+ // The panel's empty message is role="presentation" and reaches assistive tech
875
+ // only through this live region, so the region has to speak whatever the
876
+ // panel shows. A ReactNode override cannot be spoken; fall back to the
877
+ // catalog copy for that case rather than announcing nothing.
878
+ const emptyAnnouncement =
879
+ typeof emptyText === 'string' ? emptyText : t('@astryx.selector.empty');
880
+ const emptySearchAnnouncement =
881
+ typeof emptySearchText === 'string'
882
+ ? emptySearchText
883
+ : t('@astryx.selector.emptySearchResults');
884
+
843
885
  // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
844
886
  // tooltip listeners attach to the trigger container (which already exists)
845
887
  // and the trigger button stays perceivable via aria-disabled instead of the
@@ -907,6 +949,7 @@ export function Selector<T extends SelectorOptionType>(
907
949
  // Layer for dropdown positioning
908
950
  const handleLayerHide = useCallback(() => {
909
951
  setSearchQuery('');
952
+ searchQueryRef.current = '';
910
953
  resetTypeaheadRef.current();
911
954
  // Clear any lingering result count when the popover closes so stale status
912
955
  // text does not linger in the a11y tree.
@@ -935,28 +978,76 @@ export function Selector<T extends SelectorOptionType>(
935
978
  // eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
936
979
  }, []);
937
980
 
938
- // Announce the filtered result count from the query-change handler (matching
981
+ // Announce the filtered result count from the query-change handlers (matching
939
982
  // BaseTypeahead) rather than a reactive effect: computing the count for the
940
983
  // next query here fires the announcement exactly once per keystroke and does
941
- // not re-speak on unrelated re-renders.
942
- const handleSearchChange = useCallback(
984
+ // not re-speak on unrelated re-renders. Split out from handleSearchChange so
985
+ // the type-to-open seed below reaches the same live region — a query the user
986
+ // typed is a query however it arrived.
987
+ const announceSearchResults = useCallback(
943
988
  (nextQuery: string) => {
944
- setSearchQuery(nextQuery);
945
989
  if (nextQuery.length === 0) {
946
990
  // Emptying the query clears the region rather than announcing a count.
947
991
  announce('');
948
992
  return;
949
993
  }
994
+ // While isLoading the panel deliberately shows nothing, so announcing a
995
+ // result would put a claim in the one channel the screen has gone quiet
996
+ // for.
997
+ if (isLoading) {
998
+ announce('');
999
+ return;
1000
+ }
950
1001
  const count = filterOptionsByQuery(selectableItems, nextQuery).length;
951
1002
  announce(
952
1003
  count === 0
953
- ? t('@astryx.selector.emptySearchResults')
1004
+ ? emptySearchAnnouncement
954
1005
  : t('@astryx.selector.resultCount', {count}),
955
1006
  );
956
1007
  },
957
- [announce, selectableItems, t],
1008
+ [announce, isLoading, selectableItems, emptySearchAnnouncement, t],
1009
+ );
1010
+
1011
+ const handleSearchChange = useCallback(
1012
+ (nextQuery: string) => {
1013
+ setSearchQuery(nextQuery);
1014
+ searchQueryRef.current = nextQuery;
1015
+ announceSearchResults(nextQuery);
1016
+ },
1017
+ [announceSearchResults],
958
1018
  );
959
1019
 
1020
+ // The panel's empty message is role="presentation", so this region is the
1021
+ // only route to assistive tech. It has to watch the STATE rather than the
1022
+ // open event: the panel can become empty either on open or when a fetch
1023
+ // lands with nothing in it, and an open-only announcement leaves the second
1024
+ // case silent while the message sits on screen. The ref makes it fire once
1025
+ // per arrival at that state rather than on every re-render.
1026
+ const announcedEmptyRef = useRef<string | null>(null);
1027
+ useEffect(() => {
1028
+ const isPanelEmpty =
1029
+ popover.isOpen &&
1030
+ !isLoading &&
1031
+ searchQuery === '' &&
1032
+ selectableItems.length === 0;
1033
+ if (!isPanelEmpty) {
1034
+ announcedEmptyRef.current = null;
1035
+ return;
1036
+ }
1037
+ if (announcedEmptyRef.current === emptyAnnouncement) {
1038
+ return;
1039
+ }
1040
+ announcedEmptyRef.current = emptyAnnouncement;
1041
+ announce(emptyAnnouncement);
1042
+ }, [
1043
+ popover.isOpen,
1044
+ isLoading,
1045
+ searchQuery,
1046
+ selectableItems.length,
1047
+ emptyAnnouncement,
1048
+ announce,
1049
+ ]);
1050
+
960
1051
  // Calculate offset to position selected item over trigger. Explicit
961
1052
  // placement opts out of the selector-specific overlay behavior and uses the
962
1053
  // standard layer positioning API instead.
@@ -992,10 +1083,20 @@ export function Selector<T extends SelectorOptionType>(
992
1083
  }, [onChange, changeAction, startTransition, setOptimisticValue]);
993
1084
 
994
1085
  // Type-to-find appends to the query rather than replacing it: characters
995
- // typed before focus reaches the search input must not be dropped.
996
- const appendSearchQuery = useCallback((char: string) => {
997
- setSearchQuery(query => query + char);
998
- }, []);
1086
+ // typed before focus reaches the search input must not be dropped. The ref
1087
+ // is what makes that safe without a state updater — it advances
1088
+ // synchronously, so a second character seeded in the same tick still sees
1089
+ // the first, and the announcement can be computed here rather than inside a
1090
+ // setState callback.
1091
+ const appendSearchQuery = useCallback(
1092
+ (char: string) => {
1093
+ const nextQuery = searchQueryRef.current + char;
1094
+ searchQueryRef.current = nextQuery;
1095
+ setSearchQuery(nextQuery);
1096
+ announceSearchResults(nextQuery);
1097
+ },
1098
+ [announceSearchResults],
1099
+ );
999
1100
 
1000
1101
  const commitValue = useCallback(
1001
1102
  (newValue: string) => {
@@ -1313,8 +1414,11 @@ export function Selector<T extends SelectorOptionType>(
1313
1414
  const renderOptions = useCallback(() => {
1314
1415
  const isSearching = hasSearch && Boolean(searchQuery);
1315
1416
 
1316
- // Nothing matched across every group/option: show the empty state.
1317
- if (isSearching && filteredItems.length === 0) {
1417
+ // Nothing to show — either the query matched nothing, or no options were
1418
+ // given at all. Both render the same slot with different copy. While
1419
+ // isLoading the options have not arrived yet, so asserting either would be
1420
+ // a claim the component cannot make; the trigger's spinner covers it.
1421
+ if (filteredItems.length === 0 && !isLoading) {
1318
1422
  // role="presentation" keeps the message out of the listbox's
1319
1423
  // accessibility tree (role="listbox" only permits option/group
1320
1424
  // children); the no-results outcome is announced via the
@@ -1327,7 +1431,7 @@ export function Selector<T extends SelectorOptionType>(
1327
1431
  themeProps('selector-empty-state'),
1328
1432
  stylex.props(styles.emptyState),
1329
1433
  )}>
1330
- No results found
1434
+ {isSearching ? emptySearchText : emptyText}
1331
1435
  </div>,
1332
1436
  ];
1333
1437
  }
@@ -1344,7 +1448,17 @@ export function Selector<T extends SelectorOptionType>(
1344
1448
  if (isSearching) {
1345
1449
  continue;
1346
1450
  }
1347
- elements.push(<Divider key={`divider-${i}`} xstyle={styles.divider} />);
1451
+ // role="listbox" only permits option/group children; the divider
1452
+ // carries no information the options don't, so it's hidden from the
1453
+ // accessibility tree entirely rather than exposing role="separator"
1454
+ // as a disallowed listbox child (axe aria-required-children).
1455
+ elements.push(
1456
+ <Divider
1457
+ key={`divider-${i}`}
1458
+ aria-hidden="true"
1459
+ xstyle={styles.divider}
1460
+ />,
1461
+ );
1348
1462
  } else if (isSection(option)) {
1349
1463
  const sectionItems: ReactNode[] = [];
1350
1464
  for (const opt of option.options) {
@@ -1391,7 +1505,16 @@ export function Selector<T extends SelectorOptionType>(
1391
1505
  }
1392
1506
 
1393
1507
  return elements;
1394
- }, [options, renderItem, hasSearch, searchQuery, filteredItems]);
1508
+ }, [
1509
+ options,
1510
+ renderItem,
1511
+ hasSearch,
1512
+ searchQuery,
1513
+ filteredItems,
1514
+ isLoading,
1515
+ emptyText,
1516
+ emptySearchText,
1517
+ ]);
1395
1518
 
1396
1519
  // The detached message box renders its own leading status icon, so the
1397
1520
  // on-field icon would duplicate it — keep the chevron indicator instead.
@@ -131,8 +131,8 @@ export const docs = {
131
131
  bestPractices: [
132
132
  {guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
133
133
  {guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
134
- {guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
135
- {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
134
+ {guidance: true, description: 'Mark the current page with isSelected: it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
135
+ {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short; they are the accessible name in both expanded and icon-only modes.'},
136
136
  {guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
137
137
  {guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
138
138
  {guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
@@ -153,8 +153,8 @@ export const docsZh = {
153
153
  bestPractices: [
154
154
  {guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
155
155
  {guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
156
- {guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
157
- {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
156
+ {guidance: true, description: 'Mark the current page with isSelected: it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
157
+ {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short; they are the accessible name in both expanded and icon-only modes.'},
158
158
  {guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
159
159
  {guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
160
160
  {guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
@@ -175,8 +175,8 @@ export const docsDense = {
175
175
  bestPractices: [
176
176
  {guidance: true, description: 'Use sections to group related navigation items and help users scan for their destination.'},
177
177
  {guidance: true, description: 'Pair outline and filled icon variants so the selected state is visually distinct.'},
178
- {guidance: true, description: 'Mark the current page with isSelected — it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
179
- {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short — they are the accessible name in both expanded and icon-only modes.'},
178
+ {guidance: true, description: 'Mark the current page with isSelected: it sets aria-current="page", so the current destination is announced rather than carried by color alone.'},
179
+ {guidance: true, description: 'SideNav renders a navigation landmark, and a collapsible item follows the WAI-ARIA APG Disclosure pattern (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/): the toggle carries aria-expanded and aria-controls, and the group it owns is inert while collapsed. Keep item labels short; they are the accessible name in both expanded and icon-only modes.'},
180
180
  {guidance: true, description: 'While the nav is collapsed, an item with children shows them in a submenu flyout. On a device that can hover, pointing at the item opens it after a short delay and moving away closes it; a flyout opened by clicking stays open until it is dismissed. On touch, it opens on tap. Do not put an action in there that has no other route to it.'},
181
181
  {guidance: false, description: 'Include a SideNavHeading when a TopNav is already providing app identity; this duplicates branding.'},
182
182
  {guidance: false, description: 'Use for filtering content; use tabs or filter buttons instead.'},
@@ -32,7 +32,7 @@ import type {BaseProps} from '../BaseProps';
32
32
  import * as stylex from '@stylexjs/stylex';
33
33
  import type {StyleXStyles} from '@stylexjs/stylex';
34
34
  import {spacingVars} from '../theme/tokens.stylex';
35
- import {mergeProps, mergeRefs} from '../utils';
35
+ import {mergeProps} from '../utils';
36
36
  import {
37
37
  SideNavCollapseContext,
38
38
  type SideNavCollapseState,
@@ -49,6 +49,7 @@ import {themeProps} from '../utils/themeProps';
49
49
  import {SizeProvider} from '../SizeContext/SizeContext';
50
50
  import {useTranslator} from '../i18n';
51
51
 
52
+ import {useMergedRefs} from '../hooks/useMergedRefs';
52
53
  // =============================================================================
53
54
  // Constants
54
55
  // =============================================================================
@@ -349,6 +350,7 @@ export function SideNav({
349
350
  useState(defaultIsCollapsed);
350
351
  const collapsed = isControlled ? controlledCollapsed : uncontrolledCollapsed;
351
352
  const navRef = useRef<HTMLElement>(null);
353
+ const mergedNavRef = useMergedRefs(ref, navRef);
352
354
  const collapseStateRef = useRef<SideNavCollapseState>({
353
355
  isCollapsed: collapsed,
354
356
  toggle: () => {},
@@ -517,7 +519,7 @@ export function SideNav({
517
519
 
518
520
  const navElement = (
519
521
  <nav
520
- ref={mergeRefs(ref, navRef)}
522
+ ref={mergedNavRef}
521
523
  role="navigation"
522
524
  aria-label={t('@astryx.sideNav.label')}
523
525
  data-testid={testId}
@@ -40,7 +40,8 @@ import {focusOutlineProps} from '../utils/focusOutline.stylex';
40
40
  import {useSideNavCollapse} from './SideNavCollapseContext';
41
41
  import {useLinkComponent} from '../Link/useLinkComponent';
42
42
  import type {LinkComponentType} from '../Link/types';
43
- import {mergeProps, mergeRefs} from '../utils';
43
+ import {mergeProps} from '../utils';
44
+ import {useMergedRefs} from '../hooks/useMergedRefs';
44
45
  import type {BaseProps} from '../BaseProps';
45
46
  import {useMenuHover} from '../hooks/useMenuHover';
46
47
  import {NavHeadingCloseContext} from '../NavMenu/NavMenuContext';
@@ -59,7 +60,7 @@ const styles = stylex.create({
59
60
  minHeight: spacingVars['--spacing-8'],
60
61
  paddingInlineStart: {
61
62
  default: spacingVars['--spacing-2'],
62
- ':has(.astryx-navicon)': 0,
63
+ ':has(.astryx-nav-icon)': 0,
63
64
  },
64
65
  paddingInlineEnd: spacingVars['--spacing-2'],
65
66
  paddingBlock: 0,
@@ -211,7 +212,7 @@ const styles = stylex.create({
211
212
  minHeight: spacingVars['--spacing-8'],
212
213
  paddingInlineStart: {
213
214
  default: spacingVars['--spacing-2'],
214
- ':has(.astryx-navicon)': 0,
215
+ ':has(.astryx-nav-icon)': 0,
215
216
  },
216
217
  paddingInlineEnd: spacingVars['--spacing-2'],
217
218
  paddingBlock: 0,
@@ -399,11 +400,18 @@ export function SideNavHeading({
399
400
  // setTriggerEl belongs on the chevron button, not this root: it is the
400
401
  // focus-restore target and a <div> cannot take focus. triggerRef stays here
401
402
  // because the panel anchors to the whole heading.
402
- const setRef = mergeRefs<HTMLDivElement>(
403
+ const setRef = useMergedRefs<HTMLDivElement>(
403
404
  rootRef,
404
405
  ref,
405
406
  menu ? popover.triggerRef : undefined,
406
407
  );
408
+ const collapsedSetRef = useMergedRefs<HTMLElement>(
409
+ collapsedItemRef,
410
+ ref,
411
+ // Collapsed, this button is the trigger, so it takes both roles.
412
+ menu ? popover.triggerRef : undefined,
413
+ menu ? setTriggerEl : undefined,
414
+ );
407
415
 
408
416
  // In collapsed mode: hide if no icon, show icon-only if has icon
409
417
  if (isCollapsed && !icon) {
@@ -412,14 +420,6 @@ export function SideNavHeading({
412
420
  if (isCollapsed && icon) {
413
421
  const collapsedIcon = <span {...stylex.props(styles.icon)}>{icon}</span>;
414
422
 
415
- const collapsedSetRef = mergeRefs<HTMLElement>(
416
- collapsedItemRef,
417
- ref,
418
- // Collapsed, this button is the trigger, so it takes both roles.
419
- menu ? popover.triggerRef : undefined,
420
- menu ? setTriggerEl : undefined,
421
- );
422
-
423
423
  let collapsedElement: ReactNode;
424
424
 
425
425
  if (headingHref) {
@@ -47,7 +47,7 @@ import {useLinkComponent} from '../Link/useLinkComponent';
47
47
  import type {LinkComponentType} from '../Link/types';
48
48
  import {usePopover} from '../Popover/usePopover';
49
49
  import {useMenuHover} from '../hooks/useMenuHover';
50
- import {mergeProps, mergeRefs} from '../utils';
50
+ import {mergeProps} from '../utils';
51
51
  import type {BaseProps} from '../BaseProps';
52
52
  import {Tooltip} from '../Tooltip';
53
53
  import {navItemStyles, type NavItemSize} from '../NavItem/navItemStyles.stylex';
@@ -61,6 +61,7 @@ import {useAppShellMobile} from '../AppShell/AppShellMobileContext';
61
61
  import {themeProps} from '../utils/themeProps';
62
62
  import {useTranslator} from '../i18n';
63
63
 
64
+ import {useMergedRefs} from '../hooks/useMergedRefs';
64
65
  // =============================================================================
65
66
  // Styles
66
67
  // =============================================================================
@@ -408,6 +409,7 @@ export function SideNavItem({
408
409
  hasCloseButton: false,
409
410
  dialogLabel: t('@astryx.sideNavItem.submenuLabel', {label}),
410
411
  });
412
+ const mergedTriggerRef = useMergedRefs(ref, popover.triggerRef);
411
413
 
412
414
  // Collapse state for items with children
413
415
  const itemCollapsibleConfig = useMemo(
@@ -524,7 +526,7 @@ export function SideNavItem({
524
526
  return (
525
527
  <div {...stylex.props(styles.root, xstyle)}>
526
528
  <button
527
- ref={mergeRefs(ref, popover.triggerRef)}
529
+ ref={mergedTriggerRef}
528
530
  type="button"
529
531
  {...rest}
530
532
  {...hoverTriggerProps}
@@ -13,7 +13,10 @@ import {useState} from 'react';
13
13
  import {describe, it, expect, vi, beforeEach} from 'vitest';
14
14
  import {render, screen, act, fireEvent, waitFor} from '@testing-library/react';
15
15
  import userEvent from '@testing-library/user-event';
16
+ import * as stylex from '@stylexjs/stylex';
16
17
  import {Slider} from './Slider';
18
+ import {focusOutlineStyles} from '../utils/focusOutline.stylex';
19
+ import {__resetInteractionModalityForTest} from '../utils/interactionModality';
17
20
 
18
21
  // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
19
22
  // reflects its open state via a `popover-open` attribute the tests can assert.
@@ -854,7 +857,7 @@ describe('Slider', () => {
854
857
  />,
855
858
  );
856
859
  const thumb = screen.getByRole('slider');
857
- thumb.focus();
860
+ act(() => thumb.focus());
858
861
  await user.keyboard('{ArrowRight}');
859
862
  expect(onChange).not.toHaveBeenCalled();
860
863
  });
@@ -902,4 +905,106 @@ describe('Slider', () => {
902
905
  ]).toEqual([]);
903
906
  });
904
907
  });
908
+
909
+ describe('focus ring modality', () => {
910
+ beforeEach(() => {
911
+ __resetInteractionModalityForTest();
912
+ });
913
+
914
+ // The ring is a stylex class, so derive it from the same source the
915
+ // component applies rather than hardcoding a hash.
916
+ const RING = stylex
917
+ .props(focusOutlineStyles.focusVisible)
918
+ .className!.split(' ');
919
+ const isRinged = (el: HTMLElement) =>
920
+ RING.every(c => el.classList.contains(c));
921
+
922
+ // The track has no layout in jsdom; pointer maths needs a real rect.
923
+ const grabTrack = (thumb: HTMLElement) => {
924
+ const track = thumb.parentElement!;
925
+ track.getBoundingClientRect = () => ({
926
+ left: 0,
927
+ top: 0,
928
+ right: 200,
929
+ bottom: 20,
930
+ width: 200,
931
+ height: 20,
932
+ x: 0,
933
+ y: 0,
934
+ toJSON: () => {},
935
+ });
936
+ fireEvent.pointerDown(track, {clientX: 100, clientY: 10, pointerId: 1});
937
+ };
938
+
939
+ it('rings when the thumb is reached with the keyboard', async () => {
940
+ const user = userEvent.setup();
941
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
942
+ await user.tab();
943
+ expect(isRinged(screen.getByRole('slider'))).toBe(true);
944
+ });
945
+
946
+ it('does not ring when the thumb is grabbed with the mouse', () => {
947
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
948
+ const thumb = screen.getByRole('slider');
949
+ grabTrack(thumb);
950
+ expect(thumb).toHaveFocus();
951
+ expect(isRinged(thumb)).toBe(false);
952
+ });
953
+
954
+ it('drops the ring when the mouse grabs a thumb that already had it', async () => {
955
+ const user = userEvent.setup();
956
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
957
+ await user.tab();
958
+ const thumb = screen.getByRole('slider');
959
+ expect(isRinged(thumb)).toBe(true);
960
+ // Already focused, so focus() fires no focus event.
961
+ grabTrack(thumb);
962
+ expect(isRinged(thumb)).toBe(false);
963
+ });
964
+
965
+ it('brings the ring back when an arrow key follows a mouse drag', () => {
966
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
967
+ const thumb = screen.getByRole('slider');
968
+ grabTrack(thumb);
969
+ expect(isRinged(thumb)).toBe(false);
970
+ fireEvent.keyDown(thumb, {key: 'ArrowRight'});
971
+ expect(isRinged(thumb)).toBe(true);
972
+ });
973
+
974
+ it('leaves the ring off for a modifier chord after a mouse drag', () => {
975
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
976
+ const thumb = screen.getByRole('slider');
977
+ grabTrack(thumb);
978
+ // Copying or reloading is not navigation, and the mouse is still on it.
979
+ fireEvent.keyDown(thumb, {key: 'c', metaKey: true});
980
+ expect(isRinged(thumb)).toBe(false);
981
+ });
982
+
983
+ it('drops the ring on blur', async () => {
984
+ const user = userEvent.setup();
985
+ render(<Slider label="Volume" value={50} onChange={vi.fn()} />);
986
+ await user.tab();
987
+ const thumb = screen.getByRole('slider');
988
+ expect(isRinged(thumb)).toBe(true);
989
+ act(() => thumb.blur());
990
+ expect(isRinged(thumb)).toBe(false);
991
+ });
992
+
993
+ it('rings the range thumb the keyboard reached, not its sibling', async () => {
994
+ const user = userEvent.setup();
995
+ render(
996
+ <Slider
997
+ label="Price"
998
+ value={[20, 80] as [number, number]}
999
+ onChange={vi.fn()}
1000
+ />,
1001
+ );
1002
+ await user.tab();
1003
+ await user.tab();
1004
+ const thumbs = screen.getAllByRole('slider');
1005
+ expect(thumbs[1]).toHaveFocus();
1006
+ expect(isRinged(thumbs[1])).toBe(true);
1007
+ expect(isRinged(thumbs[0])).toBe(false);
1008
+ });
1009
+ });
905
1010
  });