@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
@@ -0,0 +1,246 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 3
4
+ kind: component
5
+ id: component:Resizable
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Resizable/ResizeHandle.test.tsx,
16
+ packages/core/src/Resizable/useResizable.test.ts,
17
+ packages/core/src/Resizable/shippedDirections.test.ts,
18
+ packages/core/src/Resizable/utils.test.ts,
19
+ packages/core/src/theme/themingTargets.test.ts,
20
+ scripts/check-knowledge.mjs,
21
+ ]
22
+ modules: []
23
+ families: []
24
+ design_specs: []
25
+ architecture:
26
+ [
27
+ architecture:component-theming-surface,
28
+ architecture:interaction-modality,
29
+ architecture:public-component-api,
30
+ ]
31
+ contributing: []
32
+ system_specs: [spec:AST-010]
33
+ ---
34
+
35
+ # Resizable component contract
36
+
37
+ ## Intent
38
+
39
+ Resizable combines the useResizable state and behavior hook with a focusable
40
+ ResizeHandle. The handle presents a default Grip pill over an enlarged invisible
41
+ Grab zone. AST-010 adds explicit pixel and percentage configuration while keeping
42
+ selected state, persistence, callbacks, paint, and ARIA in resolved pixels.
43
+
44
+ ## Compatibility and migration
45
+
46
+ - Released default preserved: `yes`; numeric and exact `Npx` stay pixels, exact
47
+ `N%` retains released one-time default behavior, and the broad `defaultSize`
48
+ string type remains source-compatible
49
+ - Compatibility class: additive `minSize` / `maxSize`, structured percentage helper,
50
+ and optional container basis; deprecated pixel aliases remain supported
51
+ - Controlled/uncontrolled behavior: unchanged
52
+ - Migration decision: CSS-like `min()` / `max()` strings are not supported. Use
53
+ `percent(value, {min: pixel(value)})` or
54
+ `percent(value, {max: pixel(value)})`.
55
+
56
+ Consumer migration instructions belong in consumer docs and release notes.
57
+
58
+ ## Ownership boundary
59
+
60
+ **Owns**
61
+
62
+ - useResizable size, collapse, snapping, persistence, and resize lifecycle.
63
+ - ResizeHandle's separator semantics, pointer and keyboard interaction, and
64
+ current `resize-handle` target.
65
+ - The default Grip pill and its current `resize-handle-pill` target, plus the
66
+ aligned invisible Grab zone.
67
+
68
+ **Does not own / non-goals**
69
+
70
+ - The structural region being resized — owned by LayoutPanel, SideNav, or another
71
+ caller.
72
+ - Layout-region topology, inset, divider, or scrolling rules.
73
+ - A public target for the Grab zone; none exists on current `main`.
74
+ - Fixing the missing runtime reflection for the documented Handle `direction`
75
+ theming state; that remains a separate theming follow-up.
76
+
77
+ ## Public concepts
78
+
79
+ `ResizableSize` is a Resizable-specific union: pixel number, exact `Npx`, exact
80
+ `N%`, `PixelWidth`, or `ResizablePercentSize`.
81
+ `pixel(value)` is Table's existing canonical structured pixel helper;
82
+ `percent(value, {min: pixel(value)})` creates a literal percentage with a pixel
83
+ floor; `percent(value, {max: pixel(value)})` creates one with a pixel ceiling.
84
+ The options argument is required and the two bounds are mutually exclusive.
85
+ `defaultSize` retains its released broad `SizeValue` type and adds the structured
86
+ `PixelWidth` and `ResizablePercentSize` values; runtime validation remains
87
+ authoritative for legacy strings.
88
+
89
+ Numbers and exact `Npx` remain pixels. A descriptor default resolves once; a
90
+ descriptor bound remains live. Consumer props, outputs, and examples are documented
91
+ in `Resizable.doc.mjs` and `useResizable.doc.mjs`.
92
+
93
+ ## Behavioral and layout contract
94
+
95
+ | ID | Candidate invariant | Basis | Draft review state |
96
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------- |
97
+ | FR1 | ResizeHandle renders one focusable Handle and one enlarged Grab zone; the default render also contains a Grip pill, while caller-provided children replace that pill. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
98
+ | FR2 | Handle carries `resize-handle`, Grip pill carries `resize-handle-pill`, and Grab zone carries no public target. | Current source and public docs | Verified current target inventory; no target change |
99
+ | FR3 | Pointer, keyboard, collapse, snap, persistence, reversal, and size behavior remain owned by ResizeHandle and useResizable rather than layout-regions collaborators. | Current source, docs, and tests | Verified current ownership; resize semantics remain unchanged |
100
+ | FR4 | Resizable metadata advertises `direction` as a Handle visual state, but current runtime calls `themeProps('resize-handle')` without reflecting that value, so direction selectors cannot match. | Current source and public docs | Verified audit gap; deliberately not fixed in this change |
101
+ | FR5 | `percent(value, {min: pixel(value)})` and `percent(value, {max: pixel(value)})` resolve against the AST-010 basis; a default selects pixels once, while a bound re-resolves and clamps selected pixels. Both/neither bounds are invalid. | AST-010, source, tests, Chromium | New structured sizing behavior; selected state remains pixels |
102
+ | FR6 | Deprecated pixel aliases remain source-compatible and mutually exclusive with unified bounds; untyped exact atomic strings keep their released runtime behavior, and untyped conflicts prefer the unified value and warn. | AST-010, source, tests | Additive migration with deterministic precedence |
103
+
104
+ ### Allowed variation
105
+
106
+ - **AV1 — Axis.** Handle may resize along the horizontal or vertical layout axis;
107
+ separator orientation and cursor follow the current implementation.
108
+ - **AV2 — Placement.** Handle may participate inline or overlay its parent, and
109
+ Grip pill may sit at start, end, center, or the current automatic side.
110
+ - **AV3 — Grip content.** Caller-provided children may replace Grip pill without
111
+ changing Handle or Grab zone ownership.
112
+ - **AV4 — Divider.** Handle may paint its current divider treatment; a composing
113
+ region remains responsible for avoiding a duplicate adjacent divider.
114
+ - **AV5 — Structured percentage bound.** A descriptor carries exactly one `min`
115
+ or `max`, whose value is a `PixelWidth`. Atomic percentages use the existing
116
+ exact `N%` string; arbitrary CSS expressions are outside this contract.
117
+
118
+ ### Representative states
119
+
120
+ | State | Required invariant | Allowed variation |
121
+ | ---------------------- | --------------------------------------------------------------------- | -------------------------------------------------- |
122
+ | Default handle | Handle, Grab zone, and Grip pill render. | Divider and always-visible treatment may vary. |
123
+ | Custom handle content | Handle and Grab zone remain; caller content replaces Grip pill. | Caller content remains caller-owned. |
124
+ | Horizontal or vertical | Interaction follows the selected axis and current ARIA orientation. | Size, reversal, and pill placement may vary. |
125
+ | Collapsed or expanded | Handle preserves valid separator value semantics and resize controls. | Grip pill automatic side follows current behavior. |
126
+ | Disabled | Handle remains represented but is removed from interactive tab order. | Painted treatment follows existing styles. |
127
+
128
+ ### Transformation and precedence order
129
+
130
+ - Parse atomic strings exactly; validate `percent()` descriptors as a finite 0–100
131
+ percentage with exactly one finite non-negative pixel bound.
132
+ - Resolve percentages against one active basis, then apply the descriptor's floor or
133
+ ceiling.
134
+ - Commit a structured default as one initial pixel choice; keep percentage bounds
135
+ live and clamp with `Math.min(max, Math.max(min, size))`.
136
+ - Maximum wins when resolved bounds conflict; interaction and persistence stay
137
+ pixel-only.
138
+
139
+ ### Performance and resources
140
+
141
+ - Atomic pixel paths add no observer or render work.
142
+ - A structured default observes a supplied container only until its initial pixel
143
+ choice is final; persisted pixels skip that measurement.
144
+ - Percentage bounds reuse the shared ResizeObserver and remain subscribed while
145
+ live. Structured validation and resolution are constant-time; no CSS parser is
146
+ introduced.
147
+
148
+ ## Accessibility contract
149
+
150
+ The structured API does not change ResizeHandle's separator role, label,
151
+ orientation, collapsed value text, focus, pointer, or keyboard behavior. Its
152
+ `aria-valuemin`, `aria-valuemax`, and `aria-valuenow` remain the same resolved pixel
153
+ values used by hook state and painted geometry.
154
+
155
+ ## Design relationships
156
+
157
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
158
+ | ---------------- | ----------------------------------------------------------------------- | ------------------------------ | -------------- | ------------------ |
159
+ | Handle | Presents the focusable resize separator and optional divider treatment. | Current source and public docs | Prominent | FR1, FR2, FR3 |
160
+ | Grip pill | Presents the default visible resize affordance. | Current source and public docs | Supporting | FR1, FR2 |
161
+ | Grab zone | Enlarges pointer reach while remaining visually transparent. | Current source and tests | Supporting | FR1, FR2 |
162
+ | Direction state | Selects horizontal or vertical resize presentation. | Current public metadata | Supporting | FR3, FR4 |
163
+
164
+ Grab zone is interaction structure rather than a painted public theming seam. Its
165
+ `none` disposition records factual reachability. The missing Handle direction
166
+ reflection is a separate current audit gap: the target exists, but runtime does
167
+ not emit the documented target state.
168
+
169
+ ### Theming anatomy
170
+
171
+ <!-- anatomy-theming:v1 -->
172
+
173
+ ```json
174
+ {
175
+ "Handle": {"target": "resize-handle"},
176
+ "Grip pill": {"target": "resize-handle-pill"},
177
+ "Grab zone": {
178
+ "none": {
179
+ "reason": "intentional: The invisible pointer hit area is nonvisual interaction structure and has no public target"
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Family and system relationships
186
+
187
+ - `architecture:component-theming-surface` owns anatomy qualification, target
188
+ mapping, and target-state reflection requirements.
189
+ - `architecture:interaction-modality` owns shared modality behavior;
190
+ `architecture:public-component-api` owns the additive structured API and
191
+ compatibility boundary.
192
+ - `Resizable/utils` is server-safe and re-exports Table's exact `pixel()` binding
193
+ plus `PixelWidth`, alongside Resizable's `percent()` and structured types.
194
+ - `family:layout-regions` lists useResizable and ResizeHandle as collaborators
195
+ that may drive a LayoutPanel's size. Resizable is not a layout-regions member,
196
+ and that family does not own or redefine resize state, snapping, persistence,
197
+ collapse, pointer, or keyboard semantics.
198
+
199
+ ## Verification map
200
+
201
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
202
+ | ------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
203
+ | FR1 | `ResizeHandle.test.tsx` plus source inspection | Default, custom, collapsed, disabled, both axes | Handle and Grab zone regressions fail focused tests; custom Grip replacement currently relies on source review. | `audit:Resizable/anatomy` |
204
+ | FR2 | Source inspection and `themingTargets.test.ts` | Handle, Grip pill, and Grab zone | Removing a target or documenting an unshipped Grab zone target fails evidence or inventory. | `audit:Resizable/theming` |
205
+ | FR3 | `ResizeHandle.test.tsx` and `useResizable.test.ts` | Pointer, keyboard, snap, collapse, persistence | Moving resize ownership or changing current transformations fails focused behavior coverage. | `audit:Resizable/behavior` |
206
+ | FR4 | Source and public metadata comparison | Horizontal and vertical Handle | No current test detects that documented `direction` is absent from runtime target-state reflection. | `audit:Resizable/theming` |
207
+ | FR5 | Hook/utils tests, typecheck, and Chromium | Canonical floor/ceiling/default; invalid XOR shape; SSR/hydration; basis changes; numeric `containerRef` parity | Wrong resolution, rescaling defaults, stale clamps, observer leaks, extra numeric renders/ref reads, or state/paint/storage/ARIA divergence fails. | `audit:Resizable/behavior` |
208
+ | FR6 | Hook type/runtime tests | Old-only aliases; untyped exact atomic strings; typed and untyped conflicts | Alias incompatibility, ambiguous config, or wrong precedence fails focused tests. | `audit:Resizable/behavior` |
209
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and two current targets | Missing, extra, prefixed, stale, or multiply assigned mappings fail repository validation. | `audit:Resizable/theming` |
210
+
211
+ Current tests exercise Handle and Grab zone structure and behavior but do not
212
+ render `children` to prove that custom content replaces Grip pill. That branch is
213
+ source-inspected and lacks focused regression evidence. The documented
214
+ `direction` visual axis is a closed union, so `extensibleAxes.test.ts` does not
215
+ cover its missing runtime reflection; that mismatch is currently unguarded.
216
+
217
+ ## Decision log
218
+
219
+ ### Structured percentages use a Table-style helper
220
+
221
+ **Decision:** `percent(value, {min: pixel(value)})` or
222
+ `percent(value, {max: pixel(value)})` represents a percentage with exactly one
223
+ pixel bound. Reuse Table's exact `pixel()` binding and `PixelWidth`; do not create
224
+ a parallel static helper. The required options and XOR type avoid a second
225
+ spelling for plain percentages and make every supported combination explicit.
226
+ The pure helper is available with the same `pixel()` binding from the server-safe
227
+ `Resizable/utils` subpath, matching Table's established helper/export pattern.
228
+ `proportional()` is deliberately not reused because it means sibling weight, not a
229
+ literal percentage.
230
+
231
+ **Rejected:** parsing recursive CSS `min()` / `max()` strings. That would create a
232
+ partial second CSS implementation without a demonstrated need for arbitrary
233
+ composition.
234
+
235
+ ## Open questions
236
+
237
+ - **OQ1 — Which follow-up should add and verify the missing Handle `direction`
238
+ target-state reflection?** (`checkable`)
239
+ - **OQ2 — Which focused test should pin custom content replacing Grip pill?**
240
+ (`checkable`)
241
+
242
+ ## Content boundary
243
+
244
+ This file does not duplicate consumer prop tables/examples, resize algorithms,
245
+ layout-region rules, current audit results, or implementation steps. It links to
246
+ their owners.
@@ -44,6 +44,27 @@ function getSeparator(): HTMLElement {
44
44
  return screen.getByRole('separator');
45
45
  }
46
46
 
47
+ function getHitArea(): HTMLElement {
48
+ return getSeparator().firstElementChild as HTMLElement;
49
+ }
50
+
51
+ /** Stub region props, so drag assertions read the calls directly. */
52
+ function makeResizable(): ResizableProps {
53
+ return {
54
+ _size: 200,
55
+ _isCollapsed: false,
56
+ _onResizeStart: vi.fn(),
57
+ _onResizeMove: vi.fn(),
58
+ _onResizeEnd: vi.fn(),
59
+ _minSizePx: 100,
60
+ _maxSizePx: 400,
61
+ _snaps: [],
62
+ _collapsedSize: 40,
63
+ _collapsible: false,
64
+ _isResizableProps: true,
65
+ };
66
+ }
67
+
47
68
  describe('ResizeHandle', () => {
48
69
  // --- ARIA wiring ---
49
70
 
@@ -120,7 +141,12 @@ describe('ResizeHandle', () => {
120
141
  it('resizes along the block axis for a vertical handle', () => {
121
142
  render(
122
143
  <Harness
123
- config={{defaultSize: 200, minSizePx: 100, maxSizePx: 400}}
144
+ config={{
145
+ defaultSize: 200,
146
+ minSizePx: 100,
147
+ maxSizePx: 400,
148
+ direction: 'vertical',
149
+ }}
124
150
  handleProps={{direction: 'vertical'}}
125
151
  />,
126
152
  );
@@ -136,6 +162,25 @@ describe('ResizeHandle', () => {
136
162
  expect(separator).toHaveAttribute('aria-valuenow', '200');
137
163
  });
138
164
 
165
+ it('warns when the handle and hook use different axes', () => {
166
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
167
+ render(
168
+ <Harness
169
+ config={{
170
+ defaultSize: 200,
171
+ direction: 'vertical',
172
+ }}
173
+ handleProps={{direction: 'horizontal'}}
174
+ />,
175
+ );
176
+ expect(warn).toHaveBeenCalledWith(
177
+ 'ResizeHandle: direction="horizontal" but its useResizable region is ' +
178
+ '"vertical". They must match: the hook measures one axis and the ' +
179
+ 'handle drags the other.',
180
+ );
181
+ warn.mockRestore();
182
+ });
183
+
139
184
  it('collapses on Enter when the region is collapsible', () => {
140
185
  render(
141
186
  <Harness
@@ -213,37 +258,128 @@ describe('ResizeHandle', () => {
213
258
 
214
259
  // --- Drag listener lifecycle ---
215
260
 
216
- it('stops driving the region and releases window listeners when unmounted mid-drag', () => {
217
- const resizable: ResizableProps = {
218
- _size: 200,
219
- _isCollapsed: false,
220
- _onResizeStart: vi.fn(),
221
- _onResizeMove: vi.fn(),
222
- _onResizeEnd: vi.fn(),
223
- _minSizePx: 100,
224
- _maxSizePx: 400,
225
- _snaps: [],
226
- _collapsedSize: 40,
227
- _collapsible: false,
228
- _isResizableProps: true,
229
- };
261
+ it('takes pointer capture on the grab zone when a drag starts', () => {
262
+ const resizable = makeResizable();
263
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
264
+ const hitArea = getHitArea();
265
+ const setPointerCapture = vi.fn();
266
+ Object.defineProperty(hitArea, 'setPointerCapture', {
267
+ value: setPointerCapture,
268
+ configurable: true,
269
+ });
270
+
271
+ fireEvent.pointerDown(hitArea, {pointerId: 7, clientX: 0, clientY: 0});
272
+ // Capture keeps the rest of the gesture on this element, so an embedded
273
+ // frame under the cursor can't swallow the drag into its own document.
274
+ expect(setPointerCapture).toHaveBeenCalledWith(7);
275
+ });
276
+
277
+ it('drives and ends the drag from events on the grab zone', () => {
278
+ const resizable = makeResizable();
279
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
280
+ const hitArea = getHitArea();
281
+
282
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
283
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 30, clientY: 0});
284
+ expect(resizable._onResizeMove).toHaveBeenLastCalledWith(30);
285
+ fireEvent.pointerUp(hitArea, {pointerId: 1, clientX: 30, clientY: 0});
286
+ expect(resizable._onResizeEnd).toHaveBeenCalledTimes(1);
287
+
288
+ // The drag is over: a later move must not resize anything.
289
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 90, clientY: 0});
290
+ expect(resizable._onResizeMove).toHaveBeenCalledTimes(1);
291
+ expect(document.body.style.cursor).toBe('');
292
+ expect(document.body.style.userSelect).toBe('');
293
+ });
294
+
295
+ it('ignores moves from a pointer that does not own the drag', () => {
296
+ const resizable = makeResizable();
297
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
298
+ const hitArea = getHitArea();
299
+
300
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
301
+ fireEvent.pointerMove(hitArea, {pointerId: 2, clientX: 30, clientY: 0});
302
+ expect(resizable._onResizeMove).not.toHaveBeenCalled();
303
+ });
304
+
305
+ it.each([
306
+ ['pointercancel', fireEvent.pointerCancel],
307
+ ['lostpointercapture', fireEvent.lostPointerCapture],
308
+ ])('ends the drag on %s without signalling a resize end', (_name, fire) => {
309
+ const resizable = makeResizable();
310
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
311
+ const hitArea = getHitArea();
312
+
313
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
314
+ fire(hitArea, {pointerId: 1});
315
+ expect(resizable._onResizeEnd).not.toHaveBeenCalled();
316
+ expect(document.body.style.cursor).toBe('');
317
+ expect(document.body.style.userSelect).toBe('');
318
+
319
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 30, clientY: 0});
320
+ expect(resizable._onResizeMove).not.toHaveBeenCalled();
321
+ });
322
+
323
+ it.each([
324
+ ['pointercancel', fireEvent.pointerCancel],
325
+ ['lostpointercapture', fireEvent.lostPointerCapture],
326
+ ])('tells the region the gesture is over on %s', (_name, fire) => {
327
+ // A cancelled drag is not a resize end, but it IS the end of the gesture:
328
+ // the region freezes its percentage basis for the duration of a drag, and
329
+ // without this it stays frozen at the container size from grab time.
330
+ const resizable = {...makeResizable(), _onResizeCancel: vi.fn()};
331
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
332
+ const hitArea = getHitArea();
333
+
334
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
335
+ fire(hitArea, {pointerId: 1});
336
+ expect(resizable._onResizeCancel).toHaveBeenCalledTimes(1);
337
+ expect(resizable._onResizeEnd).not.toHaveBeenCalled();
338
+ });
339
+
340
+ it('does not report a cancel for a drag that ended normally', () => {
341
+ const resizable = {...makeResizable(), _onResizeCancel: vi.fn()};
342
+ render(<ResizeHandle resizable={resizable} label="Resize" />);
343
+ const hitArea = getHitArea();
344
+
345
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
346
+ fireEvent.pointerUp(hitArea, {pointerId: 1, clientX: 30, clientY: 0});
347
+ expect(resizable._onResizeEnd).toHaveBeenCalledTimes(1);
348
+ // lostpointercapture follows every release; the drag is already cleared.
349
+ fireEvent.lostPointerCapture(hitArea, {pointerId: 1});
350
+ expect(resizable._onResizeCancel).not.toHaveBeenCalled();
351
+ });
352
+
353
+ it('tells the region the gesture is over when unmounted mid-drag', () => {
354
+ // The region outlives the handle here, so nothing else would release it.
355
+ const resizable = {...makeResizable(), _onResizeCancel: vi.fn()};
230
356
  const {unmount} = render(
231
357
  <ResizeHandle resizable={resizable} label="Resize" />,
232
358
  );
233
- const separator = screen.getByRole('separator');
234
- const hitArea = separator.firstElementChild as HTMLElement;
359
+ fireEvent.pointerDown(getHitArea(), {pointerId: 1, clientX: 0, clientY: 0});
360
+ unmount();
361
+ expect(resizable._onResizeCancel).toHaveBeenCalledTimes(1);
362
+ expect(resizable._onResizeEnd).not.toHaveBeenCalled();
363
+ });
364
+
365
+ it('stops driving the region and releases body styles when unmounted mid-drag', () => {
366
+ const resizable = makeResizable();
367
+ const {unmount} = render(
368
+ <ResizeHandle resizable={resizable} label="Resize" />,
369
+ );
370
+ const hitArea = getHitArea();
235
371
 
236
372
  // Start a drag and confirm moves reach the region.
237
- fireEvent.pointerDown(hitArea, {clientX: 0, clientY: 0});
373
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
238
374
  expect(resizable._onResizeStart).toHaveBeenCalledTimes(1);
239
- fireEvent.pointerMove(window, {clientX: 10, clientY: 0});
375
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 10, clientY: 0});
240
376
  expect(resizable._onResizeMove).toHaveBeenCalledTimes(1);
241
377
 
242
- // Unmount mid-drag: the window listeners must be torn down, so further
243
- // pointer moves no longer resize the (still-mounted) region, and the
244
- // body cursor/user-select overrides are released.
378
+ // Unmounting removes the capturing element, which implicitly releases the
379
+ // pointer and takes its listeners with it. The body cursor/user-select
380
+ // overrides live outside the element, so they must be released here.
245
381
  unmount();
246
- fireEvent.pointerMove(window, {clientX: 50, clientY: 0});
382
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 50, clientY: 0});
247
383
  expect(resizable._onResizeMove).toHaveBeenCalledTimes(1);
248
384
  expect(document.body.style.cursor).toBe('');
249
385
  expect(document.body.style.userSelect).toBe('');
@@ -252,43 +388,18 @@ describe('ResizeHandle', () => {
252
388
  // --- RTL pointer-drag direction ---
253
389
 
254
390
  it('drives the region with the raw pointer delta under LTR', () => {
255
- const resizable: ResizableProps = {
256
- _size: 200,
257
- _isCollapsed: false,
258
- _onResizeStart: vi.fn(),
259
- _onResizeMove: vi.fn(),
260
- _onResizeEnd: vi.fn(),
261
- _minSizePx: 100,
262
- _maxSizePx: 400,
263
- _snaps: [],
264
- _collapsedSize: 40,
265
- _collapsible: false,
266
- _isResizableProps: true,
267
- };
391
+ const resizable = makeResizable();
268
392
  render(<ResizeHandle resizable={resizable} label="Resize" />);
269
- const hitArea = screen.getByRole('separator')
270
- .firstElementChild as HTMLElement;
393
+ const hitArea = getHitArea();
271
394
 
272
- fireEvent.pointerDown(hitArea, {clientX: 0, clientY: 0});
395
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
273
396
  // Pointer moves +40px to the right → panel grows by +40 under LTR.
274
- fireEvent.pointerMove(window, {clientX: 40, clientY: 0});
397
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 40, clientY: 0});
275
398
  expect(resizable._onResizeMove).toHaveBeenLastCalledWith(40);
276
399
  });
277
400
 
278
401
  it('inverts the pointer delta under RTL so dragging resizes intuitively', () => {
279
- const resizable: ResizableProps = {
280
- _size: 200,
281
- _isCollapsed: false,
282
- _onResizeStart: vi.fn(),
283
- _onResizeMove: vi.fn(),
284
- _onResizeEnd: vi.fn(),
285
- _minSizePx: 100,
286
- _maxSizePx: 400,
287
- _snaps: [],
288
- _collapsedSize: 40,
289
- _collapsible: false,
290
- _isResizableProps: true,
291
- };
402
+ const resizable = makeResizable();
292
403
  // Under RTL the start panel sits on the RIGHT, so a pointer move to the
293
404
  // right (+clientX) must SHRINK it — the delta is inverted. The handle reads
294
405
  // its own computed `direction` via getRTLMultiplier(); jsdom doesn't resolve
@@ -308,9 +419,9 @@ describe('ResizeHandle', () => {
308
419
  return realGetComputedStyle(el, pseudo ?? undefined);
309
420
  });
310
421
 
311
- fireEvent.pointerDown(hitArea, {clientX: 0, clientY: 0});
422
+ fireEvent.pointerDown(hitArea, {pointerId: 1, clientX: 0, clientY: 0});
312
423
  // Same +40px physical move as LTR, but mirrored → −40 delta under RTL.
313
- fireEvent.pointerMove(window, {clientX: 40, clientY: 0});
424
+ fireEvent.pointerMove(hitArea, {pointerId: 1, clientX: 40, clientY: 0});
314
425
  expect(resizable._onResizeMove).toHaveBeenLastCalledWith(-40);
315
426
 
316
427
  gcsSpy.mockRestore();
@@ -352,7 +463,17 @@ describe('ResizeHandle', () => {
352
463
  ['horizontal' as const, 'translateX'],
353
464
  ['vertical' as const, 'translateY'],
354
465
  ])('offsets the %s grab zone along the pill axis only', (direction, axis) => {
355
- render(<Harness handleProps={{direction, pillPlacement: 'start'}} />);
466
+ render(
467
+ <Harness
468
+ config={{
469
+ defaultSize: 200,
470
+ minSizePx: 100,
471
+ maxSizePx: 400,
472
+ direction,
473
+ }}
474
+ handleProps={{direction, pillPlacement: 'start'}}
475
+ />,
476
+ );
356
477
  const hitArea = getSeparator().firstElementChild as HTMLElement;
357
478
  const translates = (hitArea.getAttribute('style') ?? '')
358
479
  .split(';')