@astryxdesign/core 0.6.2 → 0.6.3-canary.0949cb1

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 (384) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposer.js +10 -5
  10. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  11. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  12. package/dist/Chat/ChatComposerDrawer.js +48 -23
  13. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  14. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  15. package/dist/Chat/ChatComposerInput.js +63 -16
  16. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  17. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  18. package/dist/Chat/ChatDictationButton.js +6 -4
  19. package/dist/Chat/ChatSendButton.js +1 -1
  20. package/dist/Chat/index.d.ts +1 -1
  21. package/dist/Chat/index.d.ts.map +1 -1
  22. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  23. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  24. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  25. package/dist/Collapsible/Collapsible.js +6 -1
  26. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  28. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  29. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  30. package/dist/DateInput/DateInput.d.ts.map +1 -1
  31. package/dist/DateInput/DateInput.js +4 -4
  32. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  33. package/dist/DateInput/NativeDateField.js +16 -16
  34. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  35. package/dist/DateInput/TouchDateField.js +2 -2
  36. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  37. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  38. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  39. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  40. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  41. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  42. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  43. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  44. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  45. package/dist/Dialog/DialogHeader.d.ts +8 -1
  46. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  47. package/dist/Dialog/DialogHeader.js +10 -1
  48. package/dist/Field/InputClearButton.d.ts +4 -2
  49. package/dist/Field/InputClearButton.d.ts.map +1 -1
  50. package/dist/Field/InputClearButton.js +4 -2
  51. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  52. package/dist/Field/PanelSearchInput.js +1 -1
  53. package/dist/FileInput/FileInput.d.ts.map +1 -1
  54. package/dist/FileInput/FileInput.js +7 -7
  55. package/dist/Kbd/Kbd.d.ts +5 -3
  56. package/dist/Kbd/Kbd.d.ts.map +1 -1
  57. package/dist/Kbd/Kbd.js +36 -42
  58. package/dist/Link/Link.d.ts.map +1 -1
  59. package/dist/Link/Link.js +6 -2
  60. package/dist/Markdown/Markdown.d.ts +8 -2
  61. package/dist/Markdown/Markdown.d.ts.map +1 -1
  62. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  63. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  64. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  65. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  66. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  67. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  68. package/dist/Markdown/Markdown.js +262 -109
  69. package/dist/Markdown/ast.d.ts +162 -0
  70. package/dist/Markdown/ast.d.ts.map +1 -0
  71. package/dist/Markdown/ast.js +74 -0
  72. package/dist/Markdown/index.d.ts +5 -3
  73. package/dist/Markdown/index.d.ts.map +1 -1
  74. package/dist/Markdown/index.js +3 -2
  75. package/dist/Markdown/parser/index.d.ts +10 -0
  76. package/dist/Markdown/parser/index.d.ts.map +1 -0
  77. package/dist/Markdown/parser/index.js +10 -0
  78. package/dist/Markdown/parser.d.ts +126 -58
  79. package/dist/Markdown/parser.d.ts.map +1 -1
  80. package/dist/Markdown/parser.js +716 -167
  81. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  82. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  83. package/dist/Markdown/plugins/frontmatter.js +132 -0
  84. package/dist/Markdown/plugins/index.d.ts +16 -0
  85. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  86. package/dist/Markdown/plugins/index.js +13 -0
  87. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  88. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  89. package/dist/Markdown/plugins/protocol.js +957 -0
  90. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  91. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  92. package/dist/Markdown/plugins/semanticFence.js +213 -0
  93. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  94. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  95. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  96. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  97. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  98. package/dist/Markdown/plugins/textTransform.js +374 -0
  99. package/dist/Markdown/remark.d.ts +196 -0
  100. package/dist/Markdown/remark.d.ts.map +1 -0
  101. package/dist/Markdown/remark.js +1062 -0
  102. package/dist/Markdown/url.d.ts +4 -0
  103. package/dist/Markdown/url.d.ts.map +1 -0
  104. package/dist/Markdown/url.js +25 -0
  105. package/dist/Markdown/utils.d.ts +4 -2
  106. package/dist/Markdown/utils.d.ts.map +1 -1
  107. package/dist/Markdown/utils.js +2 -1
  108. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  109. package/dist/NumberInput/NumberInput.js +4 -4
  110. package/dist/Outline/index.d.ts +1 -0
  111. package/dist/Outline/index.d.ts.map +1 -1
  112. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  113. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  114. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  115. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  116. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  117. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  118. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  119. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  120. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  121. package/dist/ProgressBar/ProgressBar.js +36 -31
  122. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  123. package/dist/RadioList/RadioListItem.js +13 -1
  124. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  125. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  126. package/dist/Selector/Selector.d.ts +9 -7
  127. package/dist/Selector/Selector.d.ts.map +1 -1
  128. package/dist/Selector/Selector.js +7 -5
  129. package/dist/SideNav/SideNav.d.ts +2 -1
  130. package/dist/SideNav/SideNav.d.ts.map +1 -1
  131. package/dist/SideNav/SideNav.js +9 -3
  132. package/dist/Slider/Slider.d.ts.map +1 -1
  133. package/dist/Slider/Slider.js +28 -12
  134. package/dist/Spinner/Spinner.d.ts.map +1 -1
  135. package/dist/Spinner/Spinner.js +23 -7
  136. package/dist/Switch/Switch.d.ts.map +1 -1
  137. package/dist/Switch/Switch.js +11 -0
  138. package/dist/TabList/Tab.d.ts +1 -1
  139. package/dist/TabList/Tab.d.ts.map +1 -1
  140. package/dist/TabList/Tab.js +20 -7
  141. package/dist/Table/Table.d.ts +1 -0
  142. package/dist/Table/Table.d.ts.map +1 -1
  143. package/dist/Table/Table.js +29 -13
  144. package/dist/TextArea/TextArea.d.ts.map +1 -1
  145. package/dist/TextArea/TextArea.js +1 -1
  146. package/dist/TextInput/TextInput.d.ts.map +1 -1
  147. package/dist/TextInput/TextInput.js +2 -2
  148. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  149. package/dist/TimeInput/TimeInput.js +4 -4
  150. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  151. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  152. package/dist/ToggleButton/ToggleButton.js +15 -27
  153. package/dist/TreeList/TreeList.d.ts.map +1 -1
  154. package/dist/TreeList/TreeList.js +3 -0
  155. package/dist/TreeList/TreeListItem.d.ts +6 -1
  156. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  157. package/dist/TreeList/TreeListItem.js +12 -2
  158. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  159. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  160. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  161. package/dist/Typeahead/BaseTypeahead.js +16 -7
  162. package/dist/astryx.css +18 -7
  163. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  164. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  165. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  166. package/dist/hooks/useScrollableArea.d.ts +6 -2
  167. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  168. package/dist/hooks/useScrollableArea.js +17 -5
  169. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  170. package/dist/theme/generateThemeRules.js +10 -0
  171. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  172. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  173. package/dist/utils/interactionOverlay.stylex.js +9 -0
  174. package/locales/af-ZA.json +530 -1
  175. package/locales/ar-SA.json +490 -1
  176. package/locales/ca-ES.json +514 -1
  177. package/locales/cs-CZ.json +498 -1
  178. package/locales/da-DK.json +522 -1
  179. package/locales/de-DE.json +522 -1
  180. package/locales/el-GR.json +498 -1
  181. package/locales/es-ES.json +510 -1
  182. package/locales/fi-FI.json +498 -1
  183. package/locales/fr-FR.json +534 -1
  184. package/locales/he-IL.json +498 -1
  185. package/locales/hu-HU.json +498 -1
  186. package/locales/it-IT.json +510 -1
  187. package/locales/ja-JP.json +490 -1
  188. package/locales/ko-KR.json +498 -1
  189. package/locales/nl-NL.json +534 -1
  190. package/locales/no-NO.json +510 -1
  191. package/locales/pl-PL.json +506 -1
  192. package/locales/pt-BR.json +506 -1
  193. package/locales/pt-PT.json +506 -1
  194. package/locales/ro-RO.json +506 -1
  195. package/locales/ru-RU.json +498 -1
  196. package/locales/sr-SP.json +498 -1
  197. package/locales/sv-SE.json +502 -1
  198. package/locales/tr-TR.json +498 -1
  199. package/locales/uk-UA.json +498 -1
  200. package/locales/vi-VN.json +502 -1
  201. package/locales/zh-CN.json +490 -1
  202. package/locales/zh-TW.json +490 -1
  203. package/package.json +19 -4
  204. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  205. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  206. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  207. package/src/BottomSheet/BottomSheet.tsx +3 -1
  208. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  209. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  210. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  211. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  212. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  213. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  214. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  215. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  216. package/src/Chat/ChatComposer.doc.mjs +14 -0
  217. package/src/Chat/ChatComposer.spec.md +230 -0
  218. package/src/Chat/ChatComposer.test.tsx +57 -0
  219. package/src/Chat/ChatComposer.tsx +21 -9
  220. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  221. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  222. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  223. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  224. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  225. package/src/Chat/ChatComposerInput.spec.md +185 -0
  226. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  227. package/src/Chat/ChatComposerInput.tsx +78 -13
  228. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  229. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  230. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  231. package/src/Chat/ChatDictationButton.spec.md +160 -0
  232. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  233. package/src/Chat/ChatDictationButton.tsx +8 -6
  234. package/src/Chat/ChatSendButton.test.tsx +1 -4
  235. package/src/Chat/ChatSendButton.tsx +1 -1
  236. package/src/Chat/index.ts +1 -0
  237. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  238. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  239. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  240. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  241. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  242. package/src/Collapsible/Collapsible.test.tsx +21 -0
  243. package/src/Collapsible/Collapsible.tsx +5 -0
  244. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  245. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  246. package/src/DateInput/DateInput.tsx +6 -1
  247. package/src/DateInput/NativeDateField.tsx +24 -8
  248. package/src/DateInput/TouchDateField.tsx +6 -2
  249. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  250. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  251. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  252. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  253. package/src/Dialog/DialogHeader.doc.mjs +12 -0
  254. package/src/Dialog/DialogHeader.test.tsx +97 -0
  255. package/src/Dialog/DialogHeader.tsx +25 -3
  256. package/src/Dialog/modules/DialogHeader.spec.md +75 -38
  257. package/src/Field/InputClearButton.test.tsx +17 -3
  258. package/src/Field/InputClearButton.tsx +5 -2
  259. package/src/Field/PanelSearchInput.tsx +5 -2
  260. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  261. package/src/FileInput/FileInput.tsx +11 -2
  262. package/src/Kbd/Kbd.doc.mjs +3 -3
  263. package/src/Kbd/Kbd.test.tsx +57 -1
  264. package/src/Kbd/Kbd.tsx +57 -37
  265. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  266. package/src/Link/Link.doc.mjs +11 -0
  267. package/src/Link/Link.test.tsx +24 -0
  268. package/src/Link/Link.tsx +5 -0
  269. package/src/List/List.spec.md +200 -0
  270. package/src/Markdown/Markdown.doc.mjs +272 -0
  271. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  272. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  273. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  274. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  275. package/src/Markdown/Markdown.public.test.ts +126 -3
  276. package/src/Markdown/Markdown.spec.md +197 -44
  277. package/src/Markdown/Markdown.test.tsx +11 -0
  278. package/src/Markdown/Markdown.tsx +414 -135
  279. package/src/Markdown/ast.test.ts +307 -0
  280. package/src/Markdown/ast.ts +337 -0
  281. package/src/Markdown/incremental.test.ts +12 -0
  282. package/src/Markdown/index.ts +37 -1
  283. package/src/Markdown/modules/remark.spec.md +269 -0
  284. package/src/Markdown/parser/index.ts +64 -0
  285. package/src/Markdown/parser.public.test.ts +103 -0
  286. package/src/Markdown/parser.ts +1180 -235
  287. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  288. package/src/Markdown/plugins/frontmatter.ts +198 -0
  289. package/src/Markdown/plugins/index.ts +53 -0
  290. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  291. package/src/Markdown/plugins/protocol.ts +1618 -0
  292. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  293. package/src/Markdown/plugins/semanticFence.ts +424 -0
  294. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  295. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  296. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  297. package/src/Markdown/plugins/textTransform.ts +554 -0
  298. package/src/Markdown/remark.test.tsx +1877 -0
  299. package/src/Markdown/remark.ts +1574 -0
  300. package/src/Markdown/url.ts +30 -0
  301. package/src/Markdown/utils.ts +13 -1
  302. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  303. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  304. package/src/NumberInput/NumberInput.tsx +6 -1
  305. package/src/Outline/Outline.doc.mjs +7 -5
  306. package/src/Outline/Outline.spec.md +1 -1
  307. package/src/Outline/index.ts +1 -0
  308. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  309. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  310. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  311. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  312. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  313. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  314. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  315. package/src/ProgressBar/ProgressBar.tsx +59 -72
  316. package/src/RadioList/RadioList.doc.mjs +11 -0
  317. package/src/RadioList/RadioList.test.tsx +32 -0
  318. package/src/RadioList/RadioListItem.tsx +25 -1
  319. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  320. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  321. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  322. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  323. package/src/Selector/Selector.doc.mjs +2 -2
  324. package/src/Selector/Selector.spec.md +51 -49
  325. package/src/Selector/Selector.test.tsx +61 -55
  326. package/src/Selector/Selector.tsx +23 -14
  327. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  328. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  329. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  330. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  331. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  332. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  333. package/src/SideNav/SideNav.doc.mjs +1 -1
  334. package/src/SideNav/SideNav.test.tsx +10 -0
  335. package/src/SideNav/SideNav.tsx +14 -2
  336. package/src/Slider/Slider.doc.mjs +17 -3
  337. package/src/Slider/Slider.test.tsx +221 -0
  338. package/src/Slider/Slider.tsx +40 -9
  339. package/src/Spinner/Spinner.test.tsx +4 -0
  340. package/src/Spinner/Spinner.tsx +31 -18
  341. package/src/Switch/Switch.doc.mjs +11 -0
  342. package/src/Switch/Switch.test.tsx +16 -0
  343. package/src/Switch/Switch.tsx +28 -0
  344. package/src/TabList/Tab.tsx +35 -7
  345. package/src/TabList/TabList.doc.mjs +11 -0
  346. package/src/TabList/TabList.spec.md +204 -0
  347. package/src/TabList/TabList.test.tsx +66 -0
  348. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  349. package/src/Table/Table.doc.mjs +1 -1
  350. package/src/Table/Table.spec.md +54 -40
  351. package/src/Table/Table.test.tsx +83 -10
  352. package/src/Table/Table.tsx +30 -22
  353. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  354. package/src/TextArea/TextArea.tsx +6 -1
  355. package/src/TextInput/TextInput.test.tsx +20 -1
  356. package/src/TextInput/TextInput.tsx +6 -1
  357. package/src/TimeInput/TimeInput.tsx +6 -1
  358. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  359. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  360. package/src/ToggleButton/ToggleButton.tsx +26 -33
  361. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  362. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  363. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  364. package/src/TreeList/TreeList.doc.mjs +3 -3
  365. package/src/TreeList/TreeList.spec.md +123 -75
  366. package/src/TreeList/TreeList.test.tsx +29 -0
  367. package/src/TreeList/TreeList.tsx +3 -0
  368. package/src/TreeList/TreeListItem.tsx +15 -1
  369. package/src/TreeList/TreeListTypes.ts +11 -1
  370. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  371. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  372. package/src/Typeahead/Typeahead.test.tsx +53 -0
  373. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  374. package/src/__tests__/pressState.ts +93 -0
  375. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  376. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  377. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  378. package/src/hooks/useScrollableArea.test.tsx +59 -1
  379. package/src/hooks/useScrollableArea.ts +34 -10
  380. package/src/inputFontFloor.test.ts +106 -0
  381. package/src/tailwind-theme.css +0 -6
  382. package/src/theme/generateThemeRules.test.ts +30 -0
  383. package/src/theme/generateThemeRules.ts +10 -0
  384. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -2,14 +2,17 @@
2
2
 
3
3
  /**
4
4
  * @file parser.ts
5
- * @input Markdown string
6
- * @output Array of MarkdownNode AST nodes, including opt-in math; heading slug
7
- * helpers
8
- * (inlineText, slugify, uniqueSlug) shared by Markdown rendering and
9
- * Outline's parseOutlineFromMarkdown
10
- * @position Core parser; consumed by Markdown.tsx and Outline
5
+ * @input Markdown string, released parse options, and optional ordered plugins
6
+ * @output Canonical MDAST-aligned nodes for public/server consumers plus unchanged
7
+ * released parser-node projections; shared heading slug helpers
8
+ * @position Core parser and compatibility boundary; consumed by public parser entry,
9
+ * Markdown, and Outline
11
10
  */
12
11
 
12
+ import { getMarkdownAstLegacyCodeLanguage, markMarkdownAstLegacyCodeLanguage } from "./ast.js";
13
+ import { applyMarkdownTransforms, freezeMarkdownPluginData, isMarkdownPluginData, prepareMarkdownPlugins, reportMarkdownPluginFailure } from "./plugins/protocol.js";
14
+ import { isSafeMarkdownParserUrl } from "./url.js";
15
+
13
16
  // ---------------------------------------------------------------------------
14
17
  // Types
15
18
  // ---------------------------------------------------------------------------
@@ -36,6 +39,240 @@
36
39
  * optional fields without breaking anyone.
37
40
  */
38
41
 
42
+ function projectRange(position) {
43
+ const start = position?.start.offset;
44
+ const end = position?.end.offset;
45
+ return start == null || end == null ? {} : {
46
+ range: {
47
+ start,
48
+ end
49
+ }
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Canonical nodes always carry Core-authored positions; the released block
55
+ * shape exposes them as `range` only when the caller passed `sourceRanges`,
56
+ * so this projection stays byte-for-byte what it has always returned.
57
+ *
58
+ * An extension node is the one kind the projection passes through instead of
59
+ * rebuilding field by field, so it is the one kind that could carry a
60
+ * canonical `position` out into released output. Strip it unless the caller
61
+ * asked for provenance, in which case the node keeps exactly the `position`
62
+ * it has always exposed.
63
+ */
64
+ function projectExtensionNode(node, withRanges) {
65
+ if (withRanges || node.position === undefined) {
66
+ return node;
67
+ }
68
+ const {
69
+ position: _canonicalPosition,
70
+ ...released
71
+ } = node;
72
+ return released;
73
+ }
74
+ function projectInlineNode(node, cache, withRanges) {
75
+ const cached = cache.get(node);
76
+ if (cached != null) {
77
+ return cached;
78
+ }
79
+ let projected;
80
+ switch (node.type) {
81
+ case 'text':
82
+ projected = {
83
+ type: 'text',
84
+ content: node.value
85
+ };
86
+ break;
87
+ case 'strong':
88
+ projected = {
89
+ type: 'bold',
90
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges))
91
+ };
92
+ break;
93
+ case 'emphasis':
94
+ projected = {
95
+ type: 'italic',
96
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges))
97
+ };
98
+ break;
99
+ case 'delete':
100
+ projected = {
101
+ type: 'strikethrough',
102
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges))
103
+ };
104
+ break;
105
+ case 'inlineCode':
106
+ projected = {
107
+ type: 'code',
108
+ content: node.value
109
+ };
110
+ break;
111
+ case 'inlineMath':
112
+ projected = {
113
+ type: 'math',
114
+ value: node.value
115
+ };
116
+ break;
117
+ case 'link':
118
+ projected = {
119
+ type: 'link',
120
+ href: node.url,
121
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges))
122
+ };
123
+ break;
124
+ case 'image':
125
+ projected = {
126
+ type: 'image',
127
+ src: node.url,
128
+ alt: node.alt
129
+ };
130
+ break;
131
+ case 'citation':
132
+ projected = {
133
+ type: 'citation',
134
+ sourceId: node.sourceId
135
+ };
136
+ break;
137
+ case 'break':
138
+ projected = {
139
+ type: 'break'
140
+ };
141
+ break;
142
+ case 'extension':
143
+ projected = projectExtensionNode(node, withRanges);
144
+ break;
145
+ }
146
+ cache.set(node, projected);
147
+ return projected;
148
+ }
149
+ function projectTableCell(node, cache, withRanges) {
150
+ const cached = cache.get(node);
151
+ if (cached != null) {
152
+ return cached;
153
+ }
154
+ const projected = {
155
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges))
156
+ };
157
+ cache.set(node, projected);
158
+ return projected;
159
+ }
160
+ function projectListItem(node, cache, withRanges) {
161
+ const cached = cache.get(node);
162
+ if (cached != null) {
163
+ return cached;
164
+ }
165
+ const projected = {
166
+ checked: node.checked,
167
+ children: node.children.map(child => projectBlockNode(child, cache, withRanges))
168
+ };
169
+ cache.set(node, projected);
170
+ return projected;
171
+ }
172
+ function projectBlockNode(node, cache, withRanges) {
173
+ const cached = cache.get(node);
174
+ if (cached != null) {
175
+ return cached;
176
+ }
177
+ const metadata = withRanges ? projectRange(node.position) : {};
178
+ let projected;
179
+ switch (node.type) {
180
+ case 'heading':
181
+ projected = {
182
+ type: 'heading',
183
+ level: node.depth,
184
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
185
+ ...metadata
186
+ };
187
+ break;
188
+ case 'paragraph':
189
+ projected = {
190
+ type: 'paragraph',
191
+ children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
192
+ ...metadata
193
+ };
194
+ break;
195
+ case 'code':
196
+ projected = {
197
+ type: 'codeblock',
198
+ language: getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext',
199
+ content: node.value,
200
+ ...metadata
201
+ };
202
+ break;
203
+ case 'math':
204
+ projected = {
205
+ type: 'math',
206
+ value: node.value,
207
+ ...metadata
208
+ };
209
+ break;
210
+ case 'blockquote':
211
+ projected = {
212
+ type: 'blockquote',
213
+ children: node.children.map(child => projectBlockNode(child, cache, withRanges)),
214
+ ...metadata
215
+ };
216
+ break;
217
+ case 'list':
218
+ projected = {
219
+ type: 'list',
220
+ ordered: node.ordered,
221
+ start: node.start,
222
+ delimiter: node.delimiter,
223
+ loose: node.spread,
224
+ items: node.children.map(item => projectListItem(item, cache, withRanges)),
225
+ ...metadata
226
+ };
227
+ break;
228
+ case 'table':
229
+ {
230
+ const [header = {
231
+ type: 'tableRow',
232
+ children: []
233
+ }, ...rows] = node.children;
234
+ projected = {
235
+ type: 'table',
236
+ headers: header.children.map(cell => projectTableCell(cell, cache, withRanges)),
237
+ alignments: [...node.align],
238
+ rows: rows.map(row => row.children.map(cell => projectTableCell(cell, cache, withRanges))),
239
+ ...metadata
240
+ };
241
+ break;
242
+ }
243
+ case 'thematicBreak':
244
+ projected = {
245
+ type: 'hr',
246
+ ...metadata
247
+ };
248
+ break;
249
+ case 'image':
250
+ projected = {
251
+ type: 'image',
252
+ alt: node.alt,
253
+ src: node.url,
254
+ ...metadata
255
+ };
256
+ break;
257
+ case 'extension':
258
+ projected = projectExtensionNode(node, withRanges);
259
+ break;
260
+ }
261
+ cache.set(node, projected);
262
+ return projected;
263
+ }
264
+ function projectInlineNodes(nodes, withRanges, cache = new WeakMap()) {
265
+ return nodes.map(node => projectInlineNode(node, cache, withRanges));
266
+ }
267
+ function projectMarkdownRoot(root, withRanges, cache = new WeakMap()) {
268
+ return root.children.map(node => projectBlockNode(node, cache, withRanges));
269
+ }
270
+
271
+ /** Whether a caller asked for the released `range` field on its blocks. */
272
+ function wantsLegacyRanges(arg) {
273
+ return arg != null && typeof arg.has !== 'function' && arg.sourceRanges === true;
274
+ }
275
+
39
276
  // ---------------------------------------------------------------------------
40
277
  // Parse options
41
278
  // ---------------------------------------------------------------------------
@@ -56,13 +293,37 @@
56
293
  * from silently widening an exhaustive node switch.
57
294
  */
58
295
 
59
- const EMPTY_OPTS = {
60
- sourceIds: undefined,
61
- autolink: undefined
62
- };
63
- function resolveOptions(arg) {
296
+ /**
297
+ * Every resolved options object is built here, so all of them share one key
298
+ * set in one order — and therefore one hidden class. The parser reads these
299
+ * fields in its hottest loops; letting an omitted-options parse and an
300
+ * options-bag parse produce differently-shaped objects makes those reads
301
+ * polymorphic in any process that does both, which is exactly what a caller
302
+ * comparing an empty pipeline against a configured one does.
303
+ */
304
+ function makeResolvedOptions(fields) {
305
+ return {
306
+ sourceIds: fields.sourceIds,
307
+ autolink: fields.autolink,
308
+ math: fields.math,
309
+ sourceRanges: fields.sourceRanges,
310
+ astPositions: fields.astPositions ?? true,
311
+ plugins: fields.plugins,
312
+ isFinal: fields.isFinal,
313
+ allowBlockSyntax: fields.allowBlockSyntax ?? true,
314
+ baseOffset: fields.baseOffset,
315
+ linkDefs: fields.linkDefs
316
+ };
317
+ }
318
+ const EMPTY_OPTS = makeResolvedOptions({
319
+ isFinal: true
320
+ });
321
+ const EMPTY_INCREMENTAL_OPTS = makeResolvedOptions({
322
+ isFinal: false
323
+ });
324
+ function resolveOptions(arg, incremental = false) {
64
325
  if (arg == null) {
65
- return EMPTY_OPTS;
326
+ return incremental ? EMPTY_INCREMENTAL_OPTS : EMPTY_OPTS;
66
327
  }
67
328
  // Duck-type the legacy `ReadonlySet<string>` form: any object whose
68
329
  // `.has` is callable is treated as the legacy sourceIds set. This is
@@ -70,18 +331,20 @@ function resolveOptions(arg) {
70
331
  // or polyfilled `ReadonlySet` implementations as a `ParseOptions` bag
71
332
  // and silently lose citation resolution.
72
333
  if (typeof arg.has === 'function') {
73
- return {
334
+ return makeResolvedOptions({
74
335
  sourceIds: arg,
75
- autolink: undefined
76
- };
336
+ isFinal: !incremental
337
+ });
77
338
  }
78
339
  const opts = arg;
79
- return {
340
+ return makeResolvedOptions({
80
341
  sourceIds: opts.sourceIds,
81
342
  autolink: opts.autolink,
82
343
  math: opts.math === true ? true : undefined,
83
- sourceRanges: opts.sourceRanges
84
- };
344
+ sourceRanges: opts.sourceRanges,
345
+ plugins: opts.plugins != null && opts.plugins.length > 0 ? prepareMarkdownPlugins(opts.plugins) : undefined,
346
+ isFinal: incremental ? opts.isFinal === true : true
347
+ });
85
348
  }
86
349
 
87
350
  // ---------------------------------------------------------------------------
@@ -325,6 +588,11 @@ function extractLinkDefinitions(input, math = false) {
325
588
  };
326
589
  }
327
590
 
591
+ /** Order-independent signature of a citation-source set, for cache checks. */
592
+ function sourceIdsSignature(sourceIds) {
593
+ return sourceIds == null || sourceIds.size === 0 ? '' : [...sourceIds].sort().join('\u0000');
594
+ }
595
+
328
596
  /** Order-independent signature of a link-definition set, for cache checks. */
329
597
  function linkDefsSignature(defs) {
330
598
  if (defs.size === 0) {
@@ -339,6 +607,12 @@ function linkDefsSignature(defs) {
339
607
  * Returns the node plus the index just past the reference, or null when it is
340
608
  * not a resolvable reference (caller falls through to literal handling).
341
609
  */
610
+ function protectedInlineOptions(opts) {
611
+ return opts.plugins == null ? opts : {
612
+ ...opts,
613
+ plugins: undefined
614
+ };
615
+ }
342
616
  function matchReferenceLink(text, start, linkDefs, opts) {
343
617
  const textClose = text.indexOf(']', start + 1);
344
618
  if (textClose === -1) {
@@ -355,12 +629,12 @@ function matchReferenceLink(text, start, linkDefs, opts) {
355
629
  // matches nothing.
356
630
  const label = rawLabel === '' ? linkText : rawLabel;
357
631
  const href = linkDefs.get(normalizeLinkLabel(label));
358
- if (href != null && isSafeUrl(href)) {
632
+ if (href != null && isSafeMarkdownParserUrl(href)) {
359
633
  return {
360
634
  node: {
361
635
  type: 'link',
362
- href,
363
- children: parseInlineImpl(linkText, opts)
636
+ url: href,
637
+ children: parseInlineImpl(linkText, protectedInlineOptions(opts))
364
638
  },
365
639
  end: labelClose + 1
366
640
  };
@@ -374,14 +648,14 @@ function matchReferenceLink(text, start, linkDefs, opts) {
374
648
  return null;
375
649
  }
376
650
  const href = linkDefs.get(normalizeLinkLabel(linkText));
377
- if (href == null || !isSafeUrl(href)) {
651
+ if (href == null || !isSafeMarkdownParserUrl(href)) {
378
652
  return null;
379
653
  }
380
654
  return {
381
655
  node: {
382
656
  type: 'link',
383
- href,
384
- children: parseInlineImpl(linkText, opts)
657
+ url: href,
658
+ children: parseInlineImpl(linkText, protectedInlineOptions(opts))
385
659
  },
386
660
  end: textClose + 1
387
661
  };
@@ -400,11 +674,11 @@ function matchReferenceImage(text, start, linkDefs) {
400
674
  const rawLabel = text.slice(altClose + 2, labelClose);
401
675
  const label = rawLabel === '' ? alt : rawLabel;
402
676
  const src = linkDefs.get(normalizeLinkLabel(label));
403
- if (src != null && isSafeUrl(src)) {
677
+ if (src != null && isSafeMarkdownParserUrl(src)) {
404
678
  return {
405
679
  node: {
406
680
  type: 'image',
407
- src,
681
+ url: src,
408
682
  alt
409
683
  },
410
684
  end: labelClose + 1
@@ -417,13 +691,13 @@ function matchReferenceImage(text, start, linkDefs) {
417
691
  return null;
418
692
  }
419
693
  const src = linkDefs.get(normalizeLinkLabel(alt));
420
- if (src == null || !isSafeUrl(src)) {
694
+ if (src == null || !isSafeMarkdownParserUrl(src)) {
421
695
  return null;
422
696
  }
423
697
  return {
424
698
  node: {
425
699
  type: 'image',
426
- src,
700
+ url: src,
427
701
  alt
428
702
  },
429
703
  end: altClose + 1
@@ -500,26 +774,6 @@ function findInlineMathEnd(text, start) {
500
774
  }
501
775
 
502
776
  // ---------------------------------------------------------------------------
503
- // URL scheme sanitization
504
- // ---------------------------------------------------------------------------
505
-
506
- /**
507
- * Reject URLs with dangerous schemes (javascript:, vbscript:, data:) that
508
- * could execute arbitrary code when rendered as link hrefs or image srcs.
509
- * Returns true if the URL is safe to use, false otherwise.
510
- */
511
- function isSafeUrl(url) {
512
- // Trim and collapse whitespace/control chars that browsers tolerate but
513
- // could bypass a naive prefix check (e.g. "java\nscript:alert(1)").
514
- // eslint-disable-next-line no-control-regex -- control chars are the bypass
515
- const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
516
- const lower = normalized.toLowerCase();
517
- if (lower.startsWith('javascript:') || lower.startsWith('vbscript:') || lower.startsWith('data:text/html')) {
518
- return false;
519
- }
520
- return true;
521
- }
522
-
523
777
  // ---------------------------------------------------------------------------
524
778
  // Inline parser
525
779
  // ---------------------------------------------------------------------------
@@ -570,8 +824,137 @@ function matchBracketCitation(text, i, opts) {
570
824
  end: closeIndex + 1
571
825
  };
572
826
  }
827
+ function currentLineStart(source, offset) {
828
+ return source.lastIndexOf('\n', offset - 1) + 1;
829
+ }
830
+ function matchExtensionSyntax(source, offset, context, opts) {
831
+ const byFirstCharacter = context === 'inline' ? opts.plugins?.inlineByFirstCharacter : opts.allowBlockSyntax === false ? undefined : opts.plugins?.blockByFirstCharacter;
832
+ const candidates = byFirstCharacter?.get(source[offset]);
833
+ if (candidates == null) {
834
+ return {
835
+ status: 'none'
836
+ };
837
+ }
838
+ const seen = new Set();
839
+ for (const candidate of candidates) {
840
+ if (seen.has(candidate)) {
841
+ continue;
842
+ }
843
+ seen.add(candidate);
844
+ let fullPrefix = false;
845
+ for (const prefix of candidate.contribution.startsWith) {
846
+ if (source.startsWith(prefix, offset)) {
847
+ fullPrefix = true;
848
+ break;
849
+ }
850
+ if (!opts.isFinal) {
851
+ const remaining = source.slice(offset);
852
+ if (remaining.length < prefix.length && prefix.startsWith(remaining)) {
853
+ return {
854
+ status: 'defer'
855
+ };
856
+ }
857
+ }
858
+ }
859
+ if (!fullPrefix) {
860
+ continue;
861
+ }
862
+ const end = Math.min(source.length, offset + candidate.contribution.maxSpan);
863
+ let result;
864
+ try {
865
+ result = candidate.contribution.tokenize({
866
+ source: end === source.length ? source : source.slice(0, end),
867
+ offset,
868
+ end,
869
+ isFinal: opts.isFinal,
870
+ context,
871
+ lineStart: currentLineStart(source, offset),
872
+ column: offset - currentLineStart(source, offset)
873
+ });
874
+ } catch (error) {
875
+ reportMarkdownPluginFailure(candidate.pluginName, 'syntax', error);
876
+ continue;
877
+ }
878
+ if (result == null || typeof result !== 'object' || typeof result.then === 'function') {
879
+ reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid result'));
880
+ continue;
881
+ }
882
+ if (result.status === 'no-match') {
883
+ continue;
884
+ }
885
+ if (result.status === 'defer') {
886
+ return !opts.isFinal && end === source.length && source.length - offset < candidate.contribution.maxSpan ? {
887
+ status: 'defer'
888
+ } : {
889
+ status: 'none'
890
+ };
891
+ }
892
+ if (result.status !== 'match' || !Number.isInteger(result.end) || result.end <= offset || result.end > end || result.node.type !== 'extension' || result.node.plugin !== candidate.pluginName || result.node.display !== context || result.node.name.trim() === '' || !isMarkdownPluginData(result.node.data) || !opts.plugins?.renderers.has(`${candidate.pluginName}\0${result.node.name}`)) {
893
+ reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid extension node'));
894
+ continue;
895
+ }
896
+ const rawNode = result.node;
897
+ const {
898
+ source: _ignoredSource,
899
+ position: _ignoredPosition,
900
+ ...safeNode
901
+ } = rawNode;
902
+ const absoluteStart = (opts.baseOffset ?? 0) + offset;
903
+ const absoluteEnd = (opts.baseOffset ?? 0) + result.end;
904
+ return {
905
+ status: 'match',
906
+ end: result.end,
907
+ node: Object.freeze({
908
+ ...safeNode,
909
+ data: freezeMarkdownPluginData(result.node.data),
910
+ source: source.slice(offset, result.end),
911
+ ...(opts.sourceRanges && context === 'block' ? {
912
+ position: Object.freeze({
913
+ start: Object.freeze({
914
+ offset: absoluteStart
915
+ }),
916
+ end: Object.freeze({
917
+ offset: absoluteEnd
918
+ })
919
+ })
920
+ } : null)
921
+ })
922
+ };
923
+ }
924
+ return {
925
+ status: 'none'
926
+ };
927
+ }
573
928
  export function parseInline(text, arg) {
574
- return parseInlineEntry(text, resolveOptions(arg));
929
+ return projectInlineNodes(parseInlineAstRuntime(text, arg), wantsLegacyRanges(arg));
930
+ }
931
+
932
+ /**
933
+ * Parse inline Markdown into the canonical immutable Astryx AST.
934
+ *
935
+ * Unlike `parseInline`, this returns MDAST-aligned node names and fields. Math
936
+ * nodes are present only when `math: true`, and plugin extension nodes are
937
+ * inferred from the ordered `plugins` tuple.
938
+ */
939
+
940
+ export function parseInlineAst(text, arg) {
941
+ return parseInlineAstRuntime(text, arg);
942
+ }
943
+ function parseInlineAstRuntime(text, arg) {
944
+ const opts = resolveOptions(arg);
945
+ const nodes = parseInlineEntry(text, opts);
946
+ if ((opts.plugins?.transforms.length ?? 0) === 0) {
947
+ return nodes;
948
+ }
949
+ const transformed = applyMarkdownTransforms({
950
+ type: 'root',
951
+ children: [{
952
+ type: 'paragraph',
953
+ children: nodes
954
+ }]
955
+ }, opts.plugins, text, opts.isFinal, 'inline');
956
+ const paragraph = transformed.children[0];
957
+ return paragraph?.type === 'paragraph' ? [...paragraph.children] : nodes;
575
958
  }
576
959
 
577
960
  /**
@@ -589,13 +972,18 @@ function parseInlineEntry(text, opts) {
589
972
  }
590
973
  function parseInlineImpl(text, opts) {
591
974
  const nodes = [];
975
+ // Only a plugin that actually contributes INLINE syntax may cost anything
976
+ // per source position. A transform-only list contributes none, so it takes
977
+ // the same path as an omitted or empty one: no candidate probe per
978
+ // position, and no map lookup per character in the plain-text scan below.
979
+ const inlineExtensionStarts = opts.plugins != null && opts.plugins.inlineByFirstCharacter.size > 0 ? opts.plugins.inlineByFirstCharacter : undefined;
592
980
  let i = 0;
593
981
  while (i < text.length) {
594
982
  // --- Escape ---
595
983
  if (text[i] === '\\' && i + 1 < text.length) {
596
984
  nodes.push({
597
985
  type: 'text',
598
- content: text[i + 1]
986
+ value: text[i + 1]
599
987
  });
600
988
  i += 2;
601
989
  continue;
@@ -608,8 +996,8 @@ function parseInlineImpl(text, opts) {
608
996
  const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
609
997
  if (closeIndex !== -1) {
610
998
  nodes.push({
611
- type: 'code',
612
- content: text.slice(openIndex, closeIndex)
999
+ type: 'inlineCode',
1000
+ value: text.slice(openIndex, closeIndex)
613
1001
  });
614
1002
  i = closeIndex + tickCount;
615
1003
  continue;
@@ -621,7 +1009,7 @@ function parseInlineImpl(text, opts) {
621
1009
  const closeIndex = findInlineMathEnd(text, i);
622
1010
  if (closeIndex !== -1) {
623
1011
  nodes.push({
624
- type: 'math',
1012
+ type: 'inlineMath',
625
1013
  value: text.slice(i + 1, closeIndex)
626
1014
  });
627
1015
  i = closeIndex + 1;
@@ -649,16 +1037,16 @@ function parseInlineImpl(text, opts) {
649
1037
  const srcClose = findClosingParen(text, altClose + 2);
650
1038
  if (srcClose !== -1) {
651
1039
  const src = text.slice(altClose + 2, srcClose);
652
- if (!isSafeUrl(src)) {
1040
+ if (!isSafeMarkdownParserUrl(src)) {
653
1041
  // Dangerous scheme — emit as plain text.
654
1042
  nodes.push({
655
1043
  type: 'text',
656
- content: text.slice(i, srcClose + 1)
1044
+ value: text.slice(i, srcClose + 1)
657
1045
  });
658
1046
  } else {
659
1047
  nodes.push({
660
1048
  type: 'image',
661
- src,
1049
+ url: src,
662
1050
  alt: text.slice(i + 2, altClose)
663
1051
  });
664
1052
  }
@@ -698,17 +1086,17 @@ function parseInlineImpl(text, opts) {
698
1086
  const urlClose = findClosingParen(text, textClose + 2);
699
1087
  if (urlClose !== -1) {
700
1088
  const href = text.slice(textClose + 2, urlClose);
701
- if (!isSafeUrl(href)) {
1089
+ if (!isSafeMarkdownParserUrl(href)) {
702
1090
  // Dangerous scheme — emit as plain text instead of a link.
703
1091
  nodes.push({
704
1092
  type: 'text',
705
- content: text.slice(i, urlClose + 1)
1093
+ value: text.slice(i, urlClose + 1)
706
1094
  });
707
1095
  } else {
708
1096
  nodes.push({
709
1097
  type: 'link',
710
- href,
711
- children: parseInlineImpl(text.slice(i + 1, textClose), opts)
1098
+ url: href,
1099
+ children: parseInlineImpl(text.slice(i + 1, textClose), protectedInlineOptions(opts))
712
1100
  });
713
1101
  }
714
1102
  i = urlClose + 1;
@@ -737,9 +1125,9 @@ function parseInlineImpl(text, opts) {
737
1125
  const closeIndex = text.indexOf(marker, i + 3);
738
1126
  if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 3]))) {
739
1127
  nodes.push({
740
- type: 'bold',
1128
+ type: 'strong',
741
1129
  children: [{
742
- type: 'italic',
1130
+ type: 'emphasis',
743
1131
  children: parseInlineImpl(text.slice(i + 3, closeIndex), opts)
744
1132
  }]
745
1133
  });
@@ -759,7 +1147,7 @@ function parseInlineImpl(text, opts) {
759
1147
  const closeIndex = text.indexOf(marker, i + 2);
760
1148
  if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 2]))) {
761
1149
  nodes.push({
762
- type: 'bold',
1150
+ type: 'strong',
763
1151
  children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
764
1152
  });
765
1153
  i = closeIndex + 2;
@@ -773,7 +1161,7 @@ function parseInlineImpl(text, opts) {
773
1161
  const closeIndex = text.indexOf('~~', i + 2);
774
1162
  if (closeIndex !== -1) {
775
1163
  nodes.push({
776
- type: 'strikethrough',
1164
+ type: 'delete',
777
1165
  children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
778
1166
  });
779
1167
  i = closeIndex + 2;
@@ -790,7 +1178,7 @@ function parseInlineImpl(text, opts) {
790
1178
  const closeIndex = text.indexOf(text[i], i + 1);
791
1179
  if (closeIndex !== -1 && closeIndex > i + 1 && (!isUnderscore || !isWordChar(text[closeIndex + 1]))) {
792
1180
  nodes.push({
793
- type: 'italic',
1181
+ type: 'emphasis',
794
1182
  children: parseInlineImpl(text.slice(i + 1, closeIndex), opts)
795
1183
  });
796
1184
  i = closeIndex + 1;
@@ -799,9 +1187,22 @@ function parseInlineImpl(text, opts) {
799
1187
  }
800
1188
  }
801
1189
 
1190
+ // --- Extension syntax (built-ins and protected contexts win) ---
1191
+ if (inlineExtensionStarts !== undefined) {
1192
+ const extension = matchExtensionSyntax(text, i, 'inline', opts);
1193
+ if (extension.status === 'match') {
1194
+ nodes.push(extension.node);
1195
+ i = extension.end;
1196
+ continue;
1197
+ }
1198
+ if (extension.status === 'defer') {
1199
+ break;
1200
+ }
1201
+ }
1202
+
802
1203
  // --- Plain text (with line-break detection) ---
803
1204
  let end = i + 1;
804
- while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end]) && !(opts.math && text[end] === '$')) {
1205
+ while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end]) && !(opts.math && text[end] === '$') && (inlineExtensionStarts === undefined || !inlineExtensionStarts.has(text[end]))) {
805
1206
  end++;
806
1207
  }
807
1208
  const content = text.slice(i, end);
@@ -813,11 +1214,14 @@ function parseInlineImpl(text, opts) {
813
1214
  if (trimmed.length > 0) {
814
1215
  const last = nodes[nodes.length - 1];
815
1216
  if (last?.type === 'text') {
816
- last.content += trimmed;
1217
+ nodes[nodes.length - 1] = {
1218
+ ...last,
1219
+ value: last.value + trimmed
1220
+ };
817
1221
  } else {
818
1222
  nodes.push({
819
1223
  type: 'text',
820
- content: trimmed
1224
+ value: trimmed
821
1225
  });
822
1226
  }
823
1227
  }
@@ -830,11 +1234,14 @@ function parseInlineImpl(text, opts) {
830
1234
  }
831
1235
  const last = nodes[nodes.length - 1];
832
1236
  if (last?.type === 'text') {
833
- last.content += content;
1237
+ nodes[nodes.length - 1] = {
1238
+ ...last,
1239
+ value: last.value + content
1240
+ };
834
1241
  } else {
835
1242
  nodes.push({
836
1243
  type: 'text',
837
- content
1244
+ value: content
838
1245
  });
839
1246
  }
840
1247
  i = end;
@@ -938,7 +1345,7 @@ function scanAutolinksInText(text) {
938
1345
  while ((m = re.exec(text)) !== null) {
939
1346
  const url = m[1];
940
1347
  // Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
941
- if (!isSafeUrl(url)) {
1348
+ if (!isSafeMarkdownParserUrl(url)) {
942
1349
  continue;
943
1350
  }
944
1351
  matches.push({
@@ -1063,7 +1470,7 @@ function splitTextOnAutolinks(content) {
1063
1470
  if (matches.length === 0) {
1064
1471
  return [{
1065
1472
  type: 'text',
1066
- content
1473
+ value: content
1067
1474
  }];
1068
1475
  }
1069
1476
  const out = [];
@@ -1072,15 +1479,15 @@ function splitTextOnAutolinks(content) {
1072
1479
  if (m.start > cursor) {
1073
1480
  out.push({
1074
1481
  type: 'text',
1075
- content: content.slice(cursor, m.start)
1482
+ value: content.slice(cursor, m.start)
1076
1483
  });
1077
1484
  }
1078
1485
  out.push({
1079
1486
  type: 'link',
1080
- href: m.href,
1487
+ url: m.href,
1081
1488
  children: [{
1082
1489
  type: 'text',
1083
- content: m.display
1490
+ value: m.display
1084
1491
  }]
1085
1492
  });
1086
1493
  cursor = m.end;
@@ -1088,7 +1495,7 @@ function splitTextOnAutolinks(content) {
1088
1495
  if (cursor < content.length) {
1089
1496
  out.push({
1090
1497
  type: 'text',
1091
- content: content.slice(cursor)
1498
+ value: content.slice(cursor)
1092
1499
  });
1093
1500
  }
1094
1501
  return out;
@@ -1106,16 +1513,19 @@ function transformAutolinks(nodes) {
1106
1513
  const out = [];
1107
1514
  for (const node of nodes) {
1108
1515
  if (node.type === 'text') {
1109
- const split = splitTextOnAutolinks(node.content);
1516
+ const split = splitTextOnAutolinks(node.value);
1110
1517
  for (const seg of split) {
1111
1518
  const last = out[out.length - 1];
1112
1519
  if (seg.type === 'text' && last?.type === 'text') {
1113
- last.content += seg.content;
1520
+ out[out.length - 1] = {
1521
+ ...last,
1522
+ value: last.value + seg.value
1523
+ };
1114
1524
  } else {
1115
1525
  out.push(seg);
1116
1526
  }
1117
1527
  }
1118
- } else if (node.type === 'bold' || node.type === 'italic' || node.type === 'strikethrough') {
1528
+ } else if (node.type === 'strong' || node.type === 'emphasis' || node.type === 'delete') {
1119
1529
  out.push({
1120
1530
  ...node,
1121
1531
  children: transformAutolinks(node.children)
@@ -1200,10 +1610,19 @@ function isTableSeparator(line) {
1200
1610
  * offset into it would not address the document.
1201
1611
  */
1202
1612
  function nested(opts) {
1203
- return opts.sourceRanges ? {
1613
+ const nestedOptions = opts.allowBlockSyntax === false ? opts : {
1204
1614
  ...opts,
1205
- sourceRanges: false
1206
- } : opts;
1615
+ allowBlockSyntax: false
1616
+ };
1617
+ return nestedOptions.sourceRanges || nestedOptions.astPositions ? {
1618
+ ...nestedOptions,
1619
+ sourceRanges: false,
1620
+ astPositions: false
1621
+ } : nestedOptions;
1622
+ }
1623
+ function blockExtensionColumn(line) {
1624
+ const indentation = line.length - line.trimStart().length;
1625
+ return indentation <= 3 ? indentation : null;
1207
1626
  }
1208
1627
 
1209
1628
  /**
@@ -1271,6 +1690,7 @@ function splitTableRow(line) {
1271
1690
  }
1272
1691
  function parseTable(lines, lineIndex, opts) {
1273
1692
  const headers = splitTableRow(lines[lineIndex]).map(cell => ({
1693
+ type: 'tableCell',
1274
1694
  children: parseInlineEntry(cell, opts)
1275
1695
  }));
1276
1696
  const alignments = splitTableRow(lines[lineIndex + 1]).map(cell => {
@@ -1282,17 +1702,24 @@ function parseTable(lines, lineIndex, opts) {
1282
1702
  const rows = [];
1283
1703
  let rowIndex = lineIndex + 2;
1284
1704
  while (rowIndex < lines.length && lines[rowIndex].includes('|') && lines[rowIndex].trim() !== '') {
1285
- rows.push(splitTableRow(lines[rowIndex]).map(cell => ({
1286
- children: parseInlineEntry(cell, opts)
1287
- })));
1705
+ rows.push({
1706
+ type: 'tableRow',
1707
+ children: splitTableRow(lines[rowIndex]).map(cell => ({
1708
+ type: 'tableCell',
1709
+ children: parseInlineEntry(cell, opts)
1710
+ }))
1711
+ });
1288
1712
  rowIndex++;
1289
1713
  }
1714
+ const header = {
1715
+ type: 'tableRow',
1716
+ children: headers
1717
+ };
1290
1718
  return {
1291
1719
  node: {
1292
1720
  type: 'table',
1293
- headers,
1294
- alignments,
1295
- rows
1721
+ align: alignments,
1722
+ children: [header, ...rows]
1296
1723
  },
1297
1724
  nextIndex: rowIndex
1298
1725
  };
@@ -1335,6 +1762,7 @@ function parseList(lines, startIndex, ordered, opts) {
1335
1762
  itemText += '\n' + deindented.join('\n');
1336
1763
  }
1337
1764
  items.push({
1765
+ type: 'listItem',
1338
1766
  checked,
1339
1767
  children: parseMarkdownImpl(itemText, nested(opts))
1340
1768
  });
@@ -1351,15 +1779,16 @@ function parseList(lines, startIndex, ordered, opts) {
1351
1779
  index = lookahead;
1352
1780
  }
1353
1781
  }
1782
+ const node = {
1783
+ type: 'list',
1784
+ ordered,
1785
+ start,
1786
+ delimiter: ordered ? delim : undefined,
1787
+ spread: loose || undefined,
1788
+ children: items
1789
+ };
1354
1790
  return {
1355
- node: {
1356
- type: 'list',
1357
- ordered,
1358
- start,
1359
- delimiter: ordered ? delim : undefined,
1360
- loose: loose || undefined,
1361
- items
1362
- },
1791
+ node,
1363
1792
  nextIndex: index
1364
1793
  };
1365
1794
  }
@@ -1369,7 +1798,33 @@ function parseList(lines, startIndex, ordered, opts) {
1369
1798
  // ---------------------------------------------------------------------------
1370
1799
 
1371
1800
  export function parseMarkdown(input, arg) {
1372
- return parseMarkdownImpl(input, resolveOptions(arg));
1801
+ return projectMarkdownRoot(parseMarkdownAstInternal(input, arg, true), wantsLegacyRanges(arg));
1802
+ }
1803
+
1804
+ /**
1805
+ * Parse block Markdown into the canonical immutable Astryx AST.
1806
+ *
1807
+ * Unlike `parseMarkdown`, this returns the MDAST-aligned root used by Markdown,
1808
+ * transforms, and Outline. Math nodes are present only when `math: true`, and
1809
+ * plugin extension nodes are inferred from the ordered `plugins` tuple.
1810
+ */
1811
+
1812
+ export function parseMarkdownAst(input, arg) {
1813
+ return parseMarkdownAstInternal(input, arg, true);
1814
+ }
1815
+
1816
+ /** @internal Full-parse entry point with streaming finality for Outline. */
1817
+ export function parseMarkdownAstInternal(input, arg, isFinal) {
1818
+ const resolved = resolveOptions(arg);
1819
+ const opts = isFinal ? resolved : {
1820
+ ...resolved,
1821
+ isFinal: false
1822
+ };
1823
+ const root = {
1824
+ type: 'root',
1825
+ children: parseMarkdownImpl(input, opts)
1826
+ };
1827
+ return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
1373
1828
  }
1374
1829
  function parseMarkdownImpl(input, baseOpts) {
1375
1830
  // Collect this input's link reference definitions and strip their lines,
@@ -1398,14 +1853,26 @@ function parseMarkdownImpl(input, baseOpts) {
1398
1853
  linkDefs
1399
1854
  } : baseOpts;
1400
1855
  const lines = cleaned.split('\n');
1856
+ const hasBlockExtensionSyntax = opts.allowBlockSyntax !== false && (opts.plugins?.blockByFirstCharacter.size ?? 0) > 0;
1857
+ // Derived from the already-split lines rather than a second character
1858
+ // scan: every line contributes its own length plus the newline it ended on.
1859
+ const lineOffsets = [0];
1860
+ if (opts.astPositions === true || hasBlockExtensionSyntax) {
1861
+ let offset = 0;
1862
+ for (let index = 0; index < lines.length; index++) {
1863
+ offset += lines[index].length + 1;
1864
+ lineOffsets.push(offset);
1865
+ }
1866
+ }
1867
+ const blockExtensionMatches = new Map();
1401
1868
  const blocks = [];
1402
1869
  // The line each block started on, parallel to `blocks`. Only collected when
1403
1870
  // ranges were asked for; a block's end is resolved after the loop, since the
1404
1871
  // branch that produced it has already moved `index` past whatever it read.
1405
- const blockStartLines = opts.sourceRanges ? [] : null;
1872
+ const blockStartLines = opts.astPositions === true ? [] : null;
1406
1873
  // Set only by a block that consumes blank lines as content, where the
1407
1874
  // positional end derivation would trim them away.
1408
- const blockEndLines = opts.sourceRanges ? [] : null;
1875
+ const blockEndLines = opts.astPositions === true ? [] : null;
1409
1876
  let blockStartLine = 0;
1410
1877
  const pushBlock = (node, endLine) => {
1411
1878
  blocks.push(node);
@@ -1422,10 +1889,13 @@ function parseMarkdownImpl(input, baseOpts) {
1422
1889
  }
1423
1890
 
1424
1891
  // --- Fenced code block ---
1425
- const fenceMatch = line.match(/^(`{3,}|~{3,})(\w*)/);
1892
+ const fenceMatch = line.match(/^(`{3,}|~{3,})/);
1426
1893
  if (fenceMatch) {
1427
1894
  const fence = fenceMatch[1];
1428
- const language = fenceMatch[2] || 'plaintext';
1895
+ const info = line.slice(fence.length);
1896
+ const language = info.match(/^(\S+)/)?.[1] ?? null;
1897
+ const legacyLanguage = info.match(/^(\w*)/)?.[1] || null;
1898
+ const meta = language == null ? undefined : info.slice(language.length).trim() || undefined;
1429
1899
  const codeLines = [];
1430
1900
  index++;
1431
1901
  while (index < lines.length && !lines[index].startsWith(fence)) {
@@ -1436,11 +1906,14 @@ function parseMarkdownImpl(input, baseOpts) {
1436
1906
  // A fence owns its blank lines, and an unterminated one (mid-stream)
1437
1907
  // can end on them, so it states its own end rather than letting the
1438
1908
  // positional derivation trim them off.
1439
- pushBlock({
1440
- type: 'codeblock',
1441
- language,
1442
- content: codeLines.join('\n')
1443
- }, Math.min(index, lines.length) - 1);
1909
+ pushBlock(markMarkdownAstLegacyCodeLanguage({
1910
+ type: 'code',
1911
+ lang: language,
1912
+ ...(meta == null ? {} : {
1913
+ meta
1914
+ }),
1915
+ value: codeLines.join('\n')
1916
+ }, legacyLanguage), Math.min(index, lines.length) - 1);
1444
1917
  continue;
1445
1918
  }
1446
1919
 
@@ -1462,7 +1935,7 @@ function parseMarkdownImpl(input, baseOpts) {
1462
1935
  if (headingMatch) {
1463
1936
  pushBlock({
1464
1937
  type: 'heading',
1465
- level: headingMatch[1].length,
1938
+ depth: headingMatch[1].length,
1466
1939
  children: parseInlineEntry(headingMatch[2], opts)
1467
1940
  });
1468
1941
  index++;
@@ -1472,7 +1945,7 @@ function parseMarkdownImpl(input, baseOpts) {
1472
1945
  // --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
1473
1946
  if (isHorizontalRule(line)) {
1474
1947
  pushBlock({
1475
- type: 'hr'
1948
+ type: 'thematicBreak'
1476
1949
  });
1477
1950
  index++;
1478
1951
  continue;
@@ -1482,11 +1955,11 @@ function parseMarkdownImpl(input, baseOpts) {
1482
1955
  // An unsafe src falls through to the paragraph path and renders as
1483
1956
  // literal text, the same rule the inline image path applies.
1484
1957
  const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
1485
- if (imageMatch && line.trim() === imageMatch[0] && isSafeUrl(imageMatch[2])) {
1958
+ if (imageMatch && line.trim() === imageMatch[0] && isSafeMarkdownParserUrl(imageMatch[2])) {
1486
1959
  pushBlock({
1487
1960
  type: 'image',
1488
1961
  alt: imageMatch[1],
1489
- src: imageMatch[2]
1962
+ url: imageMatch[2]
1490
1963
  });
1491
1964
  index++;
1492
1965
  continue;
@@ -1530,11 +2003,52 @@ function parseMarkdownImpl(input, baseOpts) {
1530
2003
  continue;
1531
2004
  }
1532
2005
 
2006
+ // --- Extension block syntax (built-in blocks take precedence) ---
2007
+ if (hasBlockExtensionSyntax) {
2008
+ const extensionColumn = blockExtensionColumn(line);
2009
+ const extensionOffset = extensionColumn == null ? lineOffsets[index] : lineOffsets[index] + extensionColumn;
2010
+ const extension = blockExtensionMatches.get(extensionOffset) ?? (extensionColumn == null ? {
2011
+ status: 'none'
2012
+ } : matchExtensionSyntax(cleaned, extensionOffset, 'block', opts));
2013
+ blockExtensionMatches.delete(extensionOffset);
2014
+ if (extension.status === 'match') {
2015
+ const consumedEnd = extension.end;
2016
+ const consumed = cleaned.slice(lineOffsets[index], consumedEnd);
2017
+ if (consumedEnd < cleaned.length && cleaned[consumedEnd] !== '\n' && cleaned[consumedEnd - 1] !== '\n') {
2018
+ reportMarkdownPluginFailure(extension.node.plugin, 'syntax', new TypeError('Block syntax must consume complete lines'));
2019
+ } else {
2020
+ const newlineCount = consumed.split('\n').length - 1;
2021
+ const consumedLines = Math.max(1, newlineCount + (consumed.endsWith('\n') ? 0 : 1));
2022
+ pushBlock(extension.node, index + consumedLines - 1);
2023
+ index += consumedLines;
2024
+ continue;
2025
+ }
2026
+ }
2027
+ if (extension.status === 'defer') {
2028
+ break;
2029
+ }
2030
+ }
2031
+
1533
2032
  // --- Paragraph ---
1534
2033
  const paraLines = [line];
1535
2034
  index++;
1536
- while (index < lines.length && !isBlockStart(lines[index]) && !(opts.math && matchDisplayMathBlock(lines, index) != null) && lines[index].trim() !== '') {
1537
- paraLines.push(lines[index]);
2035
+ while (index < lines.length) {
2036
+ const nextLine = lines[index];
2037
+ if (isBlockStart(nextLine) || opts.math && matchDisplayMathBlock(lines, index) != null || nextLine.trim() === '') {
2038
+ break;
2039
+ }
2040
+ if (hasBlockExtensionSyntax) {
2041
+ const extensionColumn = blockExtensionColumn(nextLine);
2042
+ if (extensionColumn != null) {
2043
+ const extensionOffset = lineOffsets[index] + extensionColumn;
2044
+ const extension = matchExtensionSyntax(cleaned, extensionOffset, 'block', opts);
2045
+ if (extension.status !== 'none') {
2046
+ blockExtensionMatches.set(extensionOffset, extension);
2047
+ break;
2048
+ }
2049
+ }
2050
+ }
2051
+ paraLines.push(nextLine);
1538
2052
  index++;
1539
2053
  }
1540
2054
  pushBlock({
@@ -1586,9 +2100,13 @@ function stampSourceRanges(blocks, blockStartLines, blockEndLines, lines, lineMa
1586
2100
  const end = lineStart(endLine) + lines[endLine].length;
1587
2101
  blocks[i] = {
1588
2102
  ...blocks[i],
1589
- range: {
1590
- start: lineStart(startLine),
1591
- end
2103
+ position: {
2104
+ start: {
2105
+ offset: lineStart(startLine)
2106
+ },
2107
+ end: {
2108
+ offset: end
2109
+ }
1592
2110
  }
1593
2111
  };
1594
2112
  }
@@ -1609,6 +2127,11 @@ function makeIncrementalCache(state) {
1609
2127
  tailLinkDefs: new Map(),
1610
2128
  linkDefs: defs,
1611
2129
  linkDefsKey: linkDefsSignature(defs),
2130
+ settledAstBlocks: [],
2131
+ settledRevision: 0,
2132
+ projectedRevision: -1,
2133
+ projectedSettledBlocks: [],
2134
+ projectionCache: new WeakMap(),
1612
2135
  work: {
1613
2136
  splitCharacters: 0,
1614
2137
  boundaryLines: 0,
@@ -2041,7 +2564,7 @@ function trimUnsettledStructural(text) {
2041
2564
  * rather than into the slice.
2042
2565
  */
2043
2566
  function atOffset(opts, offset) {
2044
- return opts.sourceRanges ? {
2567
+ return opts.astPositions === true ? {
2045
2568
  ...opts,
2046
2569
  baseOffset: offset
2047
2570
  } : opts;
@@ -2066,14 +2589,13 @@ function mergeSettledBlocks(prev, delta) {
2066
2589
  ordered: prevLast.ordered,
2067
2590
  start: prevLast.start,
2068
2591
  delimiter: prevLast.delimiter,
2069
- loose: true,
2070
- items: [...prevLast.items, ...deltaFirst.items],
2071
- // One list now, so one range: from where the first half started to
2072
- // where the second half ended.
2073
- ...(prevLast.range != null && deltaFirst.range != null ? {
2074
- range: {
2075
- start: prevLast.range.start,
2076
- end: deltaFirst.range.end
2592
+ spread: true,
2593
+ children: [...prevLast.children, ...deltaFirst.children],
2594
+ // One list now, so one position spans both halves.
2595
+ ...(prevLast.position != null && deltaFirst.position != null ? {
2596
+ position: {
2597
+ start: prevLast.position.start,
2598
+ end: deltaFirst.position.end
2077
2599
  }
2078
2600
  } : null)
2079
2601
  };
@@ -2085,11 +2607,11 @@ function mergeSettledBlocks(prev, delta) {
2085
2607
  /** Append a newly-settled slice without copying the already-settled prefix. */
2086
2608
  function appendSettledBlocks(prev, delta) {
2087
2609
  if (delta.length === 0) {
2088
- return;
2610
+ return false;
2089
2611
  }
2090
2612
  if (prev.length === 0) {
2091
2613
  prev.push(...delta);
2092
- return;
2614
+ return false;
2093
2615
  }
2094
2616
  const prevLast = prev[prev.length - 1];
2095
2617
  const deltaFirst = delta[0];
@@ -2099,19 +2621,20 @@ function appendSettledBlocks(prev, delta) {
2099
2621
  ordered: prevLast.ordered,
2100
2622
  start: prevLast.start,
2101
2623
  delimiter: prevLast.delimiter,
2102
- loose: true,
2103
- items: [...prevLast.items, ...deltaFirst.items],
2104
- ...(prevLast.range != null && deltaFirst.range != null ? {
2105
- range: {
2106
- start: prevLast.range.start,
2107
- end: deltaFirst.range.end
2624
+ spread: true,
2625
+ children: [...prevLast.children, ...deltaFirst.children],
2626
+ ...(prevLast.position != null && deltaFirst.position != null ? {
2627
+ position: {
2628
+ start: prevLast.position.start,
2629
+ end: deltaFirst.position.end
2108
2630
  }
2109
2631
  } : null)
2110
2632
  };
2111
2633
  prev.push(...delta.slice(1));
2112
- return;
2634
+ return true;
2113
2635
  }
2114
2636
  prev.push(...delta);
2637
+ return false;
2115
2638
  }
2116
2639
  function sameUnsettledDefinitions(previous, next, settled) {
2117
2640
  for (const [label, destination] of previous) {
@@ -2132,12 +2655,19 @@ function resetIncrementalCache(state, cache) {
2132
2655
  state.settledBlocks = [];
2133
2656
  state.settledUpTo = 0;
2134
2657
  state.linkDefsKey = undefined;
2658
+ state.sourceIdsKey = undefined;
2659
+ state.pluginSyntaxIdentity = undefined;
2135
2660
  state.math = undefined;
2136
2661
  cache.settledEnd = 0;
2137
2662
  cache.settledLinkDefs.clear();
2138
2663
  cache.tailLinkDefs = new Map();
2139
2664
  cache.linkDefs = new Map();
2140
2665
  cache.linkDefsKey = '';
2666
+ cache.settledAstBlocks = [];
2667
+ cache.settledRevision++;
2668
+ cache.projectedRevision = -1;
2669
+ cache.projectedSettledBlocks = [];
2670
+ cache.projectionCache = new WeakMap();
2141
2671
  cache.work = {
2142
2672
  splitCharacters: 0,
2143
2673
  boundaryLines: 0,
@@ -2159,24 +2689,47 @@ function resetIncrementalCache(state, cache) {
2159
2689
  */
2160
2690
 
2161
2691
  export function parseMarkdownIncremental(input, state, arg) {
2162
- const opts = resolveOptions(arg);
2692
+ const withRanges = wantsLegacyRanges(arg);
2693
+ const root = parseMarkdownAstIncremental(input, state, arg);
2163
2694
  const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
2164
- let reparseSettled = false;
2695
+ const projected = projectMarkdownRoot(root, withRanges, cache.projectionCache);
2696
+ if (cache.projectedRevision !== cache.settledRevision) {
2697
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, withRanges));
2698
+ cache.projectedRevision = cache.settledRevision;
2699
+ }
2700
+ state.settledBlocks = cache.projectedSettledBlocks;
2701
+ return projected;
2702
+ }
2165
2703
 
2704
+ /** @internal Canonical incremental parse used by Markdown rendering. */
2705
+ export function parseMarkdownAstIncremental(input, state, arg) {
2706
+ const opts = resolveOptions(arg, true);
2707
+ const root = {
2708
+ type: 'root',
2709
+ children: parseMarkdownIncrementalAstBlocks(input, state, opts)
2710
+ };
2711
+ return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
2712
+ }
2713
+ function parseMarkdownIncrementalAstBlocks(input, state, opts) {
2714
+ const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
2715
+ let reparseSettled = false;
2716
+ const nextSourceIdsKey = sourceIdsSignature(opts.sourceIds);
2717
+ const nextPluginSyntaxIdentity = opts.plugins?.syntaxIdentity ?? '';
2166
2718
  // Invalidate cache when an option that changes parsed nodes flips — cached
2167
2719
  // settled blocks were parsed with the previous setting and would otherwise
2168
2720
  // be reused unchanged.
2169
- if (state.autolink !== opts.autolink || state.math !== opts.math || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)) {
2721
+ if (state.autolink !== opts.autolink || state.math !== opts.math || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges) || state.sourceIdsKey !== nextSourceIdsKey || state.pluginSyntaxIdentity !== nextPluginSyntaxIdentity) {
2170
2722
  reparseSettled = true;
2171
2723
  state.autolink = opts.autolink;
2172
2724
  state.math = opts.math;
2173
2725
  state.sourceRanges = opts.sourceRanges;
2726
+ state.sourceIdsKey = nextSourceIdsKey;
2727
+ state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
2174
2728
  }
2175
2729
  if (input === '') {
2176
2730
  resetIncrementalCache(state, cache);
2177
2731
  return [];
2178
2732
  }
2179
-
2180
2733
  // The settled prefix is only reusable while the input still contains it
2181
2734
  // verbatim. Lengths alone cannot tell: a same-length or longer replacement
2182
2735
  // (new args after reusing a state) disagrees with the prefix without ever
@@ -2190,6 +2743,8 @@ export function parseMarkdownIncremental(input, state, arg) {
2190
2743
  state.autolink = opts.autolink;
2191
2744
  state.math = opts.math;
2192
2745
  state.sourceRanges = opts.sourceRanges;
2746
+ state.sourceIdsKey = nextSourceIdsKey;
2747
+ state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
2193
2748
  }
2194
2749
 
2195
2750
  // The recurring parse costs are confined to the mutable suffix: splitting,
@@ -2260,22 +2815,38 @@ export function parseMarkdownIncremental(input, state, arg) {
2260
2815
  // table, which inside a fence is ordinary code: a TypeScript union or a `- `
2261
2816
  // would disappear from the code block as it streams.
2262
2817
  const unsettledText = openFence ? unsettledRaw : openMath ? trimOpenDisplayMath(unsettledRaw) : trimUnsettledStructural(unsettledRaw);
2818
+ const legacyRanges = opts.sourceRanges === true;
2263
2819
  let parsedSettledBlocks = 0;
2264
2820
  if (reparseSettled) {
2265
- state.settledBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
2266
- parsedSettledBlocks = state.settledBlocks.length;
2821
+ cache.settledAstBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
2822
+ parsedSettledBlocks = cache.settledAstBlocks.length;
2823
+ cache.settledRevision++;
2824
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
2825
+ cache.projectedRevision = cache.settledRevision;
2267
2826
  } else if (settledDelta !== '') {
2268
2827
  const deltaBlocks = parseMarkdownImpl(settledDelta, atOffset(parseOpts, oldSettledEnd));
2269
- appendSettledBlocks(state.settledBlocks, deltaBlocks);
2828
+ const mergeIndex = cache.settledAstBlocks.length - 1;
2829
+ const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
2270
2830
  parsedSettledBlocks = deltaBlocks.length;
2831
+ cache.settledRevision++;
2832
+ const projectedDelta = deltaBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
2833
+ if (mergedList) {
2834
+ const canonicalMerged = cache.settledAstBlocks[mergeIndex];
2835
+ const projectedMerged = projectBlockNode(canonicalMerged, cache.projectionCache, legacyRanges);
2836
+ cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
2837
+ cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
2838
+ } else {
2839
+ cache.projectedSettledBlocks.push(...projectedDelta);
2840
+ }
2841
+ cache.projectedRevision = cache.settledRevision;
2271
2842
  }
2272
2843
 
2273
2844
  // The unsettled tail is trimmed before parsing, so its offset in the
2274
2845
  // document is where that trimmed text actually starts — not the boundary,
2275
- // which is a line index. If it somehow can't be located, parse it without
2276
- // ranges rather than report wrong ones. Only worth searching for when
2277
- // ranges were asked for: this runs on every streamed chunk.
2278
- const unsettledStart = unsettledText && opts.sourceRanges ? input.indexOf(unsettledText, cache.settledEnd) : -1;
2846
+ // which is a line index. If it somehow cannot be located, parse it without
2847
+ // positions rather than report wrong ones. The search starts at the
2848
+ // settled end, so it scans the tail and never the prefix.
2849
+ const unsettledStart = unsettledText && opts.astPositions === true ? input.indexOf(unsettledText, cache.settledEnd) : -1;
2279
2850
  const unsettledBlocks = unsettledText ? parseMarkdownImpl(unsettledText, unsettledStart >= 0 ? atOffset(parseOpts, unsettledStart) : nested(parseOpts)) : [];
2280
2851
  state.prevInput = input;
2281
2852
 
@@ -2290,7 +2861,7 @@ export function parseMarkdownIncremental(input, state, arg) {
2290
2861
  definitionCharacters: settledDelta.length + unsettledInput.length,
2291
2862
  renderedBlocks: parsedSettledBlocks + unsettledBlocks.length
2292
2863
  };
2293
- return mergeSettledBlocks(state.settledBlocks, unsettledBlocks);
2864
+ return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
2294
2865
  }
2295
2866
 
2296
2867
  // ---------------------------------------------------------------------------
@@ -2301,28 +2872,6 @@ export function parseMarkdownIncremental(input, state, arg) {
2301
2872
  // derives its item ids from the same functions, so outline hash links always
2302
2873
  // resolve to a rendered heading by construction.
2303
2874
 
2304
- /** Flatten inline nodes into their plain text content. */
2305
- export function inlineText(nodes) {
2306
- return nodes.map(node => {
2307
- switch (node.type) {
2308
- case 'text':
2309
- case 'code':
2310
- case 'math':
2311
- return node.type === 'math' ? node.value : node.content;
2312
- case 'bold':
2313
- case 'italic':
2314
- case 'strikethrough':
2315
- case 'link':
2316
- return inlineText(node.children);
2317
- case 'image':
2318
- return node.alt;
2319
- case 'citation':
2320
- case 'break':
2321
- return '';
2322
- }
2323
- }).join('');
2324
- }
2325
-
2326
2875
  /** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
2327
2876
  export function slugify(value) {
2328
2877
  return value.trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');