@astryxdesign/core 0.4.7 → 0.5.0-canary.54bb922

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 (467) hide show
  1. package/CHANGELOG.md +161 -1
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +3 -2
  4. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  5. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  6. package/dist/AspectRatio/AspectRatio.js +23 -4
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +15 -10
  9. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  10. package/dist/AvatarGroup/AvatarGroup.js +3 -2
  11. package/dist/Banner/Banner.d.ts +40 -15
  12. package/dist/Banner/Banner.d.ts.map +1 -1
  13. package/dist/Banner/Banner.js +51 -18
  14. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  15. package/dist/BottomSheet/BottomSheet.js +17 -4
  16. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  17. package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
  18. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  19. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  20. package/dist/Button/Button.d.ts.map +1 -1
  21. package/dist/Button/Button.js +5 -4
  22. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  23. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  24. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  25. package/dist/Calendar/Calendar.d.ts +3 -1
  26. package/dist/Calendar/Calendar.d.ts.map +1 -1
  27. package/dist/Calendar/Calendar.js +13 -14
  28. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  29. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  30. package/dist/Calendar/getInitialFocusDate.js +62 -0
  31. package/dist/Carousel/Carousel.d.ts.map +1 -1
  32. package/dist/Carousel/Carousel.js +3 -3
  33. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  34. package/dist/Chat/ChatComposerInput.js +2 -3
  35. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  36. package/dist/Chat/ChatLayout.js +3 -3
  37. package/dist/Chat/ChatMessageList.d.ts +22 -3
  38. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  39. package/dist/Chat/ChatMessageList.js +6 -3
  40. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  41. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  42. package/dist/Chat/ChatTokenizedText.js +11 -6
  43. package/dist/Chat/useChatComposerTokens.js +1 -1
  44. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  45. package/dist/CheckboxInput/CheckboxInput.js +3 -2
  46. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  47. package/dist/ClickableCard/ClickableCard.js +3 -3
  48. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  49. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  50. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  51. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  52. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  53. package/dist/ContextMenu/ContextMenu.js +4 -3
  54. package/dist/DateInput/DateInput.d.ts.map +1 -1
  55. package/dist/DateInput/DateInput.js +3 -2
  56. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  57. package/dist/DateInput/TouchDateField.js +38 -4
  58. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  59. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  60. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  61. package/dist/DateTimeInput/index.d.ts +1 -1
  62. package/dist/DateTimeInput/index.d.ts.map +1 -1
  63. package/dist/Dialog/Dialog.d.ts +7 -1
  64. package/dist/Dialog/Dialog.d.ts.map +1 -1
  65. package/dist/Dialog/Dialog.js +89 -58
  66. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  67. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  68. package/dist/FileInput/FileInput.d.ts.map +1 -1
  69. package/dist/FileInput/FileInput.js +3 -2
  70. package/dist/Heading/Heading.d.ts.map +1 -1
  71. package/dist/Heading/Heading.js +6 -2
  72. package/dist/HoverCard/HoverCard.d.ts +6 -0
  73. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  74. package/dist/HoverCard/useHoverCard.d.ts +5 -0
  75. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  76. package/dist/HoverCard/useHoverCard.js +67 -25
  77. package/dist/Item/Item.d.ts.map +1 -1
  78. package/dist/Item/Item.js +4 -2
  79. package/dist/Kbd/Kbd.d.ts.map +1 -1
  80. package/dist/Kbd/Kbd.js +2 -19
  81. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  82. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  83. package/dist/Layer/LayerDepthContext.js +66 -0
  84. package/dist/Layer/index.d.ts +3 -0
  85. package/dist/Layer/index.d.ts.map +1 -1
  86. package/dist/Layer/index.js +4 -0
  87. package/dist/Layer/layerStack.d.ts +98 -0
  88. package/dist/Layer/layerStack.d.ts.map +1 -0
  89. package/dist/Layer/layerStack.js +306 -0
  90. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  91. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  92. package/dist/Layer/useLayerDismissal.js +91 -0
  93. package/dist/Lightbox/Lightbox.d.ts +1 -0
  94. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  95. package/dist/Lightbox/Lightbox.js +103 -81
  96. package/dist/Markdown/index.d.ts +1 -1
  97. package/dist/Markdown/index.d.ts.map +1 -1
  98. package/dist/Markdown/parser.d.ts +38 -1
  99. package/dist/Markdown/parser.d.ts.map +1 -1
  100. package/dist/Markdown/parser.js +177 -29
  101. package/dist/MobileNav/MobileNav.d.ts +6 -1
  102. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  103. package/dist/MobileNav/MobileNav.js +61 -36
  104. package/dist/MultiSelector/MultiSelector.d.ts +15 -1
  105. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  106. package/dist/MultiSelector/MultiSelector.js +17 -6
  107. package/dist/MultiSelector/index.d.ts +1 -1
  108. package/dist/MultiSelector/index.d.ts.map +1 -1
  109. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  110. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  111. package/dist/Outline/Outline.d.ts.map +1 -1
  112. package/dist/Outline/Outline.js +3 -2
  113. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  114. package/dist/OverflowList/OverflowList.js +3 -2
  115. package/dist/Overlay/Overlay.d.ts.map +1 -1
  116. package/dist/Overlay/Overlay.js +3 -2
  117. package/dist/Popover/usePopover.d.ts.map +1 -1
  118. package/dist/Popover/usePopover.js +20 -17
  119. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  120. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  121. package/dist/PowerSearch/PowerSearch.js +19 -6
  122. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  123. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  124. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  125. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  126. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  127. package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
  128. package/dist/PowerSearch/types.d.ts +2 -0
  129. package/dist/PowerSearch/types.d.ts.map +1 -1
  130. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  131. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  132. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  133. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  134. package/dist/Resizable/ResizeHandle.js +3 -2
  135. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  136. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  137. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  138. package/dist/SelectableCard/SelectableCard.js +3 -3
  139. package/dist/Selector/Selector.d.ts.map +1 -1
  140. package/dist/Selector/Selector.js +5 -0
  141. package/dist/SideNav/SideNav.d.ts.map +1 -1
  142. package/dist/SideNav/SideNav.js +4 -3
  143. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  144. package/dist/SideNav/SideNavHeading.js +6 -5
  145. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  146. package/dist/SideNav/SideNavItem.js +4 -3
  147. package/dist/Slider/Slider.d.ts.map +1 -1
  148. package/dist/Slider/Slider.js +3 -3
  149. package/dist/Spinner/Spinner.d.ts +3 -3
  150. package/dist/Spinner/Spinner.d.ts.map +1 -1
  151. package/dist/Spinner/Spinner.js +130 -78
  152. package/dist/Stepper/Step.d.ts +140 -0
  153. package/dist/Stepper/Step.d.ts.map +1 -0
  154. package/dist/Stepper/Step.js +1045 -0
  155. package/dist/Stepper/StepStatus.d.ts +24 -0
  156. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  157. package/dist/Stepper/StepStatus.js +1 -0
  158. package/dist/Stepper/Stepper.d.ts +93 -0
  159. package/dist/Stepper/Stepper.d.ts.map +1 -0
  160. package/dist/Stepper/Stepper.js +184 -0
  161. package/dist/Stepper/StepperContext.d.ts +41 -0
  162. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  163. package/dist/Stepper/StepperContext.js +34 -0
  164. package/dist/Stepper/index.d.ts +8 -0
  165. package/dist/Stepper/index.d.ts.map +1 -0
  166. package/dist/Stepper/index.js +7 -0
  167. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  168. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  169. package/dist/Stepper/stepper.stylex.js +20 -0
  170. package/dist/Switch/Switch.d.ts.map +1 -1
  171. package/dist/Switch/Switch.js +3 -2
  172. package/dist/TabList/Tab.d.ts +15 -2
  173. package/dist/TabList/Tab.d.ts.map +1 -1
  174. package/dist/TabList/Tab.js +32 -7
  175. package/dist/TabList/TabList.d.ts +46 -7
  176. package/dist/TabList/TabList.d.ts.map +1 -1
  177. package/dist/TabList/TabList.js +353 -23
  178. package/dist/TabList/TabListContext.d.ts +11 -1
  179. package/dist/TabList/TabListContext.d.ts.map +1 -1
  180. package/dist/TabList/TabListContext.js +11 -2
  181. package/dist/TabList/TabMenu.d.ts.map +1 -1
  182. package/dist/TabList/TabMenu.js +3 -2
  183. package/dist/TabList/index.d.ts +2 -2
  184. package/dist/TabList/index.d.ts.map +1 -1
  185. package/dist/Table/BaseTable.d.ts.map +1 -1
  186. package/dist/Table/BaseTable.js +4 -1
  187. package/dist/Table/TableRow.d.ts +16 -5
  188. package/dist/Table/TableRow.d.ts.map +1 -1
  189. package/dist/Table/TableRow.js +16 -5
  190. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  191. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  192. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  193. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  194. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  195. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  196. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  197. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  198. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  199. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  200. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  201. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  202. package/dist/Table/types.d.ts +22 -4
  203. package/dist/Table/types.d.ts.map +1 -1
  204. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  205. package/dist/Table/useBaseTablePlugins.js +5 -0
  206. package/dist/Text/Text.d.ts.map +1 -1
  207. package/dist/Text/Text.js +6 -2
  208. package/dist/TextArea/TextArea.d.ts.map +1 -1
  209. package/dist/TextArea/TextArea.js +3 -2
  210. package/dist/TextInput/TextInput.d.ts.map +1 -1
  211. package/dist/TextInput/TextInput.js +3 -2
  212. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  213. package/dist/TimeInput/TimeInput.js +3 -2
  214. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  215. package/dist/Timestamp/Timestamp.js +5 -3
  216. package/dist/Token/TokenLink.d.ts.map +1 -1
  217. package/dist/Token/TokenLink.js +2 -2
  218. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  219. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  220. package/dist/Tokenizer/Tokenizer.js +2 -0
  221. package/dist/Tooltip/Tooltip.d.ts +5 -0
  222. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  223. package/dist/Tooltip/useTooltip.d.ts +5 -0
  224. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  225. package/dist/Tooltip/useTooltip.js +39 -24
  226. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  227. package/dist/TopNav/TopNavHeading.js +3 -2
  228. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  229. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  230. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  231. package/dist/TopNav/TopNavMenu.js +4 -3
  232. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  233. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  234. package/dist/Typeahead/BaseTypeahead.js +68 -29
  235. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  236. package/dist/Typeahead/Typeahead.js +3 -2
  237. package/dist/astryx.css +68 -2
  238. package/dist/hooks/index.d.ts +1 -0
  239. package/dist/hooks/index.d.ts.map +1 -1
  240. package/dist/hooks/index.js +1 -0
  241. package/dist/hooks/useFocusTrap.d.ts +7 -4
  242. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  243. package/dist/hooks/useFocusTrap.js +60 -95
  244. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  245. package/dist/hooks/useHotkeys.js +3 -18
  246. package/dist/hooks/useMergedRefs.d.ts +18 -0
  247. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  248. package/dist/hooks/useMergedRefs.js +25 -0
  249. package/dist/index.d.ts +1 -0
  250. package/dist/index.d.ts.map +1 -1
  251. package/dist/index.js +1 -0
  252. package/dist/utils/groupItems.d.ts +5 -1
  253. package/dist/utils/groupItems.d.ts.map +1 -1
  254. package/dist/utils/groupItems.js +9 -9
  255. package/dist/utils/index.d.ts +1 -1
  256. package/dist/utils/index.d.ts.map +1 -1
  257. package/dist/utils/isApplePlatform.d.ts +18 -0
  258. package/dist/utils/isApplePlatform.d.ts.map +1 -0
  259. package/dist/utils/isApplePlatform.js +37 -0
  260. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  261. package/dist/utils/sharedResizeObserver.js +10 -1
  262. package/dist/utils/timeParser.d.ts +1 -1
  263. package/dist/utils/timeParser.d.ts.map +1 -1
  264. package/dist/utils/timeParser.js +26 -12
  265. package/locales/en.json +31 -7
  266. package/locales/pseudo.json +18 -0
  267. package/package.json +8 -3
  268. package/src/AppShell/AppShell.tsx +3 -2
  269. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  270. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  271. package/src/AspectRatio/AspectRatio.tsx +40 -1
  272. package/src/Avatar/Avatar.doc.mjs +2 -1
  273. package/src/Avatar/Avatar.test.tsx +17 -0
  274. package/src/Avatar/Avatar.tsx +61 -38
  275. package/src/AvatarGroup/AvatarGroup.tsx +3 -2
  276. package/src/Banner/Banner.doc.mjs +13 -11
  277. package/src/Banner/Banner.test.tsx +102 -15
  278. package/src/Banner/Banner.tsx +72 -25
  279. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  280. package/src/BottomSheet/BottomSheet.tsx +17 -4
  281. package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
  282. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  283. package/src/Button/Button.doc.mjs +22 -0
  284. package/src/Button/Button.test.tsx +13 -0
  285. package/src/Button/Button.tsx +5 -4
  286. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  287. package/src/ButtonGroup/ButtonGroup.test.tsx +391 -1
  288. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  289. package/src/Calendar/Calendar.doc.mjs +4 -3
  290. package/src/Calendar/Calendar.test.tsx +52 -0
  291. package/src/Calendar/Calendar.tsx +18 -15
  292. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  293. package/src/Calendar/getInitialFocusDate.ts +93 -0
  294. package/src/Carousel/Carousel.tsx +3 -2
  295. package/src/Chat/ChatComposerInput.tsx +5 -1
  296. package/src/Chat/ChatLayout.tsx +3 -2
  297. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  298. package/src/Chat/ChatMessageList.test.tsx +46 -0
  299. package/src/Chat/ChatMessageList.tsx +28 -4
  300. package/src/Chat/ChatTokenizedText.tsx +17 -10
  301. package/src/Chat/useChatComposerTokens.ts +1 -1
  302. package/src/CheckboxInput/CheckboxInput.tsx +3 -2
  303. package/src/ClickableCard/ClickableCard.tsx +3 -2
  304. package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
  305. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  306. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  307. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  308. package/src/ContextMenu/ContextMenu.tsx +4 -3
  309. package/src/DateInput/DateInput.tsx +3 -2
  310. package/src/DateInput/DateInputTouch.test.tsx +36 -0
  311. package/src/DateInput/TouchDateField.tsx +38 -4
  312. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  313. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  314. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  315. package/src/DateTimeInput/index.ts +1 -0
  316. package/src/Dialog/Dialog.doc.mjs +8 -3
  317. package/src/Dialog/Dialog.test.tsx +261 -1
  318. package/src/Dialog/Dialog.tsx +109 -58
  319. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  320. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  321. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  322. package/src/Field/Field.doc.mjs +13 -2
  323. package/src/FileInput/FileInput.tsx +3 -2
  324. package/src/Heading/Heading.test.tsx +23 -1
  325. package/src/Heading/Heading.tsx +6 -2
  326. package/src/HoverCard/HoverCard.doc.mjs +5 -0
  327. package/src/HoverCard/HoverCard.tsx +6 -0
  328. package/src/HoverCard/useHoverCard.tsx +81 -30
  329. package/src/Item/Item.tsx +5 -4
  330. package/src/Kbd/Kbd.tsx +2 -19
  331. package/src/Layer/LayerDepthContext.tsx +62 -0
  332. package/src/Layer/index.ts +9 -0
  333. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  334. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  335. package/src/Layer/layerStack.ts +362 -0
  336. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  337. package/src/Layer/useLayerDismissal.ts +173 -0
  338. package/src/Layout/Layout.doc.mjs +2 -2
  339. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  340. package/src/Lightbox/Lightbox.tsx +124 -102
  341. package/src/Link/Link.doc.mjs +39 -0
  342. package/src/Markdown/Markdown.doc.mjs +5 -0
  343. package/src/Markdown/incremental.test.ts +16 -2
  344. package/src/Markdown/index.ts +1 -0
  345. package/src/Markdown/parser.perf.test.ts +96 -1
  346. package/src/Markdown/parser.test.ts +145 -2
  347. package/src/Markdown/parser.ts +244 -29
  348. package/src/MobileNav/MobileNav.tsx +62 -36
  349. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  350. package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
  351. package/src/MultiSelector/MultiSelector.test.tsx +106 -0
  352. package/src/MultiSelector/MultiSelector.tsx +50 -6
  353. package/src/MultiSelector/index.ts +1 -0
  354. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  355. package/src/NumberInput/NumberInput.doc.mjs +5 -0
  356. package/src/Outline/Outline.tsx +3 -2
  357. package/src/OverflowList/OverflowList.tsx +3 -2
  358. package/src/Overlay/Overlay.tsx +3 -2
  359. package/src/Popover/usePopover.tsx +31 -28
  360. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  361. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  362. package/src/PowerSearch/PowerSearch.tsx +34 -5
  363. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +201 -0
  364. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  365. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
  366. package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
  367. package/src/PowerSearch/types.ts +2 -0
  368. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  369. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  370. package/src/Resizable/ResizeHandle.tsx +3 -2
  371. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  372. package/src/SelectableCard/SelectableCard.tsx +3 -2
  373. package/src/Selector/Selector.doc.mjs +12 -1
  374. package/src/Selector/Selector.test.tsx +45 -1
  375. package/src/Selector/Selector.tsx +11 -1
  376. package/src/SideNav/SideNav.doc.mjs +6 -6
  377. package/src/SideNav/SideNav.tsx +4 -2
  378. package/src/SideNav/SideNavHeading.tsx +10 -10
  379. package/src/SideNav/SideNavItem.tsx +4 -2
  380. package/src/Slider/Slider.tsx +3 -2
  381. package/src/Spinner/Spinner.test.tsx +147 -1
  382. package/src/Spinner/Spinner.tsx +120 -101
  383. package/src/Stepper/Step.doc.mjs +98 -0
  384. package/src/Stepper/Step.tsx +1638 -0
  385. package/src/Stepper/StepStatus.ts +26 -0
  386. package/src/Stepper/Stepper.doc.mjs +370 -0
  387. package/src/Stepper/Stepper.test.tsx +1024 -0
  388. package/src/Stepper/Stepper.tsx +258 -0
  389. package/src/Stepper/StepperContext.ts +71 -0
  390. package/src/Stepper/index.ts +18 -0
  391. package/src/Stepper/stepper.stylex.ts +19 -0
  392. package/src/Switch/Switch.tsx +3 -2
  393. package/src/TabList/Tab.doc.mjs +13 -1
  394. package/src/TabList/Tab.tsx +67 -7
  395. package/src/TabList/TabList.doc.mjs +22 -6
  396. package/src/TabList/TabList.test.tsx +559 -0
  397. package/src/TabList/TabList.tsx +490 -22
  398. package/src/TabList/TabListContext.ts +13 -2
  399. package/src/TabList/TabMenu.tsx +3 -2
  400. package/src/TabList/index.ts +6 -2
  401. package/src/Table/BaseTable.tsx +6 -3
  402. package/src/Table/Table.doc.mjs +6 -1
  403. package/src/Table/Table.test.tsx +132 -14
  404. package/src/Table/TableBody.doc.mjs +40 -0
  405. package/src/Table/TableFooter.doc.mjs +40 -0
  406. package/src/Table/TableHeader.doc.mjs +40 -0
  407. package/src/Table/TableRow.tsx +16 -5
  408. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  409. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  410. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  411. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  412. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  413. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  414. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  415. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  416. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  417. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  418. package/src/Table/types.ts +22 -4
  419. package/src/Table/useBaseTablePlugins.ts +5 -0
  420. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  421. package/src/Table/useTablePagination.doc.mjs +7 -0
  422. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  423. package/src/Table/useTableSelection.doc.mjs +31 -0
  424. package/src/Text/Text.test.tsx +23 -1
  425. package/src/Text/Text.tsx +6 -2
  426. package/src/TextArea/TextArea.tsx +3 -2
  427. package/src/TextInput/TextInput.doc.mjs +10 -0
  428. package/src/TextInput/TextInput.tsx +3 -2
  429. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  430. package/src/TimeInput/TimeInput.tsx +2 -2
  431. package/src/Timestamp/Timestamp.tsx +4 -2
  432. package/src/Toast/Toast.doc.mjs +6 -0
  433. package/src/Token/TokenLink.tsx +2 -2
  434. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  435. package/src/Tokenizer/Tokenizer.tsx +4 -0
  436. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  437. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  438. package/src/Tooltip/Tooltip.test.tsx +32 -0
  439. package/src/Tooltip/Tooltip.tsx +5 -0
  440. package/src/Tooltip/useTooltip.tsx +47 -24
  441. package/src/TopNav/TopNavHeading.tsx +3 -2
  442. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  443. package/src/TopNav/TopNavMenu.tsx +4 -3
  444. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  445. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  446. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  447. package/src/Typeahead/Typeahead.test.tsx +49 -0
  448. package/src/Typeahead/Typeahead.tsx +3 -2
  449. package/src/hooks/index.ts +2 -0
  450. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  451. package/src/hooks/useFocusTrap.test.tsx +14 -0
  452. package/src/hooks/useFocusTrap.ts +62 -111
  453. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  454. package/src/hooks/useHotkeys.ts +3 -18
  455. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  456. package/src/hooks/useMergedRefs.test.tsx +62 -0
  457. package/src/hooks/useMergedRefs.ts +36 -0
  458. package/src/index.ts +1 -0
  459. package/src/theme/MediaTheme.doc.mjs +5 -5
  460. package/src/utils/groupItems.ts +12 -6
  461. package/src/utils/index.ts +1 -1
  462. package/src/utils/isApplePlatform.test.ts +61 -0
  463. package/src/utils/isApplePlatform.ts +37 -0
  464. package/src/utils/sharedResizeObserver.ts +10 -2
  465. package/src/utils/timeParser.test.ts +6 -1
  466. package/src/utils/timeParser.ts +35 -13
  467. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -12,6 +12,7 @@
12
12
  * - /packages/core/src/Lightbox/Lightbox.doc.mjs (props table, features, implementation notes)
13
13
  * - /packages/core/src/Lightbox/Lightbox.test.tsx (tests for new/changed behavior)
14
14
  * - /packages/core/src/Lightbox/index.ts (exports if types change)
15
+ * - /packages/core/src/Layer/useLayerDismissal.ts (dismissal stack)
15
16
  * - /apps/storybook/stories/Lightbox.stories.tsx (storybook stories)
16
17
  * - /packages/cli/assets/templates/blocks/components/Lightbox/ (showcase blocks)
17
18
  */
@@ -32,13 +33,16 @@ import {IconButton} from '../IconButton';
32
33
  import {useAnnounce} from '../hooks/useAnnounce';
33
34
  import {useScrollLock} from '../hooks/useScrollLock';
34
35
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
35
- import {mergeProps, mergeRefs, rtlStyles} from '../utils';
36
+ import {mergeProps, rtlStyles} from '../utils';
36
37
  import type {BaseProps} from '../BaseProps';
37
38
  import {themeProps} from '../utils/themeProps';
38
39
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
39
40
  import {overlayPaddingReset} from '../Layout/padding.stylex';
41
+ import {LayerDepthProvider} from '../Layer/LayerDepthContext';
42
+ import {useLayerDismissal} from '../Layer/useLayerDismissal';
40
43
  import {useTranslator} from '../i18n';
41
44
 
45
+ import {useMergedRefs} from '../hooks/useMergedRefs';
42
46
  /**
43
47
  * Media type for lightbox items.
44
48
  */
@@ -315,6 +319,7 @@ export function Lightbox({
315
319
  }: LightboxProps) {
316
320
  const t = useTranslator();
317
321
  const dialogRef = useRef<HTMLDialogElement>(null);
322
+ const mergedDialogRef = useMergedRefs(ref, dialogRef);
318
323
  const containerRef = useRef<HTMLDivElement>(null);
319
324
  const imageWrapperRef = useRef<HTMLDivElement>(null);
320
325
  const triggerElementRef = useRef<Element | null>(null);
@@ -416,13 +421,28 @@ export function Lightbox({
416
421
  onOpenChange(false);
417
422
  }, [onOpenChange]);
418
423
 
419
- // Escape key
424
+ const {shouldDismissOnCloseRequest} = useLayerDismissal({
425
+ isActive: isOpen,
426
+ onDismiss: handleClose,
427
+ });
428
+
429
+ // The native `cancel` event is the browser's own close-watcher firing: an
430
+ // Android back gesture, or a close request the stack never saw a press for.
431
+ // Escape presses the stack owns never arrive here — it preventDefault()s
432
+ // those, which suppresses the close watcher.
433
+ //
434
+ // Always preventDefault so the browser cannot close a controlled <dialog>
435
+ // behind React's back, then answer with the stack's own rules: top-most
436
+ // only, and never while an IME composition is in progress.
420
437
  const handleCancel = useCallback(
421
438
  (e: React.SyntheticEvent) => {
422
439
  e.preventDefault();
440
+ if (!shouldDismissOnCloseRequest()) {
441
+ return;
442
+ }
423
443
  handleClose();
424
444
  },
425
- [handleClose],
445
+ [handleClose, shouldDismissOnCloseRequest],
426
446
  );
427
447
 
428
448
  // Backdrop click. The layout container fills the whole transparent dialog,
@@ -583,7 +603,7 @@ export function Lightbox({
583
603
 
584
604
  return (
585
605
  <dialog
586
- ref={mergeRefs(ref, dialogRef)}
606
+ ref={mergedDialogRef}
587
607
  onCancel={handleCancel}
588
608
  onClick={e => {
589
609
  handleBackdropClick(e);
@@ -601,112 +621,114 @@ export function Lightbox({
601
621
  style,
602
622
  )}
603
623
  {...props}>
604
- <div ref={containerRef} {...stylex.props(styles.container)}>
605
- {/* Close button */}
606
- <IconButton
607
- icon={<Icon icon="close" size="sm" color="inherit" />}
608
- label={t('@astryx.lightbox.close')}
609
- variant="ghost"
610
- onClick={handleClose}
611
- xstyle={[styles.closeButton, styles.controlButton]}
612
- />
613
-
614
- {/* Gallery nav: prev — stays mounted and is disabled at the start of
615
- the range so pressing/arrowing to the boundary doesn't unmount the
616
- focused control and drop focus to <body>. */}
617
- {isGallery && (
624
+ <LayerDepthProvider>
625
+ <div ref={containerRef} {...stylex.props(styles.container)}>
626
+ {/* Close button */}
618
627
  <IconButton
619
- icon={
620
- <Icon
621
- icon="chevronLeft"
622
- size="sm"
623
- color="inherit"
624
- xstyle={rtlStyles.mirror}
625
- />
626
- }
627
- label={t('@astryx.lightbox.previous')}
628
+ icon={<Icon icon="close" size="sm" color="inherit" />}
629
+ label={t('@astryx.lightbox.close')}
628
630
  variant="ghost"
629
- isDisabled={!canPrev}
630
- onClick={goToPrev}
631
- xstyle={[styles.navButton, styles.navPrev, styles.controlButton]}
631
+ onClick={handleClose}
632
+ xstyle={[styles.closeButton, styles.controlButton]}
632
633
  />
633
- )}
634
-
635
- {/* Media + caption group (centered together) */}
636
- <div {...stylex.props(styles.mediaGroup)}>
637
- <div
638
- ref={imageWrapperRef}
639
- // The wrapper is a keyboard-operable zoom toggle when zoom is
640
- // enabled: Enter/Space toggles, aria-pressed reflects state.
641
- role={isZoomTarget ? 'button' : undefined}
642
- tabIndex={isZoomTarget ? 0 : undefined}
643
- aria-pressed={isZoomTarget ? isZoomed : undefined}
644
- aria-label={isZoomTarget ? t('@astryx.lightbox.zoom') : undefined}
645
- {...stylex.props(
646
- styles.imageWrapper,
647
- isZoomTarget && focusOutlineStyles.focusVisible,
648
- !isVideo && hasZoom && !isZoomed && styles.imageWrapperZoomable,
649
- !isVideo && isZoomed && styles.imageWrapperZoomed,
650
- !isVideo && isDragging && styles.imageWrapperDragging,
651
- )}
652
- onDoubleClick={isVideo ? undefined : handleDoubleClick}
653
- onKeyDown={isZoomTarget ? handleImageKeyDown : undefined}
654
- onPointerDown={isVideo ? undefined : handlePointerDown}>
655
- {isVideo ? (
656
- <video
657
- src={currentItem.src}
658
- aria-label={currentItem.alt}
659
- controls
660
- autoPlay={hasAutoPlay}
661
- {...stylex.props(styles.video)}
662
- />
663
- ) : (
664
- <img
665
- src={currentItem.src}
666
- alt={currentItem.alt}
667
- draggable={false}
668
- {...stylex.props(
669
- styles.image,
670
- isDragging && styles.imageDragging,
671
- imageTransform != null &&
672
- dynamicStyles.imageTransform(imageTransform),
673
- )}
674
- />
675
- )}
676
- </div>
677
634
 
678
- {currentItem.caption && (
679
- <div {...stylex.props(styles.caption)}>{currentItem.caption}</div>
635
+ {/* Gallery nav: prev — stays mounted and is disabled at the start of
636
+ the range so pressing/arrowing to the boundary doesn't unmount the
637
+ focused control and drop focus to <body>. */}
638
+ {isGallery && (
639
+ <IconButton
640
+ icon={
641
+ <Icon
642
+ icon="chevronLeft"
643
+ size="sm"
644
+ color="inherit"
645
+ xstyle={rtlStyles.mirror}
646
+ />
647
+ }
648
+ label={t('@astryx.lightbox.previous')}
649
+ variant="ghost"
650
+ isDisabled={!canPrev}
651
+ onClick={goToPrev}
652
+ xstyle={[styles.navButton, styles.navPrev, styles.controlButton]}
653
+ />
680
654
  )}
681
- </div>
682
655
 
683
- {/* Gallery nav: next — see "prev" above; stays mounted and disabled at
656
+ {/* Media + caption group (centered together) */}
657
+ <div {...stylex.props(styles.mediaGroup)}>
658
+ <div
659
+ ref={imageWrapperRef}
660
+ // The wrapper is a keyboard-operable zoom toggle when zoom is
661
+ // enabled: Enter/Space toggles, aria-pressed reflects state.
662
+ role={isZoomTarget ? 'button' : undefined}
663
+ tabIndex={isZoomTarget ? 0 : undefined}
664
+ aria-pressed={isZoomTarget ? isZoomed : undefined}
665
+ aria-label={isZoomTarget ? t('@astryx.lightbox.zoom') : undefined}
666
+ {...stylex.props(
667
+ styles.imageWrapper,
668
+ isZoomTarget && focusOutlineStyles.focusVisible,
669
+ !isVideo && hasZoom && !isZoomed && styles.imageWrapperZoomable,
670
+ !isVideo && isZoomed && styles.imageWrapperZoomed,
671
+ !isVideo && isDragging && styles.imageWrapperDragging,
672
+ )}
673
+ onDoubleClick={isVideo ? undefined : handleDoubleClick}
674
+ onKeyDown={isZoomTarget ? handleImageKeyDown : undefined}
675
+ onPointerDown={isVideo ? undefined : handlePointerDown}>
676
+ {isVideo ? (
677
+ <video
678
+ src={currentItem.src}
679
+ aria-label={currentItem.alt}
680
+ controls
681
+ autoPlay={hasAutoPlay}
682
+ {...stylex.props(styles.video)}
683
+ />
684
+ ) : (
685
+ <img
686
+ src={currentItem.src}
687
+ alt={currentItem.alt}
688
+ draggable={false}
689
+ {...stylex.props(
690
+ styles.image,
691
+ isDragging && styles.imageDragging,
692
+ imageTransform != null &&
693
+ dynamicStyles.imageTransform(imageTransform),
694
+ )}
695
+ />
696
+ )}
697
+ </div>
698
+
699
+ {currentItem.caption && (
700
+ <div {...stylex.props(styles.caption)}>{currentItem.caption}</div>
701
+ )}
702
+ </div>
703
+
704
+ {/* Gallery nav: next — see "prev" above; stays mounted and disabled at
684
705
  the end of the range instead of unmounting. */}
685
- {isGallery && (
686
- <IconButton
687
- icon={
688
- <Icon
689
- icon="chevronRight"
690
- size="sm"
691
- color="inherit"
692
- xstyle={rtlStyles.mirror}
693
- />
694
- }
695
- label={t('@astryx.lightbox.next')}
696
- variant="ghost"
697
- isDisabled={!canNext}
698
- onClick={goToNext}
699
- xstyle={[styles.navButton, styles.navNext, styles.controlButton]}
700
- />
701
- )}
706
+ {isGallery && (
707
+ <IconButton
708
+ icon={
709
+ <Icon
710
+ icon="chevronRight"
711
+ size="sm"
712
+ color="inherit"
713
+ xstyle={rtlStyles.mirror}
714
+ />
715
+ }
716
+ label={t('@astryx.lightbox.next')}
717
+ variant="ghost"
718
+ isDisabled={!canNext}
719
+ onClick={goToNext}
720
+ xstyle={[styles.navButton, styles.navNext, styles.controlButton]}
721
+ />
722
+ )}
702
723
 
703
- {/* Gallery counter */}
704
- {isGallery && mediaArray.length > 1 && (
705
- <div {...stylex.props(styles.counter)}>
706
- {index + 1} / {mediaArray.length}
707
- </div>
708
- )}
709
- </div>
724
+ {/* Gallery counter */}
725
+ {isGallery && mediaArray.length > 1 && (
726
+ <div {...stylex.props(styles.counter)}>
727
+ {index + 1} / {mediaArray.length}
728
+ </div>
729
+ )}
730
+ </div>
731
+ </LayerDepthProvider>
710
732
  </dialog>
711
733
  );
712
734
  }
@@ -78,6 +78,17 @@ export const docs = {
78
78
  description:
79
79
  'Link relationship tokens. noopener noreferrer are merged automatically for target="_blank".',
80
80
  },
81
+ {
82
+ name: 'download',
83
+ type: 'string | boolean',
84
+ description:
85
+ 'Causes the browser to download the linked URL. A string specifies the suggested filename.',
86
+ },
87
+ {
88
+ name: 'referrerPolicy',
89
+ type: 'HTMLAttributeReferrerPolicy',
90
+ description: 'Referrer policy for the link.',
91
+ },
81
92
  {
82
93
  name: 'onClick',
83
94
  type: 'MouseEventHandler',
@@ -94,6 +105,34 @@ export const docs = {
94
105
  description: 'Applies base font sizing',
95
106
  default: 'false',
96
107
  },
108
+ {
109
+ name: 'size',
110
+ type: "'4xs' | '3xs' | '2xs' | 'xsm' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'",
111
+ description: 'Explicit font size override forwarded to Text.',
112
+ },
113
+ {
114
+ name: 'weight',
115
+ type: "'normal' | 'medium' | 'semibold' | 'bold'",
116
+ description: 'Font weight override forwarded to Text.',
117
+ },
118
+ {
119
+ name: 'color',
120
+ type: "'primary' | 'secondary' | 'disabled' | 'placeholder' | 'accent' | 'inherit'",
121
+ description: 'Text color forwarded to Text.',
122
+ default: "'accent'",
123
+ },
124
+ {
125
+ name: 'display',
126
+ type: "'inline' | 'block'",
127
+ description: 'Display type for Text.',
128
+ default: "'inline'",
129
+ },
130
+ {
131
+ name: 'maxLines',
132
+ type: 'number',
133
+ description: 'Maximum lines before truncation.',
134
+ default: '0',
135
+ },
97
136
  {
98
137
  name: 'children',
99
138
  type: 'ReactNode',
@@ -96,6 +96,11 @@ export const docs = {
96
96
  description:
97
97
  "Opt-in autolinking of bare URLs and emails. 'gfm' applies GitHub-Flavored Markdown autolink-literal rules: bare https?://..., www...., <scheme:url>, <email>, and user@host all become links. Trailing sentence punctuation and unbalanced trailing close-parens are excluded; matches inside code spans, code blocks, existing links, and image alt text are skipped. Default behavior (option unset) is unchanged.",
98
98
  },
99
+ {
100
+ name: 'components',
101
+ type: 'MarkdownComponents',
102
+ description: 'Custom React component overrides for rendered Markdown elements (code, inlineCode, link, heading, paragraph, image, blockquote, hr, citation).',
103
+ },
99
104
  {
100
105
  name: 'xstyle',
101
106
  type: 'StyleXStyles',
@@ -131,12 +131,26 @@ describe('parseMarkdownIncremental', () => {
131
131
  expect(state.settledText).toBe('');
132
132
  });
133
133
 
134
- it('unclosed fence keeps everything unsettled', () => {
134
+ it('settles what precedes an unclosed fence, and nothing inside it', () => {
135
135
  const state = createIncrementalState();
136
136
  const input =
137
137
  '# Title\n\n```python\ndef foo():\n pass\n\n# still inside fence';
138
138
  parseMarkdownIncremental(input, state);
139
- expect(state.settledBlocks).toHaveLength(0);
139
+ expect(state.settledText).toBe('# Title');
140
+ expect(state.settledBlocks).toHaveLength(1);
141
+ });
142
+
143
+ it('does not hold back lines inside an unclosed fence', () => {
144
+ const state = createIncrementalState();
145
+ const blocks = parseMarkdownIncremental(
146
+ 'Intro\n\n```ts\ntype Id = string | number;',
147
+ state,
148
+ );
149
+ const code = blocks.find(block => block.type === 'codeblock');
150
+ expect(code).toMatchObject({
151
+ type: 'codeblock',
152
+ content: 'type Id = string | number;',
153
+ });
140
154
  });
141
155
 
142
156
  it('handles task list streaming', () => {
@@ -25,6 +25,7 @@ export {
25
25
  export type {
26
26
  BlockNode,
27
27
  InlineNode,
28
+ SourceRange,
28
29
  ListItemNode,
29
30
  TableCellNode,
30
31
  TableAlignment,
@@ -6,7 +6,7 @@ import {
6
6
  parseMarkdownIncremental,
7
7
  createIncrementalState,
8
8
  } from './parser';
9
- import type {BlockNode} from './parser';
9
+ import type {BlockNode, ParseOptions} from './parser';
10
10
 
11
11
  function generateAIResponse(paragraphs: number): string {
12
12
  const sections: string[] = [];
@@ -69,6 +69,44 @@ function simulateStreamingIncremental(
69
69
  return parseMarkdownIncremental(fullText, state);
70
70
  }
71
71
 
72
+ /**
73
+ * How many blocks each chunk of a streamed document had to build.
74
+ *
75
+ * A block the parser kept in its settled cache is handed back as the very same
76
+ * object, so a returned block that is not one of the previous chunk's is one
77
+ * this chunk built. That makes the cache countable without reaching into the
78
+ * parser's internals.
79
+ */
80
+ function blocksBuiltPerChunk(
81
+ fullText: string,
82
+ chunkSize: number,
83
+ options?: ParseOptions,
84
+ ): number[] {
85
+ const state = createIncrementalState();
86
+ let previous: ReadonlySet<BlockNode> = new Set();
87
+ const built: number[] = [];
88
+ const step = (text: string) => {
89
+ const blocks =
90
+ options == null
91
+ ? parseMarkdownIncremental(text, state)
92
+ : parseMarkdownIncremental(text, state, options);
93
+ built.push(blocks.reduce((n, b) => (previous.has(b) ? n : n + 1), 0));
94
+ previous = new Set(blocks);
95
+ };
96
+ for (let i = chunkSize; i <= fullText.length; i += chunkSize) {
97
+ step(fullText.slice(0, i));
98
+ }
99
+ step(fullText);
100
+ return built;
101
+ }
102
+
103
+ const percentile = (values: number[], p: number): number => {
104
+ const sorted = [...values].sort((a, b) => a - b);
105
+ return sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))];
106
+ };
107
+
108
+ const sum = (values: number[]): number => values.reduce((a, b) => a + b, 0);
109
+
72
110
  // A single wall-clock measurement is flaky when the whole suite runs in
73
111
  // parallel forks: scheduler contention can inflate a millisecond-scale
74
112
  // sample well past a tight budget (observed 30.9ms against the 20ms Small
@@ -204,3 +242,60 @@ describe('parseMarkdown performance', () => {
204
242
  expect(incrElapsed).toBeLessThanOrEqual(fullElapsed * 1.1);
205
243
  });
206
244
  });
245
+
246
+ // What the incremental parser exists for is a cache, so these assert the cache:
247
+ // integers that come out the same on a loaded CI box as on an idle laptop,
248
+ // unlike the wall-clock budgets above.
249
+ describe('parseMarkdownIncremental cache', () => {
250
+ it('builds a bounded number of blocks per chunk however long the document is', () => {
251
+ const chunkSize = 50;
252
+ const medians = [50, 200, 500].map(paragraphs =>
253
+ percentile(
254
+ blocksBuiltPerChunk(generateAIResponse(paragraphs), chunkSize),
255
+ 0.5,
256
+ ),
257
+ );
258
+ console.log(
259
+ ` median blocks built per chunk (50/200/500 paragraphs): ${medians.join('/')}`,
260
+ );
261
+ // A chunk re-parses its unsettled tail, not the document — so the typical
262
+ // chunk's cost is a constant, and a 10x longer document does not move it.
263
+ expect(medians).toEqual([1, 1, 1]);
264
+ });
265
+
266
+ it('bounds the worst chunk, not just the typical one, at every document length', () => {
267
+ const chunkSize = 50;
268
+ const worst = [50, 200, 500].map(paragraphs =>
269
+ Math.max(
270
+ ...blocksBuiltPerChunk(generateAIResponse(paragraphs), chunkSize),
271
+ ),
272
+ );
273
+ console.log(
274
+ ` worst chunk's blocks built (50/200/500 paragraphs): ${worst.join('/')}`,
275
+ );
276
+ // A cache that dumps on a minority of chunks is flat at the median and at
277
+ // p95 while the total work is still quadratic: the blank-line path of
278
+ // #5378 hit ~1.4% of chunks on this fixture, and no percentile bound saw
279
+ // it. The worst chunk does, and each collapse costs more as the document
280
+ // grows — so this asserts the worst one, and that a 10x document does not
281
+ // move it. Deterministic: same fixture, same chunking, integers with no
282
+ // timing input.
283
+ expect(worst).toEqual([3, 3, 3]);
284
+ });
285
+
286
+ it('keeps the cache when source ranges are asked for', () => {
287
+ const text = generateAIResponse(200);
288
+ const chunkSize = 50;
289
+ const without = sum(blocksBuiltPerChunk(text, chunkSize));
290
+ const withRanges = sum(
291
+ blocksBuiltPerChunk(text, chunkSize, {sourceRanges: true}),
292
+ );
293
+ console.log(
294
+ ` blocks built streaming ${text.length} chars: ${without} without ranges, ${withRanges} with`,
295
+ );
296
+ // Stamping offsets must not cost a cache entry. An implementation that
297
+ // tracked positions by invalidating the settled cache each chunk would
298
+ // still pass any time budget generous enough not to flake; it fails here.
299
+ expect(withRanges).toBe(without);
300
+ });
301
+ });
@@ -1,8 +1,13 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  import {describe, it, expect} from 'vitest';
4
- import {parseMarkdown, parseInline} from './parser';
5
- import type {InlineNode} from './parser';
4
+ import {
5
+ createIncrementalState,
6
+ parseInline,
7
+ parseMarkdown,
8
+ parseMarkdownIncremental,
9
+ } from './parser';
10
+ import type {BlockNode, InlineNode} from './parser';
6
11
 
7
12
  describe('parseInline', () => {
8
13
  it('parses plain text', () => {
@@ -1209,3 +1214,141 @@ describe('link reference definitions', () => {
1209
1214
  expect(blocks[0].type).toBe('paragraph');
1210
1215
  });
1211
1216
  });
1217
+
1218
+ describe('sourceRanges', () => {
1219
+ const slice = (source: string, block: BlockNode) =>
1220
+ block.range == null
1221
+ ? null
1222
+ : source.slice(block.range.start, block.range.end);
1223
+
1224
+ it('is off by default', () => {
1225
+ const [block] = parseMarkdown('# Title');
1226
+ expect(block.range).toBeUndefined();
1227
+ });
1228
+
1229
+ it('addresses a block with named start and end offsets', () => {
1230
+ const source = 'One.\n\nTwo.';
1231
+ const [, second] = parseMarkdown(source, {sourceRanges: true});
1232
+ expect(second.range).toEqual({start: 6, end: 10});
1233
+ });
1234
+
1235
+ it('gives every top-level block the source it came from', () => {
1236
+ const source = [
1237
+ '# Title',
1238
+ '',
1239
+ 'A paragraph that',
1240
+ 'wraps onto two lines.',
1241
+ '',
1242
+ '- one',
1243
+ '- two',
1244
+ '',
1245
+ '```js',
1246
+ 'const x = 1;',
1247
+ '```',
1248
+ '',
1249
+ '| a | b |',
1250
+ '| --- | --- |',
1251
+ '| 1 | 2 |',
1252
+ '',
1253
+ '> quoted',
1254
+ '',
1255
+ '---',
1256
+ ].join('\n');
1257
+ const blocks = parseMarkdown(source, {sourceRanges: true});
1258
+ expect(blocks.map(b => slice(source, b))).toEqual([
1259
+ '# Title',
1260
+ 'A paragraph that\nwraps onto two lines.',
1261
+ '- one\n- two',
1262
+ '```js\nconst x = 1;\n```',
1263
+ '| a | b |\n| --- | --- |\n| 1 | 2 |',
1264
+ '> quoted',
1265
+ '---',
1266
+ ]);
1267
+ });
1268
+
1269
+ it('reports offsets into the input, not into the link-definition-stripped text', () => {
1270
+ // The definition lines are removed before the block loop runs, so a naive
1271
+ // offset would drift by their length for everything after them.
1272
+ const source = [
1273
+ '[ref]: https://example.com',
1274
+ '',
1275
+ 'See [the docs][ref].',
1276
+ '',
1277
+ 'Another paragraph.',
1278
+ ].join('\n');
1279
+ const blocks = parseMarkdown(source, {sourceRanges: true});
1280
+ expect(blocks.map(b => slice(source, b))).toEqual([
1281
+ 'See [the docs][ref].',
1282
+ 'Another paragraph.',
1283
+ ]);
1284
+ });
1285
+
1286
+ it('reports absolute offsets when the document is parsed incrementally', () => {
1287
+ const source = ['# Title', '', 'One.', '', 'Two.', '', 'Three.'].join('\n');
1288
+ const state = createIncrementalState();
1289
+ let blocks: BlockNode[] = [];
1290
+ for (let end = 1; end <= source.length; end++) {
1291
+ blocks = parseMarkdownIncremental(source.slice(0, end), state, {
1292
+ sourceRanges: true,
1293
+ });
1294
+ }
1295
+ expect(blocks.map(b => slice(source, b))).toEqual([
1296
+ '# Title',
1297
+ 'One.',
1298
+ 'Two.',
1299
+ 'Three.',
1300
+ ]);
1301
+ });
1302
+
1303
+ it('keeps the blank lines an unterminated fence owns', () => {
1304
+ // Mid-stream the closing fence has not arrived, and the blank lines are
1305
+ // part of the code, not spacing between blocks.
1306
+ const source = '```\ncode\n\n';
1307
+ const [block] = parseMarkdown(source, {sourceRanges: true});
1308
+ // The whole thing: the fence consumed those lines as code.
1309
+ expect(slice(source, block)).toBe(source);
1310
+ });
1311
+
1312
+ it('slices to something that re-parses to the same block', () => {
1313
+ // The property a consumer actually needs, and the one that says the
1314
+ // offsets are right: what the range points at is the block.
1315
+ const check = (source: string) => {
1316
+ for (const block of parseMarkdown(source, {sourceRanges: true})) {
1317
+ const {range: _range, ...node} = block;
1318
+ expect(parseMarkdown(slice(source, block)!)).toEqual([node]);
1319
+ }
1320
+ };
1321
+ check('# Title\n\nA paragraph.\n\n- one\n- two\n\n> quoted');
1322
+ // CRLF: the parser keeps the `\r` in its own content, so the range does
1323
+ // too rather than slicing to text that parses differently.
1324
+ check('# Title\r\n\r\nA paragraph.\r\n');
1325
+ });
1326
+
1327
+ it('re-parses when the caller flips the option on an existing state', () => {
1328
+ const source = 'One.\n\nTwo.\n\nThree.';
1329
+ const state = createIncrementalState();
1330
+ const without = parseMarkdownIncremental(source, state);
1331
+ expect(without.every(b => b.range == null)).toBe(true);
1332
+ const with_ = parseMarkdownIncremental(source, state, {
1333
+ sourceRanges: true,
1334
+ });
1335
+ expect(with_.map(b => slice(source, b))).toEqual([
1336
+ 'One.',
1337
+ 'Two.',
1338
+ 'Three.',
1339
+ ]);
1340
+ const back = parseMarkdownIncremental(source, state);
1341
+ expect(back.every(b => b.range == null)).toBe(true);
1342
+ });
1343
+
1344
+ it('spans both halves of a list the incremental parser merged', () => {
1345
+ const source = '1. one\n\n1. two';
1346
+ const state = createIncrementalState();
1347
+ parseMarkdownIncremental('1. one\n\n', state, {sourceRanges: true});
1348
+ const blocks = parseMarkdownIncremental(source, state, {
1349
+ sourceRanges: true,
1350
+ });
1351
+ expect(blocks).toHaveLength(1);
1352
+ expect(slice(source, blocks[0])).toBe(source);
1353
+ });
1354
+ });