@astryxdesign/core 0.3.0 → 0.4.0

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 (551) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/AlertDialog/AlertDialog.d.ts +10 -2
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +22 -3
  5. package/dist/AppShell/AppShell.d.ts +2 -2
  6. package/dist/AppShell/AppShell.d.ts.map +1 -1
  7. package/dist/AppShell/AppShell.js +40 -9
  8. package/dist/AspectRatio/AspectRatio.d.ts +13 -0
  9. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  10. package/dist/AspectRatio/AspectRatio.js +9 -0
  11. package/dist/Avatar/Avatar.d.ts.map +1 -1
  12. package/dist/Avatar/Avatar.js +33 -20
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -3
  15. package/dist/Banner/Banner.d.ts.map +1 -1
  16. package/dist/Banner/Banner.js +15 -14
  17. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/BreadcrumbItem.js +21 -8
  19. package/dist/Breadcrumbs/index.d.ts +2 -1
  20. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/index.js +1 -0
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +11 -15
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +2 -1
  26. package/dist/Calendar/styles.d.ts +0 -2
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +0 -2
  29. package/dist/Carousel/Carousel.d.ts.map +1 -1
  30. package/dist/Carousel/Carousel.js +8 -12
  31. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  32. package/dist/Chat/ChatDictationButton.js +2 -1
  33. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  34. package/dist/Chat/ChatLayoutScrollButton.js +3 -1
  35. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  36. package/dist/Chat/ChatSendButton.js +4 -3
  37. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  38. package/dist/Chat/ChatToolCalls.js +17 -17
  39. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  40. package/dist/Chat/useTriggerMenu.js +2 -6
  41. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  42. package/dist/CheckboxInput/CheckboxInput.js +36 -125
  43. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  44. package/dist/ClickableCard/ClickableCard.js +3 -11
  45. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  46. package/dist/CodeBlock/CodeBlock.js +84 -67
  47. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  48. package/dist/Collapsible/Collapsible.js +35 -15
  49. package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -1
  50. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  51. package/dist/CommandPalette/CommandPaletteItem.js +6 -3
  52. package/dist/CommandPalette/CommandPaletteList.js +1 -1
  53. package/dist/ComplexSelector/ComplexSelector.d.ts +1 -1
  54. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  55. package/dist/ComplexSelector/ComplexSelector.js +35 -33
  56. package/dist/ContextMenu/ContextMenu.d.ts +2 -2
  57. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  58. package/dist/ContextMenu/ContextMenu.js +1 -1
  59. package/dist/ContextMenu/index.d.ts +2 -1
  60. package/dist/ContextMenu/index.d.ts.map +1 -1
  61. package/dist/ContextMenu/index.js +1 -0
  62. package/dist/DateInput/DateInput.d.ts +9 -2
  63. package/dist/DateInput/DateInput.d.ts.map +1 -1
  64. package/dist/DateInput/DateInput.js +24 -23
  65. package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
  66. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  67. package/dist/DateRangeInput/DateRangeInput.js +11 -16
  68. package/dist/DateTimeInput/DateTimeInput.d.ts +9 -1
  69. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  70. package/dist/DateTimeInput/DateTimeInput.js +22 -17
  71. package/dist/Dialog/Dialog.d.ts.map +1 -1
  72. package/dist/Dialog/Dialog.js +4 -4
  73. package/dist/DropdownMenu/DropdownMenu.d.ts +33 -8
  74. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  75. package/dist/DropdownMenu/DropdownMenu.js +62 -20
  76. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +8 -11
  77. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +32 -31
  79. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +25 -0
  80. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuDivider.js +66 -0
  82. package/dist/DropdownMenu/DropdownMenuItem.d.ts +15 -2
  83. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuItem.js +20 -8
  85. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +5 -5
  86. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  87. package/dist/DropdownMenu/DropdownMenuRadioItem.js +24 -67
  88. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  89. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -8
  90. package/dist/DropdownMenu/index.d.ts +2 -1
  91. package/dist/DropdownMenu/index.d.ts.map +1 -1
  92. package/dist/DropdownMenu/index.js +4 -0
  93. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  94. package/dist/DropdownMenu/renderDropdownItems.js +35 -27
  95. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  96. package/dist/EmptyState/EmptyState.js +9 -5
  97. package/dist/Field/InputClearButton.d.ts +16 -3
  98. package/dist/Field/InputClearButton.d.ts.map +1 -1
  99. package/dist/Field/InputClearButton.js +15 -4
  100. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  101. package/dist/HoverCard/useHoverCard.js +3 -19
  102. package/dist/Indicator/CheckIndicator.d.ts +25 -0
  103. package/dist/Indicator/CheckIndicator.d.ts.map +1 -0
  104. package/dist/Indicator/CheckIndicator.js +155 -0
  105. package/dist/Indicator/CheckboxIndicator.d.ts +22 -0
  106. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -0
  107. package/dist/Indicator/CheckboxIndicator.js +173 -0
  108. package/dist/Indicator/RadioIndicator.d.ts +23 -0
  109. package/dist/Indicator/RadioIndicator.d.ts.map +1 -0
  110. package/dist/Indicator/RadioIndicator.js +137 -0
  111. package/dist/Indicator/index.d.ts +24 -0
  112. package/dist/Indicator/index.d.ts.map +1 -0
  113. package/dist/Indicator/index.js +26 -0
  114. package/dist/Indicator/indicator.markers.stylex.d.ts +16 -0
  115. package/dist/Indicator/indicator.markers.stylex.d.ts.map +1 -0
  116. package/dist/Indicator/indicator.markers.stylex.js +21 -0
  117. package/dist/Indicator/indicatorRegistry.d.ts +71 -0
  118. package/dist/Indicator/indicatorRegistry.d.ts.map +1 -0
  119. package/dist/Indicator/indicatorRegistry.js +93 -0
  120. package/dist/Indicator/types.d.ts +167 -0
  121. package/dist/Indicator/types.d.ts.map +1 -0
  122. package/dist/Indicator/types.js +1 -0
  123. package/dist/Indicator/useIndicator.d.ts +18 -0
  124. package/dist/Indicator/useIndicator.d.ts.map +1 -0
  125. package/dist/Indicator/useIndicator.js +30 -0
  126. package/dist/Item/Item.d.ts.map +1 -1
  127. package/dist/Item/Item.js +4 -8
  128. package/dist/Layer/useLayer.d.ts +14 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +25 -2
  131. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  132. package/dist/Lightbox/Lightbox.js +56 -53
  133. package/dist/Link/Link.d.ts.map +1 -1
  134. package/dist/Link/Link.js +4 -5
  135. package/dist/MobileNav/MobileNav.js +8 -8
  136. package/dist/MoreMenu/MoreMenu.d.ts +14 -1
  137. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  138. package/dist/MoreMenu/MoreMenu.js +4 -0
  139. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  140. package/dist/MultiSelector/MultiSelector.js +78 -75
  141. package/dist/NumberInput/NumberInput.d.ts +27 -1
  142. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  143. package/dist/NumberInput/NumberInput.js +230 -42
  144. package/dist/Outline/Outline.d.ts.map +1 -1
  145. package/dist/Outline/Outline.js +2 -3
  146. package/dist/Pagination/Pagination.d.ts.map +1 -1
  147. package/dist/Pagination/Pagination.js +61 -63
  148. package/dist/Popover/Popover.d.ts.map +1 -1
  149. package/dist/Popover/Popover.js +6 -8
  150. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  151. package/dist/PowerSearch/PowerSearch.js +1 -1
  152. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  153. package/dist/PowerSearch/PowerSearchEditPopover.js +7 -0
  154. package/dist/ProgressBar/ProgressBar.d.ts +11 -4
  155. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  156. package/dist/ProgressBar/ProgressBar.js +96 -6
  157. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  158. package/dist/RadioList/RadioListItem.js +29 -84
  159. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  160. package/dist/Resizable/ResizeHandle.js +2 -3
  161. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  162. package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
  163. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  164. package/dist/SelectableCard/SelectableCard.js +3 -11
  165. package/dist/Selector/Selector.d.ts +2 -1
  166. package/dist/Selector/Selector.d.ts.map +1 -1
  167. package/dist/Selector/Selector.js +164 -94
  168. package/dist/Selector/hooks.d.ts +18 -6
  169. package/dist/Selector/hooks.d.ts.map +1 -1
  170. package/dist/Selector/hooks.js +57 -40
  171. package/dist/SideNav/SideNav.d.ts.map +1 -1
  172. package/dist/SideNav/SideNav.js +1 -1
  173. package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
  174. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  175. package/dist/SideNav/SideNavCollapseButton.js +29 -14
  176. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  177. package/dist/SideNav/SideNavHeading.js +56 -20
  178. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  179. package/dist/SideNav/SideNavItem.js +28 -22
  180. package/dist/Stack/stack.stylex.d.ts +3 -3
  181. package/dist/Switch/Switch.js +11 -10
  182. package/dist/TabList/Tab.d.ts.map +1 -1
  183. package/dist/TabList/Tab.js +2 -3
  184. package/dist/TabList/TabMenu.d.ts.map +1 -1
  185. package/dist/TabList/TabMenu.js +41 -19
  186. package/dist/Table/BaseTable.d.ts.map +1 -1
  187. package/dist/Table/BaseTable.js +8 -0
  188. package/dist/Table/index.d.ts +1 -1
  189. package/dist/Table/index.d.ts.map +1 -1
  190. package/dist/Table/index.js +1 -1
  191. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
  192. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  193. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +22 -18
  194. package/dist/Table/plugins/rowExpansion/index.d.ts +1 -1
  195. package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -1
  196. package/dist/Table/plugins/rowExpansion/index.js +1 -1
  197. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +41 -76
  198. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  199. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +94 -345
  200. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  201. package/dist/Table/plugins/sortable/useTableSortable.js +23 -4
  202. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  203. package/dist/Table/plugins/tree/useTableTreeData.js +29 -33
  204. package/dist/Table/tableContextMenu.d.ts.map +1 -1
  205. package/dist/Table/tableContextMenu.js +2 -1
  206. package/dist/Table/types.d.ts +11 -0
  207. package/dist/Table/types.d.ts.map +1 -1
  208. package/dist/TextArea/TextArea.d.ts +10 -1
  209. package/dist/TextArea/TextArea.d.ts.map +1 -1
  210. package/dist/TextArea/TextArea.js +16 -6
  211. package/dist/TextInput/TextInput.d.ts +10 -1
  212. package/dist/TextInput/TextInput.d.ts.map +1 -1
  213. package/dist/TextInput/TextInput.js +13 -19
  214. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  215. package/dist/Thumbnail/Thumbnail.js +32 -26
  216. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  217. package/dist/TimeInput/TimeInput.js +7 -15
  218. package/dist/Timestamp/TimestampHoverCard.d.ts.map +1 -1
  219. package/dist/Timestamp/TimestampHoverCard.js +16 -34
  220. package/dist/Token/Token.d.ts.map +1 -1
  221. package/dist/Token/Token.js +20 -11
  222. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  223. package/dist/Tokenizer/Tokenizer.js +4 -2
  224. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  225. package/dist/Tooltip/useTooltip.js +3 -19
  226. package/dist/TopNav/TopNav.d.ts.map +1 -1
  227. package/dist/TopNav/TopNav.js +6 -5
  228. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  229. package/dist/TopNav/TopNavHeading.js +54 -16
  230. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  231. package/dist/TopNav/TopNavItem.js +3 -9
  232. package/dist/TopNav/TopNavMegaMenu.d.ts +7 -0
  233. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  234. package/dist/TopNav/TopNavMegaMenu.js +146 -60
  235. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  236. package/dist/TopNav/TopNavMegaMenuItem.js +22 -3
  237. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  238. package/dist/TopNav/TopNavMenu.js +83 -38
  239. package/dist/TreeList/TreeList.d.ts.map +1 -1
  240. package/dist/TreeList/TreeList.js +13 -0
  241. package/dist/TreeList/TreeListBranches.d.ts +1 -1
  242. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  243. package/dist/TreeList/TreeListBranches.js +14 -4
  244. package/dist/TreeList/TreeListItem.d.ts +11 -1
  245. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  246. package/dist/TreeList/TreeListItem.js +57 -30
  247. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  248. package/dist/Typeahead/BaseTypeahead.js +16 -8
  249. package/dist/astryx.css +74 -85
  250. package/dist/astryx.umd.js +56 -56
  251. package/dist/astryx.umd.js.map +4 -4
  252. package/dist/hooks/containerReveal.stylex.d.ts +61 -0
  253. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -0
  254. package/dist/hooks/containerReveal.stylex.js +63 -0
  255. package/dist/hooks/index.d.ts +3 -0
  256. package/dist/hooks/index.d.ts.map +1 -1
  257. package/dist/hooks/index.js +2 -0
  258. package/dist/hooks/useClipboard.d.ts +59 -0
  259. package/dist/hooks/useClipboard.d.ts.map +1 -0
  260. package/dist/hooks/useClipboard.js +106 -0
  261. package/dist/hooks/useContainerReveal.d.ts +14 -8
  262. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  263. package/dist/hooks/useContainerReveal.js +28 -69
  264. package/dist/hooks/useIndicatorFocusRing.d.ts +28 -0
  265. package/dist/hooks/useIndicatorFocusRing.d.ts.map +1 -0
  266. package/dist/hooks/useIndicatorFocusRing.js +96 -0
  267. package/dist/hooks/useLongPress.d.ts.map +1 -1
  268. package/dist/hooks/useLongPress.js +12 -2
  269. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  270. package/dist/hooks/useScrollLock.js +33 -18
  271. package/dist/hooks/useStreamingText.d.ts +18 -3
  272. package/dist/hooks/useStreamingText.d.ts.map +1 -1
  273. package/dist/hooks/useStreamingText.js +47 -7
  274. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  275. package/dist/hooks/useTreeFocus.js +9 -3
  276. package/dist/hooks/useTypeahead.d.ts +3 -2
  277. package/dist/hooks/useTypeahead.d.ts.map +1 -1
  278. package/dist/hooks/useTypeahead.js +19 -7
  279. package/dist/index.d.ts +1 -0
  280. package/dist/index.d.ts.map +1 -1
  281. package/dist/index.js +1 -0
  282. package/dist/theme/defineTheme.d.ts +16 -0
  283. package/dist/theme/defineTheme.d.ts.map +1 -1
  284. package/dist/theme/defineTheme.js +8 -0
  285. package/dist/theme/derivedVarRegistry.d.ts +8 -0
  286. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  287. package/dist/theme/derivedVarRegistry.js +14 -13
  288. package/dist/theme/expandColorScale.d.ts +3 -1
  289. package/dist/theme/expandColorScale.d.ts.map +1 -1
  290. package/dist/theme/expandColorScale.js +17 -7
  291. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  292. package/dist/theme/expandTypeScale.js +17 -0
  293. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  294. package/dist/theme/generateThemeRules.js +11 -0
  295. package/dist/theme/index.d.ts +1 -0
  296. package/dist/theme/index.d.ts.map +1 -1
  297. package/dist/utils/focusOutline.stylex.d.ts +107 -0
  298. package/dist/utils/focusOutline.stylex.d.ts.map +1 -0
  299. package/dist/utils/focusOutline.stylex.js +130 -0
  300. package/dist/utils/focusReturn.d.ts +24 -0
  301. package/dist/utils/focusReturn.d.ts.map +1 -0
  302. package/dist/utils/focusReturn.js +29 -0
  303. package/dist/utils/index.d.ts +1 -0
  304. package/dist/utils/index.d.ts.map +1 -1
  305. package/dist/utils/index.js +1 -0
  306. package/dist/utils/mergeRefs.d.ts.map +1 -1
  307. package/dist/utils/mergeRefs.js +7 -5
  308. package/dist/utils/rtlStyles.d.ts +2 -2
  309. package/dist/utils/rtlStyles.js +2 -2
  310. package/dist/utils/themeProps.d.ts +20 -1
  311. package/dist/utils/themeProps.d.ts.map +1 -1
  312. package/dist/utils/themeProps.js +8 -2
  313. package/locales/en.json +12 -0
  314. package/locales/pseudo.json +9 -0
  315. package/package.json +7 -2
  316. package/src/AlertDialog/AlertDialog.doc.mjs +16 -3
  317. package/src/AlertDialog/AlertDialog.test.tsx +208 -1
  318. package/src/AlertDialog/AlertDialog.tsx +21 -3
  319. package/src/AppShell/AppShell.doc.mjs +11 -0
  320. package/src/AppShell/AppShell.test.tsx +139 -0
  321. package/src/AppShell/AppShell.tsx +32 -22
  322. package/src/AspectRatio/AspectRatio.doc.mjs +27 -3
  323. package/src/AspectRatio/AspectRatio.test.tsx +30 -0
  324. package/src/AspectRatio/AspectRatio.tsx +13 -0
  325. package/src/Avatar/Avatar.doc.mjs +2 -13
  326. package/src/Avatar/Avatar.test.tsx +33 -8
  327. package/src/Avatar/Avatar.tsx +52 -50
  328. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -9
  329. package/src/Banner/Banner.tsx +9 -6
  330. package/src/Breadcrumbs/BreadcrumbItem.tsx +16 -6
  331. package/src/Breadcrumbs/index.ts +5 -1
  332. package/src/Button/Button.tsx +21 -41
  333. package/src/Calendar/Calendar.tsx +2 -1
  334. package/src/Calendar/styles.ts +0 -8
  335. package/src/Carousel/Carousel.doc.mjs +3 -0
  336. package/src/Carousel/Carousel.tsx +10 -6
  337. package/src/Chat/Chat.doc.mjs +2 -0
  338. package/src/Chat/ChatDictationButton.tsx +7 -1
  339. package/src/Chat/ChatLayoutScrollButton.test.tsx +22 -0
  340. package/src/Chat/ChatLayoutScrollButton.tsx +8 -1
  341. package/src/Chat/ChatSendButton.tsx +2 -3
  342. package/src/Chat/ChatToolCalls.test.tsx +35 -0
  343. package/src/Chat/ChatToolCalls.tsx +36 -16
  344. package/src/Chat/useTriggerMenu.tsx +2 -5
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +4 -2
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +115 -0
  347. package/src/CheckboxInput/CheckboxInput.tsx +45 -202
  348. package/src/ClickableCard/ClickableCard.tsx +6 -11
  349. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  350. package/src/CodeBlock/CodeBlock.test.tsx +91 -0
  351. package/src/CodeBlock/CodeBlock.tsx +57 -80
  352. package/src/Collapsible/Collapsible.tsx +22 -18
  353. package/src/CommandPalette/CommandPalette.test.tsx +29 -0
  354. package/src/CommandPalette/CommandPaletteItem.tsx +7 -4
  355. package/src/CommandPalette/CommandPaletteList.tsx +2 -2
  356. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  357. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -0
  358. package/src/ComplexSelector/ComplexSelector.tsx +46 -29
  359. package/src/ContextMenu/ContextMenu.doc.mjs +1 -1
  360. package/src/ContextMenu/ContextMenu.test.tsx +41 -0
  361. package/src/ContextMenu/ContextMenu.tsx +3 -3
  362. package/src/ContextMenu/index.ts +5 -1
  363. package/src/DateInput/DateInput.doc.mjs +18 -3
  364. package/src/DateInput/DateInput.test.tsx +93 -11
  365. package/src/DateInput/DateInput.tsx +41 -19
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +10 -2
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +110 -14
  368. package/src/DateRangeInput/DateRangeInput.tsx +24 -12
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -2
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +75 -0
  371. package/src/DateTimeInput/DateTimeInput.tsx +35 -8
  372. package/src/Dialog/Dialog.tsx +2 -9
  373. package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -9
  374. package/src/DropdownMenu/DropdownMenu.test.tsx +534 -4
  375. package/src/DropdownMenu/DropdownMenu.tsx +113 -52
  376. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +27 -30
  377. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +34 -0
  378. package/src/DropdownMenu/DropdownMenuDivider.tsx +81 -0
  379. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +39 -4
  380. package/src/DropdownMenu/DropdownMenuItem.tsx +48 -10
  381. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +30 -76
  382. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +25 -19
  383. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
  384. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
  385. package/src/DropdownMenu/index.ts +8 -1
  386. package/src/DropdownMenu/renderDropdownItems.tsx +33 -34
  387. package/src/EmptyState/EmptyState.doc.mjs +4 -0
  388. package/src/EmptyState/EmptyState.test.tsx +62 -0
  389. package/src/EmptyState/EmptyState.tsx +16 -6
  390. package/src/Field/Field.doc.mjs +1 -0
  391. package/src/Field/InputClearButton.test.tsx +117 -0
  392. package/src/Field/InputClearButton.tsx +30 -3
  393. package/src/HoverCard/useHoverCard.tsx +2 -23
  394. package/src/Icon/Icon.doc.mjs +2 -1
  395. package/src/Indicator/CheckIndicator.tsx +174 -0
  396. package/src/Indicator/CheckboxIndicator.tsx +257 -0
  397. package/src/Indicator/Indicator.doc.mjs +344 -0
  398. package/src/Indicator/Indicator.test.tsx +393 -0
  399. package/src/Indicator/RadioIndicator.tsx +206 -0
  400. package/src/Indicator/index.ts +45 -0
  401. package/src/Indicator/indicator.markers.stylex.ts +19 -0
  402. package/src/Indicator/indicatorRegistry.test.tsx +106 -0
  403. package/src/Indicator/indicatorRegistry.ts +120 -0
  404. package/src/Indicator/types.ts +186 -0
  405. package/src/Indicator/useIndicator.ts +39 -0
  406. package/src/Item/Item.doc.mjs +1 -1
  407. package/src/Item/Item.tsx +7 -14
  408. package/src/Layer/useLayer.doc.mjs +2 -2
  409. package/src/Layer/useLayer.test.tsx +85 -0
  410. package/src/Layer/useLayer.tsx +39 -1
  411. package/src/Lightbox/Lightbox.doc.mjs +1 -1
  412. package/src/Lightbox/Lightbox.tsx +43 -38
  413. package/src/Link/Link.tsx +4 -11
  414. package/src/MobileNav/MobileNav.tsx +3 -3
  415. package/src/MoreMenu/MoreMenu.doc.mjs +31 -0
  416. package/src/MoreMenu/MoreMenu.test.tsx +68 -1
  417. package/src/MoreMenu/MoreMenu.tsx +19 -0
  418. package/src/MultiSelector/MultiSelector.doc.mjs +9 -1
  419. package/src/MultiSelector/MultiSelector.test.tsx +255 -16
  420. package/src/MultiSelector/MultiSelector.tsx +103 -99
  421. package/src/NumberInput/NumberInput.doc.mjs +265 -50
  422. package/src/NumberInput/NumberInput.test.tsx +726 -21
  423. package/src/NumberInput/NumberInput.tsx +353 -59
  424. package/src/Outline/Outline.tsx +2 -5
  425. package/src/Pagination/Pagination.doc.mjs +1 -1
  426. package/src/Pagination/Pagination.test.tsx +22 -7
  427. package/src/Pagination/Pagination.tsx +38 -35
  428. package/src/Popover/Popover.tsx +6 -7
  429. package/src/PowerSearch/PowerSearch.tsx +1 -1
  430. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +42 -0
  431. package/src/PowerSearch/PowerSearchEditPopover.tsx +7 -0
  432. package/src/ProgressBar/ProgressBar.doc.mjs +27 -5
  433. package/src/ProgressBar/ProgressBar.test.tsx +224 -1
  434. package/src/ProgressBar/ProgressBar.tsx +124 -22
  435. package/src/RadioList/RadioList.doc.mjs +4 -2
  436. package/src/RadioList/RadioListItem.tsx +31 -130
  437. package/src/Resizable/ResizeHandle.tsx +2 -9
  438. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -9
  439. package/src/SelectableCard/SelectableCard.tsx +4 -9
  440. package/src/Selector/Selector.doc.mjs +7 -4
  441. package/src/Selector/Selector.test.tsx +935 -25
  442. package/src/Selector/Selector.tsx +189 -106
  443. package/src/Selector/hooks.ts +77 -50
  444. package/src/SideNav/SideNav.test.tsx +46 -0
  445. package/src/SideNav/SideNav.tsx +1 -0
  446. package/src/SideNav/SideNavCollapseButton.tsx +15 -10
  447. package/src/SideNav/SideNavHeading.tsx +48 -10
  448. package/src/SideNav/SideNavItem.tsx +20 -12
  449. package/src/Stack/Stack.doc.mjs +110 -0
  450. package/src/StatusDot/StatusDot.doc.mjs +12 -3
  451. package/src/Switch/Switch.test.tsx +32 -0
  452. package/src/Switch/Switch.tsx +8 -8
  453. package/src/TabList/Tab.tsx +2 -9
  454. package/src/TabList/TabMenu.tsx +14 -21
  455. package/src/Table/BaseTable.tsx +11 -0
  456. package/src/Table/index.ts +1 -4
  457. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +30 -14
  458. package/src/Table/plugins/rowExpansion/index.ts +1 -4
  459. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +125 -175
  460. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +115 -458
  461. package/src/Table/plugins/sortable/useTableSortable.tsx +2 -6
  462. package/src/Table/plugins/tree/useTableTreeData.tsx +43 -24
  463. package/src/Table/tableContextMenu.test.tsx +27 -0
  464. package/src/Table/tableContextMenu.tsx +1 -0
  465. package/src/Table/types.ts +11 -0
  466. package/src/Table/useTableGroupedRows.doc.mjs +1 -1
  467. package/src/Table/useTableRowExpansion.doc.mjs +43 -66
  468. package/src/TextArea/TextArea.doc.mjs +42 -2
  469. package/src/TextArea/TextArea.test.tsx +219 -0
  470. package/src/TextArea/TextArea.tsx +34 -7
  471. package/src/TextInput/TextInput.doc.mjs +18 -2
  472. package/src/TextInput/TextInput.test.tsx +189 -0
  473. package/src/TextInput/TextInput.tsx +25 -31
  474. package/src/Thumbnail/Thumbnail.tsx +2 -9
  475. package/src/TimeInput/TimeInput.doc.mjs +2 -2
  476. package/src/TimeInput/TimeInput.test.tsx +30 -3
  477. package/src/TimeInput/TimeInput.tsx +9 -26
  478. package/src/Timestamp/Timestamp.doc.mjs +4 -3
  479. package/src/Timestamp/TimestampHoverCard.tsx +13 -35
  480. package/src/Token/Token.tsx +5 -28
  481. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  482. package/src/Tokenizer/Tokenizer.test.tsx +45 -3
  483. package/src/Tokenizer/Tokenizer.tsx +10 -2
  484. package/src/Tooltip/useTooltip.tsx +2 -23
  485. package/src/TopNav/TopNav.tsx +1 -3
  486. package/src/TopNav/TopNavHeading.tsx +42 -13
  487. package/src/TopNav/TopNavItem.tsx +3 -21
  488. package/src/TopNav/TopNavMegaMenu.test.tsx +433 -33
  489. package/src/TopNav/TopNavMegaMenu.tsx +118 -53
  490. package/src/TopNav/TopNavMegaMenuItem.tsx +2 -9
  491. package/src/TopNav/TopNavMenu.tsx +34 -37
  492. package/src/TreeList/TreeList.doc.mjs +5 -0
  493. package/src/TreeList/TreeList.test.tsx +206 -7
  494. package/src/TreeList/TreeList.tsx +22 -0
  495. package/src/TreeList/TreeListBranches.tsx +25 -2
  496. package/src/TreeList/TreeListItem.tsx +89 -52
  497. package/src/Typeahead/BaseTypeahead.tsx +19 -6
  498. package/src/Typeahead/Typeahead.doc.mjs +1 -0
  499. package/src/Typeahead/Typeahead.test.tsx +76 -3
  500. package/src/hooks/containerReveal.stylex.ts +102 -0
  501. package/src/hooks/index.ts +4 -0
  502. package/src/hooks/useClipboard.doc.mjs +87 -0
  503. package/src/hooks/useClipboard.test.tsx +151 -0
  504. package/src/hooks/useClipboard.ts +135 -0
  505. package/src/hooks/useContainerReveal.doc.mjs +7 -7
  506. package/src/hooks/useContainerReveal.test.tsx +42 -62
  507. package/src/hooks/useContainerReveal.ts +36 -91
  508. package/src/hooks/useImageMode.test.ts +1 -1
  509. package/src/hooks/useIndicatorFocusRing.tsx +120 -0
  510. package/src/hooks/useLongPress.test.tsx +18 -0
  511. package/src/hooks/useLongPress.ts +12 -2
  512. package/src/hooks/useScrollLock.test.ts +72 -0
  513. package/src/hooks/useScrollLock.ts +51 -19
  514. package/src/hooks/useStreamingText.test.ts +46 -1
  515. package/src/hooks/useStreamingText.ts +49 -7
  516. package/src/hooks/useTreeFocus.test.tsx +50 -0
  517. package/src/hooks/useTreeFocus.ts +14 -3
  518. package/src/hooks/useTypeahead.test.tsx +150 -0
  519. package/src/hooks/useTypeahead.ts +24 -9
  520. package/src/i18n/InternationalizationProvider.doc.mjs +7 -2
  521. package/src/index.ts +1 -0
  522. package/src/theme/defineTheme.ts +24 -0
  523. package/src/theme/derivedVarRegistry.test.ts +21 -1
  524. package/src/theme/derivedVarRegistry.ts +19 -6
  525. package/src/theme/expandColorScale.test.ts +14 -0
  526. package/src/theme/expandColorScale.ts +24 -9
  527. package/src/theme/expandTypeScale.test.ts +43 -0
  528. package/src/theme/expandTypeScale.ts +17 -0
  529. package/src/theme/generateThemeRules.test.ts +61 -20
  530. package/src/theme/generateThemeRules.ts +11 -0
  531. package/src/theme/index.ts +13 -0
  532. package/src/utils/focusOutline.stylex.ts +163 -0
  533. package/src/utils/focusReturn.test.ts +42 -0
  534. package/src/utils/focusReturn.ts +31 -0
  535. package/src/utils/index.ts +1 -0
  536. package/src/utils/mergeRefs.test.ts +48 -0
  537. package/src/utils/mergeRefs.ts +12 -6
  538. package/src/utils/rtlStyles.ts +2 -2
  539. package/src/utils/themeProps.ts +26 -4
  540. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts +0 -9
  541. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts.map +0 -1
  542. package/dist/CheckboxInput/checkbox.markers.stylex.js +0 -14
  543. package/dist/RadioList/radio.markers.stylex.d.ts +0 -7
  544. package/dist/RadioList/radio.markers.stylex.d.ts.map +0 -1
  545. package/dist/RadioList/radio.markers.stylex.js +0 -12
  546. package/dist/hooks/containerReveal.pool.stylex.d.ts +0 -303
  547. package/dist/hooks/containerReveal.pool.stylex.d.ts.map +0 -1
  548. package/dist/hooks/containerReveal.pool.stylex.js +0 -292
  549. package/src/CheckboxInput/checkbox.markers.stylex.ts +0 -12
  550. package/src/RadioList/radio.markers.stylex.ts +0 -10
  551. package/src/hooks/containerReveal.pool.stylex.ts +0 -571
@@ -3,61 +3,55 @@
3
3
  /**
4
4
  * @file useContainerReveal.test.tsx
5
5
  * @input Uses vitest, @testing-library/react, useContainerReveal
6
- * @output Unit tests for the marker-pool free-list, per-instance scoping, the
7
- * inert (disabled) path, and the content-option → style-block mapping.
8
- * @position Testing; validates useContainerReveal.ts and its pool.
6
+ * @output Unit tests for the enabled/disabled contract, the dynamic isEnabled
7
+ * prop, the content-option → style-block mapping, and the promise that a
8
+ * large flat list mounts without dev warnings.
9
+ * @position Testing; validates useContainerReveal.ts.
10
+ *
11
+ * Nesting isolation is a cascade behavior jsdom does not implement, so it is
12
+ * verified in a real browser (Storybook's NestedIsolation story) rather than
13
+ * asserted here.
9
14
  *
10
15
  * SYNC: When useContainerReveal.ts changes, update these tests.
11
16
  */
12
17
 
13
- import {describe, it, expect} from 'vitest';
14
- import {renderHook, render, act} from '@testing-library/react';
18
+ import {describe, it, expect, vi, afterEach} from 'vitest';
19
+ import {renderHook, render} from '@testing-library/react';
15
20
  import {useContainerReveal} from './useContainerReveal';
16
- import {POOL_SIZE} from './containerReveal.pool.stylex';
21
+
22
+ afterEach(() => {
23
+ vi.restoreAllMocks();
24
+ });
17
25
 
18
26
  describe('useContainerReveal', () => {
19
27
  it('returns spreadable getter props for container and content', () => {
20
28
  const {result} = renderHook(() => useContainerReveal());
21
29
  const container = result.current.getContainerProps();
22
30
  const content = result.current.getContentRevealProps();
23
- expect(container).toHaveProperty('className');
24
- expect(content).toHaveProperty('className');
25
31
  expect(typeof container.className).toBe('string');
26
32
  expect(typeof content.className).toBe('string');
27
33
  });
28
34
 
29
- it('is inert when disabled: no marker class, empty content props', () => {
30
- const {result} = renderHook(() =>
31
- useContainerReveal({isEnabled: false}),
32
- );
35
+ it('is inert when disabled: no container class, empty content props', () => {
36
+ const {result} = renderHook(() => useContainerReveal({isEnabled: false}));
33
37
  expect(result.current.getContainerProps()).toEqual({});
34
38
  expect(result.current.getContentRevealProps()).toEqual({});
35
39
  });
36
40
 
37
- it('gives two concurrently mounted containers DISTINCT marker classes', () => {
38
- // The core leak-safety property: nested/concurrent containers must never
39
- // share a marker, or an ancestor container's :hover would reveal a
40
- // descendant container's content.
41
- const a = renderHook(() => useContainerReveal());
42
- const b = renderHook(() => useContainerReveal());
43
- const classA = a.result.current.getContainerProps().className;
44
- const classB = b.result.current.getContainerProps().className;
45
- expect(classA).toBeTruthy();
46
- expect(classB).toBeTruthy();
47
- expect(classA).not.toBe(classB);
48
- a.unmount();
49
- b.unmount();
50
- });
41
+ it('follows isEnabled after mount, in both directions', () => {
42
+ const {result, rerender} = renderHook(
43
+ ({isEnabled}) => useContainerReveal({isEnabled}),
44
+ {initialProps: {isEnabled: true}},
45
+ );
46
+ expect(result.current.getContentRevealProps().className).toBeTruthy();
51
47
 
52
- it('recycles a slot after unmount (free-list release)', () => {
53
- const first = renderHook(() => useContainerReveal());
54
- const firstClass = first.result.current.getContainerProps().className;
55
- first.unmount();
56
- // A fresh mount should be able to reclaim the just-released slot.
57
- const second = renderHook(() => useContainerReveal());
58
- const secondClass = second.result.current.getContainerProps().className;
59
- expect(secondClass).toBe(firstClass);
60
- second.unmount();
48
+ rerender({isEnabled: false});
49
+ expect(result.current.getContainerProps()).toEqual({});
50
+ expect(result.current.getContentRevealProps()).toEqual({});
51
+
52
+ rerender({isEnabled: true});
53
+ expect(result.current.getContainerProps().className).toBeTruthy();
54
+ expect(result.current.getContentRevealProps().className).toBeTruthy();
61
55
  });
62
56
 
63
57
  it('reveal and conceal map to different style blocks', () => {
@@ -80,37 +74,23 @@ describe('useContainerReveal', () => {
80
74
  expect(clipped).not.toBe(preserved);
81
75
  });
82
76
 
83
- it('assigns distinct markers across a full pool of concurrent containers', () => {
84
- function Probe({onClass}: {onClass: (c: string) => void}) {
85
- const {getContainerProps} = useContainerReveal();
86
- onClass(getContainerProps().className ?? '');
87
- return null;
77
+ it('mounts a large flat list without a dev warning', () => {
78
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
79
+ function Row() {
80
+ const {getContainerProps, getContentRevealProps} = useContainerReveal();
81
+ return (
82
+ <div {...getContainerProps()}>
83
+ <span {...getContentRevealProps()}>actions</span>
84
+ </div>
85
+ );
88
86
  }
89
- const classes: string[] = [];
90
- const {unmount} = render(
87
+ render(
91
88
  <>
92
- {Array.from({length: POOL_SIZE}, (_, i) => (
93
- <Probe key={i} onClass={(c) => classes.push(c)} />
89
+ {Array.from({length: 500}, (_, i) => (
90
+ <Row key={i} />
94
91
  ))}
95
92
  </>,
96
93
  );
97
- const unique = new Set(classes.filter(Boolean));
98
- expect(unique.size).toBe(POOL_SIZE);
99
- unmount();
100
- });
101
-
102
- it('survives repeated mount/unmount without leaking the pool', () => {
103
- // Mount and unmount a full pool several times; each cycle must still hand
104
- // out POOL_SIZE distinct markers, proving slots are released.
105
- for (let cycle = 0; cycle < 3; cycle++) {
106
- const hooks = Array.from({length: POOL_SIZE}, () =>
107
- renderHook(() => useContainerReveal()),
108
- );
109
- const classes = hooks.map(
110
- (h) => h.result.current.getContainerProps().className,
111
- );
112
- expect(new Set(classes).size).toBe(POOL_SIZE);
113
- act(() => hooks.forEach((h) => h.unmount()));
114
- }
94
+ expect(warn).not.toHaveBeenCalled();
115
95
  });
116
96
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file useContainerReveal.ts
7
- * @input Uses React, the generated marker pool, useDevWarning
7
+ * @input Uses the containerReveal styles
8
8
  * @output Exports useContainerReveal — a headless hover/focus reveal primitive
9
9
  * @position Core hook. Consumed by any component that reveals (or conceals)
10
10
  * content when its container is hovered or focused — e.g. Thumbnail's remove
@@ -12,8 +12,13 @@
12
12
  *
13
13
  * Gives a container a scoped hover/focus-within trigger that reveals or
14
14
  * conceals content inside it, entirely in CSS (no hover state in JS, no
15
- * re-render on hover). The caller authors NO StyleX: the hook hands out a
16
- * marker + matching reveal styles from a pre-compiled pool.
15
+ * re-render on hover). The caller authors NO StyleX: the hook hands out the
16
+ * container style and the matching content styles.
17
+ *
18
+ * Scoping is by inheritance: the container publishes its reveal state as
19
+ * custom properties on itself and the content reads them, so a nested
20
+ * container shadows its ancestor's state for its own subtree. See
21
+ * containerReveal.stylex.ts.
17
22
  *
18
23
  * ACCESSIBILITY (WCAG 2.2 by construction):
19
24
  * - Revealed content is visually hidden at rest via position + opacity, so it
@@ -27,56 +32,19 @@
27
32
  *
28
33
  * SYNC: When modified, update:
29
34
  * - /packages/core/src/hooks/index.ts (export)
30
- * - containerReveal.pool.stylex.ts (if the slot shape changes)
35
+ * - containerReveal.stylex.ts (if the style blocks change)
31
36
  */
32
37
 
33
- import {useState, useEffect, type CSSProperties} from 'react';
38
+ import {useMemo, type CSSProperties} from 'react';
34
39
  import * as stylex from '@stylexjs/stylex';
35
- import {POOL, POOL_SIZE, type RevealSlot} from './containerReveal.pool.stylex';
36
- import {useDevWarning} from './useDevWarning';
37
-
38
- // Module-level free-list. Each mounted, enabled useContainerReveal claims a
39
- // distinct pool slot so that any two CONCURRENTLY mounted containers — in
40
- // particular a nested container inside another's revealed content — get
41
- // different markers and cannot leak hover/focus into one another. Slots are
42
- // returned on unmount. Sibling containers that would only ever collide when
43
- // the pool is exhausted are harmless (a sibling is never an ancestor), so the
44
- // pool only needs to cover the number of concurrently mounted containers, not
45
- // instances over time.
46
- const claimed: boolean[] = new Array(POOL_SIZE).fill(false);
47
-
48
- interface Claim {
49
- /** Pool index to use. */
50
- index: number;
51
- /** True when the pool was full and this claim fell back to a shared slot. */
52
- isExhausted: boolean;
53
- }
54
-
55
- function claimSlot(): Claim {
56
- for (let i = 0; i < POOL_SIZE; i++) {
57
- if (!claimed[i]) {
58
- claimed[i] = true;
59
- return {index: i, isExhausted: false};
60
- }
61
- }
62
- // Exhausted: fall back to slot 0. Safe for siblings (a sibling is never an
63
- // ancestor); only nesting deeper than POOL_SIZE could reintroduce a leak.
64
- // Surfaced via useDevWarning so the pool can be grown.
65
- return {index: 0, isExhausted: true};
66
- }
67
-
68
- function releaseSlot(index: number, isExhausted: boolean): void {
69
- // A fallback claim never owned the slot exclusively; leave it as-is.
70
- if (!isExhausted) {
71
- claimed[index] = false;
72
- }
73
- }
40
+ import {styles} from './containerReveal.stylex';
74
41
 
75
42
  export interface UseContainerRevealOptions {
76
43
  /**
77
- * When false the hook is inert: no marker is applied and content getters
78
- * return no styles, so content is always shown. Lets a component gate on its
79
- * own prop (e.g. `revealOn === 'hover'`).
44
+ * When false the hook is inert: the container gets no styles and content
45
+ * getters return no styles, so content is always shown. Read on every
46
+ * render, so a component can flip it with its own prop (e.g.
47
+ * `revealOn === 'hover'`).
80
48
  * @default true
81
49
  */
82
50
  isEnabled?: boolean;
@@ -114,7 +82,7 @@ const EMPTY = Object.freeze({});
114
82
  * Scoped, CSS-only hover/focus reveal for content inside a container.
115
83
  *
116
84
  * @example
117
- * ```tsx
85
+ * ```
118
86
  * const {getContainerProps, getContentRevealProps} = useContainerReveal({
119
87
  * isEnabled: revealOn === 'hover',
120
88
  * });
@@ -132,50 +100,27 @@ export function useContainerReveal(
132
100
  ): UseContainerRevealReturn {
133
101
  const {isEnabled = true} = options;
134
102
 
135
- // Claim a slot for this container's lifetime. Assigned in the initializer
136
- // (not the render body) and released on unmount so the free-list stays
137
- // effect-scoped and StrictMode-safe.
138
- const [claim] = useState<Claim | null>(() =>
139
- isEnabled ? claimSlot() : null,
140
- );
141
-
142
- useEffect(() => {
143
- if (claim == null) {
144
- return;
103
+ return useMemo(() => {
104
+ if (!isEnabled) {
105
+ return {
106
+ getContainerProps: () => EMPTY,
107
+ getContentRevealProps: () => EMPTY,
108
+ };
145
109
  }
146
- return () => releaseSlot(claim.index, claim.isExhausted);
147
- }, [claim]);
148
-
149
- useDevWarning(
150
- 'useContainerReveal',
151
- `More than ${POOL_SIZE} reveal containers are mounted at once; nested ` +
152
- `containers beyond the pool may share a marker. Add markers to ` +
153
- `containerReveal.pool.stylex.ts and raise POOL_SIZE.`,
154
- claim?.isExhausted ?? false,
155
- );
156
-
157
- if (claim == null) {
158
110
  return {
159
- getContainerProps: () => EMPTY,
160
- getContentRevealProps: () => EMPTY,
111
+ getContainerProps: () => stylex.props(styles.container),
112
+ getContentRevealProps: (contentOptions: ContentRevealOptions = {}) => {
113
+ const {isRevealInverted = false, isLayoutPreserved = false} =
114
+ contentOptions;
115
+ const style = isRevealInverted
116
+ ? isLayoutPreserved
117
+ ? styles.concealLayoutPreserved
118
+ : styles.conceal
119
+ : isLayoutPreserved
120
+ ? styles.revealLayoutPreserved
121
+ : styles.reveal;
122
+ return stylex.props(style);
123
+ },
161
124
  };
162
- }
163
-
164
- const slot: RevealSlot = POOL[claim.index];
165
-
166
- return {
167
- getContainerProps: () => stylex.props(slot.marker),
168
- getContentRevealProps: (contentOptions: ContentRevealOptions = {}) => {
169
- const {isRevealInverted = false, isLayoutPreserved = false} =
170
- contentOptions;
171
- const style = isRevealInverted
172
- ? isLayoutPreserved
173
- ? slot.concealLayoutPreserved
174
- : slot.conceal
175
- : isLayoutPreserved
176
- ? slot.revealLayoutPreserved
177
- : slot.reveal;
178
- return stylex.props(style);
179
- },
180
- };
125
+ }, [isEnabled]);
181
126
  }
@@ -105,7 +105,7 @@ describe('useImageMode', () => {
105
105
  stubCorsBlockedFetch();
106
106
  const {result} = renderHook(() =>
107
107
  useImageMode(
108
- 'https://lookaside.facebook.com/assets/astryx/moody-scene-vertical-2.png',
108
+ 'https://cdn.example.com/photos/moody-scene-vertical-2.png',
109
109
  {fallback: null},
110
110
  ),
111
111
  );
@@ -0,0 +1,120 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useIndicatorFocusRing.tsx
7
+ * @input A ref to the element wrapping only the indicator
8
+ * @output Focus handlers that draw the standard ring on the indicator itself
9
+ * @position Used by controls whose focusable input is visually hidden
10
+ *
11
+ * SYNC: When modified, update:
12
+ * - /packages/core/src/hooks/index.ts (exports)
13
+ *
14
+ * A checkbox or radio focuses a native `<input>` that is `opacity: 0`, so the
15
+ * visible focus indicator has to appear on the picture beside it — and that
16
+ * picture is a themeable indicator, which is what makes "who draws the ring"
17
+ * awkward:
18
+ *
19
+ * - Only the indicator's own element can shape the ring: `outline` follows
20
+ * that element's `border-radius`. Draw it on a wrapper and the owner has to
21
+ * hardcode a guess about whatever indicator it hosts (RadioListItem used to
22
+ * carry `border-radius: 50%` for a circle it did not own).
23
+ * - But an indicator supplied by a theme is third-party code. If drawing the
24
+ * ring is its job, a replacement that simply doesn't ships a control with no
25
+ * visible focus (WCAG 2.4.7) — and that is the DEFAULT outcome: the sample
26
+ * replacement in our own Storybook destructures `{state, size, isDisabled}`
27
+ * and drops the rest, which is what a theme author plausibly writes. Nothing
28
+ * can enforce otherwise; a component ignoring every prop is still assignable
29
+ * to `IndicatorComponent`, and branding the return type does not work either
30
+ * (JSX types every expression as `JSX.Element`).
31
+ *
32
+ * So the owner draws it, on the indicator's own element, imperatively at focus
33
+ * time. No cooperation is required and none can be forgotten.
34
+ *
35
+ * Two details that are load-bearing, both measured rather than assumed:
36
+ *
37
+ * 1. **Inline style, not a class.** React replaces `className` wholesale on
38
+ * re-render, so an injected class disappears the moment the control changes
39
+ * state — pressing Space on a focused checkbox lost the ring. React
40
+ * reconciles `style` per property, so an `outline` set here survives.
41
+ * 2. **`:focus-visible`, not `:focus`.** The CSS this replaces got
42
+ * keyboard-vs-pointer for free; an event handler has to ask. Checking the
43
+ * element keeps the browser's heuristic, including `focus({focusVisible:
44
+ * true})`, which a hand-rolled modality guess would miss.
45
+ *
46
+ * Imperative styling on an interaction event follows `useTableSelection`,
47
+ * which sets row-selection styles the same way.
48
+ */
49
+
50
+ import {useCallback, useMemo} from 'react';
51
+ import type {FocusEvent, RefObject} from 'react';
52
+ import {
53
+ FOCUS_OUTLINE_PARTS,
54
+ FOCUS_OUTLINE_PARTS_NONE,
55
+ } from '../utils/focusOutline.stylex';
56
+
57
+ export interface UseIndicatorFocusRingReturn {
58
+ /** Spread onto the element that owns the focusable input. */
59
+ focusProps: {
60
+ onFocus: (event: FocusEvent<HTMLElement>) => void;
61
+ onBlur: () => void;
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Draw the standard focus ring on the indicator inside `containerRef`, shaped
67
+ * by whatever that indicator actually is.
68
+ *
69
+ * @param containerRef - Wraps ONLY the indicator, so its single element child
70
+ * is unambiguously the thing to ring, whatever a theme renders there.
71
+ * @param isDisabled - Skip the ring; a disabled control is not focusable.
72
+ *
73
+ * @example
74
+ * ```
75
+ * const indicatorRef = useRef<HTMLSpanElement>(null);
76
+ * const {focusProps} = useIndicatorFocusRing(indicatorRef, isDisabled);
77
+ * <div {...focusProps}>
78
+ * <input type="checkbox" />
79
+ * <span ref={indicatorRef}><Indicator state="checked" /></span>
80
+ * </div>
81
+ * ```
82
+ */
83
+ export function useIndicatorFocusRing(
84
+ containerRef: RefObject<HTMLElement | null>,
85
+ isDisabled = false,
86
+ ): UseIndicatorFocusRingReturn {
87
+ const paint = useCallback(
88
+ (on: boolean) => {
89
+ // `firstElementChild`, not a search: the container holds the indicator
90
+ // and nothing else, so there is no ambiguity — and no way for a later
91
+ // sibling to quietly become the target.
92
+ const indicator = containerRef.current?.firstElementChild;
93
+ if (indicator instanceof HTMLElement) {
94
+ Object.assign(
95
+ indicator.style,
96
+ on ? FOCUS_OUTLINE_PARTS : FOCUS_OUTLINE_PARTS_NONE,
97
+ );
98
+ }
99
+ },
100
+ [containerRef],
101
+ );
102
+
103
+ const onFocus = useCallback(
104
+ (event: FocusEvent<HTMLElement>) => {
105
+ if (isDisabled) {
106
+ return;
107
+ }
108
+ const target = event.target;
109
+ if (target instanceof HTMLElement && !target.matches(':focus-visible')) {
110
+ return;
111
+ }
112
+ paint(true);
113
+ },
114
+ [isDisabled, paint],
115
+ );
116
+
117
+ const onBlur = useCallback(() => paint(false), [paint]);
118
+
119
+ return useMemo(() => ({focusProps: {onFocus, onBlur}}), [onFocus, onBlur]);
120
+ }
@@ -81,6 +81,24 @@ describe('useLongPress', () => {
81
81
  expect(onLongPress).not.toHaveBeenCalled();
82
82
  });
83
83
 
84
+ it('cancels when a second finger touches down mid-press', () => {
85
+ const onLongPress = vi.fn();
86
+ const {result} = renderHook(() => useLongPress({onLongPress}));
87
+
88
+ act(() => {
89
+ result.current.onTouchStart(touchEvent([{clientX: 0, clientY: 0}]));
90
+ // A second finger joins before the press fires (e.g. pinch-to-zoom).
91
+ result.current.onTouchStart(
92
+ touchEvent([
93
+ {clientX: 0, clientY: 0},
94
+ {clientX: 50, clientY: 50},
95
+ ]),
96
+ );
97
+ vi.advanceTimersByTime(500);
98
+ });
99
+ expect(onLongPress).not.toHaveBeenCalled();
100
+ });
101
+
84
102
  it('cancels when the finger moves past the threshold', () => {
85
103
  const onLongPress = vi.fn();
86
104
  const {result} = renderHook(() => useLongPress({onLongPress}));
@@ -70,7 +70,12 @@ export function useLongPress(
70
70
 
71
71
  const onTouchStart = useCallback(
72
72
  (e: React.TouchEvent) => {
73
- if (disabled || e.touches.length !== 1) {
73
+ if (disabled) {
74
+ return;
75
+ }
76
+ if (e.touches.length !== 1) {
77
+ // Multi-touch cancels any pending long-press.
78
+ clear();
74
79
  return;
75
80
  }
76
81
  const touch = e.touches[0];
@@ -92,7 +97,12 @@ export function useLongPress(
92
97
  const onTouchMove = useCallback(
93
98
  (e: React.TouchEvent) => {
94
99
  const start = startRef.current;
95
- if (start == null || e.touches.length !== 1) {
100
+ if (start == null) {
101
+ return;
102
+ }
103
+ if (e.touches.length !== 1) {
104
+ // Multi-touch cancels the pending long-press.
105
+ clear();
96
106
  return;
97
107
  }
98
108
  const touch = e.touches[0];
@@ -0,0 +1,72 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useScrollLock.test.ts
5
+ * @input Uses vitest, @testing-library/react, useScrollLock hook
6
+ * @output Unit tests for single and concurrent useScrollLock instances
7
+ * @position Testing; validates useScrollLock.ts implementation
8
+ *
9
+ * SYNC: When useScrollLock.ts changes, update tests to match new behavior
10
+ */
11
+
12
+ import {afterEach, describe, expect, it, vi} from 'vitest';
13
+ import {cleanup, renderHook} from '@testing-library/react';
14
+ import {useScrollLock} from './useScrollLock';
15
+
16
+ describe('useScrollLock', () => {
17
+ afterEach(() => {
18
+ cleanup();
19
+ document.body.style.cssText = '';
20
+ vi.restoreAllMocks();
21
+ });
22
+
23
+ it('restores body styles and scroll position after a single lock is released', () => {
24
+ document.body.style.overflow = 'auto';
25
+ document.body.style.position = 'relative';
26
+
27
+ vi.spyOn(window, 'scrollX', 'get').mockReturnValue(120);
28
+ vi.spyOn(window, 'scrollY', 'get').mockReturnValue(480);
29
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
30
+
31
+ const lock = renderHook(() => useScrollLock(true));
32
+
33
+ expect(document.body.style.overflow).toBe('hidden');
34
+ expect(document.body.style.position).toBe('fixed');
35
+
36
+ lock.unmount();
37
+
38
+ expect(document.body.style.overflow).toBe('auto');
39
+ expect(document.body.style.position).toBe('relative');
40
+ expect(window.scrollTo).toHaveBeenCalledWith(120, 480);
41
+ });
42
+
43
+ it('stays locked while a second overlay is still open, even if the first closes out of order', () => {
44
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
45
+
46
+ const first = renderHook(() => useScrollLock(true));
47
+ const second = renderHook(() => useScrollLock(true));
48
+
49
+ first.unmount();
50
+
51
+ expect(document.body.style.position).toBe('fixed');
52
+ expect(document.body.style.overflow).toBe('hidden');
53
+
54
+ second.unmount();
55
+ });
56
+
57
+ it('fully restores the body once every overlay has closed, regardless of close order', () => {
58
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
59
+
60
+ const first = renderHook(() => useScrollLock(true));
61
+ const second = renderHook(() => useScrollLock(true));
62
+
63
+ first.unmount();
64
+ second.unmount();
65
+
66
+ expect(document.body.style.position).toBe('');
67
+ expect(document.body.style.overflow).toBe('');
68
+ expect(document.body.style.top).toBe('');
69
+ expect(document.body.style.left).toBe('');
70
+ expect(document.body.style.right).toBe('');
71
+ });
72
+ });
@@ -17,6 +17,19 @@
17
17
 
18
18
  import {useEffect} from 'react';
19
19
 
20
+ interface ScrollLockSnapshot {
21
+ scrollX: number;
22
+ scrollY: number;
23
+ overflow: string;
24
+ position: string;
25
+ top: string;
26
+ left: string;
27
+ right: string;
28
+ }
29
+
30
+ let lockCount = 0;
31
+ let originalBodyState: ScrollLockSnapshot | null = null;
32
+
20
33
  /**
21
34
  * Locks body scroll when `isLocked` is true.
22
35
  *
@@ -36,28 +49,47 @@ export function useScrollLock(isLocked: boolean): void {
36
49
  return;
37
50
  }
38
51
 
39
- const scrollX = window.scrollX;
40
- const scrollY = window.scrollY;
41
52
  const {body} = document;
42
- const prevOverflow = body.style.overflow;
43
- const prevPosition = body.style.position;
44
- const prevTop = body.style.top;
45
- const prevLeft = body.style.left;
46
- const prevRight = body.style.right;
47
-
48
- body.style.overflow = 'hidden';
49
- body.style.position = 'fixed';
50
- body.style.top = `-${scrollY}px`;
51
- body.style.left = '0';
52
- body.style.right = '0';
53
+
54
+ if (lockCount === 0) {
55
+ const scrollX = window.scrollX;
56
+ const scrollY = window.scrollY;
57
+
58
+ originalBodyState = {
59
+ scrollX,
60
+ scrollY,
61
+ overflow: body.style.overflow,
62
+ position: body.style.position,
63
+ top: body.style.top,
64
+ left: body.style.left,
65
+ right: body.style.right,
66
+ };
67
+
68
+ body.style.overflow = 'hidden';
69
+ body.style.position = 'fixed';
70
+ body.style.top = `-${scrollY}px`;
71
+ body.style.left = '0';
72
+ body.style.right = '0';
73
+ }
74
+
75
+ lockCount += 1;
53
76
 
54
77
  return () => {
55
- body.style.overflow = prevOverflow;
56
- body.style.position = prevPosition;
57
- body.style.top = prevTop;
58
- body.style.left = prevLeft;
59
- body.style.right = prevRight;
60
- window.scrollTo(scrollX, scrollY);
78
+ lockCount -= 1;
79
+
80
+ if (lockCount !== 0 || originalBodyState == null) {
81
+ return;
82
+ }
83
+
84
+ const state = originalBodyState;
85
+ originalBodyState = null;
86
+
87
+ body.style.overflow = state.overflow;
88
+ body.style.position = state.position;
89
+ body.style.top = state.top;
90
+ body.style.left = state.left;
91
+ body.style.right = state.right;
92
+ window.scrollTo(state.scrollX, state.scrollY);
61
93
  };
62
94
  }, [isLocked]);
63
95
  }