@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7

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 (428) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +30 -19
  3. package/dist/AppShell/AppShell.d.ts.map +1 -1
  4. package/dist/AppShell/AppShell.js +6 -2
  5. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  6. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  7. package/dist/Avatar/Avatar.d.ts +10 -5
  8. package/dist/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/Avatar/Avatar.js +12 -8
  10. package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
  11. package/dist/Avatar/AvatarSizeContext.js +1 -1
  12. package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
  13. package/dist/Avatar/AvatarStatusDot.js +2 -2
  14. package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
  15. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.js +2 -2
  17. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  18. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  19. package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
  20. package/dist/Banner/Banner.d.ts +9 -1
  21. package/dist/Banner/Banner.d.ts.map +1 -1
  22. package/dist/Banner/Banner.js +31 -2
  23. package/dist/Button/Button.d.ts +8 -2
  24. package/dist/Button/Button.d.ts.map +1 -1
  25. package/dist/Button/Button.js +31 -2
  26. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  27. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  28. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  29. package/dist/Card/Card.d.ts +8 -2
  30. package/dist/Card/Card.d.ts.map +1 -1
  31. package/dist/Card/Card.js +28 -2
  32. package/dist/Carousel/Carousel.d.ts +6 -2
  33. package/dist/Carousel/Carousel.d.ts.map +1 -1
  34. package/dist/Carousel/Carousel.js +55 -5
  35. package/dist/Chat/ChatComposer.d.ts +8 -0
  36. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposer.js +31 -6
  38. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  39. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  40. package/dist/Chat/ChatComposerDrawer.js +11 -1
  41. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +19 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  46. package/dist/Chat/ChatMessage.d.ts +1 -1
  47. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessage.js +1 -1
  49. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  50. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  51. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  52. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  53. package/dist/Chat/ChatToolCalls.js +12 -3
  54. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  55. package/dist/Citation/Citation.d.ts.map +1 -1
  56. package/dist/Citation/Citation.js +10 -4
  57. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  58. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  59. package/dist/ClickableCard/ClickableCard.js +3 -0
  60. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  61. package/dist/CodeBlock/CodeBlock.js +8 -1
  62. package/dist/Collapsible/Collapsible.d.ts +11 -1
  63. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  64. package/dist/Collapsible/Collapsible.js +36 -4
  65. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  66. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  67. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  68. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  69. package/dist/ContextMenu/ContextMenu.js +4 -3
  70. package/dist/ContextMenu/index.d.ts +3 -0
  71. package/dist/ContextMenu/index.d.ts.map +1 -1
  72. package/dist/ContextMenu/index.js +7 -1
  73. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  74. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  75. package/dist/Dialog/Dialog.d.ts.map +1 -1
  76. package/dist/Dialog/Dialog.js +2 -1
  77. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenu.js +8 -4
  79. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
  80. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
  82. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
  83. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
  85. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
  86. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
  87. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
  88. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
  89. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
  90. package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
  91. package/dist/DropdownMenu/index.d.ts +5 -1
  92. package/dist/DropdownMenu/index.d.ts.map +1 -1
  93. package/dist/DropdownMenu/index.js +10 -1
  94. package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
  95. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
  96. package/dist/DropdownMenu/menuItemRoles.js +15 -0
  97. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  98. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  99. package/dist/FieldStatus/FieldStatus.js +22 -4
  100. package/dist/FileInput/FileInput.d.ts.map +1 -1
  101. package/dist/FileInput/FileInput.js +10 -2
  102. package/dist/HoverCard/HoverCard.d.ts +9 -1
  103. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  104. package/dist/HoverCard/HoverCard.js +2 -0
  105. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  106. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  107. package/dist/HoverCard/useHoverCard.js +7 -2
  108. package/dist/Icon/Icon.d.ts +43 -13
  109. package/dist/Icon/Icon.d.ts.map +1 -1
  110. package/dist/Icon/Icon.js +62 -27
  111. package/dist/Layer/useLayer.d.ts +6 -0
  112. package/dist/Layer/useLayer.d.ts.map +1 -1
  113. package/dist/Layer/useLayer.js +2 -0
  114. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  115. package/dist/MobileNav/MobileNav.js +6 -1
  116. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  117. package/dist/MobileNav/MobileNavToggle.js +4 -0
  118. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  119. package/dist/MultiSelector/MultiSelector.js +39 -12
  120. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  121. package/dist/MultiSelector/hooks.js +16 -0
  122. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  123. package/dist/Outline/Outline.d.ts +68 -3
  124. package/dist/Outline/Outline.d.ts.map +1 -1
  125. package/dist/Outline/Outline.js +128 -26
  126. package/dist/Outline/useScrollSpy.d.ts +27 -7
  127. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  128. package/dist/Outline/useScrollSpy.js +202 -70
  129. package/dist/OverflowList/OverflowList.d.ts +20 -3
  130. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  131. package/dist/OverflowList/OverflowList.js +29 -4
  132. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  133. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  134. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  135. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  136. package/dist/SelectableCard/SelectableCard.js +14 -11
  137. package/dist/Selector/Selector.d.ts.map +1 -1
  138. package/dist/Selector/Selector.js +4 -2
  139. package/dist/Selector/hooks.d.ts.map +1 -1
  140. package/dist/Selector/hooks.js +16 -0
  141. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  142. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  143. package/dist/SideNav/SideNavHeading.js +21 -8
  144. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  145. package/dist/Slider/Slider.d.ts.map +1 -1
  146. package/dist/Slider/Slider.js +15 -1
  147. package/dist/TabList/Tab.d.ts.map +1 -1
  148. package/dist/TabList/Tab.js +2 -2
  149. package/dist/TabList/TabList.d.ts.map +1 -1
  150. package/dist/TabList/TabList.js +2 -0
  151. package/dist/TabList/TabMenu.d.ts.map +1 -1
  152. package/dist/TabList/TabMenu.js +1 -1
  153. package/dist/Table/Table.d.ts +1 -1
  154. package/dist/Table/Table.js +1 -1
  155. package/dist/Table/index.d.ts +4 -0
  156. package/dist/Table/index.d.ts.map +1 -1
  157. package/dist/Table/index.js +2 -0
  158. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  159. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  160. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
  161. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  162. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  163. package/dist/Table/plugins/rowStatus/index.js +3 -0
  164. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  165. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  166. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  167. package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
  168. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  169. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  170. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  171. package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
  172. package/dist/Table/plugins/tree/index.d.ts +5 -0
  173. package/dist/Table/plugins/tree/index.d.ts.map +1 -0
  174. package/dist/Table/plugins/tree/index.js +4 -0
  175. package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
  176. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
  177. package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
  178. package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
  179. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
  180. package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
  181. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  182. package/dist/Table/useBaseTablePlugins.js +6 -3
  183. package/dist/Thumbnail/Thumbnail.d.ts +8 -1
  184. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  185. package/dist/Thumbnail/Thumbnail.js +46 -8
  186. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  187. package/dist/TimeInput/TimeInput.js +15 -4
  188. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  189. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  190. package/dist/Tokenizer/Tokenizer.js +16 -4
  191. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  192. package/dist/Toolbar/Toolbar.js +8 -1
  193. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  194. package/dist/TopNav/TopNavHeading.js +18 -8
  195. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  196. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  197. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  198. package/dist/TopNav/TopNavMenu.js +68 -6
  199. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  200. package/dist/TreeList/TreeListItem.js +5 -14
  201. package/dist/astryx.css +51 -16
  202. package/dist/astryx.umd.js +56 -56
  203. package/dist/astryx.umd.js.map +4 -4
  204. package/dist/authoring/doc.d.ts.map +1 -1
  205. package/dist/hooks/computeOverflow.d.ts +47 -0
  206. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  207. package/dist/hooks/computeOverflow.js +213 -0
  208. package/dist/hooks/useOverflow.d.ts +19 -0
  209. package/dist/hooks/useOverflow.d.ts.map +1 -1
  210. package/dist/hooks/useOverflow.js +37 -25
  211. package/dist/theme/Theme.js +1 -1
  212. package/dist/theme/defineTheme.d.ts +1 -1
  213. package/dist/theme/defineTheme.js +1 -1
  214. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  215. package/dist/theme/generateThemeRules.js +51 -0
  216. package/dist/theme/hct.d.ts +0 -1
  217. package/dist/theme/hct.d.ts.map +1 -1
  218. package/dist/theme/hct.js +0 -3
  219. package/dist/utils/types.d.ts +13 -0
  220. package/dist/utils/types.d.ts.map +1 -1
  221. package/docs.mjs +32 -461
  222. package/locales/en.json +212 -172
  223. package/locales/fr-FR.json +10 -0
  224. package/locales/pseudo.json +30 -0
  225. package/package.json +3 -1
  226. package/scripts/postinstall.mjs +93 -0
  227. package/src/AppShell/AppShell.tsx +6 -0
  228. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  229. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  230. package/src/Avatar/Avatar.doc.mjs +6 -6
  231. package/src/Avatar/Avatar.tsx +16 -11
  232. package/src/Avatar/AvatarSizeContext.ts +1 -1
  233. package/src/Avatar/AvatarStatusDot.tsx +2 -2
  234. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  235. package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
  236. package/src/AvatarGroup/AvatarGroup.tsx +3 -3
  237. package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
  238. package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
  239. package/src/Banner/Banner.doc.mjs +9 -0
  240. package/src/Banner/Banner.test.tsx +30 -0
  241. package/src/Banner/Banner.tsx +47 -3
  242. package/src/Button/Button.doc.mjs +9 -0
  243. package/src/Button/Button.test.tsx +49 -0
  244. package/src/Button/Button.tsx +25 -1
  245. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  246. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  247. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  248. package/src/Card/Card.doc.mjs +9 -0
  249. package/src/Card/Card.test.tsx +21 -3
  250. package/src/Card/Card.tsx +35 -2
  251. package/src/Carousel/Carousel.doc.mjs +1 -0
  252. package/src/Carousel/Carousel.test.tsx +149 -1
  253. package/src/Carousel/Carousel.tsx +62 -5
  254. package/src/Chat/Chat.doc.mjs +1 -1
  255. package/src/Chat/ChatComposer.doc.mjs +9 -0
  256. package/src/Chat/ChatComposer.test.tsx +35 -0
  257. package/src/Chat/ChatComposer.tsx +58 -9
  258. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  259. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  260. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  261. package/src/Chat/ChatComposerInput.test.tsx +87 -0
  262. package/src/Chat/ChatComposerInput.tsx +35 -3
  263. package/src/Chat/ChatLayout.tsx +3 -1
  264. package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
  265. package/src/Chat/ChatMessage.tsx +4 -2
  266. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  267. package/src/Chat/ChatSendButton.tsx +5 -1
  268. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  269. package/src/Chat/ChatToolCalls.tsx +13 -1
  270. package/src/Chat/useTriggerMenu.tsx +3 -1
  271. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  272. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  273. package/src/CheckboxList/CheckboxList.test.tsx +9 -1
  274. package/src/Citation/Citation.test.tsx +51 -0
  275. package/src/Citation/Citation.tsx +8 -2
  276. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  277. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  278. package/src/ClickableCard/ClickableCard.tsx +11 -1
  279. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  280. package/src/CodeBlock/CodeBlock.tsx +9 -1
  281. package/src/Collapsible/Collapsible.doc.mjs +7 -0
  282. package/src/Collapsible/Collapsible.test.tsx +80 -0
  283. package/src/Collapsible/Collapsible.tsx +58 -5
  284. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  285. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  286. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  287. package/src/ContextMenu/ContextMenu.test.tsx +75 -1
  288. package/src/ContextMenu/ContextMenu.tsx +10 -5
  289. package/src/ContextMenu/index.ts +15 -0
  290. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  291. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  292. package/src/Dialog/Dialog.tsx +12 -2
  293. package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
  294. package/src/DropdownMenu/DropdownMenu.tsx +11 -6
  295. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
  296. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
  297. package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
  298. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
  299. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
  300. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
  301. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
  302. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
  303. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
  304. package/src/DropdownMenu/index.ts +23 -3
  305. package/src/DropdownMenu/menuItemRoles.ts +22 -0
  306. package/src/Field/Field.doc.mjs +1 -1
  307. package/src/Field/Field.test.tsx +57 -12
  308. package/src/Field/FieldLabel.doc.mjs +1 -1
  309. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  310. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  311. package/src/FieldStatus/FieldStatus.tsx +22 -4
  312. package/src/FileInput/FileInput.test.tsx +52 -3
  313. package/src/FileInput/FileInput.tsx +13 -2
  314. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  315. package/src/HoverCard/HoverCard.test.tsx +21 -2
  316. package/src/HoverCard/HoverCard.tsx +11 -0
  317. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  318. package/src/HoverCard/useHoverCard.tsx +24 -2
  319. package/src/Icon/Icon.doc.mjs +12 -0
  320. package/src/Icon/Icon.test.tsx +142 -0
  321. package/src/Icon/Icon.tsx +98 -30
  322. package/src/IconButton/IconButton.doc.mjs +8 -0
  323. package/src/IconButton/IconButton.test.tsx +15 -18
  324. package/src/Item/Item.doc.mjs +1 -1
  325. package/src/Layer/useLayer.tsx +8 -0
  326. package/src/MobileNav/MobileNav.tsx +12 -1
  327. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  328. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  329. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  330. package/src/MultiSelector/MultiSelector.test.tsx +162 -1
  331. package/src/MultiSelector/MultiSelector.tsx +55 -12
  332. package/src/MultiSelector/hooks.ts +17 -0
  333. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  334. package/src/NumberInput/NumberInput.test.tsx +11 -7
  335. package/src/NumberInput/NumberInput.tsx +1 -2
  336. package/src/Outline/Outline.doc.mjs +123 -3
  337. package/src/Outline/Outline.test.tsx +735 -0
  338. package/src/Outline/Outline.tsx +183 -33
  339. package/src/Outline/useScrollSpy.ts +248 -72
  340. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  341. package/src/OverflowList/OverflowList.test.tsx +111 -0
  342. package/src/OverflowList/OverflowList.tsx +48 -8
  343. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  344. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  345. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  346. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  347. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  348. package/src/SelectableCard/SelectableCard.tsx +27 -14
  349. package/src/Selector/Selector.test.tsx +69 -0
  350. package/src/Selector/Selector.tsx +5 -1
  351. package/src/Selector/SelectorOption.doc.mjs +1 -1
  352. package/src/Selector/hooks.ts +17 -0
  353. package/src/SideNav/SideNav.test.tsx +119 -3
  354. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  355. package/src/SideNav/SideNavHeading.tsx +24 -8
  356. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  357. package/src/SideNav/SideNavItem.tsx +5 -1
  358. package/src/Slider/Slider.test.tsx +62 -2
  359. package/src/Slider/Slider.tsx +15 -1
  360. package/src/Switch/Switch.doc.mjs +1 -1
  361. package/src/Switch/Switch.test.tsx +32 -1
  362. package/src/TabList/Tab.tsx +5 -1
  363. package/src/TabList/TabList.doc.mjs +3 -3
  364. package/src/TabList/TabList.test.tsx +66 -0
  365. package/src/TabList/TabList.tsx +8 -0
  366. package/src/TabList/TabMenu.tsx +4 -1
  367. package/src/Table/Table.doc.mjs +2 -0
  368. package/src/Table/Table.tsx +1 -1
  369. package/src/Table/index.ts +12 -0
  370. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  371. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
  372. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
  373. package/src/Table/plugins/rowStatus/index.ts +7 -0
  374. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  375. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  376. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  377. package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
  378. package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
  379. package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
  380. package/src/Table/plugins/tree/index.ts +12 -0
  381. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
  382. package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
  383. package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
  384. package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
  385. package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
  386. package/src/Table/useBaseTablePlugins.ts +6 -2
  387. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  388. package/src/Table/useTableSelection.doc.mjs +65 -14
  389. package/src/Table/useTableTreeData.doc.mjs +75 -0
  390. package/src/Table/useTableTreeState.doc.mjs +98 -0
  391. package/src/TextArea/TextArea.doc.mjs +1 -1
  392. package/src/TextArea/TextArea.test.tsx +9 -1
  393. package/src/TextInput/TextInput.doc.mjs +1 -1
  394. package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
  395. package/src/Thumbnail/Thumbnail.test.tsx +38 -8
  396. package/src/Thumbnail/Thumbnail.tsx +28 -1
  397. package/src/TimeInput/TimeInput.test.tsx +76 -1
  398. package/src/TimeInput/TimeInput.tsx +19 -6
  399. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  400. package/src/Tokenizer/Tokenizer.test.tsx +137 -2
  401. package/src/Tokenizer/Tokenizer.tsx +16 -4
  402. package/src/Toolbar/Toolbar.tsx +10 -3
  403. package/src/TopNav/TopNav.test.tsx +80 -1
  404. package/src/TopNav/TopNavHeading.tsx +24 -8
  405. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  406. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  407. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  408. package/src/TopNav/TopNavMenu.tsx +83 -5
  409. package/src/TreeList/TreeList.test.tsx +32 -1
  410. package/src/TreeList/TreeListItem.tsx +19 -10
  411. package/src/authoring/doc.ts +1 -3
  412. package/src/docs-script.test.mjs +18 -13
  413. package/src/hooks/computeOverflow.test.ts +422 -0
  414. package/src/hooks/computeOverflow.ts +290 -0
  415. package/src/hooks/useOverflow.test.ts +96 -4
  416. package/src/hooks/useOverflow.ts +77 -33
  417. package/src/postinstall.test.mjs +71 -0
  418. package/src/theme/Theme.doc.mjs +4 -4
  419. package/src/theme/Theme.tsx +1 -1
  420. package/src/theme/defineTheme.ts +1 -1
  421. package/src/theme/generateThemeRules.test.ts +65 -0
  422. package/src/theme/generateThemeRules.ts +56 -0
  423. package/src/theme/hct.ts +0 -4
  424. package/src/utils/types.ts +14 -0
  425. package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
  426. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
  427. package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
  428. package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
@@ -0,0 +1,290 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file computeOverflow.ts
5
+ * @input Plain measurements (item widths, gap, available width, indicator width, limits)
6
+ * @output Pure overflow computation: how many items are visible and their row layout
7
+ * @position Pure helper consumed by useOverflow; contains no DOM or React code so the
8
+ * fit/clamp math and the multi-row packing can be unit-tested in isolation.
9
+ *
10
+ * There is a single source of truth for `visibleCount`:
11
+ * visibleCount = clamp(fitCount, minVisibleItems, maxVisibleItems ?? itemCount)
12
+ *
13
+ * SYNC: When modified, update:
14
+ * - /packages/core/src/hooks/useOverflow.ts (the only caller)
15
+ */
16
+
17
+ export interface ComputeOverflowInput {
18
+ /** Measured widths of each item, in original DOM order. */
19
+ widths: number[];
20
+ /** Gap between items, in pixels. */
21
+ gap: number;
22
+ /** Width available to lay items out, in pixels. */
23
+ availableWidth: number;
24
+ /** Measured width of the overflow indicator (0 if none). */
25
+ indicatorWidth: number;
26
+ /** Floor — always show at least this many items. */
27
+ minVisibleItems: number;
28
+ /** Ceiling — never show more than this many items. `undefined` = no cap. */
29
+ maxVisibleItems?: number;
30
+ /**
31
+ * Bounded multi-row: wrap items across up to this many rows, then collapse
32
+ * the rest into the overflow indicator. `undefined` (or `1`) = single line.
33
+ */
34
+ maxRows?: number;
35
+ /** Which end items collapse from. */
36
+ collapseFrom: 'start' | 'end';
37
+ }
38
+
39
+ export interface ComputeOverflowResult {
40
+ /** Number of items that should be rendered in the visible container. */
41
+ visibleCount: number;
42
+ /** Number of rows the visible items occupy (always 1 for the single-line path). */
43
+ rows: number;
44
+ }
45
+
46
+ function clamp(value: number, min: number, max: number): number {
47
+ return Math.max(Math.min(value, max), min);
48
+ }
49
+
50
+ /**
51
+ * Resolve the ceiling and floor into a single [floor, ceiling] pair.
52
+ *
53
+ * `maxVisibleItems` is clamped to `itemCount` (a cap larger than the list is a
54
+ * no-op). When `maxVisibleItems < minVisibleItems`, the floor wins (D1) via the
55
+ * final clamp — the hook is responsible for the dev-only warning.
56
+ */
57
+ function resolveBounds(
58
+ itemCount: number,
59
+ minVisibleItems: number,
60
+ maxVisibleItems: number | undefined,
61
+ ): {floor: number; ceiling: number} {
62
+ const floor = Math.max(0, Math.min(minVisibleItems, itemCount));
63
+ const rawCeiling = maxVisibleItems == null ? itemCount : maxVisibleItems;
64
+ const ceiling = Math.max(0, Math.min(rawCeiling, itemCount));
65
+ return {floor, ceiling};
66
+ }
67
+
68
+ /**
69
+ * Single-line greedy fit: how many items fit in one row of `availableWidth`,
70
+ * reserving indicator space for every non-final admitted item. Mirrors the
71
+ * original in-hook loop so single-line behavior is unchanged.
72
+ */
73
+ function computeSingleLineFit(
74
+ orderedWidths: number[],
75
+ gap: number,
76
+ availableWidth: number,
77
+ indicatorWidth: number,
78
+ floor: number,
79
+ ceiling: number,
80
+ ): number {
81
+ let totalWidth = 0;
82
+ let count = 0;
83
+
84
+ for (let i = 0; i < orderedWidths.length; i++) {
85
+ if (count >= ceiling) {
86
+ break;
87
+ }
88
+
89
+ const itemWidth = orderedWidths[i];
90
+ const gapWidth = i > 0 ? gap : 0;
91
+ const candidateWidth = totalWidth + itemWidth + gapWidth;
92
+
93
+ const isLastItem = i === orderedWidths.length - 1;
94
+ const reservedWidth = isLastItem
95
+ ? 0
96
+ : indicatorWidth + (count > 0 || indicatorWidth > 0 ? gap : 0);
97
+
98
+ if (candidateWidth + reservedWidth > availableWidth && count >= floor) {
99
+ break;
100
+ }
101
+
102
+ totalWidth = candidateWidth;
103
+ count++;
104
+ }
105
+
106
+ return count;
107
+ }
108
+
109
+ /**
110
+ * Pack items into rows, wrapping when the next item would exceed
111
+ * `availableWidth`. On the last allowed row, keep `indicatorReserve` px free so
112
+ * the overflow indicator fits. Stops admitting once no further item can be
113
+ * placed within `maxRows`.
114
+ */
115
+ function packRows(
116
+ orderedWidths: number[],
117
+ gap: number,
118
+ availableWidth: number,
119
+ indicatorReserve: number,
120
+ maxRows: number,
121
+ ): {placed: number; rows: number} {
122
+ let placed = 0;
123
+ let row = 1;
124
+ let rowWidth = 0;
125
+
126
+ for (let i = 0; i < orderedWidths.length; i++) {
127
+ const w = orderedWidths[i];
128
+ const isFirstInRow = rowWidth === 0;
129
+ const candidate = isFirstInRow ? w : rowWidth + gap + w;
130
+
131
+ const onLastRow = row === maxRows;
132
+ const reserve =
133
+ onLastRow && indicatorReserve > 0 ? indicatorReserve + gap : 0;
134
+
135
+ if (candidate + reserve <= availableWidth) {
136
+ rowWidth = candidate;
137
+ placed++;
138
+ continue;
139
+ }
140
+
141
+ if (isFirstInRow) {
142
+ // A single item wider than the row occupies this row alone (it will be
143
+ // clipped visually). But if it can't coexist with the reserved indicator
144
+ // on the last row, stop here.
145
+ if (onLastRow && reserve > 0) {
146
+ break;
147
+ }
148
+ rowWidth = candidate;
149
+ placed++;
150
+ continue;
151
+ }
152
+
153
+ if (row >= maxRows) {
154
+ break;
155
+ }
156
+ row++;
157
+ rowWidth = 0;
158
+ i--; // re-attempt this item as the first on the new row
159
+ }
160
+
161
+ return {placed, rows: row};
162
+ }
163
+
164
+ /** Count how many rows a set of items occupies when wrapped at availableWidth. */
165
+ function countRows(
166
+ orderedWidths: number[],
167
+ gap: number,
168
+ availableWidth: number,
169
+ ): number {
170
+ if (orderedWidths.length === 0) {
171
+ return 0;
172
+ }
173
+ let rows = 1;
174
+ let rowWidth = 0;
175
+ for (let i = 0; i < orderedWidths.length; i++) {
176
+ const w = orderedWidths[i];
177
+ const isFirstInRow = rowWidth === 0;
178
+ const candidate = isFirstInRow ? w : rowWidth + gap + w;
179
+ if (candidate <= availableWidth || isFirstInRow) {
180
+ rowWidth = candidate;
181
+ } else {
182
+ rows++;
183
+ rowWidth = w;
184
+ }
185
+ }
186
+ return rows;
187
+ }
188
+
189
+ /**
190
+ * Multi-row packing (Strategy A): simulate wrapping items into rows of
191
+ * `availableWidth`, admitting items until a new item would require a row beyond
192
+ * `maxRows`, reserving indicator space on the final row when items overflow.
193
+ * Returns the admitted count and how many rows those items occupy. Assumes
194
+ * uniform row height.
195
+ */
196
+ function computeMultiRowFit(
197
+ orderedWidths: number[],
198
+ gap: number,
199
+ availableWidth: number,
200
+ indicatorWidth: number,
201
+ maxRows: number,
202
+ ): {count: number; rows: number} {
203
+ const n = orderedWidths.length;
204
+ if (n === 0) {
205
+ return {count: 0, rows: 0};
206
+ }
207
+
208
+ // If everything fits within maxRows without reserving indicator space, there
209
+ // is no overflow and no indicator is needed.
210
+ const packAll = packRows(orderedWidths, gap, availableWidth, 0, maxRows);
211
+ if (packAll.placed === n) {
212
+ return {count: n, rows: packAll.rows};
213
+ }
214
+
215
+ // Overflow → reserve indicator space on the final row and re-pack.
216
+ const packWithIndicator = packRows(
217
+ orderedWidths,
218
+ gap,
219
+ availableWidth,
220
+ indicatorWidth,
221
+ maxRows,
222
+ );
223
+
224
+ const count = packWithIndicator.placed;
225
+ const rows = countRows(orderedWidths.slice(0, count), gap, availableWidth);
226
+
227
+ return {count, rows: Math.max(count > 0 ? 1 : 0, rows)};
228
+ }
229
+
230
+ /**
231
+ * Pure overflow computation. Given plain measurements and the configured
232
+ * limits, return how many items should be visible and how many rows they
233
+ * occupy. No DOM, no React — safe to unit-test directly.
234
+ */
235
+ export function computeOverflow(
236
+ input: ComputeOverflowInput,
237
+ ): ComputeOverflowResult {
238
+ const {
239
+ widths,
240
+ gap,
241
+ availableWidth,
242
+ indicatorWidth,
243
+ minVisibleItems,
244
+ maxVisibleItems,
245
+ maxRows,
246
+ collapseFrom,
247
+ } = input;
248
+
249
+ const itemCount = widths.length;
250
+ if (itemCount === 0) {
251
+ return {visibleCount: 0, rows: 0};
252
+ }
253
+
254
+ const {floor, ceiling} = resolveBounds(
255
+ itemCount,
256
+ minVisibleItems,
257
+ maxVisibleItems,
258
+ );
259
+
260
+ const orderedWidths = collapseFrom === 'end' ? widths : [...widths].reverse();
261
+
262
+ const multiRow = maxRows != null && maxRows > 1;
263
+
264
+ if (!multiRow) {
265
+ const fitCount = computeSingleLineFit(
266
+ orderedWidths,
267
+ gap,
268
+ availableWidth,
269
+ indicatorWidth,
270
+ floor,
271
+ ceiling,
272
+ );
273
+ const visibleCount = clamp(fitCount, floor, ceiling);
274
+ return {visibleCount, rows: visibleCount > 0 ? 1 : 0};
275
+ }
276
+
277
+ const {count, rows} = computeMultiRowFit(
278
+ orderedWidths,
279
+ gap,
280
+ availableWidth,
281
+ indicatorWidth,
282
+ maxRows,
283
+ );
284
+ const visibleCount = clamp(count, floor, ceiling);
285
+ const resolvedRows =
286
+ visibleCount === count
287
+ ? rows
288
+ : countRows(orderedWidths.slice(0, visibleCount), gap, availableWidth);
289
+ return {visibleCount, rows: visibleCount > 0 ? Math.max(1, resolvedRows) : 0};
290
+ }
@@ -14,12 +14,15 @@ function mockContainer(width: number): HTMLElement {
14
14
  function mockMeasure(
15
15
  itemWidths: number[],
16
16
  indicatorWidth?: number,
17
+ itemHeight = 0,
17
18
  ): HTMLElement {
18
- const children: {offsetWidth: number}[] = itemWidths.map(w => ({
19
- offsetWidth: w,
20
- }));
19
+ const children: {offsetWidth: number; offsetHeight: number}[] =
20
+ itemWidths.map(w => ({
21
+ offsetWidth: w,
22
+ offsetHeight: itemHeight,
23
+ }));
21
24
  if (indicatorWidth != null) {
22
- children.push({offsetWidth: indicatorWidth});
25
+ children.push({offsetWidth: indicatorWidth, offsetHeight: itemHeight});
23
26
  }
24
27
  return {children} as unknown as HTMLElement;
25
28
  }
@@ -332,6 +335,95 @@ describe('useOverflow', () => {
332
335
  });
333
336
  });
334
337
 
338
+ describe('useOverflow with maxVisibleItems (cap)', () => {
339
+ it('caps visible items even when they all fit', () => {
340
+ const {result} = renderHook(() =>
341
+ useOverflow(5, {gap: 10, maxVisibleItems: 3}),
342
+ );
343
+
344
+ act(() => {
345
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50], 30));
346
+ result.current.containerRef(mockContainer(10000));
347
+ });
348
+
349
+ expect(result.current.visibleCount).toBe(3);
350
+ expect(result.current.hasOverflow).toBe(true);
351
+ });
352
+
353
+ it('cap does not raise a fit-limited count', () => {
354
+ const {result} = renderHook(() =>
355
+ useOverflow(4, {gap: 10, maxVisibleItems: 4}),
356
+ );
357
+
358
+ act(() => {
359
+ result.current.measureRef(mockMeasure([50, 50, 50, 50], 30));
360
+ result.current.containerRef(mockContainer(150));
361
+ });
362
+
363
+ expect(result.current.visibleCount).toBe(2);
364
+ });
365
+
366
+ it('min wins when maxVisibleItems < minVisibleItems and warns in dev', () => {
367
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
368
+ const {result} = renderHook(() =>
369
+ useOverflow(5, {gap: 10, minVisibleItems: 3, maxVisibleItems: 1}),
370
+ );
371
+
372
+ act(() => {
373
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50], 30));
374
+ result.current.containerRef(mockContainer(10000));
375
+ });
376
+
377
+ expect(result.current.visibleCount).toBe(3);
378
+ expect(warn).toHaveBeenCalled();
379
+ warn.mockRestore();
380
+ });
381
+ });
382
+
383
+ describe('useOverflow with maxRows (multi-row)', () => {
384
+ it('exposes rows and rowHeight', () => {
385
+ const {result} = renderHook(() => useOverflow(6, {gap: 10, maxRows: 2}));
386
+
387
+ act(() => {
388
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50, 50], 40, 24));
389
+ result.current.containerRef(mockContainer(170));
390
+ });
391
+
392
+ // 3 items per row (170), 2 rows → all 6 fit
393
+ expect(result.current.visibleCount).toBe(6);
394
+ expect(result.current.rows).toBe(2);
395
+ expect(result.current.rowHeight).toBe(24);
396
+ expect(result.current.hasOverflow).toBe(false);
397
+ });
398
+
399
+ it('collapses items beyond maxRows into the indicator', () => {
400
+ const {result} = renderHook(() => useOverflow(8, {gap: 10, maxRows: 2}));
401
+
402
+ act(() => {
403
+ result.current.measureRef(
404
+ mockMeasure([50, 50, 50, 50, 50, 50, 50, 50], 40, 24),
405
+ );
406
+ result.current.containerRef(mockContainer(170));
407
+ });
408
+
409
+ expect(result.current.visibleCount).toBe(5);
410
+ expect(result.current.rows).toBe(2);
411
+ expect(result.current.hasOverflow).toBe(true);
412
+ });
413
+
414
+ it('maxRows=1 matches single-line behavior', () => {
415
+ const {result} = renderHook(() => useOverflow(4, {gap: 10, maxRows: 1}));
416
+
417
+ act(() => {
418
+ result.current.measureRef(mockMeasure([50, 50, 50, 50], 30, 24));
419
+ result.current.containerRef(mockContainer(150));
420
+ });
421
+
422
+ expect(result.current.visibleCount).toBe(2);
423
+ expect(result.current.rows).toBe(1);
424
+ });
425
+ });
426
+
335
427
  describe('useOverflow with behavior=observeParent', () => {
336
428
  function mockContainerWithParent(
337
429
  parentWidth: number,
@@ -10,15 +10,19 @@
10
10
  *
11
11
  * Measures children rendered in a hidden container to determine how many fit
12
12
  * in the available width, without flickering. Uses ResizeObserver to react
13
- * to container size changes.
13
+ * to container size changes. Supports an optional item cap (`maxVisibleItems`)
14
+ * and bounded multi-row wrapping (`maxRows`); the fit/clamp/row-packing math
15
+ * lives in the pure `computeOverflow` helper.
14
16
  *
15
17
  * SYNC: When modified, update:
16
18
  * - /packages/core/src/hooks/index.ts
19
+ * - /packages/core/src/hooks/computeOverflow.ts (the pure computation)
17
20
  */
18
21
 
19
22
  import {useState, useCallback, useRef} from 'react';
20
23
  import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
21
24
  import {observeResize, unobserveResize} from '../utils/sharedResizeObserver';
25
+ import {computeOverflow} from './computeOverflow';
22
26
 
23
27
  export interface UseOverflowOptions {
24
28
  /**
@@ -33,6 +37,23 @@ export interface UseOverflowOptions {
33
37
  */
34
38
  minVisibleItems?: number;
35
39
 
40
+ /**
41
+ * Maximum number of items to ever show, even if they all fit. The ceiling
42
+ * partner to `minVisibleItems`. `undefined` means no cap. When it is less
43
+ * than `minVisibleItems`, the floor wins and a dev-only warning is emitted.
44
+ * @default undefined
45
+ */
46
+ maxVisibleItems?: number;
47
+
48
+ /**
49
+ * Wrap items across up to this many rows before collapsing the rest into the
50
+ * overflow indicator. `undefined` (or `1`) keeps the single-line behavior.
51
+ * A number, not a boolean: unbounded wrapping is a plain flex-wrap layout,
52
+ * not overflow collapse. Assumes uniform row height.
53
+ * @default undefined
54
+ */
55
+ maxRows?: number;
56
+
36
57
  /**
37
58
  * Which end to collapse items from.
38
59
  * @default 'end'
@@ -61,6 +82,10 @@ export interface UseOverflowReturn {
61
82
  visibleCount: number;
62
83
  /** Whether any items are overflowing */
63
84
  hasOverflow: boolean;
85
+ /** Number of rows the visible items occupy (1 for the single-line path) */
86
+ rows: number;
87
+ /** Measured max item height in pixels; used to size the multi-row container */
88
+ rowHeight: number;
64
89
  }
65
90
 
66
91
  /**
@@ -88,13 +113,29 @@ export function useOverflow(
88
113
  const {
89
114
  gap = 0,
90
115
  minVisibleItems = 0,
116
+ maxVisibleItems,
117
+ maxRows,
91
118
  collapseFrom = 'end',
92
119
  behavior = 'observeSelf',
93
120
  } = options;
94
121
 
122
+ if (
123
+ process.env.NODE_ENV !== 'production' &&
124
+ maxVisibleItems != null &&
125
+ maxVisibleItems < minVisibleItems
126
+ ) {
127
+ console.warn(
128
+ `useOverflow: maxVisibleItems (${maxVisibleItems}) is less than ` +
129
+ `minVisibleItems (${minVisibleItems}); the floor wins and ` +
130
+ `minVisibleItems items will be shown.`,
131
+ );
132
+ }
133
+
95
134
  const observeParent = behavior === 'observeParent';
96
135
 
97
136
  const [visibleCount, setVisibleCount] = useState(itemCount);
137
+ const [rows, setRows] = useState(1);
138
+ const [rowHeight, setRowHeight] = useState(0);
98
139
  const containerElRef = useRef<HTMLElement | null>(null);
99
140
  const measureElRef = useRef<HTMLElement | null>(null);
100
141
  const observedElRef = useRef<HTMLElement | null>(null);
@@ -134,44 +175,45 @@ export function useOverflow(
134
175
  if (children.length === 0) {
135
176
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- overflow count is derived from measured DOM widths
136
177
  setVisibleCount(0);
178
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
179
+ setRows(0);
137
180
  return;
138
181
  }
139
182
 
140
- // Collect widths of all children
141
183
  const widths = children.map(child => child.offsetWidth);
142
-
143
- // Determine how many items fit
144
- let totalWidth = 0;
145
- let count = 0;
146
-
147
- const orderedWidths =
148
- collapseFrom === 'end' ? widths : [...widths].reverse();
149
-
150
- for (let i = 0; i < orderedWidths.length; i++) {
151
- const itemWidth = orderedWidths[i];
152
- const gapWidth = i > 0 ? gap : 0;
153
- const candidateWidth = totalWidth + itemWidth + gapWidth;
154
-
155
- // If this isn't the last item, we need to reserve space for the indicator
156
- const isLastItem = i === orderedWidths.length - 1;
157
- const reservedWidth = isLastItem
158
- ? 0
159
- : indicatorWidth + (count > 0 || indicatorWidth > 0 ? gap : 0);
160
-
161
- if (
162
- candidateWidth + reservedWidth > availableWidth &&
163
- count >= minVisibleItems
164
- ) {
165
- break;
166
- }
167
-
168
- totalWidth = candidateWidth;
169
- count++;
170
- }
184
+ const measuredRowHeight = children.reduce(
185
+ (max, child) => Math.max(max, child.offsetHeight || 0),
186
+ 0,
187
+ );
188
+
189
+ const {visibleCount: nextVisible, rows: nextRows} = computeOverflow({
190
+ widths,
191
+ gap,
192
+ availableWidth,
193
+ indicatorWidth,
194
+ minVisibleItems,
195
+ maxVisibleItems,
196
+ maxRows,
197
+ collapseFrom,
198
+ });
171
199
 
172
200
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- overflow count is derived from measured DOM widths
173
- setVisibleCount(Math.max(Math.min(count, itemCount), minVisibleItems));
174
- }, [itemCount, gap, minVisibleItems, collapseFrom, observeParent]);
201
+ setVisibleCount(nextVisible);
202
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
203
+ setRows(prev => (prev === nextRows ? prev : nextRows));
204
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
205
+ setRowHeight(prev =>
206
+ prev === measuredRowHeight ? prev : measuredRowHeight,
207
+ );
208
+ }, [
209
+ itemCount,
210
+ gap,
211
+ minVisibleItems,
212
+ maxVisibleItems,
213
+ maxRows,
214
+ collapseFrom,
215
+ observeParent,
216
+ ]);
175
217
 
176
218
  const containerRef = useCallback(
177
219
  (el: HTMLElement | null) => {
@@ -217,5 +259,7 @@ export function useOverflow(
217
259
  measureRef,
218
260
  visibleCount,
219
261
  hasOverflow,
262
+ rows,
263
+ rowHeight,
220
264
  };
221
265
  }
@@ -0,0 +1,71 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Guardrail tests for the @astryxdesign/core postinstall nudge (layer 1).
5
+ *
6
+ * The marker check mirrors the CLI's single source of truth (core can't import
7
+ * the CLI). Tests that it detects the marker across EVERY agent-doc location
8
+ * (incl. Hermes) + legacy markers, and that the nudge decision matrix is correct.
9
+ */
10
+
11
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
12
+ import * as fs from 'node:fs';
13
+ import * as os from 'node:os';
14
+ import * as path from 'node:path';
15
+ import {isAstryxInitialized, shouldNudge} from '../scripts/postinstall.mjs';
16
+
17
+ const MARKER = '<!-- ASTRYX:START -->';
18
+
19
+ let tmp;
20
+ beforeEach(() => {
21
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-core-postinstall-'));
22
+ });
23
+ afterEach(() => {
24
+ fs.rmSync(tmp, {recursive: true, force: true});
25
+ });
26
+ function write(rel, body) {
27
+ const p = path.join(tmp, rel);
28
+ fs.mkdirSync(path.dirname(p), {recursive: true});
29
+ fs.writeFileSync(p, body);
30
+ }
31
+
32
+ describe('core postinstall — isAstryxInitialized (mirrors CLI contract)', () => {
33
+ it('is false in an empty project', () => {
34
+ expect(isAstryxInitialized(tmp)).toBe(false);
35
+ });
36
+
37
+ it.each(['AGENTS.md', 'CLAUDE.md', '.claude/CLAUDE.md', '.cursorrules', '.hermes.md', 'HERMES.md'])(
38
+ 'detects the marker in %s',
39
+ file => {
40
+ write(file, `# doc\n${MARKER}\nbody`);
41
+ expect(isAstryxInitialized(tmp)).toBe(true);
42
+ },
43
+ );
44
+
45
+ it('detects the legacy XDS marker', () => {
46
+ write('AGENTS.md', '<!-- XDS:START -->');
47
+ expect(isAstryxInitialized(tmp)).toBe(true);
48
+ });
49
+ });
50
+
51
+ describe('core postinstall — shouldNudge', () => {
52
+ const DEP = '/proj/node_modules/@astryxdesign/core/scripts/postinstall.mjs';
53
+ const NPX = '/Users/x/.npm/_npx/a1/node_modules/@astryxdesign/core/scripts/postinstall.mjs';
54
+ const REPO = '/repo/packages/core/scripts/postinstall.mjs';
55
+
56
+ it('nudges for a real dependency install when not set up', () => {
57
+ expect(shouldNudge({scriptPath: DEP, npmCommand: 'install', isSetUp: false})).toBe(true);
58
+ });
59
+ it('quiet in the monorepo/source', () => {
60
+ expect(shouldNudge({scriptPath: REPO, npmCommand: 'install', isSetUp: false})).toBe(false);
61
+ });
62
+ it('quiet during npx transient install (_npx path)', () => {
63
+ expect(shouldNudge({scriptPath: NPX, npmCommand: 'install', isSetUp: false})).toBe(false);
64
+ });
65
+ it('quiet during npx (npm_command=exec)', () => {
66
+ expect(shouldNudge({scriptPath: DEP, npmCommand: 'exec', isSetUp: false})).toBe(false);
67
+ });
68
+ it('quiet once set up', () => {
69
+ expect(shouldNudge({scriptPath: DEP, npmCommand: 'install', isSetUp: true})).toBe(false);
70
+ });
71
+ });
@@ -39,12 +39,12 @@ export const docs = {
39
39
  },
40
40
  usage: {
41
41
  description:
42
- 'Wraps a subtree with a specific Astryx theme. For static production themes, use `npx astryx theme build` and import the generated CSS plus built theme object for first-paint and SSR performance. Use runtime `defineTheme()` when themes are dynamic or for prototyping.\n\n`defineTheme` accepts a `tokens` object whose keys are CSS custom property names (always prefixed with `--`). Common token names include `--color-accent`, `--color-background-surface`, `--color-background-body`, `--color-text-primary`, `--color-text-secondary`, `--radius-container`, `--spacing-1` through `--spacing-6`. Values can be a string (same for light/dark) or a `[light, dark]` tuple.\n\nExample:\n```ts\nimport {defineTheme} from \'@astryxdesign/core/theme\';\nconst myTheme = defineTheme({\n name: \'ocean\',\n tokens: {\n \'--color-accent\': [\'#0077B6\', \'#48CAE4\'],\n \'--color-background-surface\': [\'#F0F8FF\', \'#0A1628\'],\n \'--color-text-primary\': [\'#0A1317\', \'#FFFFFF\'],\n \'--radius-container\': \'16px\',\n },\n});\n```',
42
+ 'Wraps a subtree with a specific Astryx theme. For static production themes, use `astryx theme build` and import the generated CSS plus built theme object for first-paint and SSR performance. Use runtime `defineTheme()` when themes are dynamic or for prototyping.\n\n`defineTheme` accepts a `tokens` object whose keys are CSS custom property names (always prefixed with `--`). Common token names include `--color-accent`, `--color-background-surface`, `--color-background-body`, `--color-text-primary`, `--color-text-secondary`, `--radius-container`, `--spacing-1` through `--spacing-6`. Values can be a string (same for light/dark) or a `[light, dark]` tuple.\n\nExample:\n```ts\nimport {defineTheme} from \'@astryxdesign/core/theme\';\nconst myTheme = defineTheme({\n name: \'ocean\',\n tokens: {\n \'--color-accent\': [\'#0077B6\', \'#48CAE4\'],\n \'--color-background-surface\': [\'#F0F8FF\', \'#0A1628\'],\n \'--color-text-primary\': [\'#0A1317\', \'#FFFFFF\'],\n \'--radius-container\': \'16px\',\n },\n});\n```',
43
43
  bestPractices: [
44
44
  {
45
45
  guidance: true,
46
46
  description:
47
- 'Build app themes that are known ahead of time with `npx astryx theme build`, then import the generated CSS and built theme object.',
47
+ 'Build app themes that are known ahead of time with `astryx theme build`, then import the generated CSS and built theme object.',
48
48
  },
49
49
  {
50
50
  guidance: true,
@@ -90,12 +90,12 @@ export const docs = {
90
90
  export const docsDense = {
91
91
  usage: {
92
92
  description:
93
- 'Wraps subtree w/ specific Astryx theme. For static production themes, use `npx astryx theme build` + generated CSS + built theme object for first-paint/SSR performance. Use runtime `defineTheme()` for dynamic themes or prototyping. Token names always start with `--` (e.g. `--color-accent`, `--color-background-surface`).',
93
+ 'Wraps subtree w/ specific Astryx theme. For static production themes, use `astryx theme build` + generated CSS + built theme object for first-paint/SSR performance. Use runtime `defineTheme()` for dynamic themes or prototyping. Token names always start with `--` (e.g. `--color-accent`, `--color-background-surface`).',
94
94
  bestPractices: [
95
95
  {
96
96
  guidance: true,
97
97
  description:
98
- 'Build app themes known ahead of time w/ `npx astryx theme build`; import generated CSS + built theme object.',
98
+ 'Build app themes known ahead of time w/ `astryx theme build`; import generated CSS + built theme object.',
99
99
  },
100
100
  {
101
101
  guidance: true,
@@ -127,7 +127,7 @@ function useThemeStyleInjection(theme: DefinedTheme): void {
127
127
  `For better performance, use the pre-built theme:\n\n` +
128
128
  ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` +
129
129
  ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` +
130
- `For custom themes, run \`npx astryx theme build <file>\` to generate ` +
130
+ `For custom themes, run \`npx @astryxdesign/cli theme build <file>\` to generate ` +
131
131
  `the built artifacts.`,
132
132
  );
133
133
  }