@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
@@ -20,6 +20,9 @@ import {
20
20
  useMemo,
21
21
  useState,
22
22
  useCallback,
23
+ type FocusEvent,
24
+ type FormEvent,
25
+ type KeyboardEvent,
23
26
  type ReactNode,
24
27
  } from 'react';
25
28
  import * as stylex from '@stylexjs/stylex';
@@ -29,6 +32,10 @@ import {Button} from '../../../Button';
29
32
  import {Popover} from '../../../Popover';
30
33
  import {TextInput} from '../../../TextInput';
31
34
  import {NumberInput} from '../../../NumberInput';
35
+ import {
36
+ parseNumberInput,
37
+ resolveNumberInputCommit,
38
+ } from '../../../NumberInput/numberInputCommit';
32
39
  import {DateInput} from '../../../DateInput';
33
40
  import type {ISODateString} from '../../../utils/dateTypes';
34
41
  import {TimeInput} from '../../../TimeInput';
@@ -42,7 +49,7 @@ import type {
42
49
  HeaderCellRenderProps,
43
50
  } from '../../types';
44
51
  import {proportional} from '../../columnUtils';
45
- import {useTranslator} from '../../../i18n';
52
+ import {useLocale, useTranslator} from '../../../i18n';
46
53
  import type {
47
54
  PowerSearchConfig,
48
55
  PowerSearchField,
@@ -445,6 +452,80 @@ function TextFilterControl({
445
452
  );
446
453
  }
447
454
 
455
+ function useLiveNumberFilter({
456
+ value,
457
+ min,
458
+ max,
459
+ hasClear,
460
+ onChange,
461
+ }: {
462
+ value: number | null;
463
+ min?: number | null;
464
+ max?: number | null;
465
+ hasClear: boolean;
466
+ onChange: (value: number | null) => void;
467
+ }) {
468
+ const locale = useLocale();
469
+ const editStartValueRef = useRef(value);
470
+
471
+ const handleChange = useCallback(
472
+ (nextValue: number | null) => {
473
+ editStartValueRef.current = nextValue;
474
+ onChange(nextValue);
475
+ },
476
+ [onChange],
477
+ );
478
+ const handleFocus = useCallback(() => {
479
+ editStartValueRef.current = value;
480
+ }, [value]);
481
+ const handleInput = useCallback(
482
+ (event: FormEvent<HTMLElement>) => {
483
+ const nextValue = parseNumberInput(
484
+ (event.currentTarget as HTMLInputElement).value,
485
+ {
486
+ min,
487
+ max,
488
+ locale,
489
+ },
490
+ );
491
+ if (nextValue !== null && nextValue !== value) {
492
+ onChange(nextValue);
493
+ }
494
+ },
495
+ [locale, max, min, onChange, value],
496
+ );
497
+ const restoreRejectedDraft = useCallback(
498
+ (input: HTMLInputElement) => {
499
+ const decision = resolveNumberInputCommit(input.value, {
500
+ min,
501
+ max,
502
+ locale,
503
+ hasClear,
504
+ });
505
+ if (decision.type === 'revert' && value !== editStartValueRef.current) {
506
+ onChange(editStartValueRef.current);
507
+ }
508
+ },
509
+ [hasClear, locale, max, min, onChange, value],
510
+ );
511
+ const handleBlur = useCallback(
512
+ (event: FocusEvent<HTMLInputElement>) => {
513
+ restoreRejectedDraft(event.currentTarget);
514
+ },
515
+ [restoreRejectedDraft],
516
+ );
517
+ const handleKeyDown = useCallback(
518
+ (event: KeyboardEvent<HTMLInputElement>) => {
519
+ if (event.key === 'Enter') {
520
+ restoreRejectedDraft(event.currentTarget);
521
+ }
522
+ },
523
+ [restoreRejectedDraft],
524
+ );
525
+
526
+ return {handleBlur, handleChange, handleFocus, handleInput, handleKeyDown};
527
+ }
528
+
448
529
  function NumberFilterControl({
449
530
  columnKey,
450
531
  header,
@@ -466,12 +547,19 @@ function NumberFilterControl({
466
547
 
467
548
  const step = operatorValue.type === 'integer' ? 1 : null;
468
549
 
469
- const handleChange = useCallback(
550
+ const updateFilter = useCallback(
470
551
  (newValue: number | null) => {
471
552
  store.getConfig().onFilterChange(columnKey, newValue);
472
553
  },
473
554
  [store, columnKey],
474
555
  );
556
+ const liveFilter = useLiveNumberFilter({
557
+ value: numValue,
558
+ min: operatorValue.minValue,
559
+ max: operatorValue.maxValue,
560
+ hasClear: !!hasClear,
561
+ onChange: updateFilter,
562
+ });
475
563
 
476
564
  if (hasClear) {
477
565
  return (
@@ -479,7 +567,11 @@ function NumberFilterControl({
479
567
  label={t('@astryx.tableFiltering.filterByColumn', {header})}
480
568
  isLabelHidden
481
569
  value={numValue}
482
- onChange={handleChange}
570
+ onChange={liveFilter.handleChange}
571
+ onFocus={liveFilter.handleFocus}
572
+ onInput={liveFilter.handleInput}
573
+ onBlur={liveFilter.handleBlur}
574
+ onKeyDown={liveFilter.handleKeyDown}
483
575
  placeholder={t('@astryx.tableFiltering.filterByColumn', {header})}
484
576
  min={operatorValue.minValue ?? null}
485
577
  max={operatorValue.maxValue ?? null}
@@ -495,7 +587,11 @@ function NumberFilterControl({
495
587
  label={t('@astryx.tableFiltering.filterByColumn', {header})}
496
588
  isLabelHidden
497
589
  value={numValue}
498
- onChange={handleChange}
590
+ onChange={liveFilter.handleChange}
591
+ onFocus={liveFilter.handleFocus}
592
+ onInput={liveFilter.handleInput}
593
+ onBlur={liveFilter.handleBlur}
594
+ onKeyDown={liveFilter.handleKeyDown}
499
595
  placeholder={t('@astryx.tableFiltering.filterByColumn', {header})}
500
596
  min={operatorValue.minValue ?? null}
501
597
  max={operatorValue.maxValue ?? null}
@@ -0,0 +1,112 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useTableGroupedRows-perf.test.tsx
5
+ * @input Table, useTableGroupedRows, React testing utilities
6
+ * @output Performance tests for grouped-rows render behavior
7
+ * @position Test file; validates that grouping costs no extra row renders
8
+ *
9
+ * The plugin keeps group headers out of cell renderers per cell, at render
10
+ * time. Doing it by rewriting the columns instead would hand BaseTable a new
11
+ * column object on every render, and its element-by-element check on the
12
+ * resolved column array is what stops every row re-rendering.
13
+ */
14
+
15
+ import {describe, it, expect} from 'vitest';
16
+ import {render, screen, act} from '@testing-library/react';
17
+ import {useCallback, useMemo, useState} from 'react';
18
+ import userEvent from '@testing-library/user-event';
19
+ import {Table} from '../../Table';
20
+ import type {TableColumn} from '../../types';
21
+ import {useTableGroupedRows} from './useTableGroupedRows';
22
+
23
+ interface Person extends Record<string, unknown> {
24
+ id: string;
25
+ name: string;
26
+ team: string;
27
+ }
28
+
29
+ const people: Person[] = [
30
+ {id: 'a', name: 'Alice', team: 'Core'},
31
+ {id: 'b', name: 'Bob', team: 'Core'},
32
+ {id: 'c', name: 'Carol', team: 'Infra'},
33
+ ];
34
+
35
+ const EMPTY = new Set<string>();
36
+
37
+ function GroupedRenderCountTable({
38
+ renderCounts,
39
+ }: {
40
+ renderCounts: Record<string, number>;
41
+ }) {
42
+ const [tick, setTick] = useState(0);
43
+ const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(EMPTY);
44
+
45
+ const columns = useMemo<TableColumn<Person>[]>(
46
+ () => [
47
+ {
48
+ key: 'name',
49
+ header: 'Name',
50
+ renderCell: (item: Person) => {
51
+ renderCounts[item.id] = (renderCounts[item.id] ?? 0) + 1;
52
+ return item.name;
53
+ },
54
+ },
55
+ ],
56
+ [renderCounts],
57
+ );
58
+
59
+ const grouped = useTableGroupedRows<Person>({
60
+ data: people,
61
+ groupBy: useCallback((p: Person) => p.team, []),
62
+ collapsedGroups,
63
+ onToggleGroup: useCallback(
64
+ (key: string) =>
65
+ setCollapsedGroups(prev => {
66
+ const next = new Set(prev);
67
+ if (!next.delete(key)) {
68
+ next.add(key);
69
+ }
70
+ return next;
71
+ }),
72
+ [],
73
+ ),
74
+ getRowKey: useCallback((p: Person) => p.id, []),
75
+ });
76
+
77
+ return (
78
+ <>
79
+ <button type="button" onClick={() => setTick(t => t + 1)}>
80
+ bump {tick}
81
+ </button>
82
+ <Table
83
+ data={grouped.data}
84
+ columns={columns}
85
+ idKey={grouped.idKey}
86
+ plugins={{grouped: grouped.plugin}}
87
+ />
88
+ </>
89
+ );
90
+ }
91
+
92
+ describe('Grouped rows render performance', () => {
93
+ it('renders each real row once and no group header at all', () => {
94
+ const renderCounts: Record<string, number> = {};
95
+ render(<GroupedRenderCountTable renderCounts={renderCounts} />);
96
+ expect(renderCounts).toEqual({a: 1, b: 1, c: 1});
97
+ });
98
+
99
+ it('a re-render of the surrounding component re-renders no rows', async () => {
100
+ const user = userEvent.setup();
101
+ const renderCounts: Record<string, number> = {};
102
+ render(<GroupedRenderCountTable renderCounts={renderCounts} />);
103
+ const before = {...renderCounts};
104
+
105
+ await act(async () => {
106
+ await user.click(screen.getByRole('button', {name: /bump/}));
107
+ });
108
+
109
+ expect(screen.getByRole('button', {name: 'bump 1'})).toBeInTheDocument();
110
+ expect(renderCounts).toEqual(before);
111
+ });
112
+ });
@@ -5,6 +5,7 @@ import {render, screen, fireEvent, within} from '@testing-library/react';
5
5
  import {useState, useCallback} from 'react';
6
6
  import {Table} from '../../Table';
7
7
  import type {TableColumn} from '../../types';
8
+ import {useTableRowStatus, type TableRowStatus} from '../rowStatus';
8
9
  import {useTableGroupedRows} from './useTableGroupedRows';
9
10
 
10
11
  interface Person extends Record<string, unknown> {
@@ -175,6 +176,105 @@ describe('useTableGroupedRows', () => {
175
176
  ).not.toBeInTheDocument();
176
177
  });
177
178
 
179
+ it('never runs a column renderCell against a group header row', () => {
180
+ // The renderer that breaks: it keys a lookup off a field rather than
181
+ // printing it, so the header Proxy answering `''` is no better than
182
+ // `undefined`.
183
+ const TEAM_META: Record<string, {dot: string}> = {
184
+ Core: {dot: 'green'},
185
+ Infra: {dot: 'blue'},
186
+ };
187
+ const seen: Person[] = [];
188
+ const lookupColumns: TableColumn<Person>[] = [
189
+ {
190
+ key: 'team',
191
+ header: 'Team',
192
+ renderCell: item => {
193
+ seen.push(item);
194
+ return <span>{TEAM_META[item.team].dot}</span>;
195
+ },
196
+ },
197
+ ];
198
+
199
+ function LookupHarness() {
200
+ const [collapsed, setCollapsed] = useState<Set<string>>(EMPTY);
201
+ const grouped = useTableGroupedRows<Person>({
202
+ data: people,
203
+ groupBy: p => p.team,
204
+ collapsedGroups: collapsed,
205
+ onToggleGroup: k => setCollapsed(new Set([k])),
206
+ getRowKey: p => p.id,
207
+ });
208
+ return (
209
+ <Table
210
+ data={grouped.data}
211
+ columns={lookupColumns}
212
+ idKey={grouped.idKey}
213
+ plugins={{grouped: grouped.plugin}}
214
+ />
215
+ );
216
+ }
217
+
218
+ expect(() => render(<LookupHarness />)).not.toThrow();
219
+ expect(seen.map(p => p.name)).toEqual(['Alice', 'Bob', 'Carol']);
220
+ expect(screen.getByText('Core')).toBeInTheDocument();
221
+ expect(screen.getByText('Infra')).toBeInTheDocument();
222
+ });
223
+
224
+ describe.each([
225
+ ['grouped first', true],
226
+ ['rowStatus first', false],
227
+ ])(
228
+ 'with a column contributed by another plugin (%s)',
229
+ (_label, groupedFirst) => {
230
+ // `grouped` and `rowStatus` are both absent from PLUGIN_ORDER, so the
231
+ // record's insertion order is the pipeline order. Whichever way round
232
+ // the consumer types it, the status column's renderCell must not see a
233
+ // header row.
234
+ const STATUS_BY_TEAM: Record<string, TableRowStatus> = {
235
+ Core: {color: 'success', label: 'Healthy'},
236
+ Infra: {color: 'error', label: 'Paging'},
237
+ };
238
+
239
+ function StatusHarness() {
240
+ const [collapsed, setCollapsed] = useState<Set<string>>(EMPTY);
241
+ const grouped = useTableGroupedRows<Person>({
242
+ data: people,
243
+ groupBy: p => p.team,
244
+ collapsedGroups: collapsed,
245
+ onToggleGroup: k => setCollapsed(new Set([k])),
246
+ getRowKey: p => p.id,
247
+ });
248
+ const rowStatus = useTableRowStatus<Person>({
249
+ getStatus: useCallback((item: Person) => {
250
+ const {color, label} = STATUS_BY_TEAM[item.team];
251
+ return {color, label};
252
+ }, []),
253
+ });
254
+ return (
255
+ <Table
256
+ data={grouped.data}
257
+ columns={columns}
258
+ idKey={grouped.idKey}
259
+ plugins={
260
+ groupedFirst
261
+ ? {grouped: grouped.plugin, rowStatus}
262
+ : {rowStatus, grouped: grouped.plugin}
263
+ }
264
+ />
265
+ );
266
+ }
267
+
268
+ it('renders the group headers without throwing', () => {
269
+ expect(() => render(<StatusHarness />)).not.toThrow();
270
+ expect(screen.getByText('Core')).toBeInTheDocument();
271
+ expect(screen.getByText('Infra')).toBeInTheDocument();
272
+ // One indicator per real row, none on the two headers.
273
+ expect(screen.getAllByRole('img')).toHaveLength(3);
274
+ });
275
+ },
276
+ );
277
+
178
278
  it('keeps a group collapsed across a data change (state keyed by group)', () => {
179
279
  function ChangingHarness() {
180
280
  const [collapsed, setCollapsed] = useState<Set<string>>(
@@ -41,8 +41,11 @@ function isGroupHeader(item: unknown): item is GroupHeader {
41
41
  );
42
42
  }
43
43
 
44
- // Proxy handler: any field access beyond the marker fields resolves to `''`
45
- // so user cell renderers (`item.name.toUpperCase()`) never throw on a header.
44
+ // Proxy handler: any field access beyond the marker fields resolves to `''`,
45
+ // which keeps anything that reads a header's data — the sortable and filtering
46
+ // plugins, a consumer's own row handler — off `undefined`. Cell renderers are
47
+ // handled at render time instead (see `transformBodyCell` below); the Proxy
48
+ // never protected them, because `''` fails a lookup exactly as `undefined` does.
46
49
  const HEADER_PROXY_HANDLER: ProxyHandler<Record<string | symbol, unknown>> = {
47
50
  // eslint-disable-next-line @typescript-eslint/promise-function-async -- Proxy get trap, not a promise-returning fn
48
51
  get(t: Record<string | symbol, unknown>, prop: string | symbol): unknown {
@@ -54,12 +57,9 @@ const HEADER_PROXY_HANDLER: ProxyHandler<Record<string | symbol, unknown>> = {
54
57
  };
55
58
 
56
59
  /**
57
- * Build a synthetic header row wrapped in a Proxy so arbitrary field access
58
- * from user cell renderers (e.g. `item.name.toUpperCase()`) resolves to `''`
59
- * instead of throwing — BaseTable evaluates `col.renderCell(item)` on every
60
- * row (including synthetic headers) before `transformBodyRow` can replace the
61
- * row's cells. `transformBodyRow` then discards those cells and renders a
62
- * single full-width header cell.
60
+ * Build a synthetic header row wrapped in a Proxy so field access resolves to
61
+ * `''` instead of `undefined`. `transformBodyRow` then discards the row's
62
+ * cells and renders a single full-width header cell.
63
63
  */
64
64
  function makeHeader<T extends Record<string, unknown>>(
65
65
  groupKey: string,
@@ -301,6 +301,15 @@ export function useTableGroupedRows<T extends Record<string, unknown>>(
301
301
 
302
302
  const plugin = useMemo(
303
303
  (): TablePlugin<T> => ({
304
+ // A header row is not one of the caller's rows, so running a cell
305
+ // renderer against it can only misread it or throw — and the cells are
306
+ // discarded by `transformBodyRow` below regardless.
307
+ transformBodyCell(props, _column, item) {
308
+ if (!isGroupHeader(item)) {
309
+ return props;
310
+ }
311
+ return {...props, isContentSuppressed: true};
312
+ },
304
313
  // Replace a header row's pre-rendered cells with one full-width cell.
305
314
  transformBodyRow(props, item) {
306
315
  if (!isGroupHeader(item)) {
@@ -229,7 +229,7 @@ describe('useTablePagination', () => {
229
229
  ).toBeTruthy();
230
230
  });
231
231
 
232
- it('transformTableContext renders Pagination above and below', () => {
232
+ it('transformTableContext renders distinctly named Pagination above and below', () => {
233
233
  render(
234
234
  <PaginatedTable
235
235
  data={generateItems(30)}
@@ -237,10 +237,40 @@ describe('useTablePagination', () => {
237
237
  position="both"
238
238
  />,
239
239
  );
240
- const navs = screen.getAllByRole('navigation', {
241
- name: 'Table pagination',
240
+ const table = screen.getByRole('table');
241
+ // Each nav landmark gets a unique accessible name (axe landmark-unique)
242
+ const topNav = screen.getByRole('navigation', {
243
+ name: 'Table pagination (top)',
244
+ });
245
+ const bottomNav = screen.getByRole('navigation', {
246
+ name: 'Table pagination (bottom)',
242
247
  });
243
- expect(navs).toHaveLength(2);
248
+ // Top nav comes before the table, bottom nav after it in DOM order
249
+ expect(
250
+ table.compareDocumentPosition(topNav) &
251
+ Node.DOCUMENT_POSITION_PRECEDING,
252
+ ).toBeTruthy();
253
+ expect(
254
+ table.compareDocumentPosition(bottomNav) &
255
+ Node.DOCUMENT_POSITION_FOLLOWING,
256
+ ).toBeTruthy();
257
+ });
258
+
259
+ it('position="both" interpolates a consumer label into distinct nav names', () => {
260
+ render(
261
+ <PaginatedTable
262
+ data={generateItems(30)}
263
+ pageSize={10}
264
+ position="both"
265
+ label="Users table"
266
+ />,
267
+ );
268
+ expect(
269
+ screen.getByRole('navigation', {name: 'Users table (top)'}),
270
+ ).toBeInTheDocument();
271
+ expect(
272
+ screen.getByRole('navigation', {name: 'Users table (bottom)'}),
273
+ ).toBeInTheDocument();
244
274
  });
245
275
 
246
276
  it('transformTableContext does not render Pagination when position is none', () => {
@@ -164,6 +164,9 @@ export interface UseTablePaginationConfig {
164
164
 
165
165
  /**
166
166
  * Accessible label for the pagination nav landmark.
167
+ * When `position` is 'both', the two navs get distinct names by suffixing
168
+ * this label — "{label} (top)" above and "{label} (bottom)" below — so
169
+ * same-type landmarks stay distinguishable to assistive technology.
167
170
  * @default 'Table pagination'
168
171
  */
169
172
  label?: string;
@@ -211,6 +214,10 @@ export function useTablePagination<T extends Record<string, unknown>>(
211
214
  label: labelFromProps,
212
215
  } = config;
213
216
  const label = labelFromProps ?? t('@astryx.table.pagination.label');
217
+ // position='both' renders two nav landmarks; suffix the base label so each
218
+ // gets a unique accessible name (axe landmark-unique).
219
+ const labelAbove = t('@astryx.table.pagination.labelAbove', {label});
220
+ const labelBelow = t('@astryx.table.pagination.labelBelow', {label});
214
221
 
215
222
  // Same guard as Pagination itself: 0/NaN/negative pageSize would produce an
216
223
  // Infinity/NaN totalPages here, which bypasses Pagination's own coercion
@@ -240,8 +247,20 @@ export function useTablePagination<T extends Record<string, unknown>>(
240
247
 
241
248
  // Keep current config in a ref so the plugin can read the latest values
242
249
  // without needing to recreate the plugin object on every change.
243
- const configRef = useRef({paginationProps, position, align});
244
- configRef.current = {paginationProps, position, align};
250
+ const configRef = useRef({
251
+ paginationProps,
252
+ position,
253
+ align,
254
+ labelAbove,
255
+ labelBelow,
256
+ });
257
+ configRef.current = {
258
+ paginationProps,
259
+ position,
260
+ align,
261
+ labelAbove,
262
+ labelBelow,
263
+ };
245
264
 
246
265
  // Stable plugin object \u2014 created once, reads config via ref.
247
266
  return useMemo(
@@ -251,6 +270,8 @@ export function useTablePagination<T extends Record<string, unknown>>(
251
270
  position: pos,
252
271
  paginationProps: props,
253
272
  align: a,
273
+ labelAbove: above,
274
+ labelBelow: below,
254
275
  } = configRef.current;
255
276
  if (pos === 'none') {
256
277
  return children;
@@ -277,7 +298,18 @@ export function useTablePagination<T extends Record<string, unknown>>(
277
298
  a === 'end' && styles.alignEnd,
278
299
  a === 'start' && styles.alignStart,
279
300
  )}>
280
- <Pagination {...props} />
301
+ <Pagination
302
+ {...props}
303
+ // Two navs with the same name are indistinguishable landmarks;
304
+ // give each side a unique name when both render.
305
+ label={
306
+ pos === 'both'
307
+ ? side === 'above'
308
+ ? above
309
+ : below
310
+ : props.label
311
+ }
312
+ />
281
313
  </div>
282
314
  );
283
315
 
@@ -21,6 +21,7 @@ import {useMemo, useRef, type ReactNode} from 'react';
21
21
  import * as stylex from '@stylexjs/stylex';
22
22
  import {spacingVars, colorVars, radiusVars} from '../../../theme/tokens.stylex';
23
23
  import {Icon} from '../../../Icon';
24
+ import {VisuallyHidden} from '../../../VisuallyHidden';
24
25
  import {resolveContextActions} from '../../tableContextMenu';
25
26
  import {useTranslator} from '../../../i18n';
26
27
  import {rtlStyles} from '../../../utils';
@@ -207,7 +208,13 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
207
208
  const expansionColumn = useMemo(
208
209
  (): TableColumn<T> => ({
209
210
  key: '__expansion',
210
- header: '',
211
+ // A `<th>` with no discernible text is announced as an unlabelled
212
+ // column; the label is hidden because the column shows only chevrons.
213
+ header: (
214
+ <VisuallyHidden>
215
+ {t('@astryx.tableRowExpansion.columnHeader')}
216
+ </VisuallyHidden>
217
+ ),
211
218
  width: EXPANSION_COLUMN_WIDTH,
212
219
  resizable: false,
213
220
  renderCell: (item: T) => {
@@ -226,7 +233,7 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
226
233
  );
227
234
  },
228
235
  }),
229
- [expandedKeys, onToggle, getRowKey, getIsItemExpandable],
236
+ [expandedKeys, onToggle, getRowKey, getIsItemExpandable, t],
230
237
  );
231
238
 
232
239
  return useMemo(
@@ -44,12 +44,18 @@ function Harness({
44
44
  }
45
45
 
46
46
  describe('useTableRowStatus', () => {
47
- it('prepends a narrow status column with an empty header', () => {
47
+ it('names the status column header for assistive technology', () => {
48
48
  render(<Harness />);
49
- const headers = screen.getAllByRole('columnheader');
50
- // Status column is first; its header is empty.
51
- expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
52
- expect(headers[0].textContent).toBe('');
49
+ // The gutter looks blank but its th carries a visually hidden name.
50
+ const header = screen.getByRole('columnheader', {name: 'Row status'});
51
+ expect(header).toHaveAttribute('data-column-key', '__rowStatus');
52
+ // Status column is first.
53
+ expect(screen.getAllByRole('columnheader')[0]).toBe(header);
54
+ // The name must come from the clipped VisuallyHidden span — bare th text
55
+ // would be a visible header on what should stay a blank gutter.
56
+ const hiddenText = within(header).getByText('Row status');
57
+ expect(hiddenText.tagName).toBe('SPAN');
58
+ expect(hiddenText.className).not.toBe('');
53
59
  });
54
60
 
55
61
  it('renders a labeled dot for rows with a status', () => {
@@ -131,8 +137,8 @@ describe('useTableRowStatus', () => {
131
137
 
132
138
  it('renders the status header with empty data and no indicators', () => {
133
139
  render(<Harness rows={[]} />);
134
- const headers = screen.getAllByRole('columnheader');
135
- expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
140
+ const header = screen.getByRole('columnheader', {name: 'Row status'});
141
+ expect(header).toHaveAttribute('data-column-key', '__rowStatus');
136
142
  expect(screen.queryByRole('img')).not.toBeInTheDocument();
137
143
  });
138
144
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file useTableRowStatus.tsx
7
- * @input React, StyleX, Icon, Table types
7
+ * @input React, StyleX, Icon, i18n, VisuallyHidden, Table types
8
8
  * @output Exports useTableRowStatus hook + config type
9
9
  * @position Row-status plugin; consumed by Table via plugins prop
10
10
  *
@@ -16,6 +16,8 @@ import {useMemo} from 'react';
16
16
  import * as stylex from '@stylexjs/stylex';
17
17
  import {Icon, type IconColor, type IconName} from '../../../Icon';
18
18
  import {Tooltip} from '../../../Tooltip';
19
+ import {useTranslator} from '../../../i18n';
20
+ import {VisuallyHidden} from '../../../VisuallyHidden';
19
21
  import type {TableColumn, TablePlugin} from '../../types';
20
22
 
21
23
  /**
@@ -142,6 +144,7 @@ const styles = stylex.create({
142
144
  export function useTableRowStatus<T extends Record<string, unknown>>(
143
145
  config: UseTableRowStatusConfig<T>,
144
146
  ): TablePlugin<T> {
147
+ const t = useTranslator();
145
148
  const {getStatus} = config;
146
149
 
147
150
  return useMemo(
@@ -149,7 +152,13 @@ export function useTableRowStatus<T extends Record<string, unknown>>(
149
152
  transformColumns(columns) {
150
153
  const statusColumn: TableColumn<T> = {
151
154
  key: '__rowStatus',
152
- header: '',
155
+ // The gutter stays visually blank, but the th needs a discernible
156
+ // name for assistive technology (axe: empty-table-header).
157
+ header: (
158
+ <VisuallyHidden>
159
+ {t('@astryx.table.rowStatus.columnHeader')}
160
+ </VisuallyHidden>
161
+ ),
153
162
  width: STATUS_COLUMN_WIDTH,
154
163
  resizable: false,
155
164
  renderCell: (item: T) => {
@@ -184,6 +193,6 @@ export function useTableRowStatus<T extends Record<string, unknown>>(
184
193
  return [statusColumn, ...columns];
185
194
  },
186
195
  }),
187
- [getStatus],
196
+ [getStatus, t],
188
197
  );
189
198
  }