@astryxdesign/core 0.5.2-canary.2da7a39 → 0.5.2-canary.2e51213

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 (375) hide show
  1. package/dist/AppShell/AppShell.d.ts +2 -2
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +10 -7
  4. package/dist/Banner/Banner.d.ts +5 -0
  5. package/dist/Banner/Banner.d.ts.map +1 -1
  6. package/dist/Banner/Banner.js +7 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  8. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  9. package/dist/Breadcrumbs/BreadcrumbItem.js +22 -15
  10. package/dist/Carousel/Carousel.d.ts.map +1 -1
  11. package/dist/Carousel/Carousel.js +2 -2
  12. package/dist/Chat/ChatComposer.d.ts +2 -2
  13. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposer.js +21 -2
  15. package/dist/Chat/ChatToolCalls.d.ts +1 -1
  16. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  17. package/dist/Chat/ChatToolCalls.js +12 -34
  18. package/dist/Chat/useChatNewMessages.js +3 -3
  19. package/dist/CheckboxList/CheckboxListItem.d.ts +5 -3
  20. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  21. package/dist/DateTimeInput/DateTimeInput.d.ts +47 -13
  22. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  23. package/dist/DateTimeInput/DateTimeInput.js +269 -162
  24. package/dist/DateTimeInput/NativeDateSegment.d.ts +24 -0
  25. package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -0
  26. package/dist/DateTimeInput/NativeDateSegment.js +235 -0
  27. package/dist/DateTimeInput/NativeTimeSegment.d.ts +30 -0
  28. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -0
  29. package/dist/DateTimeInput/NativeTimeSegment.js +248 -0
  30. package/dist/DateTimeInput/index.d.ts +1 -1
  31. package/dist/DateTimeInput/index.d.ts.map +1 -1
  32. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +95 -0
  33. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -0
  34. package/dist/DateTimeInput/nativePickerSegmentStyles.js +103 -0
  35. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  36. package/dist/DropdownMenu/DropdownMenu.js +2 -4
  37. package/dist/Field/Field.d.ts.map +1 -1
  38. package/dist/Field/Field.js +10 -4
  39. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  40. package/dist/Field/PanelSearchInput.js +3 -5
  41. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  42. package/dist/FieldStatus/FieldStatus.js +4 -2
  43. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  44. package/dist/Layer/useTouchTrigger.js +2 -4
  45. package/dist/Layout/Layout.d.ts +7 -8
  46. package/dist/Layout/Layout.d.ts.map +1 -1
  47. package/dist/Layout/Layout.js +7 -8
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +89 -45
  51. package/dist/RadioList/RadioListItem.d.ts +30 -5
  52. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  53. package/dist/RadioList/RadioListItem.js +17 -4
  54. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  55. package/dist/Resizable/ResizeHandle.js +102 -44
  56. package/dist/Resizable/index.d.ts +2 -0
  57. package/dist/Resizable/index.d.ts.map +1 -1
  58. package/dist/Resizable/index.js +1 -0
  59. package/dist/Resizable/useResizable.d.ts +89 -8
  60. package/dist/Resizable/useResizable.d.ts.map +1 -1
  61. package/dist/Resizable/useResizable.js +521 -38
  62. package/dist/Resizable/utils.d.ts +35 -0
  63. package/dist/Resizable/utils.d.ts.map +1 -0
  64. package/dist/Resizable/utils.js +27 -0
  65. package/dist/Selector/Selector.d.ts +10 -0
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +105 -57
  68. package/dist/Selector/useSelectorPresentation.d.ts +1 -0
  69. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  70. package/dist/Selector/useSelectorPresentation.js +17 -2
  71. package/dist/Slider/Slider.d.ts.map +1 -1
  72. package/dist/Slider/Slider.js +3 -5
  73. package/dist/Spinner/Spinner.d.ts.map +1 -1
  74. package/dist/Spinner/Spinner.js +9 -10
  75. package/dist/Stepper/Step.d.ts +2 -1
  76. package/dist/Stepper/Step.d.ts.map +1 -1
  77. package/dist/Stepper/Step.js +75 -11
  78. package/dist/Stepper/Stepper.d.ts.map +1 -1
  79. package/dist/Stepper/Stepper.js +1 -0
  80. package/dist/TabList/TabList.d.ts.map +1 -1
  81. package/dist/TabList/TabList.js +2 -3
  82. package/dist/Table/index.d.ts +16 -1
  83. package/dist/Table/index.d.ts.map +1 -1
  84. package/dist/Table/index.js +4 -0
  85. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  86. package/dist/Table/plugins/columnResize/useTableColumnResize.js +8 -9
  87. package/dist/Table/plugins/rowStatus/index.d.ts +1 -1
  88. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -1
  89. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +11 -30
  90. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  91. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +108 -62
  92. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  93. package/dist/Table/plugins/sortable/useTableSortable.js +9 -1
  94. package/dist/Text/useTruncation.d.ts +1 -1
  95. package/dist/Text/useTruncation.d.ts.map +1 -1
  96. package/dist/Text/useTruncation.js +8 -7
  97. package/dist/TimeInput/TimeInput.d.ts +16 -1
  98. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  99. package/dist/TimeInput/TimeInput.js +98 -60
  100. package/dist/TimeInput/index.d.ts +1 -1
  101. package/dist/TimeInput/index.d.ts.map +1 -1
  102. package/dist/Timestamp/Timestamp.d.ts +2 -2
  103. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  104. package/dist/Timestamp/Timestamp.js +4 -145
  105. package/dist/Timestamp/formatRelativeTime.d.ts +20 -0
  106. package/dist/Timestamp/formatRelativeTime.d.ts.map +1 -0
  107. package/dist/Timestamp/formatRelativeTime.js +81 -0
  108. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  109. package/dist/Toast/ToastViewport.js +125 -77
  110. package/dist/Tokenizer/Tokenizer.d.ts +0 -38
  111. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  112. package/dist/Tokenizer/Tokenizer.js +94 -26
  113. package/dist/Tokenizer/useEndLaneReserve.d.ts +31 -0
  114. package/dist/Tokenizer/useEndLaneReserve.d.ts.map +1 -0
  115. package/dist/Tokenizer/useEndLaneReserve.js +136 -0
  116. package/dist/Tooltip/useTooltip.d.ts +1 -1
  117. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  118. package/dist/Tooltip/useTooltip.js +53 -42
  119. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  120. package/dist/Typeahead/BaseTypeahead.js +85 -30
  121. package/dist/Typeahead/Typeahead.d.ts +0 -21
  122. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  123. package/dist/Typeahead/Typeahead.js +108 -67
  124. package/dist/Typeahead/busyIndicatorLane.d.ts +62 -0
  125. package/dist/Typeahead/busyIndicatorLane.d.ts.map +1 -0
  126. package/dist/Typeahead/busyIndicatorLane.js +98 -0
  127. package/dist/astryx.css +25 -7
  128. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -1
  129. package/dist/hooks/useFocusReturnVisibility.js +3 -5
  130. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  131. package/dist/hooks/useIsomorphicLayoutEffect.d.ts.map +1 -1
  132. package/dist/hooks/useOverflow.js +11 -11
  133. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  134. package/dist/hooks/useScrollOverflow.js +7 -4
  135. package/dist/theme/defineTheme.d.ts +13 -0
  136. package/dist/theme/defineTheme.d.ts.map +1 -1
  137. package/dist/theme/defineTheme.js +7 -0
  138. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  139. package/dist/theme/generateThemeRules.js +1 -1
  140. package/dist/theme/localTokens.d.ts +18 -0
  141. package/dist/theme/localTokens.d.ts.map +1 -0
  142. package/dist/theme/localTokens.js +162 -0
  143. package/dist/utils/interactionModality.d.ts +6 -28
  144. package/dist/utils/interactionModality.d.ts.map +1 -1
  145. package/dist/utils/interactionModality.js +72 -37
  146. package/dist/utils/sharedResizeObserver.d.ts +17 -9
  147. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  148. package/dist/utils/sharedResizeObserver.js +54 -12
  149. package/locales/en.json +8 -0
  150. package/locales/pseudo.json +6 -0
  151. package/package.json +7 -2
  152. package/src/AlertDialog/useImperativeAlertDialog.test.tsx +163 -0
  153. package/src/AppShell/AppShell.doc.mjs +4 -3
  154. package/src/AppShell/AppShell.test.tsx +21 -0
  155. package/src/AppShell/AppShell.tsx +12 -7
  156. package/src/Banner/Banner.doc.mjs +5 -1
  157. package/src/Banner/Banner.tsx +12 -1
  158. package/src/BottomSheet/BottomSheet.doc.mjs +31 -0
  159. package/src/BottomSheet/BottomSheet.spec.md +178 -0
  160. package/src/Breadcrumbs/BreadcrumbItem.tsx +18 -9
  161. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +4 -1
  162. package/src/Breadcrumbs/Breadcrumbs.test.tsx +12 -0
  163. package/src/Button/Button.doc.mjs +56 -0
  164. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  165. package/src/Calendar/hooks/useCalendarConstraints.test.ts +153 -0
  166. package/src/Calendar/hooks/useCalendarDays.test.ts +265 -0
  167. package/src/Calendar/hooks/useCalendarNavigation.test.ts +252 -0
  168. package/src/Carousel/Carousel.test.tsx +83 -0
  169. package/src/Carousel/Carousel.tsx +8 -2
  170. package/src/Chat/ChatComposer.doc.mjs +7 -4
  171. package/src/Chat/ChatComposer.test.tsx +84 -2
  172. package/src/Chat/ChatComposer.tsx +45 -3
  173. package/src/Chat/ChatSendButton.test.tsx +189 -0
  174. package/src/Chat/ChatToolCalls.doc.mjs +1 -1
  175. package/src/Chat/ChatToolCalls.test.tsx +33 -1
  176. package/src/Chat/ChatToolCalls.tsx +13 -34
  177. package/src/Chat/useChatNewMessages.ts +3 -3
  178. package/src/Chat/useChatPasteAsToken.test.ts +191 -0
  179. package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
  180. package/src/CheckboxList/CheckboxList.spec.md +215 -0
  181. package/src/CheckboxList/CheckboxList.test.tsx +45 -0
  182. package/src/CheckboxList/CheckboxListItem.doc.mjs +11 -9
  183. package/src/CheckboxList/CheckboxListItem.tsx +5 -3
  184. package/src/Collapsible/useCollapsible.test.tsx +236 -0
  185. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  186. package/src/CommandPalette/CommandPalette.spec.md +224 -0
  187. package/src/CommandPalette/CommandPaletteEmpty.test.tsx +101 -0
  188. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  189. package/src/ComplexSelector/ComplexSelector.spec.md +190 -0
  190. package/src/ContextMenu/ContextMenu.doc.mjs +48 -0
  191. package/src/ContextMenu/ContextMenu.spec.md +207 -0
  192. package/src/DateTimeInput/DateTimeInput.doc.mjs +43 -23
  193. package/src/DateTimeInput/DateTimeInput.tsx +388 -181
  194. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +10 -2
  195. package/src/DateTimeInput/NativeDateSegment.tsx +308 -0
  196. package/src/DateTimeInput/NativePickerSegments.test.tsx +626 -0
  197. package/src/DateTimeInput/NativeTimeSegment.tsx +331 -0
  198. package/src/DateTimeInput/index.ts +1 -0
  199. package/src/DateTimeInput/nativePickerSegmentStyles.ts +120 -0
  200. package/src/Divider/Divider.doc.mjs +24 -0
  201. package/src/Divider/Divider.spec.md +157 -0
  202. package/src/DropdownMenu/DropdownMenu.doc.mjs +104 -0
  203. package/src/DropdownMenu/DropdownMenu.spec.md +246 -0
  204. package/src/DropdownMenu/DropdownMenu.tsx +2 -4
  205. package/src/Field/Field.doc.mjs +1 -0
  206. package/src/Field/Field.test.tsx +39 -0
  207. package/src/Field/Field.tsx +18 -1
  208. package/src/Field/InputClearButton.test.tsx +68 -6
  209. package/src/Field/PanelSearchInput.tsx +3 -5
  210. package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
  211. package/src/FieldStatus/FieldStatus.spec.md +213 -0
  212. package/src/FieldStatus/FieldStatus.test.tsx +17 -0
  213. package/src/FieldStatus/FieldStatus.tsx +7 -2
  214. package/src/Grid/Grid.doc.mjs +19 -0
  215. package/src/Grid/Grid.spec.md +162 -0
  216. package/src/Icon/Icon.doc.mjs +196 -48
  217. package/src/Icon/Icon.spec.md +192 -0
  218. package/src/IconButton/IconButton.doc.mjs +38 -0
  219. package/src/Kbd/Kbd.doc.mjs +18 -0
  220. package/src/Kbd/Kbd.spec.md +142 -0
  221. package/src/Layer/useTouchTrigger.ts +2 -4
  222. package/src/Layout/Layout.doc.mjs +57 -17
  223. package/src/Layout/Layout.spec.md +235 -0
  224. package/src/Layout/Layout.test.tsx +1 -1
  225. package/src/Layout/Layout.tsx +7 -8
  226. package/src/Layout/LayoutHeader.doc.mjs +9 -0
  227. package/src/Layout/LayoutPanel.doc.mjs +4 -3
  228. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  229. package/src/Lightbox/Lightbox.spec.md +198 -0
  230. package/src/Lightbox/useLightbox.test.tsx +263 -0
  231. package/src/Markdown/Markdown.doc.mjs +60 -0
  232. package/src/Markdown/Markdown.spec.md +207 -0
  233. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  234. package/src/MobileNav/MobileNav.spec.md +209 -0
  235. package/src/MoreMenu/MoreMenu.doc.mjs +42 -0
  236. package/src/MoreMenu/MoreMenu.spec.md +222 -0
  237. package/src/MultiSelector/MultiSelector.doc.mjs +124 -1
  238. package/src/MultiSelector/MultiSelector.spec.md +293 -0
  239. package/src/MultiSelector/MultiSelector.test.tsx +305 -2
  240. package/src/MultiSelector/MultiSelector.tsx +124 -55
  241. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  242. package/src/NavIcon/NavIcon.spec.md +134 -0
  243. package/src/NavMenu/NavHeadingMenu.spec.md +210 -0
  244. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  245. package/src/Outline/Outline.doc.mjs +35 -0
  246. package/src/Outline/Outline.spec.md +162 -0
  247. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  248. package/src/OverflowList/OverflowList.spec.md +157 -0
  249. package/src/Pagination/Pagination.doc.mjs +74 -0
  250. package/src/Pagination/Pagination.spec.md +226 -0
  251. package/src/Popover/Popover.doc.mjs +264 -65
  252. package/src/Popover/Popover.spec.md +421 -0
  253. package/src/Popover/usePopover.doc.mjs +208 -33
  254. package/src/PowerSearch/PowerSearch.spec.md +291 -0
  255. package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +253 -0
  256. package/src/PowerSearch/PowerSearchToken.test.tsx +228 -0
  257. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  258. package/src/ProgressBar/ProgressBar.spec.md +261 -0
  259. package/src/RadioList/RadioList.test.tsx +136 -2
  260. package/src/RadioList/RadioListItem.doc.mjs +30 -11
  261. package/src/RadioList/RadioListItem.tsx +44 -8
  262. package/src/Resizable/Resizable.doc.mjs +130 -24
  263. package/src/Resizable/Resizable.spec.md +246 -0
  264. package/src/Resizable/ResizeHandle.test.tsx +177 -56
  265. package/src/Resizable/ResizeHandle.tsx +128 -45
  266. package/src/Resizable/index.ts +2 -0
  267. package/src/Resizable/shippedDirections.test.ts +462 -0
  268. package/src/Resizable/useResizable.doc.mjs +124 -29
  269. package/src/Resizable/useResizable.test.ts +1277 -3
  270. package/src/Resizable/useResizable.ts +760 -48
  271. package/src/Resizable/utils.test.ts +74 -0
  272. package/src/Resizable/utils.ts +35 -0
  273. package/src/Section/Section.doc.mjs +17 -0
  274. package/src/Section/Section.spec.md +139 -0
  275. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  276. package/src/SegmentedControl/SegmentedControl.spec.md +152 -0
  277. package/src/Selector/Selector.doc.mjs +133 -97
  278. package/src/Selector/Selector.spec.md +300 -0
  279. package/src/Selector/Selector.test.tsx +318 -1
  280. package/src/Selector/Selector.tsx +127 -60
  281. package/src/Selector/useSelectorPresentation.ts +24 -4
  282. package/src/SideNav/SideNav.test.tsx +3 -3
  283. package/src/SideNav/SideNavCollapseButton.doc.mjs +8 -0
  284. package/src/SideNav/SideNavItem.doc.mjs +1 -1
  285. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  286. package/src/Skeleton/Skeleton.spec.md +127 -0
  287. package/src/Slider/Slider.doc.mjs +184 -30
  288. package/src/Slider/Slider.spec.md +224 -0
  289. package/src/Slider/Slider.tsx +2 -5
  290. package/src/Spinner/Spinner.doc.mjs +5 -5
  291. package/src/Spinner/Spinner.test.tsx +32 -7
  292. package/src/Spinner/Spinner.tsx +46 -14
  293. package/src/Stack/Stack.doc.mjs +22 -0
  294. package/src/Stack/Stack.spec.md +155 -0
  295. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  296. package/src/StatusDot/StatusDot.spec.md +138 -0
  297. package/src/Stepper/Step.doc.mjs +1 -1
  298. package/src/Stepper/Step.tsx +118 -5
  299. package/src/Stepper/Stepper.doc.mjs +51 -1
  300. package/src/Stepper/Stepper.spec.md +245 -0
  301. package/src/Stepper/Stepper.test.tsx +315 -0
  302. package/src/Stepper/Stepper.tsx +8 -0
  303. package/src/Switch/Switch.doc.mjs +40 -0
  304. package/src/Switch/Switch.spec.md +175 -0
  305. package/src/TabList/TabList.tsx +2 -3
  306. package/src/Table/Table.doc.mjs +102 -24
  307. package/src/Table/Table.perf.test.tsx +51 -1
  308. package/src/Table/Table.spec.md +325 -0
  309. package/src/Table/TableHeaderCell.test.tsx +291 -0
  310. package/src/Table/index.ts +19 -4
  311. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -12
  312. package/src/Table/plugins/filtering/useTableFilterState.test.tsx +143 -0
  313. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +2 -1
  314. package/src/Table/plugins/rowStatus/index.ts +1 -4
  315. package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +92 -0
  316. package/src/Table/plugins/rowStatus/useTableRowStatus.spec.md +354 -0
  317. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +431 -56
  318. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +155 -84
  319. package/src/Table/plugins/sortable/useTableSortable.test.tsx +72 -0
  320. package/src/Table/plugins/sortable/useTableSortable.tsx +18 -1
  321. package/src/Table/useTableGroupedRows.doc.mjs +2 -2
  322. package/src/Table/useTableRowStatus.doc.mjs +5 -5
  323. package/src/Text/Text.doc.mjs +190 -43
  324. package/src/Text/Text.spec.md +206 -0
  325. package/src/Text/useTruncation.ts +8 -7
  326. package/src/TextArea/TextArea.doc.mjs +255 -45
  327. package/src/TextArea/TextArea.spec.md +205 -0
  328. package/src/TimeInput/NativeTimeInput.test.tsx +269 -0
  329. package/src/TimeInput/TimeInput.doc.mjs +47 -23
  330. package/src/TimeInput/TimeInput.tsx +109 -50
  331. package/src/TimeInput/index.ts +1 -0
  332. package/src/Timestamp/Timestamp.doc.mjs +2 -2
  333. package/src/Timestamp/Timestamp.test.tsx +75 -3
  334. package/src/Timestamp/Timestamp.tsx +7 -153
  335. package/src/Timestamp/formatRelativeTime.ts +96 -0
  336. package/src/Toast/ToastViewport.test.tsx +194 -0
  337. package/src/Toast/ToastViewport.tsx +136 -58
  338. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  339. package/src/Tokenizer/Tokenizer.doc.mjs +2 -0
  340. package/src/Tokenizer/Tokenizer.spec.md +293 -0
  341. package/src/Tokenizer/Tokenizer.test.tsx +335 -1
  342. package/src/Tokenizer/Tokenizer.tsx +129 -46
  343. package/src/Tokenizer/useEndLaneReserve.ts +146 -0
  344. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  345. package/src/Toolbar/Toolbar.spec.md +174 -0
  346. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  347. package/src/Tooltip/Tooltip.spec.md +142 -0
  348. package/src/Tooltip/Tooltip.test.tsx +21 -1
  349. package/src/Tooltip/useTooltip.test.tsx +318 -0
  350. package/src/Tooltip/useTooltip.tsx +60 -39
  351. package/src/TreeList/TreeList.doc.mjs +58 -0
  352. package/src/TreeList/TreeList.spec.md +224 -0
  353. package/src/Typeahead/BaseTypeahead.tsx +96 -25
  354. package/src/Typeahead/Typeahead.doc.mjs +89 -1
  355. package/src/Typeahead/Typeahead.spec.md +257 -0
  356. package/src/Typeahead/Typeahead.test.tsx +502 -1
  357. package/src/Typeahead/Typeahead.tsx +187 -69
  358. package/src/Typeahead/TypeaheadItem.test.tsx +93 -2
  359. package/src/Typeahead/busyIndicatorLane.tsx +109 -0
  360. package/src/__tests__/stylexDeclarations.ts +51 -0
  361. package/src/hooks/useFocusReturnVisibility.ts +3 -5
  362. package/src/hooks/useOverflow.ts +11 -11
  363. package/src/hooks/useScrollLock.test.ts +103 -0
  364. package/src/hooks/useScrollOverflow.ts +7 -4
  365. package/src/i18n/useTranslator.test.tsx +197 -0
  366. package/src/theme/defineTheme.test.ts +227 -0
  367. package/src/theme/defineTheme.ts +30 -0
  368. package/src/theme/derivedVarRegistry.test.ts +6 -0
  369. package/src/theme/generateThemeRules.ts +4 -1
  370. package/src/theme/localTokens.ts +272 -0
  371. package/src/theme/themingTargets.test.ts +126 -28
  372. package/src/utils/interactionModality.test.ts +83 -0
  373. package/src/utils/interactionModality.ts +88 -36
  374. package/src/utils/sharedResizeObserver.test.ts +124 -5
  375. package/src/utils/sharedResizeObserver.ts +60 -14
@@ -4,17 +4,29 @@
4
4
 
5
5
  /**
6
6
  * @file useResizable.ts
7
- * @input Resize configuration (defaultSize, minSizePx, maxSizePx, snaps) and
8
- * collapse configuration (collapsible, defaultIsCollapsed, isCollapsed)
7
+ * @input Resize configuration (defaultSize, minSize, maxSize, legacy pixel
8
+ * aliases, snaps) and collapse configuration (collapsible,
9
+ * defaultIsCollapsed, isCollapsed)
9
10
  * @output Hook return: size, isCollapsed, collapse/expand/resize methods, props for handle
10
11
  * @position Public hook; consumed by layout components via `resizable` prop
11
12
  */
12
- import { useCallback, useEffect, useRef, useState } from 'react';
13
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
14
+ import { observeResize } from "../utils/sharedResizeObserver.js";
15
+ import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
16
+ import { devWarn } from "../utils/devWarning.js";
13
17
 
14
18
  // =============================================================================
15
19
  // Types
16
20
  // =============================================================================
17
21
 
22
+ /**
23
+ * The minimum, in one of two spellings. Exactly one may be supplied: the union
24
+ * makes passing both a type error, so a migration cannot leave a stale
25
+ * deprecated value shadowing the new one (AST-010 API4).
26
+ */
27
+
28
+ /** The maximum, in one of two spellings. See {@link ResizableMinConfig}. */
29
+
18
30
  /**
19
31
  * Shared config shape for any component that integrates built-in resize
20
32
  * (e.g. SideNav `resizable` prop). Provides a simplified API surface
@@ -111,21 +123,217 @@ function persistState(key, state) {
111
123
  /* ignore */
112
124
  }
113
125
  }
114
- function resolveDefaultSize(defaultSize) {
115
- if (defaultSize == null) {
116
- return 250;
117
- }
118
- if (typeof defaultSize === 'number') {
119
- return defaultSize;
120
- }
121
- if (defaultSize.endsWith('%')) {
122
- const pct = parseFloat(defaultSize);
123
- if (!isNaN(pct)) {
124
- const approx = typeof window !== 'undefined' ? window.innerWidth : 1200;
125
- return Math.round(pct / 100 * approx);
126
+
127
+ /** The released compatibility basis when there is no window to measure. */
128
+ const SERVER_BASIS = 1200;
129
+ const DEFAULT_SIZE_FALLBACK = 250;
130
+ const RESIZABLE_SIZE_GUIDANCE = 'Use a non-negative number of pixels, an exact "Npx" string, an exact ' + '"N%" string from 0% to 100%, pixel(value), or ' + 'percent(value, {min: pixel(value)}) / percent(value, {max: pixel(value)}).';
131
+ const LEGACY_PIXEL_GUIDANCE = 'Use a non-negative number of pixels.';
132
+ const LEGACY_MAX_PIXEL_GUIDANCE = 'Use a non-negative number of pixels or explicit Infinity.';
133
+
134
+ /** Parsed, validated input. */
135
+
136
+ const PX_PATTERN = /^(\d+(?:\.\d+)?)px$/;
137
+ const PERCENT_PATTERN = /^(\d+(?:\.\d+)?)%$/;
138
+ function parseSizeString(value) {
139
+ // Preserve the released atomic fast path exactly.
140
+ const px = PX_PATTERN.exec(value);
141
+ if (px) {
142
+ const parsed = Number(px[1]);
143
+ return Number.isFinite(parsed) ? {
144
+ kind: 'px',
145
+ value: parsed
146
+ } : null;
147
+ }
148
+ const percentValue = PERCENT_PATTERN.exec(value);
149
+ if (percentValue) {
150
+ const parsed = Number(percentValue[1]);
151
+ return Number.isFinite(parsed) && parsed <= 100 ? {
152
+ kind: 'percent',
153
+ value: parsed
154
+ } : null;
155
+ }
156
+ return null;
157
+ }
158
+ function parsePixelSize(value) {
159
+ if (typeof value !== 'object' || value == null || Array.isArray(value)) {
160
+ return null;
161
+ }
162
+ const candidate = value;
163
+ return candidate.type === 'pixel' && typeof candidate.value === 'number' && Number.isFinite(candidate.value) && candidate.value >= 0 ? candidate.value : null;
164
+ }
165
+ function parsePercentSize(value) {
166
+ if (Array.isArray(value)) {
167
+ return null;
168
+ }
169
+ const candidate = value;
170
+ if (candidate.type !== 'percent' || typeof candidate.value !== 'number' || !Number.isFinite(candidate.value) || candidate.value < 0 || candidate.value > 100) {
171
+ return null;
172
+ }
173
+ const hasMin = candidate.min !== undefined;
174
+ const hasMax = candidate.max !== undefined;
175
+ // Exactly one bound is required. The helper's public type enforces this for
176
+ // typed callers; this branch preserves it for JavaScript and `any`.
177
+ if (hasMin === hasMax) {
178
+ return null;
179
+ }
180
+ if (hasMin) {
181
+ const min = parsePixelSize(candidate.min);
182
+ return min == null ? null : {
183
+ kind: 'percent',
184
+ value: candidate.value,
185
+ min
186
+ };
187
+ }
188
+ const max = parsePixelSize(candidate.max);
189
+ return max == null ? null : {
190
+ kind: 'percent',
191
+ value: candidate.value,
192
+ max
193
+ };
194
+ }
195
+ function parseSize(value) {
196
+ if (typeof value === 'number') {
197
+ return Number.isFinite(value) && value >= 0 ? {
198
+ kind: 'px',
199
+ value
200
+ } : null;
201
+ }
202
+ if (typeof value === 'string') {
203
+ return parseSizeString(value);
204
+ }
205
+ if (typeof value === 'object' && value != null) {
206
+ const pixelValue = parsePixelSize(value);
207
+ return pixelValue == null ? parsePercentSize(value) : {
208
+ kind: 'px',
209
+ value: pixelValue
210
+ };
211
+ }
212
+ return null;
213
+ }
214
+ function parseLegacyPixel(value, allowInfinity) {
215
+ if (allowInfinity && value === Infinity) {
216
+ return {
217
+ kind: 'px',
218
+ value
219
+ };
220
+ }
221
+ if (typeof value === 'number') {
222
+ return Number.isFinite(value) && value >= 0 ? {
223
+ kind: 'px',
224
+ value
225
+ } : null;
226
+ }
227
+ // Preserve the released runtime compatibility for untyped callers. The
228
+ // public aliases remain number-only, but exact atomic strings worked before
229
+ // they were deprecated and continue to resolve through the same fast path.
230
+ return typeof value === 'string' ? parseSizeString(value) : null;
231
+ }
232
+
233
+ /** Whether this parsed value must be recomputed when its basis changes. */
234
+ function dependsOnBasis(parsed) {
235
+ return parsed?.kind === 'percent';
236
+ }
237
+
238
+ /** Resolve a parsed size against its basis, then apply its one pixel bound. */
239
+ function toPixels(parsed, basis) {
240
+ if (parsed.kind === 'px') {
241
+ return parsed.value;
242
+ }
243
+ if (basis == null) {
244
+ return null;
245
+ }
246
+ const percentagePx = Math.round(parsed.value / 100 * basis);
247
+ if (parsed.min !== undefined) {
248
+ return Math.max(percentagePx, parsed.min);
249
+ }
250
+ if (parsed.max !== undefined) {
251
+ return Math.min(percentagePx, parsed.max);
252
+ }
253
+ return percentagePx;
254
+ }
255
+ function describeUnknown(value) {
256
+ if (typeof value === 'number' && !Number.isFinite(value)) {
257
+ return String(value);
258
+ }
259
+ try {
260
+ return JSON.stringify(value) ?? String(value);
261
+ } catch {
262
+ return String(value);
263
+ }
264
+ }
265
+ function describePixelCandidate(value) {
266
+ if (typeof value !== 'object' || value == null || Array.isArray(value)) {
267
+ return describeUnknown(value);
268
+ }
269
+ const candidate = value;
270
+ return candidate.type === 'pixel' ? `{"type":"pixel","value":${describeUnknown(candidate.value)}}` : describeUnknown(value);
271
+ }
272
+ function describeSize(value) {
273
+ if (typeof value === 'object' && value != null && !Array.isArray(value)) {
274
+ const candidate = value;
275
+ if (candidate.type === 'pixel') {
276
+ return describePixelCandidate(value);
277
+ }
278
+ if (candidate.type === 'percent') {
279
+ const fields = ['"type":"percent"', `"value":${describeUnknown(candidate.value)}`];
280
+ if (candidate.min !== undefined) {
281
+ fields.push(`"min":${describePixelCandidate(candidate.min)}`);
282
+ }
283
+ if (candidate.max !== undefined) {
284
+ fields.push(`"max":${describePixelCandidate(candidate.max)}`);
285
+ }
286
+ return `{${fields.join(',')}}`;
126
287
  }
127
288
  }
128
- return 250;
289
+ return describeUnknown(value);
290
+ }
291
+
292
+ /**
293
+ * Resolves one configured value, applying its role's fallback when the input is
294
+ * not accepted. The warning remains development-only through `devWarn`; every
295
+ * build takes the same deterministic fallback.
296
+ */
297
+ function resolveBound(raw, parsed, basis, fallback, label, accepted, didWarnRef) {
298
+ if (raw === undefined) {
299
+ return fallback;
300
+ }
301
+ // Preserve the released explicit unbounded maximum on both the unified and
302
+ // deprecated spellings. Other roles still reject non-finite numbers.
303
+ if (raw === Infinity && fallback === Infinity) {
304
+ return Infinity;
305
+ }
306
+ if (parsed == null) {
307
+ if (!didWarnRef.current) {
308
+ didWarnRef.current = true;
309
+ devWarn('useResizable', `${label}: ${describeSize(raw)} is not a size. ${accepted} ` + `Falling back to ${fallback}.`);
310
+ }
311
+ return fallback;
312
+ }
313
+ return toPixels(parsed, basis) ?? fallback;
314
+ }
315
+
316
+ /**
317
+ * The container's content-box size on the active axis.
318
+ *
319
+ * Content box, not border box: a bordered or padded container would otherwise
320
+ * report a basis a few pixels larger than the space a percentage is actually a
321
+ * share of, and that error lands in `aria-valuemax`. The observer entry
322
+ * carries `contentBoxSize` natively, which is already writing-mode aware;
323
+ * measuring the element is the fallback for the synthetic entry
324
+ * `observeResize` fires on registration.
325
+ */
326
+ function measureContentBox(element, direction, entry) {
327
+ const box = entry?.contentBoxSize?.[0];
328
+ if (box) {
329
+ return direction === 'vertical' ? box.blockSize : box.inlineSize;
330
+ }
331
+ const style = typeof window === 'undefined' ? null : window.getComputedStyle(element);
332
+ if (style == null) {
333
+ return null;
334
+ }
335
+ const size = direction === 'vertical' ? element.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom) : element.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
336
+ return Number.isFinite(size) ? size : null;
129
337
  }
130
338
 
131
339
  // =============================================================================
@@ -135,8 +343,12 @@ function resolveDefaultSize(defaultSize) {
135
343
  function useSingleResizable(config) {
136
344
  const {
137
345
  defaultSize,
138
- minSizePx = DEFAULT_MIN,
139
- maxSizePx = Infinity,
346
+ minSize,
347
+ maxSize,
348
+ minSizePx,
349
+ maxSizePx,
350
+ containerRef,
351
+ direction = 'horizontal',
140
352
  collapsible = false,
141
353
  collapsedSize = DEFAULT_COLLAPSED_SIZE,
142
354
  snaps: configuredSnaps,
@@ -148,14 +360,226 @@ function useSingleResizable(config) {
148
360
  } = config;
149
361
  const emptySnapsRef = useRef([]);
150
362
  const snaps = configuredSnaps ?? emptySnapsRef.current;
151
- const resolvedDefault = resolveDefaultSize(defaultSize);
363
+
364
+ // The unified prop wins over its deprecated alias. The types make supplying
365
+ // both an error, so reaching here means untyped JavaScript, an `any`, or a
366
+ // spread — exactly the case where a stale alias hidden in an object would
367
+ // otherwise silently beat the explicit migration target.
368
+ const hasUnifiedMin = minSize !== undefined;
369
+ const hasUnifiedMax = maxSize !== undefined;
370
+ const minConflict = hasUnifiedMin && minSizePx !== undefined;
371
+ const maxConflict = hasUnifiedMax && maxSizePx !== undefined;
372
+ const rawMin = hasUnifiedMin ? minSize : minSizePx;
373
+ const rawMax = hasUnifiedMax ? maxSize : maxSizePx;
374
+ const parsedDefault = parseSize(defaultSize);
375
+ const parsedMin = hasUnifiedMin ? parseSize(minSize) : parseLegacyPixel(minSizePx, false);
376
+ const parsedMax = hasUnifiedMax ? parseSize(maxSize) : parseLegacyPixel(maxSizePx, true);
377
+ const hasContainer = containerRef != null;
152
378
  const persisted = autoSaveId ? loadPersistedState(autoSaveId) : null;
153
- const initial = persisted?.size ?? resolvedDefault;
379
+ const initialDefaultRef = useRef(null);
380
+ const [, setDefaultBasisCleanupTick] = useState(0);
381
+ const defaultDependsOnBasis = dependsOnBasis(parsedDefault);
382
+ const boundsDependOnBasis = dependsOnBasis(parsedMin) || dependsOnBasis(parsedMax);
383
+ // A default needs a live container only until its initial pixel choice is
384
+ // final. A persisted pixel choice wins without measuring the unused default.
385
+ // Bounds are different: percentages keep following the basis so they can
386
+ // re-clamp the selected pixel size.
387
+ const defaultNeedsInitialBasis = persisted?.size == null && defaultDependsOnBasis && initialDefaultRef.current?.isFinal !== true;
388
+ const observeContainerBasis = hasContainer && (boundsDependOnBasis || defaultNeedsInitialBasis);
389
+ const observeViewportBasis = !hasContainer && boundsDependOnBasis;
390
+
391
+ // The container basis, read through the shared observer. `useSyncExternalStore`
392
+ // rather than an effect: the store IS the measurement, so there is no render
393
+ // that paints before the basis is known and no `setState` in an effect.
394
+ //
395
+ // The subscription depends on the ELEMENT, not on the ref object. A ref is
396
+ // stable across an element swap, so `containerRef.current` can become a
397
+ // different node — a remount behind the same ref, a conditional branch, a
398
+ // portal moving — without any input to this hook changing. Keyed on the ref,
399
+ // the old detached node stayed observed (and a detached node measures 0,
400
+ // which is how a replacement zeroed the bounds) and the new one was never
401
+ // observed at all.
402
+ const containerElementRef = useRef(null);
403
+ // DOM ref changes are invisible to React. This state is only a version tick
404
+ // while basis observation is active; the element itself lives in a ref so it
405
+ // can be released without adding a cleanup render.
406
+ const [, setContainerElementVersion] = useState(0);
407
+ const containerBasisRef = useRef(null);
408
+ const containerElement = observeContainerBasis ? containerElementRef.current : null;
409
+
410
+ // Follow the ref only while some configuration needs its basis. A numeric-only
411
+ // region never reads the ref. Once observation stops, drop both the element
412
+ // and its measurement without a state update; a later percentage starts from
413
+ // the ref's then-current element and a fresh basis.
414
+ useIsomorphicLayoutEffect(() => {
415
+ if (!observeContainerBasis) {
416
+ containerElementRef.current = null;
417
+ containerBasisRef.current = null;
418
+ return;
419
+ }
420
+ const element = containerRef?.current ?? null;
421
+ if (element !== containerElementRef.current) {
422
+ containerBasisRef.current = null;
423
+ containerElementRef.current = element;
424
+ setContainerElementVersion(version => version + 1);
425
+ }
426
+ });
427
+ const subscribeToContainer = useCallback(onStoreChange => {
428
+ if (!observeContainerBasis || containerElement == null || containerElement !== containerRef?.current) {
429
+ return () => {};
430
+ }
431
+ // Unsubscribe by callback: several regions share one container, and any
432
+ // number of unrelated hooks may observe the same node.
433
+ return observeResize(containerElement, entry => {
434
+ containerBasisRef.current = measureContentBox(containerElement, direction, entry);
435
+ onStoreChange();
436
+ });
437
+ }, [containerElement, observeContainerBasis, direction, containerRef]);
438
+ const readContainerBasis = useCallback(() => {
439
+ if (!observeContainerBasis || containerElement == null) {
440
+ // An inactive subscription owns no current measurement. Clearing this is
441
+ // important when a percentage bound is added later: the first active
442
+ // render must measure the container as it is now, not clamp against the
443
+ // last basis seen by a default-only subscription.
444
+ containerBasisRef.current = null;
445
+ return null;
446
+ }
447
+ // Cached in a ref so the snapshot is referentially stable between resizes;
448
+ // returning a fresh measurement every call would loop the store.
449
+ if (containerBasisRef.current == null) {
450
+ containerBasisRef.current = measureContentBox(containerElement, direction);
451
+ }
452
+ return containerBasisRef.current;
453
+ }, [containerElement, observeContainerBasis, direction]);
454
+ const containerBasis = useSyncExternalStore(subscribeToContainer, readContainerBasis, () => null);
455
+
456
+ // The viewport basis is always readable synchronously. Percentage-dependent
457
+ // BOUNDS subscribe to later resizes (FR6); a default reads the initial
458
+ // snapshot only, because it becomes a pixel choice immediately.
459
+ const subscribeToViewport = useCallback(onStoreChange => {
460
+ if (hasContainer || !observeViewportBasis || typeof window === 'undefined') {
461
+ return () => {};
462
+ }
463
+ window.addEventListener('resize', onStoreChange);
464
+ return () => window.removeEventListener('resize', onStoreChange);
465
+ }, [hasContainer, observeViewportBasis]);
466
+ const viewportBasis = useSyncExternalStore(subscribeToViewport, () => typeof window === 'undefined' ? SERVER_BASIS : window.innerWidth, () => SERVER_BASIS);
467
+
468
+ // With a container, its content box is the basis; without one, the released
469
+ // viewport basis. Before a supplied container has been measured — the first
470
+ // render, the server, and any moment it is display:none or detached — the
471
+ // deterministic 1200px stands in, and the first real measurement replaces it.
472
+ //
473
+ // A zero measurement is not a measurement. A hidden or detached container
474
+ // reports 0, and resolving `'50%'` against it produced a real maximum of 0:
475
+ // the panel collapsed and, with `autoSaveId`, that 0 was written over a
476
+ // perfectly good saved width. Treating 0 as unmeasured keeps the temporary
477
+ // basis until the container is actually laid out (AST-010 Platform support).
478
+ const needsContainerBasis = observeContainerBasis;
479
+ const hasPositiveMeasurement = containerBasis != null && containerBasis > 0;
480
+ const liveBasis = hasContainer ? hasPositiveMeasurement ? containerBasis : SERVER_BASIS : viewportBasis;
481
+ const isBasisMeasured = needsContainerBasis ? hasPositiveMeasurement : true;
154
482
 
155
- // `size` is always the *expanded* size: collapse hides it behind the
156
- // isCollapsed gate below rather than zeroing it, so the width the user had
157
- // survives a collapse, a persist round-trip, and an expand.
158
- const [size, setSize] = useState(() => clampSize(initial, minSizePx, maxSizePx, snaps));
483
+ // One stable basis per gesture. A container that resizes mid-drag would
484
+ // otherwise move the bound under the pointer; the new basis applies once the
485
+ // gesture ends.
486
+ const gestureBasisRef = useRef(null);
487
+ // Only forces the render that releases the frozen basis when a gesture ends.
488
+ const [gestureTick, setGestureTick] = useState(0);
489
+ void gestureTick;
490
+ const basis = gestureBasisRef.current ?? liveBasis;
491
+ const didWarnMinRef = useRef(false);
492
+ const didWarnMaxRef = useRef(false);
493
+ const didWarnDefaultRef = useRef(false);
494
+ const didWarnInvertedRef = useRef(false);
495
+ const didWarnMinAliasRef = useRef(false);
496
+ const didWarnMaxAliasRef = useRef(false);
497
+
498
+ // Bounds re-resolve from the live basis on every render — that is the whole
499
+ // point of a percentage bound — and clamp the pixel selection below.
500
+ const resolvedMin = resolveBound(rawMin, parsedMin, basis, DEFAULT_MIN, hasUnifiedMin ? 'minSize' : 'minSizePx', hasUnifiedMin ? RESIZABLE_SIZE_GUIDANCE : LEGACY_PIXEL_GUIDANCE, didWarnMinRef);
501
+ const resolvedMax = resolveBound(rawMax, parsedMax, basis, Infinity, hasUnifiedMax ? 'maxSize' : 'maxSizePx', hasUnifiedMax ? RESIZABLE_SIZE_GUIDANCE : LEGACY_MAX_PIXEL_GUIDANCE, didWarnMaxRef);
502
+
503
+ // A basis-dependent default is resolved ONCE into a pixel size. Latched in a
504
+ // ref (React's sanctioned lazy-init shape) rather than recomputed, because a
505
+ // default that kept tracking its basis would be a second, responsive sizing
506
+ // mode — the thing this API deliberately does not have.
507
+ //
508
+ // Not final while a supplied container is still unmeasured: that render used
509
+ // the temporary 1200px stand-in, and the first real measurement replaces it.
510
+ // Once final, a default-only subscription is removed. The correction is not a
511
+ // user interaction, so it neither persists the placeholder nor fires
512
+ // `onSizeChange`.
513
+ if (initialDefaultRef.current == null || !initialDefaultRef.current.isFinal) {
514
+ initialDefaultRef.current = {
515
+ px: resolveBound(defaultSize, parsedDefault, basis, DEFAULT_SIZE_FALLBACK, 'defaultSize', RESIZABLE_SIZE_GUIDANCE, didWarnDefaultRef),
516
+ isFinal: isBasisMeasured
517
+ };
518
+ }
519
+ const initialDefaultPx = initialDefaultRef.current.px;
520
+
521
+ // The size the user has settled on, or null while a container-relative
522
+ // default is still waiting for its first positive measurement. Once that
523
+ // basis is real, the default becomes an ordinary pixel selection — including
524
+ // any clamp applied at initialization. Leaving it represented by `null`
525
+ // allowed the raw default to be re-read later: 321px first clamped to 200px
526
+ // in a 400px container, then revived to 321px when the container grew.
527
+ //
528
+ // Initialized outright whenever the basis is already real on the first
529
+ // render — every pixel-only configuration, and the no-container percentage
530
+ // path. Only a supplied container needs a measurement that does not exist
531
+ // until after commit, so only it starts null. Initializing lazily in all
532
+ // cases cost every legacy pixel-only mount a second render pass, because the
533
+ // adjustment below then had to run on the first render to fill the value in.
534
+ const [chosenSize, setChosenSize] = useState(() => {
535
+ if (persisted?.size != null) {
536
+ return persisted.size;
537
+ }
538
+ return isBasisMeasured ? clampSize(initialDefaultPx, resolvedMin, resolvedMax, snaps) : null;
539
+ });
540
+ if (hasContainer && !boundsDependOnBasis && isBasisMeasured && defaultNeedsInitialBasis && chosenSize != null) {
541
+ // A programmatic resize may have selected pixels while the container was
542
+ // still unmeasured, so chosenSize can already be non-null here. Force the
543
+ // render that re-evaluates and removes the now-finished default subscription.
544
+ setDefaultBasisCleanupTick(tick => tick + 1);
545
+ }
546
+ if (isBasisMeasured && chosenSize == null) {
547
+ // The supplied container has now been measured, so the selection latched
548
+ // against the temporary basis is replaced by one against the real one.
549
+ // Render-phase adjustment is deliberate: React re-runs this render before
550
+ // paint, so the corrected selection, paint, persistence, and ARIA all see
551
+ // the same pixel value. It fires no interaction callback.
552
+ setChosenSize(clampSize(initialDefaultPx, resolvedMin, resolvedMax, snaps));
553
+ }
554
+
555
+ // A re-resolved bound clamps the SELECTION, not just the paint. Deriving
556
+ // alone was not enough: the stored choice survived, so a container that
557
+ // shrank and grew again revived the pre-clamp size — measured 320px
558
+ // reviving to 560px. The clamp has to be committed to be permanent.
559
+ //
560
+ // Adjusting state during render (React's documented shape for "a prop
561
+ // changed, so state must follow") rather than in an effect: React re-runs
562
+ // this render before painting, so nothing shows at the stale size, and no
563
+ // interaction callback fires for what is only a layout correction (FR10).
564
+ //
565
+ // Held until the basis is final. Before a supplied container is measured the
566
+ // bounds come from the temporary 1200px stand-in, and committing against
567
+ // that would move a persisted size to fit a basis that was never real.
568
+ const [committedBounds, setCommittedBounds] = useState({
569
+ min: resolvedMin,
570
+ max: resolvedMax
571
+ });
572
+ if (isBasisMeasured && gestureBasisRef.current == null && (committedBounds.min !== resolvedMin || committedBounds.max !== resolvedMax)) {
573
+ setCommittedBounds({
574
+ min: resolvedMin,
575
+ max: resolvedMax
576
+ });
577
+ setChosenSize(current => current == null ? null : clampSize(current, resolvedMin, resolvedMax, snaps));
578
+ }
579
+
580
+ // Derived for paint, so the clamp above and the rendered size agree on the
581
+ // very render that observes a new basis.
582
+ const size = clampSize(chosenSize ?? initialDefaultPx, resolvedMin, resolvedMax, snaps);
159
583
  const [uncontrolledIsCollapsed, setUncontrolledIsCollapsed] = useState(() => persisted?.isCollapsed ?? defaultIsCollapsed ?? false);
160
584
  const isControlled = controlledIsCollapsed !== undefined;
161
585
  const isCollapsed = collapsible && (isControlled ? controlledIsCollapsed : uncontrolledIsCollapsed);
@@ -177,14 +601,37 @@ function useSingleResizable(config) {
177
601
  setUncontrolledIsCollapsed(value);
178
602
  }
179
603
  }, [isControlled]);
604
+
605
+ // Configuration warnings live in an effect, not in render: render runs twice
606
+ // under StrictMode and must stay free of side effects.
180
607
  useEffect(() => {
181
- if (autoSaveId) {
608
+ if (minConflict && !didWarnMinAliasRef.current) {
609
+ didWarnMinAliasRef.current = true;
610
+ devWarn('useResizable', 'both `minSize` and `minSizePx` were supplied. `minSize` wins; the ' + 'deprecated `minSizePx` is ignored.');
611
+ }
612
+ if (maxConflict && !didWarnMaxAliasRef.current) {
613
+ didWarnMaxAliasRef.current = true;
614
+ devWarn('useResizable', 'both `maxSize` and `maxSizePx` were supplied. `maxSize` wins; the ' + 'deprecated `maxSizePx` is ignored.');
615
+ }
616
+ }, [minConflict, maxConflict]);
617
+ useEffect(() => {
618
+ if (resolvedMin > resolvedMax && !didWarnInvertedRef.current) {
619
+ didWarnInvertedRef.current = true;
620
+ devWarn('useResizable', `the resolved minimum (${resolvedMin}px) is above the resolved ` + `maximum (${resolvedMax}px). The maximum wins, as it always has.`);
621
+ }
622
+ }, [resolvedMin, resolvedMax]);
623
+ useEffect(() => {
624
+ // Nothing is written while the basis is still the temporary stand-in. The
625
+ // size on such a render is a placeholder, and persisting it overwrites the
626
+ // real saved size with a value derived from a basis that was never real
627
+ // (AST-010 Platform support, FR9).
628
+ if (autoSaveId && isBasisMeasured) {
182
629
  persistState(autoSaveId, {
183
630
  size,
184
631
  isCollapsed
185
632
  });
186
633
  }
187
- }, [size, isCollapsed, autoSaveId]);
634
+ }, [size, isCollapsed, autoSaveId, isBasisMeasured]);
188
635
  const collapse = useCallback(() => {
189
636
  // The already-collapsed guard keeps a repeated call from re-notifying a
190
637
  // transition that already happened.
@@ -204,9 +651,16 @@ function useSingleResizable(config) {
204
651
  onSizeChange?.(size);
205
652
  }, [setCollapsed, size, onCollapseChange, onSizeChange]);
206
653
  const resize = useCallback(newSize => {
207
- const clamped = clampSize(newSize, minSizePx, maxSizePx, snaps);
654
+ // An invalid programmatic size repairs nothing — it must not replace a
655
+ // legal selection with NaN, which would then reach paint, storage and
656
+ // `aria-valuenow`.
657
+ if (!Number.isFinite(newSize) || newSize < 0) {
658
+ devWarn('useResizable', `resize(${String(newSize)}) is not a pixel size. Keeping the ` + 'current size. Percentages configure the hook; they are not a ' + 'programmatic input.');
659
+ return;
660
+ }
661
+ const clamped = clampSize(newSize, resolvedMin, resolvedMax, snaps);
208
662
  const wasCollapsed = isCollapsedRef.current;
209
- setSize(clamped);
663
+ setChosenSize(clamped);
210
664
  setCollapsed(false);
211
665
  // Resizing out of the collapsed state is an implicit expand — notify
212
666
  // like the drag path does when it crosses back over the threshold.
@@ -214,10 +668,13 @@ function useSingleResizable(config) {
214
668
  onCollapseChange?.(false);
215
669
  }
216
670
  onSizeChange?.(clamped);
217
- }, [minSizePx, maxSizePx, snaps, setCollapsed, onCollapseChange, onSizeChange]);
671
+ }, [resolvedMin, resolvedMax, snaps, setCollapsed, onCollapseChange, onSizeChange]);
218
672
  const onResizeStart = useCallback(() => {
673
+ // Freeze the basis for the whole gesture so a container resizing mid-drag
674
+ // cannot move the bound out from under the pointer.
675
+ gestureBasisRef.current = liveBasis;
219
676
  dragStartSizeRef.current = isCollapsedRef.current ? 0 : size;
220
- }, [size]);
677
+ }, [size, liveBasis]);
221
678
  const onResizeMove = useCallback(delta => {
222
679
  const raw = dragStartSizeRef.current + delta;
223
680
  if (collapsible && raw < collapsedSize) {
@@ -232,24 +689,43 @@ function useSingleResizable(config) {
232
689
  setCollapsed(false);
233
690
  onCollapseChange?.(false);
234
691
  }
235
- const clamped = clampSize(raw, minSizePx, maxSizePx, snaps);
236
- setSize(clamped);
692
+ const clamped = clampSize(raw, resolvedMin, resolvedMax, snaps);
693
+ setChosenSize(clamped);
237
694
  onSizeChange?.(clamped);
238
- }, [collapsible, collapsedSize, setCollapsed, minSizePx, maxSizePx, snaps, onSizeChange, onCollapseChange]);
239
- const onResizeEnd = useCallback(() => {
240
- // Sizes already committed during move
695
+ }, [collapsible, collapsedSize, setCollapsed, resolvedMin, resolvedMax, snaps, onSizeChange, onCollapseChange]);
696
+
697
+ // Releasing the frozen basis needs a render to apply whatever the basis
698
+ // became during the gesture.
699
+ const releaseGestureBasis = useCallback(() => {
700
+ if (gestureBasisRef.current != null) {
701
+ gestureBasisRef.current = null;
702
+ setGestureTick(tick => tick + 1);
703
+ }
241
704
  }, []);
705
+ const onResizeEnd = useCallback(() => {
706
+ // Sizes are already committed during move; only the basis is held.
707
+ releaseGestureBasis();
708
+ }, [releaseGestureBasis]);
709
+
710
+ // A gesture that is cancelled rather than completed — pointercancel, a lost
711
+ // capture, a handle unmounted mid-drag — is still over. It does not signal a
712
+ // resize end (that is #5297's contract, and the sizes were committed during
713
+ // move anyway), but the basis it froze has to come back, or every later
714
+ // percentage bound stays pinned to the container size at grab time.
715
+ const onResizeCancel = releaseGestureBasis;
242
716
  const props = {
243
717
  _size: isCollapsed ? 0 : size,
244
718
  _isCollapsed: isCollapsed,
245
719
  _onResizeStart: onResizeStart,
246
720
  _onResizeMove: onResizeMove,
247
721
  _onResizeEnd: onResizeEnd,
248
- _minSizePx: minSizePx,
249
- _maxSizePx: maxSizePx,
722
+ _onResizeCancel: onResizeCancel,
723
+ _minSizePx: resolvedMin,
724
+ _maxSizePx: resolvedMax,
250
725
  _snaps: snaps,
251
726
  _collapsedSize: collapsedSize,
252
727
  _collapsible: collapsible,
728
+ _direction: direction,
253
729
  _isResizableProps: true
254
730
  };
255
731
  return {
@@ -275,7 +751,9 @@ function useSingleResizable(config) {
275
751
  function useMultiResizable(config) {
276
752
  const {
277
753
  regions,
278
- autoSaveId
754
+ autoSaveId,
755
+ containerRef,
756
+ direction
279
757
  } = config;
280
758
 
281
759
  // Stable key order — callers must not change region keys between renders.
@@ -288,6 +766,11 @@ function useMultiResizable(config) {
288
766
  // eslint-disable-next-line @eslint-react/rules-of-hooks -- region count is stable (documented contract)
289
767
  useSingleResizable({
290
768
  ...regionConfig,
769
+ // One container and one axis for every region (AST-010 FR11). Regions
770
+ // stay independently pixel-selected; sharing a basis introduces no ratio
771
+ // between them and no 100% total.
772
+ containerRef,
773
+ direction,
291
774
  autoSaveId: autoSaveId ? `${autoSaveId}:${key}` : undefined
292
775
  }));
293
776
  const result = {};