@astryxdesign/core 0.1.4 → 0.1.5-canary.76e6697

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 (635) hide show
  1. package/CHANGELOG.md +141 -0
  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 +20 -18
  5. package/dist/AlertDialog/useImperativeAlertDialog.d.ts +0 -2
  6. package/dist/AlertDialog/useImperativeAlertDialog.d.ts.map +1 -1
  7. package/dist/AlertDialog/useImperativeAlertDialog.js +3 -5
  8. package/dist/AppShell/AppShell.d.ts +1 -1
  9. package/dist/AppShell/AppShell.d.ts.map +1 -1
  10. package/dist/AppShell/AppShell.js +55 -53
  11. package/dist/AspectRatio/AspectRatio.d.ts +48 -10
  12. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  13. package/dist/AspectRatio/AspectRatio.js +33 -12
  14. package/dist/AspectRatio/index.d.ts +1 -1
  15. package/dist/AspectRatio/index.d.ts.map +1 -1
  16. package/dist/Avatar/Avatar.d.ts.map +1 -1
  17. package/dist/Avatar/Avatar.js +28 -26
  18. package/dist/Avatar/AvatarStatusDot.js +6 -6
  19. package/dist/AvatarGroup/AvatarGroup.js +6 -6
  20. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -7
  21. package/dist/Badge/Badge.js +3 -3
  22. package/dist/Banner/Banner.d.ts.map +1 -1
  23. package/dist/Banner/Banner.js +32 -25
  24. package/dist/Blockquote/Blockquote.js +7 -7
  25. package/dist/Breadcrumbs/BreadcrumbItem.js +17 -17
  26. package/dist/Breadcrumbs/Breadcrumbs.js +7 -7
  27. package/dist/Button/Button.js +19 -19
  28. package/dist/ButtonGroup/ButtonGroup.js +7 -7
  29. package/dist/Calendar/Calendar.d.ts.map +1 -1
  30. package/dist/Calendar/Calendar.js +68 -48
  31. package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
  32. package/dist/Calendar/styles.d.ts +2 -2
  33. package/dist/Calendar/styles.d.ts.map +1 -1
  34. package/dist/Calendar/styles.js +2 -2
  35. package/dist/Card/Card.d.ts +4 -2
  36. package/dist/Card/Card.d.ts.map +1 -1
  37. package/dist/Card/Card.js +18 -10
  38. package/dist/Carousel/Carousel.d.ts.map +1 -1
  39. package/dist/Carousel/Carousel.js +24 -21
  40. package/dist/Center/Center.js +3 -3
  41. package/dist/Chat/ChatComposer.js +23 -23
  42. package/dist/Chat/ChatComposerDrawer.js +14 -14
  43. package/dist/Chat/ChatComposerInput.d.ts +1 -1
  44. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  45. package/dist/Chat/ChatComposerInput.js +27 -15
  46. package/dist/Chat/ChatDictationButton.d.ts +1 -1
  47. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  48. package/dist/Chat/ChatDictationButton.js +13 -11
  49. package/dist/Chat/ChatLayout.js +17 -17
  50. package/dist/Chat/ChatLayoutScrollButton.d.ts +1 -1
  51. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  52. package/dist/Chat/ChatLayoutScrollButton.js +12 -10
  53. package/dist/Chat/ChatMessage.js +12 -12
  54. package/dist/Chat/ChatMessageBubble.js +7 -7
  55. package/dist/Chat/ChatMessageList.js +14 -14
  56. package/dist/Chat/ChatMessageMetadata.d.ts +1 -1
  57. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  58. package/dist/Chat/ChatMessageMetadata.js +14 -12
  59. package/dist/Chat/ChatPastedTextToken.js +13 -13
  60. package/dist/Chat/ChatSendButton.js +3 -3
  61. package/dist/Chat/ChatSystemMessage.d.ts +1 -1
  62. package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
  63. package/dist/Chat/ChatSystemMessage.js +17 -17
  64. package/dist/Chat/ChatTokenizedText.d.ts +1 -1
  65. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  66. package/dist/Chat/ChatTokenizedText.js +24 -15
  67. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  68. package/dist/Chat/ChatToolCalls.js +81 -74
  69. package/dist/Chat/index.d.ts +1 -1
  70. package/dist/Chat/index.d.ts.map +1 -1
  71. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  72. package/dist/Chat/useTriggerMenu.js +15 -15
  73. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  74. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  75. package/dist/CheckboxInput/CheckboxInput.js +25 -23
  76. package/dist/CheckboxList/CheckboxList.js +9 -9
  77. package/dist/CheckboxList/CheckboxListItem.d.ts +1 -1
  78. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  79. package/dist/CheckboxList/CheckboxListItem.js +8 -6
  80. package/dist/Citation/Citation.js +10 -10
  81. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  82. package/dist/ClickableCard/ClickableCard.js +32 -9
  83. package/dist/Code/Code.js +3 -3
  84. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  85. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  86. package/dist/CodeBlock/CodeBlock.js +88 -52
  87. package/dist/Collapsible/Collapsible.d.ts +9 -3
  88. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  89. package/dist/Collapsible/Collapsible.js +109 -22
  90. package/dist/Collapsible/CollapsibleGroup.d.ts +37 -7
  91. package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
  92. package/dist/Collapsible/CollapsibleGroup.js +64 -12
  93. package/dist/Collapsible/CollapsibleGroupContext.d.ts +24 -0
  94. package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
  95. package/dist/Collapsible/CollapsibleGroupContext.js +26 -3
  96. package/dist/Collapsible/index.d.ts +1 -0
  97. package/dist/Collapsible/index.d.ts.map +1 -1
  98. package/dist/Collapsible/useCollapsible.d.ts +4 -1
  99. package/dist/Collapsible/useCollapsible.d.ts.map +1 -1
  100. package/dist/Collapsible/useCollapsible.js +20 -6
  101. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  102. package/dist/CommandPalette/CommandPalette.js +36 -34
  103. package/dist/CommandPalette/CommandPaletteEmpty.js +3 -3
  104. package/dist/CommandPalette/CommandPaletteFooter.js +16 -16
  105. package/dist/CommandPalette/CommandPaletteGroup.js +5 -5
  106. package/dist/CommandPalette/CommandPaletteInput.js +13 -13
  107. package/dist/CommandPalette/CommandPaletteItem.js +3 -3
  108. package/dist/CommandPalette/CommandPaletteList.js +3 -3
  109. package/dist/ContextMenu/ContextMenu.d.ts +7 -2
  110. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  111. package/dist/ContextMenu/ContextMenu.js +76 -43
  112. package/dist/DateInput/DateInput.js +18 -18
  113. package/dist/DateRangeInput/DateRangeInput.js +21 -21
  114. package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
  115. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  116. package/dist/DateTimeInput/DateTimeInput.js +30 -26
  117. package/dist/DateTimeInput/index.d.ts +1 -1
  118. package/dist/DateTimeInput/index.d.ts.map +1 -1
  119. package/dist/Dialog/Dialog.js +9 -9
  120. package/dist/Dialog/DialogHeader.js +16 -16
  121. package/dist/Dialog/useImperativeDialog.js +3 -3
  122. package/dist/Divider/Divider.js +7 -7
  123. package/dist/DropdownMenu/DropdownMenu.js +10 -10
  124. package/dist/DropdownMenu/DropdownMenuItem.d.ts +2 -17
  125. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  126. package/dist/DropdownMenu/DropdownMenuItem.js +3 -3
  127. package/dist/DropdownMenu/renderDropdownItems.js +10 -10
  128. package/dist/EmptyState/EmptyState.js +9 -9
  129. package/dist/Field/Field.js +17 -17
  130. package/dist/Field/FieldLabel.js +15 -15
  131. package/dist/Field/InputClearButton.js +5 -5
  132. package/dist/FieldStatus/FieldStatus.js +3 -3
  133. package/dist/FileInput/FileInput.js +29 -29
  134. package/dist/FormLayout/FormLayout.js +5 -5
  135. package/dist/Grid/Grid.d.ts +4 -3
  136. package/dist/Grid/Grid.d.ts.map +1 -1
  137. package/dist/Grid/Grid.js +29 -12
  138. package/dist/Grid/GridSpan.js +3 -3
  139. package/dist/HStack/HStack.js +3 -3
  140. package/dist/Heading/Heading.js +10 -10
  141. package/dist/HoverCard/HoverCard.js +9 -9
  142. package/dist/HoverCard/useHoverCard.d.ts +5 -1
  143. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  144. package/dist/HoverCard/useHoverCard.js +3 -3
  145. package/dist/Icon/Icon.d.ts.map +1 -1
  146. package/dist/Icon/Icon.js +16 -12
  147. package/dist/Icon/defaultIcons.js +116 -116
  148. package/dist/IconButton/IconButton.js +3 -3
  149. package/dist/InputGroup/InputGroup.js +11 -11
  150. package/dist/InputGroup/InputGroupText.js +3 -3
  151. package/dist/Item/Item.js +18 -18
  152. package/dist/Kbd/Kbd.js +5 -5
  153. package/dist/Layer/LayerProvider.js +7 -7
  154. package/dist/Layer/useLayer.d.ts +29 -2
  155. package/dist/Layer/useLayer.d.ts.map +1 -1
  156. package/dist/Layer/useLayer.js +44 -31
  157. package/dist/Layout/Layout.js +21 -21
  158. package/dist/Layout/LayoutContent.js +3 -3
  159. package/dist/Layout/LayoutFooter.js +5 -5
  160. package/dist/Layout/LayoutHeader.js +5 -5
  161. package/dist/Layout/LayoutPanel.js +3 -3
  162. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  163. package/dist/Lightbox/Lightbox.js +55 -31
  164. package/dist/Lightbox/useLightbox.d.ts +0 -1
  165. package/dist/Lightbox/useLightbox.d.ts.map +1 -1
  166. package/dist/Lightbox/useLightbox.js +3 -4
  167. package/dist/Link/Link.d.ts +0 -2
  168. package/dist/Link/Link.d.ts.map +1 -1
  169. package/dist/Link/Link.js +15 -17
  170. package/dist/Link/LinkProvider.js +3 -3
  171. package/dist/Link/useLinkify.js +3 -3
  172. package/dist/List/List.js +11 -11
  173. package/dist/List/ListItem.js +11 -11
  174. package/dist/Markdown/Markdown.d.ts.map +1 -1
  175. package/dist/Markdown/Markdown.js +102 -102
  176. package/dist/MetadataList/MetadataList.js +10 -10
  177. package/dist/MetadataList/MetadataListItem.js +15 -15
  178. package/dist/MobileNav/MobileNav.d.ts +1 -1
  179. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  180. package/dist/MobileNav/MobileNav.js +26 -23
  181. package/dist/MobileNav/MobileNavToggle.js +5 -5
  182. package/dist/MoreMenu/MoreMenu.js +3 -3
  183. package/dist/MultiSelector/MultiSelector.js +56 -56
  184. package/dist/NavIcon/NavIcon.js +3 -3
  185. package/dist/NavMenu/NavHeadingMenu.js +5 -5
  186. package/dist/NavMenu/NavHeadingMenuItem.js +8 -8
  187. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  188. package/dist/NumberInput/NumberInput.js +48 -32
  189. package/dist/Outline/Outline.js +15 -15
  190. package/dist/OverflowList/OverflowList.js +8 -8
  191. package/dist/Overlay/Overlay.js +3 -3
  192. package/dist/Overlay/OverlayScrim.js +5 -5
  193. package/dist/Overlay/useOverlay.d.ts +0 -3
  194. package/dist/Overlay/useOverlay.d.ts.map +1 -1
  195. package/dist/Overlay/useOverlay.js +3 -6
  196. package/dist/Pagination/Pagination.d.ts +1 -1
  197. package/dist/Pagination/Pagination.d.ts.map +1 -1
  198. package/dist/Pagination/Pagination.js +125 -72
  199. package/dist/Popover/Popover.js +16 -16
  200. package/dist/Popover/usePopover.js +7 -7
  201. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  202. package/dist/PowerSearch/PowerSearch.js +110 -92
  203. package/dist/PowerSearch/PowerSearchEditPopover.js +84 -84
  204. package/dist/PowerSearch/PowerSearchFilterEditor.js +3 -3
  205. package/dist/PowerSearch/PowerSearchToken.js +5 -5
  206. package/dist/PowerSearch/PowerSearchValueEditor.js +54 -54
  207. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  208. package/dist/ProgressBar/ProgressBar.js +25 -18
  209. package/dist/RadioList/RadioList.js +7 -7
  210. package/dist/RadioList/RadioListItem.d.ts +1 -1
  211. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  212. package/dist/RadioList/RadioListItem.js +24 -21
  213. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  214. package/dist/Resizable/ResizeHandle.js +28 -10
  215. package/dist/Section/Section.js +5 -5
  216. package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
  217. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  218. package/dist/SegmentedControl/SegmentedControl.js +32 -10
  219. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  220. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  221. package/dist/SegmentedControl/SegmentedControlItem.js +18 -12
  222. package/dist/SelectableCard/SelectableCard.js +5 -5
  223. package/dist/Selector/Selector.js +46 -46
  224. package/dist/Selector/SelectorOption.js +3 -3
  225. package/dist/SideNav/SideNav.d.ts.map +1 -1
  226. package/dist/SideNav/SideNav.js +38 -34
  227. package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
  228. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  229. package/dist/SideNav/SideNavCollapseButton.js +8 -6
  230. package/dist/SideNav/SideNavHeading.js +87 -87
  231. package/dist/SideNav/SideNavItem.js +42 -42
  232. package/dist/SideNav/SideNavSection.d.ts +1 -1
  233. package/dist/SideNav/SideNavSection.d.ts.map +1 -1
  234. package/dist/SideNav/SideNavSection.js +18 -16
  235. package/dist/Skeleton/Skeleton.js +3 -3
  236. package/dist/Slider/Slider.js +23 -23
  237. package/dist/Spinner/Spinner.js +9 -9
  238. package/dist/StatusDot/StatusDot.js +5 -5
  239. package/dist/Switch/Switch.d.ts +8 -4
  240. package/dist/Switch/Switch.d.ts.map +1 -1
  241. package/dist/Switch/Switch.js +34 -29
  242. package/dist/TabList/Tab.js +18 -18
  243. package/dist/TabList/TabList.js +5 -5
  244. package/dist/TabList/TabMenu.d.ts.map +1 -1
  245. package/dist/TabList/TabMenu.js +57 -30
  246. package/dist/TabList/tab.markers.stylex.d.ts +1 -1
  247. package/dist/Table/BaseTable.d.ts.map +1 -1
  248. package/dist/Table/BaseTable.js +50 -39
  249. package/dist/Table/Table.js +7 -7
  250. package/dist/Table/TableBody.d.ts +1 -1
  251. package/dist/Table/TableBody.d.ts.map +1 -1
  252. package/dist/Table/TableBody.js +9 -5
  253. package/dist/Table/TableCell.js +3 -3
  254. package/dist/Table/TableFooter.d.ts +1 -1
  255. package/dist/Table/TableFooter.d.ts.map +1 -1
  256. package/dist/Table/TableFooter.js +9 -5
  257. package/dist/Table/TableHeader.d.ts +1 -1
  258. package/dist/Table/TableHeader.d.ts.map +1 -1
  259. package/dist/Table/TableHeader.js +9 -5
  260. package/dist/Table/TableHeaderCell.js +3 -3
  261. package/dist/Table/TableRow.js +5 -5
  262. package/dist/Table/index.d.ts +2 -0
  263. package/dist/Table/index.d.ts.map +1 -1
  264. package/dist/Table/index.js +1 -0
  265. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  266. package/dist/Table/plugins/columnResize/useTableColumnResize.js +14 -11
  267. package/dist/Table/plugins/filtering/useTableFiltering.js +71 -71
  268. package/dist/Table/plugins/groupedRows/index.d.ts +3 -0
  269. package/dist/Table/plugins/groupedRows/index.d.ts.map +1 -0
  270. package/dist/Table/plugins/groupedRows/index.js +3 -0
  271. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +78 -0
  272. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -0
  273. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +250 -0
  274. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  275. package/dist/Table/plugins/pagination/paginateData.js +5 -1
  276. package/dist/Table/plugins/pagination/useTablePagination.js +7 -7
  277. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +1 -1
  278. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +23 -23
  279. package/dist/Table/plugins/selection/useTableSelection.js +18 -18
  280. package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
  281. package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
  282. package/dist/Table/plugins/sortable/useTableSortable.js +17 -17
  283. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  284. package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
  285. package/dist/Table/tableContextMenu.js +9 -9
  286. package/dist/Text/Text.js +10 -10
  287. package/dist/TextArea/TextArea.js +14 -14
  288. package/dist/TextInput/TextInput.js +15 -15
  289. package/dist/Thumbnail/Thumbnail.d.ts +3 -2
  290. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  291. package/dist/Thumbnail/Thumbnail.js +30 -29
  292. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  293. package/dist/TimeInput/TimeInput.js +26 -21
  294. package/dist/Timestamp/Timestamp.d.ts +1 -1
  295. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  296. package/dist/Timestamp/Timestamp.js +37 -26
  297. package/dist/Toast/Toast.d.ts.map +1 -1
  298. package/dist/Toast/Toast.js +25 -17
  299. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  300. package/dist/Toast/ToastViewport.js +23 -11
  301. package/dist/Toast/useToast.d.ts.map +1 -1
  302. package/dist/Toast/useToast.js +44 -4
  303. package/dist/ToggleButton/ToggleButton.js +12 -12
  304. package/dist/ToggleButton/ToggleButtonGroup.js +5 -5
  305. package/dist/Token/Token.js +21 -21
  306. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  307. package/dist/Tokenizer/Tokenizer.js +36 -34
  308. package/dist/Toolbar/Toolbar.js +18 -18
  309. package/dist/Tooltip/Tooltip.js +11 -11
  310. package/dist/Tooltip/useTooltip.d.ts +5 -1
  311. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  312. package/dist/Tooltip/useTooltip.js +3 -3
  313. package/dist/TopNav/TopNav.js +32 -32
  314. package/dist/TopNav/TopNavHeading.js +67 -67
  315. package/dist/TopNav/TopNavItem.js +5 -5
  316. package/dist/TopNav/TopNavMegaMenu.js +30 -30
  317. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +1 -1
  318. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  319. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +25 -14
  320. package/dist/TopNav/TopNavMegaMenuItem.js +16 -16
  321. package/dist/TopNav/TopNavMenu.js +32 -32
  322. package/dist/TreeList/TreeList.js +8 -8
  323. package/dist/TreeList/TreeListBranches.js +11 -11
  324. package/dist/TreeList/TreeListItem.js +30 -30
  325. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  326. package/dist/Typeahead/BaseTypeahead.js +23 -20
  327. package/dist/Typeahead/Typeahead.js +12 -12
  328. package/dist/Typeahead/TypeaheadItem.js +10 -10
  329. package/dist/VStack/VStack.js +3 -3
  330. package/dist/VisuallyHidden/VisuallyHidden.d.ts +0 -3
  331. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
  332. package/dist/VisuallyHidden/VisuallyHidden.js +0 -3
  333. package/dist/astryx.css +7 -0
  334. package/dist/astryx.umd.js +63 -63
  335. package/dist/astryx.umd.js.map +4 -4
  336. package/dist/docs-types.d.ts +8 -2
  337. package/dist/docs-types.d.ts.map +1 -1
  338. package/dist/hooks/focusableSelector.d.ts +1 -1
  339. package/dist/hooks/focusableSelector.d.ts.map +1 -1
  340. package/dist/hooks/focusableSelector.js +1 -1
  341. package/dist/hooks/index.d.ts +2 -0
  342. package/dist/hooks/index.d.ts.map +1 -1
  343. package/dist/hooks/index.js +1 -0
  344. package/dist/hooks/useFocusTrap.d.ts +3 -0
  345. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  346. package/dist/hooks/useFocusTrap.js +38 -1
  347. package/dist/hooks/useHotkeys.d.ts +51 -0
  348. package/dist/hooks/useHotkeys.d.ts.map +1 -0
  349. package/dist/hooks/useHotkeys.js +160 -0
  350. package/dist/hooks/useInteractiveRole.d.ts +6 -2
  351. package/dist/hooks/useInteractiveRole.d.ts.map +1 -1
  352. package/dist/hooks/useInteractiveRole.js +6 -2
  353. package/dist/hooks/useKeyboardHint.js +9 -9
  354. package/dist/hooks/useStreamingText.d.ts +3 -1
  355. package/dist/hooks/useStreamingText.d.ts.map +1 -1
  356. package/dist/hooks/useStreamingText.js +14 -5
  357. package/dist/theme/MediaTheme.js +3 -3
  358. package/dist/theme/Theme.js +7 -7
  359. package/dist/theme/defineTheme.d.ts +1 -1
  360. package/dist/theme/defineTheme.d.ts.map +1 -1
  361. package/dist/theme/expandColorScale.d.ts.map +1 -1
  362. package/dist/theme/expandColorScale.js +10 -3
  363. package/dist/theme/hct.d.ts +0 -15
  364. package/dist/theme/hct.d.ts.map +1 -1
  365. package/dist/theme/hct.js +9 -9
  366. package/dist/theme/syntax/SyntaxTheme.js +5 -5
  367. package/dist/theme/tokens.d.ts +10 -2
  368. package/dist/theme/tokens.d.ts.map +1 -1
  369. package/dist/theme/tokens.js +220 -4
  370. package/dist/theme/useTheme.d.ts +3 -2
  371. package/dist/theme/useTheme.d.ts.map +1 -1
  372. package/dist/theme/useTheme.js +73 -6
  373. package/dist/utils/color.d.ts +42 -0
  374. package/dist/utils/color.d.ts.map +1 -0
  375. package/dist/utils/color.js +150 -0
  376. package/dist/utils/composeEventHandlers.d.ts +30 -0
  377. package/dist/utils/composeEventHandlers.d.ts.map +1 -0
  378. package/dist/utils/composeEventHandlers.js +40 -0
  379. package/dist/utils/index.d.ts +3 -0
  380. package/dist/utils/index.d.ts.map +1 -1
  381. package/dist/utils/index.js +3 -1
  382. package/dist/utils/timeParser.d.ts.map +1 -1
  383. package/dist/utils/timeParser.js +8 -5
  384. package/package.json +4 -4
  385. package/src/AlertDialog/AlertDialog.doc.mjs +4 -0
  386. package/src/AlertDialog/AlertDialog.tsx +2 -0
  387. package/src/AlertDialog/useImperativeAlertDialog.tsx +0 -2
  388. package/src/AppShell/AppShell.tsx +2 -0
  389. package/src/AspectRatio/AspectRatio.doc.mjs +28 -1
  390. package/src/AspectRatio/AspectRatio.test.tsx +142 -1
  391. package/src/AspectRatio/AspectRatio.tsx +70 -10
  392. package/src/AspectRatio/index.ts +5 -1
  393. package/src/Avatar/Avatar.doc.mjs +11 -0
  394. package/src/Avatar/Avatar.test.tsx +32 -1
  395. package/src/Avatar/Avatar.tsx +14 -22
  396. package/src/Avatar/AvatarStatusDot.tsx +2 -2
  397. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -0
  398. package/src/AvatarGroup/AvatarGroup.tsx +2 -2
  399. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -2
  400. package/src/Banner/Banner.doc.mjs +8 -0
  401. package/src/Banner/Banner.test.tsx +38 -0
  402. package/src/Banner/Banner.tsx +8 -1
  403. package/src/Calendar/Calendar.doc.mjs +1 -1
  404. package/src/Calendar/Calendar.test.tsx +122 -17
  405. package/src/Calendar/Calendar.tsx +26 -5
  406. package/src/Calendar/dayCellUtils.ts +3 -1
  407. package/src/Calendar/styles.ts +12 -2
  408. package/src/Card/Card.doc.mjs +1 -1
  409. package/src/Card/Card.tsx +16 -6
  410. package/src/Carousel/Carousel.doc.mjs +12 -12
  411. package/src/Carousel/Carousel.test.tsx +13 -0
  412. package/src/Carousel/Carousel.tsx +4 -2
  413. package/src/Chat/Chat.doc.mjs +1 -1
  414. package/src/Chat/ChatComposerInput.test.tsx +27 -1
  415. package/src/Chat/ChatComposerInput.tsx +20 -15
  416. package/src/Chat/ChatDictationButton.test.tsx +43 -0
  417. package/src/Chat/ChatDictationButton.tsx +4 -4
  418. package/src/Chat/ChatLayoutScrollButton.test.tsx +33 -0
  419. package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
  420. package/src/Chat/ChatMessageMetadata.test.tsx +32 -0
  421. package/src/Chat/ChatMessageMetadata.tsx +4 -6
  422. package/src/Chat/ChatSystemMessage.test.tsx +26 -11
  423. package/src/Chat/ChatSystemMessage.tsx +7 -7
  424. package/src/Chat/ChatTokenizedText.test.tsx +29 -16
  425. package/src/Chat/ChatTokenizedText.tsx +15 -8
  426. package/src/Chat/ChatToolCalls.test.tsx +55 -0
  427. package/src/Chat/ChatToolCalls.tsx +10 -12
  428. package/src/Chat/index.ts +1 -4
  429. package/src/Chat/useTriggerMenu.tsx +1 -4
  430. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
  431. package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
  432. package/src/CheckboxInput/CheckboxInput.tsx +2 -0
  433. package/src/CheckboxList/CheckboxList.test.tsx +19 -0
  434. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  435. package/src/Citation/Citation.doc.mjs +5 -0
  436. package/src/ClickableCard/ClickableCard.tsx +45 -2
  437. package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
  438. package/src/CodeBlock/CodeBlock.test.tsx +122 -3
  439. package/src/CodeBlock/CodeBlock.tsx +62 -4
  440. package/src/Collapsible/Collapsible.doc.mjs +8 -4
  441. package/src/Collapsible/Collapsible.test.tsx +403 -0
  442. package/src/Collapsible/Collapsible.tsx +85 -10
  443. package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
  444. package/src/Collapsible/CollapsibleGroup.test.tsx +314 -0
  445. package/src/Collapsible/CollapsibleGroup.tsx +97 -9
  446. package/src/Collapsible/CollapsibleGroupContext.tsx +34 -2
  447. package/src/Collapsible/index.ts +1 -0
  448. package/src/Collapsible/useCollapsible.ts +20 -6
  449. package/src/CommandPalette/CommandPalette.doc.mjs +5 -0
  450. package/src/CommandPalette/CommandPalette.tsx +5 -3
  451. package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
  452. package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
  453. package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
  454. package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
  455. package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
  456. package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
  457. package/src/ContextMenu/ContextMenu.test.tsx +87 -0
  458. package/src/ContextMenu/ContextMenu.tsx +87 -28
  459. package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
  460. package/src/DateInput/DateInput.test.tsx +11 -20
  461. package/src/DateRangeInput/DateRangeInput.doc.mjs +9 -6
  462. package/src/DateRangeInput/DateRangeInput.test.tsx +31 -44
  463. package/src/DateTimeInput/DateTimeInput.doc.mjs +3 -2
  464. package/src/DateTimeInput/DateTimeInput.test.tsx +118 -16
  465. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  466. package/src/DateTimeInput/index.ts +1 -0
  467. package/src/Dialog/Dialog.doc.mjs +4 -0
  468. package/src/Dialog/DialogHeader.doc.mjs +45 -0
  469. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
  470. package/src/DropdownMenu/DropdownMenu.test.tsx +22 -2
  471. package/src/DropdownMenu/DropdownMenuItem.tsx +5 -17
  472. package/src/EmptyState/EmptyState.doc.mjs +1 -1
  473. package/src/Field/Field.test.tsx +1 -4
  474. package/src/Field/FieldLabel.test.tsx +2 -2
  475. package/src/FieldStatus/FieldStatus.test.tsx +282 -0
  476. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
  477. package/src/Grid/Grid.test.tsx +43 -18
  478. package/src/Grid/Grid.tsx +26 -9
  479. package/src/HoverCard/HoverCard.doc.mjs +6 -6
  480. package/src/HoverCard/useHoverCard.doc.mjs +5 -5
  481. package/src/HoverCard/useHoverCard.tsx +9 -2
  482. package/src/Icon/Icon.test.tsx +41 -29
  483. package/src/Icon/Icon.tsx +6 -2
  484. package/src/InputGroup/InputGroup.doc.mjs +1 -0
  485. package/src/InputGroup/InputGroup.tsx +2 -2
  486. package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +185 -0
  487. package/src/Item/Item.tsx +1 -1
  488. package/src/Layer/useLayer.doc.mjs +2 -2
  489. package/src/Layer/useLayer.test.tsx +221 -2
  490. package/src/Layer/useLayer.tsx +69 -30
  491. package/src/Layout/Layout.test.tsx +285 -0
  492. package/src/Layout/LayoutContent.doc.mjs +12 -0
  493. package/src/Layout/LayoutPanel.doc.mjs +40 -0
  494. package/src/Layout/LayoutSlots.test.tsx +364 -0
  495. package/src/Lightbox/Lightbox.doc.mjs +5 -0
  496. package/src/Lightbox/Lightbox.test.tsx +123 -2
  497. package/src/Lightbox/Lightbox.tsx +29 -1
  498. package/src/Lightbox/useLightbox.tsx +0 -1
  499. package/src/Link/Link.tsx +0 -2
  500. package/src/Markdown/Markdown.tsx +9 -7
  501. package/src/Markdown/parser.perf.test.ts +34 -12
  502. package/src/MobileNav/MobileNav.test.tsx +56 -0
  503. package/src/MobileNav/MobileNav.tsx +13 -8
  504. package/src/NavMenu/NavMenu.doc.mjs +15 -0
  505. package/src/NumberInput/NumberInput.test.tsx +48 -3
  506. package/src/NumberInput/NumberInput.tsx +32 -15
  507. package/src/Outline/Outline.doc.mjs +2 -0
  508. package/src/Outline/parseOutlineFromMarkdown.test.ts +80 -0
  509. package/src/OverflowList/OverflowList.test.tsx +416 -0
  510. package/src/Overlay/Overlay.doc.mjs +6 -0
  511. package/src/Overlay/Overlay.test.tsx +356 -0
  512. package/src/Overlay/useOverlay.tsx +0 -3
  513. package/src/Pagination/Pagination.test.tsx +162 -0
  514. package/src/Pagination/Pagination.tsx +80 -26
  515. package/src/Popover/Popover.doc.mjs +6 -6
  516. package/src/Popover/Popover.test.tsx +65 -0
  517. package/src/Popover/usePopover.doc.mjs +1 -1
  518. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  519. package/src/PowerSearch/PowerSearch.test.tsx +109 -1
  520. package/src/PowerSearch/PowerSearch.tsx +37 -16
  521. package/src/ProgressBar/ProgressBar.doc.mjs +2 -0
  522. package/src/ProgressBar/ProgressBar.test.tsx +30 -0
  523. package/src/ProgressBar/ProgressBar.tsx +12 -5
  524. package/src/RadioList/RadioList.test.tsx +19 -0
  525. package/src/RadioList/RadioListItem.tsx +9 -4
  526. package/src/Resizable/ResizeHandle.test.tsx +218 -0
  527. package/src/Resizable/ResizeHandle.tsx +22 -4
  528. package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
  529. package/src/SegmentedControl/SegmentedControl.test.tsx +192 -0
  530. package/src/SegmentedControl/SegmentedControl.tsx +27 -5
  531. package/src/SegmentedControl/SegmentedControlItem.tsx +9 -4
  532. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  533. package/src/Selector/Selector.test.tsx +27 -1
  534. package/src/SideNav/SideNav.doc.mjs +7 -1
  535. package/src/SideNav/SideNav.test.tsx +66 -0
  536. package/src/SideNav/SideNav.tsx +6 -2
  537. package/src/SideNav/SideNavCollapseButton.tsx +4 -3
  538. package/src/SideNav/SideNavSection.tsx +6 -4
  539. package/src/SizeContext/SizeContext.test.tsx +226 -0
  540. package/src/Switch/Switch.doc.mjs +7 -7
  541. package/src/Switch/Switch.test.tsx +102 -3
  542. package/src/Switch/Switch.tsx +20 -7
  543. package/src/TabList/TabList.test.tsx +185 -1
  544. package/src/TabList/TabMenu.tsx +45 -9
  545. package/src/Table/BaseTable.tsx +13 -2
  546. package/src/Table/Table.doc.mjs +6 -1
  547. package/src/Table/Table.test.tsx +87 -0
  548. package/src/Table/TableBody.tsx +15 -2
  549. package/src/Table/TableFooter.tsx +15 -2
  550. package/src/Table/TableHeader.tsx +15 -2
  551. package/src/Table/index.ts +5 -0
  552. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
  553. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
  554. package/src/Table/plugins/groupedRows/index.ts +7 -0
  555. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +231 -0
  556. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +349 -0
  557. package/src/Table/plugins/pagination/paginateData.ts +5 -1
  558. package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
  559. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -1
  560. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
  561. package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
  562. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
  563. package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
  564. package/src/Table/useTableGroupedRows.doc.mjs +71 -0
  565. package/src/TextArea/TextArea.test.tsx +3 -3
  566. package/src/TextInput/TextInput.test.tsx +3 -3
  567. package/src/Thumbnail/Thumbnail.test.tsx +36 -2
  568. package/src/Thumbnail/Thumbnail.tsx +4 -2
  569. package/src/TimeInput/TimeInput.test.tsx +37 -0
  570. package/src/TimeInput/TimeInput.tsx +11 -1
  571. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  572. package/src/Timestamp/Timestamp.test.tsx +52 -0
  573. package/src/Timestamp/Timestamp.tsx +30 -16
  574. package/src/Toast/Toast.tsx +11 -3
  575. package/src/Toast/ToastViewport.test.tsx +64 -0
  576. package/src/Toast/ToastViewport.tsx +12 -0
  577. package/src/Toast/useToast.test.tsx +258 -0
  578. package/src/Toast/useToast.tsx +41 -0
  579. package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
  580. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  581. package/src/Tokenizer/Tokenizer.test.tsx +54 -0
  582. package/src/Tokenizer/Tokenizer.tsx +5 -4
  583. package/src/Tooltip/Tooltip.doc.mjs +6 -6
  584. package/src/Tooltip/useTooltip.doc.mjs +5 -5
  585. package/src/Tooltip/useTooltip.tsx +12 -2
  586. package/src/TopNav/TopNav.doc.mjs +1 -1
  587. package/src/TopNav/TopNav.test.tsx +23 -7
  588. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +9 -2
  589. package/src/TreeList/TreeList.doc.mjs +2 -2
  590. package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
  591. package/src/Typeahead/BaseTypeahead.tsx +5 -2
  592. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  593. package/src/Typeahead/Typeahead.test.tsx +45 -1
  594. package/src/VisuallyHidden/VisuallyHidden.tsx +0 -3
  595. package/src/__tests__/TestIcon.tsx +26 -0
  596. package/src/__tests__/fastRoleQueries.ts +66 -0
  597. package/src/docPropReferences.test.ts +178 -0
  598. package/src/docs-types.ts +9 -1
  599. package/src/hooks/focusableSelector.ts +1 -1
  600. package/src/hooks/index.ts +3 -0
  601. package/src/hooks/useFocusTrap.doc.mjs +4 -2
  602. package/src/hooks/useFocusTrap.test.tsx +149 -1
  603. package/src/hooks/useFocusTrap.ts +50 -1
  604. package/src/hooks/useHotkeys.doc.mjs +52 -0
  605. package/src/hooks/useHotkeys.test.ts +245 -0
  606. package/src/hooks/useHotkeys.ts +203 -0
  607. package/src/hooks/useInteractiveRole.ts +12 -4
  608. package/src/hooks/useStreamingText.test.ts +54 -19
  609. package/src/hooks/useStreamingText.ts +16 -5
  610. package/src/hooks/useTreeFocus.test.tsx +1 -3
  611. package/src/reset.css +29 -0
  612. package/src/theme/SyntaxTheme.doc.mjs +4 -4
  613. package/src/theme/defineTheme.ts +4 -13
  614. package/src/theme/derivedVarRegistry.test.ts +2 -2
  615. package/src/theme/expandColorScale.test.ts +29 -0
  616. package/src/theme/expandColorScale.ts +11 -6
  617. package/src/theme/hct.ts +9 -17
  618. package/src/theme/themingTargets.test.ts +353 -0
  619. package/src/theme/tokens.test.ts +182 -9
  620. package/src/theme/tokens.ts +239 -4
  621. package/src/theme/useTheme.test.tsx +126 -2
  622. package/src/theme/useTheme.ts +87 -6
  623. package/src/utils/color.test.ts +133 -0
  624. package/src/utils/color.ts +148 -0
  625. package/src/utils/composeEventHandlers.test.ts +71 -0
  626. package/src/utils/composeEventHandlers.ts +44 -0
  627. package/src/utils/index.ts +4 -0
  628. package/src/utils/mergeProps.test.ts +166 -0
  629. package/src/utils/sharedResizeObserver.test.ts +2 -2
  630. package/src/utils/timeParser.test.ts +13 -0
  631. package/src/utils/timeParser.ts +8 -5
  632. package/dist/Carousel/carousel.markers.stylex.d.ts +0 -7
  633. package/dist/Carousel/carousel.markers.stylex.d.ts.map +0 -1
  634. package/dist/Carousel/carousel.markers.stylex.js +0 -12
  635. package/src/Carousel/carousel.markers.stylex.ts +0 -10
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import {describe, it, expect, vi} from 'vitest';
11
- import {render, screen, within} from '@testing-library/react';
11
+ import {render, renderHook, screen, within} from '@testing-library/react';
12
12
  import userEvent from '@testing-library/user-event';
13
13
  import {useState} from 'react';
14
14
  import {Table} from '../../Table';
@@ -421,6 +421,40 @@ describe('useTableSortableState', () => {
421
421
  });
422
422
  });
423
423
 
424
+ describe('NaN handling', () => {
425
+ it('keeps valid numbers sorted when a NaN cell is present', () => {
426
+ const data: Employee[] = [
427
+ {id: '1', name: 'A', age: 5, department: 'X', salary: 1},
428
+ {id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
429
+ {id: '3', name: 'C', age: 1, department: 'X', salary: 1},
430
+ {id: '4', name: 'D', age: 3, department: 'X', salary: 1},
431
+ ];
432
+ const {result} = renderHook(() =>
433
+ useTableSortableState({
434
+ data,
435
+ defaultSort: [{sortKey: 'age', direction: 'ascending'}],
436
+ }),
437
+ );
438
+ // NaN groups at the end like null; the valid numbers stay ordered.
439
+ expect(result.current.sortedData.map(e => e.age)).toEqual([1, 3, 5, NaN]);
440
+ });
441
+
442
+ it('sorts NaN to the start in descending order, like null', () => {
443
+ const data: Employee[] = [
444
+ {id: '1', name: 'A', age: 5, department: 'X', salary: 1},
445
+ {id: '2', name: 'B', age: NaN, department: 'X', salary: 1},
446
+ {id: '3', name: 'C', age: 1, department: 'X', salary: 1},
447
+ ];
448
+ const {result} = renderHook(() =>
449
+ useTableSortableState({
450
+ data,
451
+ defaultSort: [{sortKey: 'age', direction: 'descending'}],
452
+ }),
453
+ );
454
+ expect(result.current.sortedData.map(e => e.age)).toEqual([NaN, 5, 1]);
455
+ });
456
+ });
457
+
424
458
  describe('null/undefined handling', () => {
425
459
  it('sorts null values to the end', () => {
426
460
  const data: Employee[] = [
@@ -168,14 +168,18 @@ function defaultCompare<T extends Record<string, unknown>>(
168
168
  const aVal = a[sortKey];
169
169
  const bVal = b[sortKey];
170
170
 
171
- // null/undefined sort to the end
172
- if (aVal == null && bVal == null) {
171
+ // null/undefined/NaN sort to the end. NaN must not reach the numeric fast
172
+ // path: a NaN comparator result reads as "equal" to Array.sort, which makes
173
+ // the comparator inconsistent and corrupts the order of the other rows.
174
+ const aMissing = aVal == null || (typeof aVal === 'number' && Number.isNaN(aVal));
175
+ const bMissing = bVal == null || (typeof bVal === 'number' && Number.isNaN(bVal));
176
+ if (aMissing && bMissing) {
173
177
  return 0;
174
178
  }
175
- if (aVal == null) {
179
+ if (aMissing) {
176
180
  return 1;
177
181
  }
178
- if (bVal == null) {
182
+ if (bMissing) {
179
183
  return -1;
180
184
  }
181
185
 
@@ -0,0 +1,71 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../docs-types').ComponentDoc} */
4
+
5
+ export const docs = {
6
+ name: 'useTableGroupedRows',
7
+ subComponentOf: 'Table',
8
+ displayName: 'useTableGroupedRows',
9
+ description:
10
+ 'Hook that groups a flat data array into collapsible section rows. Each distinct groupBy value becomes a full-width section-header row with a chevron toggle, the group label, and a member count; collapsing hides that group\u2019s data rows while keeping the header visible. Mirrors useTableRowExpansionState: the consumer owns the collapsedGroups set and the hook returns {data, plugin, idKey} \u2014 pass all three to Table (data, plugins, and idKey respectively).',
11
+ props: [
12
+ {
13
+ name: 'data',
14
+ type: 'T[]',
15
+ description: 'The flat data to group.',
16
+ required: true,
17
+ },
18
+ {
19
+ name: 'groupBy',
20
+ type: '(item: T) => string',
21
+ description:
22
+ 'Derive the group key for a row. Rows with the same key share a section.',
23
+ required: true,
24
+ },
25
+ {
26
+ name: 'collapsedGroups',
27
+ type: 'Set<string>',
28
+ description: 'Set of currently-collapsed group keys.',
29
+ required: true,
30
+ },
31
+ {
32
+ name: 'onToggleGroup',
33
+ type: '(groupKey: string) => void',
34
+ description: 'Called with a group key when its header is toggled.',
35
+ required: true,
36
+ },
37
+ {
38
+ name: 'renderGroupHeader',
39
+ type: '(groupKey: string, count: number, collapsed: boolean) => ReactNode',
40
+ description:
41
+ "Custom renderer for a group header's content (right of the chevron). Defaults to `<groupKey> (<count>)`.",
42
+ },
43
+ {
44
+ name: 'getRowKey',
45
+ type: '(item: T) => string',
46
+ description:
47
+ 'Stable key for a real row. Falls back to a positional key when omitted.',
48
+ },
49
+ {
50
+ name: 'groupOrder',
51
+ type: 'string[]',
52
+ description:
53
+ 'Explicit group ordering; groups not listed keep first-seen order after these.',
54
+ },
55
+ ],
56
+ };
57
+
58
+ /** @type {import('../docs-types').TranslationDoc} */
59
+ export const docsDense = {
60
+ description:
61
+ 'Groups a flat data array into collapsible section rows. Each groupBy value becomes a full-width header (chevron + label + count); collapsing hides its rows. Returns {data, plugin, idKey} \u2014 pass to Table data / plugins / idKey. Consumer owns the collapsedGroups set.',
62
+ propDescriptions: {
63
+ data: 'The flat data to group.',
64
+ groupBy: 'Derive a row\u2019s group key. Same key = same section.',
65
+ collapsedGroups: 'Set of currently-collapsed group keys.',
66
+ onToggleGroup: 'Called with the group key when a header is toggled.',
67
+ renderGroupHeader: "Custom header content (right of chevron). Default '<key> (<count>)'.",
68
+ getRowKey: 'Stable key for a real row; positional fallback when omitted.',
69
+ groupOrder: 'Pin these group keys first; others keep first-seen order.',
70
+ },
71
+ };
@@ -13,7 +13,7 @@ import {useState} from 'react';
13
13
  import {describe, it, expect, vi, beforeEach} from 'vitest';
14
14
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
15
15
  import userEvent from '@testing-library/user-event';
16
- import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
16
+ import {TestIcon} from '../__tests__/TestIcon';
17
17
  import {TextArea} from './TextArea';
18
18
 
19
19
  // Mock showPopover/hidePopover since jsdom does not implement them. Used by the
@@ -193,7 +193,7 @@ describe('TextArea', () => {
193
193
  label="Description"
194
194
  value=""
195
195
  onChange={() => {}}
196
- startIcon={MagnifyingGlassIcon}
196
+ startIcon={TestIcon}
197
197
  />,
198
198
  );
199
199
  expect(screen.getByRole('textbox')).toBeInTheDocument();
@@ -577,7 +577,7 @@ describe('TextArea', () => {
577
577
  label="Notes"
578
578
  value=""
579
579
  onChange={() => {}}
580
- startIcon={<MagnifyingGlassIcon />}
580
+ startIcon={<TestIcon />}
581
581
  />,
582
582
  );
583
583
 
@@ -12,7 +12,7 @@
12
12
  import {describe, it, expect, vi, beforeEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
- import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
15
+ import {TestIcon} from '../__tests__/TestIcon';
16
16
  import {TextInput} from './TextInput';
17
17
 
18
18
  // Mock showPopover/hidePopover since jsdom does not implement them. Used by the
@@ -163,7 +163,7 @@ describe('TextInput', () => {
163
163
  label="Search"
164
164
  value=""
165
165
  onChange={() => {}}
166
- startIcon={MagnifyingGlassIcon}
166
+ startIcon={TestIcon}
167
167
  />,
168
168
  );
169
169
  expect(screen.getByRole('textbox')).toBeInTheDocument();
@@ -482,7 +482,7 @@ describe('TextInput', () => {
482
482
  label="Search"
483
483
  value=""
484
484
  onChange={() => {}}
485
- startIcon={<MagnifyingGlassIcon />}
485
+ startIcon={<TestIcon />}
486
486
  />,
487
487
  );
488
488
 
@@ -37,11 +37,45 @@ describe('Thumbnail', () => {
37
37
  expect(screen.getByRole('status')).toBeInTheDocument();
38
38
  });
39
39
 
40
- it('uses label as accessible name on root', () => {
40
+ it('exposes the label as an accessible name via a valid group role', () => {
41
41
  render(<Thumbnail label="photo.png" data-testid="thumb" />);
42
- expect(screen.getByTestId('thumb')).toHaveAttribute('aria-label', 'photo.png');
42
+ // The accessible name must be carried by a valid named role (group),
43
+ // not by a bare aria-label on a generic div (aria-prohibited-attr).
44
+ const group = screen.getByRole('group', {name: 'photo.png'});
45
+ expect(group).toBe(screen.getByTestId('thumb'));
43
46
  });
44
47
 
48
+ it('does not put aria-label on a generic (roleless) element', () => {
49
+ render(<Thumbnail label="photo.png" data-testid="thumb" />);
50
+ const thumb = screen.getByTestId('thumb');
51
+ // The labeled element must declare a role so the name is legitimate.
52
+ expect(thumb).toHaveAttribute('role', 'group');
53
+ });
54
+
55
+ it('keeps interactive children accessible while exposing the group name', () => {
56
+ const onRemove = vi.fn();
57
+ render(
58
+ <Thumbnail
59
+ src="/img.jpg"
60
+ alt="Clickable"
61
+ label="file.png"
62
+ onClick={vi.fn()}
63
+ onRemove={onRemove}
64
+ />,
65
+ );
66
+ // A group role (unlike img) must not hide descendant controls.
67
+ expect(
68
+ screen.getByRole('group', {name: 'file.png — Clickable'}),
69
+ ).toBeInTheDocument();
70
+ expect(
71
+ screen.getByRole('button', {name: 'Open file.png — Clickable'}),
72
+ ).toBeInTheDocument();
73
+ expect(
74
+ screen.getByRole('button', {name: 'Remove file.png — Clickable'}),
75
+ ).toBeInTheDocument();
76
+ });
77
+
78
+
45
79
  it('label is shown via tooltip, not as inline text', () => {
46
80
  render(<Thumbnail label="photo.png" data-testid="thumb" />);
47
81
  // Label should exist in DOM (tooltip) but not as a direct child text node
@@ -58,8 +58,9 @@ export interface ThumbnailProps extends BaseProps<HTMLDivElement> {
58
58
  alt?: string;
59
59
  /**
60
60
  * Accessible label for the thumbnail (e.g. file name).
61
- * Not rendered visually — shown in a tooltip on hover and used
62
- * as accessible name for the remove button.
61
+ * Not rendered visually — shown in a tooltip on hover, exposed as the
62
+ * accessible name of the thumbnail group, and used as the accessible
63
+ * name for the remove button.
63
64
  */
64
65
  label?: string;
65
66
  /**
@@ -296,6 +297,7 @@ export function Thumbnail({
296
297
  <div
297
298
  ref={ref}
298
299
  data-testid={testId}
300
+ role="group"
299
301
  aria-label={accessibleName}
300
302
  {...mergeProps(
301
303
  themeProps('thumbnail'),
@@ -160,6 +160,43 @@ describe('TimeInput', () => {
160
160
  expect(onChange).not.toHaveBeenCalled();
161
161
  });
162
162
 
163
+ it('sets aria-invalid="true" when typed input is out of range', () => {
164
+ render(<TimeInput label="Time" onChange={() => {}} />);
165
+
166
+ const input = screen.getByRole('textbox');
167
+ fireEvent.change(input, {target: {value: '25:99'}});
168
+
169
+ expect(input).toHaveAttribute('aria-invalid', 'true');
170
+ });
171
+
172
+ it('does not set aria-invalid when typed input is a valid time', () => {
173
+ render(<TimeInput label="Time" onChange={() => {}} />);
174
+
175
+ const input = screen.getByRole('textbox');
176
+ fireEvent.change(input, {target: {value: '3:45 pm'}});
177
+
178
+ expect(input).not.toHaveAttribute('aria-invalid');
179
+ });
180
+
181
+ it('announces an alert message when typed input is invalid', () => {
182
+ render(<TimeInput label="Time" onChange={() => {}} />);
183
+
184
+ const input = screen.getByRole('textbox');
185
+ fireEvent.change(input, {target: {value: '25:99'}});
186
+
187
+ expect(screen.getByRole('alert')).toHaveTextContent('Invalid time');
188
+ });
189
+
190
+ it('does not announce an alert message when input is valid', () => {
191
+ render(<TimeInput label="Time" onChange={() => {}} />);
192
+
193
+ const input = screen.getByRole('textbox');
194
+ fireEvent.change(input, {target: {value: '3:45 pm'}});
195
+
196
+ expect(screen.getByRole('alert')).toHaveTextContent('');
197
+ expect(screen.queryByText('Invalid time')).not.toBeInTheDocument();
198
+ });
199
+
163
200
  it('calls onChange on blur when input is valid', async () => {
164
201
  const user = userEvent.setup();
165
202
  const onChange = vi.fn();
@@ -635,7 +635,9 @@ export function TimeInput({
635
635
  data-autofocus={hasAutoFocus || undefined}
636
636
  aria-describedby={ariaDescribedBy}
637
637
  aria-required={isRequired === true ? 'true' : undefined}
638
- aria-invalid={status?.type === 'error' ? 'true' : undefined}
638
+ aria-invalid={
639
+ status?.type === 'error' || !isInputValid ? 'true' : undefined
640
+ }
639
641
  aria-busy={isBusy || undefined}
640
642
  aria-labelledby={ariaLabelledBy}
641
643
  {...stylex.props(
@@ -644,6 +646,14 @@ export function TimeInput({
644
646
  !isInputValid && styles.inputInvalid,
645
647
  )}
646
648
  />
649
+ {/*
650
+ Live region announcing invalid typed input to assistive technology.
651
+ The value silently reverts on blur, so without this a screen-reader
652
+ user would get no feedback that their entry was rejected (WCAG 3.3.1).
653
+ */}
654
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
655
+ {!isInputValid ? 'Invalid time' : ''}
656
+ </VisuallyHidden>
647
657
  {isBusy && <Spinner size="sm" />}
648
658
  {hasClear && value && !isDisabled && (
649
659
  <button
@@ -84,7 +84,7 @@ export const docs = {
84
84
  ],
85
85
  theming: {
86
86
  targets: [
87
- {className: 'astryx-timestamp', visualProps: ['type', 'color']},
87
+ {className: 'astryx-timestamp', visualProps: ['type', 'color', 'format']},
88
88
  ],
89
89
  },
90
90
  usage: {
@@ -33,6 +33,31 @@ describe('Timestamp', () => {
33
33
  expect(screen.getByText('2 hours ago')).toBeInTheDocument();
34
34
  });
35
35
 
36
+ it('does not round a tier up past its own boundary', () => {
37
+ // Just under each threshold the count must stay within the tier, e.g.
38
+ // 59.98 minutes is "59 minutes ago", never "60 minutes ago".
39
+ const {rerender} = render(
40
+ <Timestamp value={Date.now() / 1000 - 3599} format="relative" />,
41
+ );
42
+ expect(screen.getByText('59 minutes ago')).toBeInTheDocument();
43
+
44
+ rerender(
45
+ <Timestamp value={Date.now() / 1000 - 86399} format="relative" />,
46
+ );
47
+ expect(screen.getByText('23 hours ago')).toBeInTheDocument();
48
+
49
+ rerender(
50
+ <Timestamp value={Date.now() / 1000 - 2591999} format="relative" />,
51
+ );
52
+ expect(screen.getByText('29 days ago')).toBeInTheDocument();
53
+
54
+ // Same guarantee on the future side.
55
+ rerender(
56
+ <Timestamp value={Date.now() / 1000 + 3599} format="relative" />,
57
+ );
58
+ expect(screen.getByText('in 59 minutes')).toBeInTheDocument();
59
+ });
60
+
36
61
  it('renders "now" for very recent times', () => {
37
62
  const fiveSecondsAgo = Date.now() / 1000 - 5;
38
63
  render(<Timestamp value={fiveSecondsAgo} format="relative" />);
@@ -290,4 +315,31 @@ describe('Timestamp', () => {
290
315
  const el = screen.getByRole('time');
291
316
  expect(el.textContent).not.toContain('ago');
292
317
  });
318
+
319
+ // --- Invalid values ---
320
+
321
+ it('renders nothing instead of crashing on an unparseable string value', () => {
322
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
323
+ try {
324
+ const {container} = render(
325
+ <Timestamp value="not-a-date" data-testid="ts" />,
326
+ );
327
+ expect(container).toBeEmptyDOMElement();
328
+ expect(warn).toHaveBeenCalledWith(
329
+ expect.stringContaining('could not parse value'),
330
+ );
331
+ } finally {
332
+ warn.mockRestore();
333
+ }
334
+ });
335
+
336
+ it('renders nothing instead of crashing on a NaN value', () => {
337
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
338
+ try {
339
+ const {container} = render(<Timestamp value={NaN} data-testid="ts" />);
340
+ expect(container).toBeEmptyDOMElement();
341
+ } finally {
342
+ warn.mockRestore();
343
+ }
344
+ });
293
345
  });
@@ -180,22 +180,22 @@ function getRelativeTimeString(date: Date, now: Date): string {
180
180
  return 'in a few seconds';
181
181
  }
182
182
  if (absDiff < HOUR) {
183
- const mins = Math.round(absDiff / MINUTE);
183
+ const mins = Math.floor(absDiff / MINUTE);
184
184
  return `in ${mins} ${mins === 1 ? 'minute' : 'minutes'}`;
185
185
  }
186
186
  if (absDiff < DAY) {
187
- const hours = Math.round(absDiff / HOUR);
187
+ const hours = Math.floor(absDiff / HOUR);
188
188
  return `in ${hours} ${hours === 1 ? 'hour' : 'hours'}`;
189
189
  }
190
190
  if (absDiff < MONTH) {
191
- const days = Math.round(absDiff / DAY);
191
+ const days = Math.floor(absDiff / DAY);
192
192
  return `in ${days} ${days === 1 ? 'day' : 'days'}`;
193
193
  }
194
194
  if (absDiff < YEAR) {
195
- const months = Math.round(absDiff / MONTH);
195
+ const months = Math.floor(absDiff / MONTH);
196
196
  return `in ${months} ${months === 1 ? 'month' : 'months'}`;
197
197
  }
198
- const years = Math.round(absDiff / YEAR);
198
+ const years = Math.floor(absDiff / YEAR);
199
199
  return `in ${years} ${years === 1 ? 'year' : 'years'}`;
200
200
  }
201
201
 
@@ -203,25 +203,25 @@ function getRelativeTimeString(date: Date, now: Date): string {
203
203
  return `${diffSeconds} seconds ago`;
204
204
  }
205
205
  if (diffSeconds < HOUR) {
206
- const mins = Math.round(diffSeconds / MINUTE);
206
+ const mins = Math.floor(diffSeconds / MINUTE);
207
207
  return `${mins} ${mins === 1 ? 'minute' : 'minutes'} ago`;
208
208
  }
209
209
  if (diffSeconds < DAY) {
210
- const hours = Math.round(diffSeconds / HOUR);
210
+ const hours = Math.floor(diffSeconds / HOUR);
211
211
  return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
212
212
  }
213
213
  if (diffSeconds < 2 * DAY) {
214
214
  return 'yesterday';
215
215
  }
216
216
  if (diffSeconds < MONTH) {
217
- const days = Math.round(diffSeconds / DAY);
217
+ const days = Math.floor(diffSeconds / DAY);
218
218
  return `${days} days ago`;
219
219
  }
220
220
  if (diffSeconds < YEAR) {
221
- const months = Math.round(diffSeconds / MONTH);
221
+ const months = Math.floor(diffSeconds / MONTH);
222
222
  return `${months} ${months === 1 ? 'month' : 'months'} ago`;
223
223
  }
224
- const years = Math.round(diffSeconds / YEAR);
224
+ const years = Math.floor(diffSeconds / YEAR);
225
225
  return `${years} ${years === 1 ? 'year' : 'years'} ago`;
226
226
  }
227
227
 
@@ -360,7 +360,12 @@ export function Timestamp({
360
360
  const [now, setNow] = useState(() => new Date());
361
361
 
362
362
  const date = parseValue(value);
363
- const isoString = date.toISOString();
363
+ // An unparseable value (a malformed date string, or a NaN timestamp from
364
+ // missing data) yields an Invalid Date, and formatting one throws "Invalid
365
+ // time value" — crashing the whole tree. Compute nothing from it here and
366
+ // bail out below (after the hooks) instead.
367
+ const isValidDate = !Number.isNaN(date.getTime());
368
+ const isoString = isValidDate ? date.toISOString() : '';
364
369
 
365
370
  // Determine effective format
366
371
  const diffSeconds = Math.round((now.getTime() - date.getTime()) / 1000);
@@ -372,19 +377,20 @@ export function Timestamp({
372
377
  : format;
373
378
 
374
379
  // Format the display text
375
- const displayText =
376
- effectiveFormat === 'relative'
380
+ const displayText = !isValidDate
381
+ ? ''
382
+ : effectiveFormat === 'relative'
377
383
  ? getRelativeTimeString(date, now)
378
384
  : isAbsoluteFormat(effectiveFormat)
379
385
  ? formatTimestamp(date, effectiveFormat, isTimezoneShown)
380
386
  : '';
381
387
 
382
388
  // Full absolute text for tooltip and aria-label
383
- const fullAbsoluteText = getFullAbsoluteString(date);
389
+ const fullAbsoluteText = isValidDate ? getFullAbsoluteString(date) : '';
384
390
 
385
391
  // Live updates
386
392
  useEffect(() => {
387
- if (!isLive || effectiveFormat !== 'relative') {
393
+ if (!isLive || !isValidDate || effectiveFormat !== 'relative') {
388
394
  return;
389
395
  }
390
396
 
@@ -394,7 +400,15 @@ export function Timestamp({
394
400
  }, interval);
395
401
 
396
402
  return () => clearInterval(timer);
397
- }, [isLive, effectiveFormat, diffSeconds]);
403
+ }, [isLive, isValidDate, effectiveFormat, diffSeconds]);
404
+
405
+ // Placed after all hooks so the hook order stays stable across renders.
406
+ if (!isValidDate) {
407
+ console.warn(
408
+ `Timestamp: could not parse value ${JSON.stringify(value)} as a date. Rendering nothing.`,
409
+ );
410
+ return null;
411
+ }
398
412
 
399
413
  const showTooltip = hasTooltip && effectiveFormat === 'relative';
400
414
 
@@ -121,8 +121,14 @@ export function Toast({
121
121
  // Will be initialized by startTimer when actually used
122
122
  const startTimeRef = useRef<number | null>(null);
123
123
 
124
+ // Read onDismiss through a ref: the viewport re-creates it on every render
125
+ // (another toast arriving/exiting), and a startTimer that depends on it
126
+ // would restart — and un-pause — this toast's timer on unrelated renders.
127
+ const onDismissRef = useRef(onDismiss);
128
+ onDismissRef.current = onDismiss;
129
+
124
130
  const startTimer = useCallback(() => {
125
- if (!isAutoHide) {
131
+ if (!isAutoHide || isPausedRef.current) {
126
132
  return;
127
133
  }
128
134
  if (timerRef.current) {
@@ -130,9 +136,9 @@ export function Toast({
130
136
  }
131
137
  startTimeRef.current = Date.now();
132
138
  timerRef.current = setTimeout(() => {
133
- onDismiss('auto');
139
+ onDismissRef.current('auto');
134
140
  }, remainingRef.current);
135
- }, [isAutoHide, onDismiss]);
141
+ }, [isAutoHide]);
136
142
 
137
143
  const pauseTimer = useCallback(() => {
138
144
  if (!isAutoHide || isPausedRef.current) {
@@ -165,6 +171,8 @@ export function Toast({
165
171
  clearTimeout(timerRef.current);
166
172
  }
167
173
  };
174
+ // startTimer's identity is stable per isAutoHide, so this runs on mount
175
+ // and on a genuine duration change — not on unrelated viewport renders.
168
176
  }, [autoHideDuration, startTimer]);
169
177
 
170
178
  // Pause the auto-hide timer while the window is not focused, so a toast
@@ -212,3 +212,67 @@ describe('ToastViewport region ARIA', () => {
212
212
  expect(region).not.toHaveAttribute('aria-modal');
213
213
  });
214
214
  });
215
+
216
+ describe('toast timer lifecycle (#3589)', () => {
217
+ it('fires onHide exactly once when dismissed twice during the exit window', () => {
218
+ const onHide = vi.fn();
219
+ renderViewport(
220
+ <ShowToastButton options={{body: 'Once', onHide}} triggerLabel="Show" />,
221
+ );
222
+ act(() => {
223
+ fireEvent.click(screen.getByText('Show'));
224
+ });
225
+ const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
226
+ act(() => {
227
+ fireEvent.click(dismiss);
228
+ });
229
+ // The toast stays mounted during its exit transition; a second click
230
+ // lands on the same still-mounted button.
231
+ act(() => {
232
+ fireEvent.click(dismiss);
233
+ });
234
+ expect(onHide).toHaveBeenCalledTimes(1);
235
+ });
236
+
237
+ it('keeps a window-blur pause alive when another toast arrives', () => {
238
+ vi.useFakeTimers();
239
+ try {
240
+ const onHide = vi.fn();
241
+ renderViewport(
242
+ <>
243
+ <ShowToastButton
244
+ options={{body: 'Paused', autoHideDuration: 3000, onHide}}
245
+ triggerLabel="Show A"
246
+ />
247
+ <ShowToastButton options={INFO_B} triggerLabel="Show B" />
248
+ </>,
249
+ );
250
+ act(() => {
251
+ fireEvent.click(screen.getByText('Show A'));
252
+ });
253
+ act(() => {
254
+ fireEvent.blur(window);
255
+ });
256
+ // A second toast arriving re-renders the viewport; the paused timer
257
+ // must not silently restart.
258
+ act(() => {
259
+ fireEvent.click(screen.getByText('Show B'));
260
+ });
261
+ act(() => {
262
+ vi.advanceTimersByTime(60_000);
263
+ });
264
+ expect(onHide).not.toHaveBeenCalled();
265
+ // Focus returns: the remaining time resumes and completes normally.
266
+ act(() => {
267
+ fireEvent.focus(window);
268
+ });
269
+ act(() => {
270
+ vi.advanceTimersByTime(60_000);
271
+ });
272
+ expect(onHide).toHaveBeenCalledTimes(1);
273
+ expect(onHide).toHaveBeenCalledWith('auto');
274
+ } finally {
275
+ vi.useRealTimers();
276
+ }
277
+ });
278
+ });
@@ -114,6 +114,9 @@ export function ToastViewport({
114
114
 
115
115
  // Show the popover on mount so it enters the top layer.
116
116
  const viewportRef = useRef<HTMLDivElement>(null);
117
+ // Toast ids whose exit has begun — guards onHide from double-firing (see
118
+ // removeToast). Mirrors exitingIds state, readable synchronously.
119
+ const exitingIdsRef = useRef<Set<string>>(new Set());
117
120
  // The element that was focused before the user jumped into the viewport
118
121
  // (via F6). Used to restore focus once all toasts are dismissed so focus
119
122
  // never falls to <body>.
@@ -171,6 +174,14 @@ export function ToastViewport({
171
174
  }, []);
172
175
 
173
176
  const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
177
+ // An exiting toast stays in toastsRef until its exit transition ends, so
178
+ // a second dismissal inside that window (double-click, auto-timer plus
179
+ // manual dismiss()) would re-fire onHide. Track exiting ids in a ref —
180
+ // the exitingIds state dedupe below runs too late to protect onHide.
181
+ if (exitingIdsRef.current.has(id)) {
182
+ return;
183
+ }
184
+ exitingIdsRef.current.add(id);
174
185
  const entry = toastsRef.current.find(t => t.id === id);
175
186
  if (entry) {
176
187
  entry.options.onHide?.(reason);
@@ -210,6 +221,7 @@ export function ToastViewport({
210
221
  }, []);
211
222
 
212
223
  const handleExited = useCallback((id: string) => {
224
+ exitingIdsRef.current.delete(id);
213
225
  setExitingIds(prev => {
214
226
  if (!prev.has(id)) {
215
227
  return prev;