@astryxdesign/core 0.6.2 → 0.6.3-canary.0788c49

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 (558) 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/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +13 -28
  10. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  11. package/dist/Chat/ChatComposer.js +10 -5
  12. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  13. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposerDrawer.js +48 -23
  15. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  16. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  17. package/dist/Chat/ChatComposerInput.js +63 -16
  18. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  19. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  20. package/dist/Chat/ChatDictationButton.js +6 -4
  21. package/dist/Chat/ChatLayout.d.ts +19 -0
  22. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  23. package/dist/Chat/ChatLayout.js +19 -0
  24. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  25. package/dist/Chat/ChatLayoutScrollButton.js +44 -34
  26. package/dist/Chat/ChatMessageBubble.d.ts +2 -0
  27. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  28. package/dist/Chat/ChatMessageBubble.js +5 -3
  29. package/dist/Chat/ChatMessageList.d.ts +2 -1
  30. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  31. package/dist/Chat/ChatMessageList.js +4 -3
  32. package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
  33. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  34. package/dist/Chat/ChatMessageMetadata.js +12 -9
  35. package/dist/Chat/ChatSendButton.d.ts +2 -1
  36. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  37. package/dist/Chat/ChatSendButton.js +6 -4
  38. package/dist/Chat/index.d.ts +1 -1
  39. package/dist/Chat/index.d.ts.map +1 -1
  40. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  41. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  42. package/dist/Citation/Citation.d.ts +1 -1
  43. package/dist/Citation/Citation.d.ts.map +1 -1
  44. package/dist/Citation/Citation.js +3 -2
  45. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  46. package/dist/CodeBlock/highlightRanges.js +14 -3
  47. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  48. package/dist/Collapsible/Collapsible.js +6 -1
  49. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  50. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  51. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  52. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  53. package/dist/DateInput/DateInput.d.ts.map +1 -1
  54. package/dist/DateInput/DateInput.js +4 -4
  55. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  56. package/dist/DateInput/NativeDateField.js +16 -16
  57. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  58. package/dist/DateInput/TouchDateField.js +2 -2
  59. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  60. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  61. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  62. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  63. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  64. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  65. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  66. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  67. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  68. package/dist/Dialog/DialogHeader.d.ts +8 -1
  69. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  70. package/dist/Dialog/DialogHeader.js +13 -4
  71. package/dist/Field/InputClearButton.d.ts +4 -2
  72. package/dist/Field/InputClearButton.d.ts.map +1 -1
  73. package/dist/Field/InputClearButton.js +4 -2
  74. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  75. package/dist/Field/PanelSearchInput.js +1 -1
  76. package/dist/FileInput/FileInput.d.ts.map +1 -1
  77. package/dist/FileInput/FileInput.js +7 -7
  78. package/dist/Icon/Icon.d.ts +6 -31
  79. package/dist/Icon/Icon.d.ts.map +1 -1
  80. package/dist/Icon/Icon.js +8 -71
  81. package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
  82. package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
  83. package/dist/Icon/IconDefaultSizeContext.js +18 -0
  84. package/dist/Icon/IconSize.stylex.d.ts +65 -0
  85. package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
  86. package/dist/Icon/IconSize.stylex.js +75 -0
  87. package/dist/Item/Item.d.ts +1 -1
  88. package/dist/Item/Item.d.ts.map +1 -1
  89. package/dist/Item/Item.js +4 -3
  90. package/dist/Kbd/Kbd.d.ts +5 -3
  91. package/dist/Kbd/Kbd.d.ts.map +1 -1
  92. package/dist/Kbd/Kbd.js +36 -42
  93. package/dist/Link/Link.d.ts.map +1 -1
  94. package/dist/Link/Link.js +6 -2
  95. package/dist/Link/useLinkComponent.d.ts +5 -15
  96. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  97. package/dist/Link/useLinkComponent.js +34 -51
  98. package/dist/List/List.d.ts +19 -1
  99. package/dist/List/List.d.ts.map +1 -1
  100. package/dist/List/List.js +9 -2
  101. package/dist/List/ListContext.d.ts +1 -0
  102. package/dist/List/ListContext.d.ts.map +1 -1
  103. package/dist/List/ListContext.js +2 -1
  104. package/dist/List/ListItem.d.ts +1 -1
  105. package/dist/List/ListItem.d.ts.map +1 -1
  106. package/dist/List/ListItem.js +9 -2
  107. package/dist/Markdown/Markdown.d.ts +8 -2
  108. package/dist/Markdown/Markdown.d.ts.map +1 -1
  109. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  110. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  111. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  112. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  113. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  114. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  115. package/dist/Markdown/Markdown.js +262 -109
  116. package/dist/Markdown/ast.d.ts +162 -0
  117. package/dist/Markdown/ast.d.ts.map +1 -0
  118. package/dist/Markdown/ast.js +74 -0
  119. package/dist/Markdown/index.d.ts +5 -3
  120. package/dist/Markdown/index.d.ts.map +1 -1
  121. package/dist/Markdown/index.js +3 -2
  122. package/dist/Markdown/parser/index.d.ts +10 -0
  123. package/dist/Markdown/parser/index.d.ts.map +1 -0
  124. package/dist/Markdown/parser/index.js +10 -0
  125. package/dist/Markdown/parser.d.ts +126 -58
  126. package/dist/Markdown/parser.d.ts.map +1 -1
  127. package/dist/Markdown/parser.js +726 -177
  128. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  129. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  130. package/dist/Markdown/plugins/frontmatter.js +132 -0
  131. package/dist/Markdown/plugins/index.d.ts +17 -0
  132. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  133. package/dist/Markdown/plugins/index.js +14 -0
  134. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  135. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  136. package/dist/Markdown/plugins/protocol.js +957 -0
  137. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  138. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  139. package/dist/Markdown/plugins/semanticFence.js +213 -0
  140. package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
  141. package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
  142. package/dist/Markdown/plugins/softBreaks.js +172 -0
  143. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  144. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  145. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  146. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  147. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  148. package/dist/Markdown/plugins/textTransform.js +374 -0
  149. package/dist/Markdown/remark.d.ts +196 -0
  150. package/dist/Markdown/remark.d.ts.map +1 -0
  151. package/dist/Markdown/remark.js +1062 -0
  152. package/dist/Markdown/url.d.ts +6 -0
  153. package/dist/Markdown/url.d.ts.map +1 -0
  154. package/dist/Markdown/url.js +31 -0
  155. package/dist/Markdown/utils.d.ts +4 -2
  156. package/dist/Markdown/utils.d.ts.map +1 -1
  157. package/dist/Markdown/utils.js +2 -1
  158. package/dist/MetadataList/MetadataList.js +3 -3
  159. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  160. package/dist/NumberInput/NumberInput.js +4 -4
  161. package/dist/Outline/index.d.ts +1 -0
  162. package/dist/Outline/index.d.ts.map +1 -1
  163. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  164. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  165. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  166. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  167. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  168. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  169. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  171. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  172. package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
  173. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  174. package/dist/ProgressBar/ProgressBar.js +36 -31
  175. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  176. package/dist/RadioList/RadioListItem.js +13 -1
  177. package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
  178. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
  179. package/dist/ScrollableArea/ScrollableArea.js +1 -0
  180. package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
  181. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  182. package/dist/SegmentedControl/SegmentedControl.js +1 -0
  183. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  184. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  185. package/dist/Selector/Selector.d.ts +9 -7
  186. package/dist/Selector/Selector.d.ts.map +1 -1
  187. package/dist/Selector/Selector.js +16 -7
  188. package/dist/SideNav/SideNav.d.ts +2 -1
  189. package/dist/SideNav/SideNav.d.ts.map +1 -1
  190. package/dist/SideNav/SideNav.js +9 -3
  191. package/dist/Slider/Slider.d.ts.map +1 -1
  192. package/dist/Slider/Slider.js +28 -12
  193. package/dist/Spinner/Spinner.d.ts.map +1 -1
  194. package/dist/Spinner/Spinner.js +23 -7
  195. package/dist/Switch/Switch.d.ts.map +1 -1
  196. package/dist/Switch/Switch.js +11 -0
  197. package/dist/TabList/Tab.d.ts +1 -1
  198. package/dist/TabList/Tab.d.ts.map +1 -1
  199. package/dist/TabList/Tab.js +20 -7
  200. package/dist/Table/Table.d.ts +1 -0
  201. package/dist/Table/Table.d.ts.map +1 -1
  202. package/dist/Table/Table.js +29 -13
  203. package/dist/TextArea/TextArea.d.ts.map +1 -1
  204. package/dist/TextArea/TextArea.js +1 -1
  205. package/dist/TextInput/TextInput.d.ts.map +1 -1
  206. package/dist/TextInput/TextInput.js +2 -2
  207. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  208. package/dist/TimeInput/TimeInput.js +4 -4
  209. package/dist/Timer/Timer.d.ts +49 -0
  210. package/dist/Timer/Timer.d.ts.map +1 -0
  211. package/dist/Timer/Timer.js +158 -0
  212. package/dist/Timer/index.d.ts +9 -0
  213. package/dist/Timer/index.d.ts.map +1 -0
  214. package/dist/Timer/index.js +10 -0
  215. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  216. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  217. package/dist/ToggleButton/ToggleButton.js +15 -27
  218. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  219. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  220. package/dist/TopNav/TopNavMenu.js +3 -2
  221. package/dist/TreeList/TreeList.d.ts.map +1 -1
  222. package/dist/TreeList/TreeList.js +3 -0
  223. package/dist/TreeList/TreeListItem.d.ts +6 -1
  224. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  225. package/dist/TreeList/TreeListItem.js +12 -2
  226. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  227. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  228. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  229. package/dist/Typeahead/BaseTypeahead.js +16 -7
  230. package/dist/astryx.css +27 -11
  231. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  232. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  233. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  234. package/dist/hooks/useClickableContainer.d.ts +10 -1
  235. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  236. package/dist/hooks/useClickableContainer.js +15 -4
  237. package/dist/hooks/useScrollableArea.d.ts +6 -2
  238. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  239. package/dist/hooks/useScrollableArea.js +17 -5
  240. package/dist/index.d.ts +1 -0
  241. package/dist/index.d.ts.map +1 -1
  242. package/dist/index.js +3 -0
  243. package/dist/theme/declarationBoundary.d.ts +17 -0
  244. package/dist/theme/declarationBoundary.d.ts.map +1 -0
  245. package/dist/theme/declarationBoundary.js +343 -0
  246. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  247. package/dist/theme/derivedVarRegistry.js +4 -0
  248. package/dist/theme/generateThemeRules.d.ts +13 -7
  249. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  250. package/dist/theme/generateThemeRules.js +140 -34
  251. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  252. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  253. package/dist/utils/interactionOverlay.stylex.js +9 -0
  254. package/dist/utils/safeUrl.d.ts +11 -0
  255. package/dist/utils/safeUrl.d.ts.map +1 -0
  256. package/dist/utils/safeUrl.js +78 -0
  257. package/locales/af-ZA.json +530 -1
  258. package/locales/ar-SA.json +490 -1
  259. package/locales/ca-ES.json +514 -1
  260. package/locales/cs-CZ.json +498 -1
  261. package/locales/da-DK.json +522 -1
  262. package/locales/de-DE.json +522 -1
  263. package/locales/el-GR.json +498 -1
  264. package/locales/es-ES.json +510 -1
  265. package/locales/fi-FI.json +498 -1
  266. package/locales/fr-FR.json +534 -1
  267. package/locales/he-IL.json +498 -1
  268. package/locales/hu-HU.json +498 -1
  269. package/locales/it-IT.json +510 -1
  270. package/locales/ja-JP.json +490 -1
  271. package/locales/ko-KR.json +498 -1
  272. package/locales/nl-NL.json +534 -1
  273. package/locales/no-NO.json +510 -1
  274. package/locales/pl-PL.json +506 -1
  275. package/locales/pt-BR.json +506 -1
  276. package/locales/pt-PT.json +506 -1
  277. package/locales/ro-RO.json +506 -1
  278. package/locales/ru-RU.json +498 -1
  279. package/locales/sr-SP.json +498 -1
  280. package/locales/sv-SE.json +502 -1
  281. package/locales/tr-TR.json +498 -1
  282. package/locales/uk-UA.json +498 -1
  283. package/locales/vi-VN.json +502 -1
  284. package/locales/zh-CN.json +490 -1
  285. package/locales/zh-TW.json +490 -1
  286. package/package.json +24 -4
  287. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  288. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  289. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  290. package/src/BottomSheet/BottomSheet.tsx +3 -1
  291. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  292. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  293. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  294. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  295. package/src/Button/Button.doc.mjs +5 -5
  296. package/src/Button/Button.tsx +15 -13
  297. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  298. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  299. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  300. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  301. package/src/Chat/ChatComposer.doc.mjs +14 -0
  302. package/src/Chat/ChatComposer.spec.md +230 -0
  303. package/src/Chat/ChatComposer.test.tsx +57 -0
  304. package/src/Chat/ChatComposer.tsx +21 -9
  305. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  306. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  307. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  308. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  309. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  310. package/src/Chat/ChatComposerInput.spec.md +185 -0
  311. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  312. package/src/Chat/ChatComposerInput.tsx +78 -13
  313. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  314. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  315. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  316. package/src/Chat/ChatDictationButton.spec.md +160 -0
  317. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  318. package/src/Chat/ChatDictationButton.tsx +8 -6
  319. package/src/Chat/ChatLayout.doc.mjs +12 -4
  320. package/src/Chat/ChatLayout.spec.md +271 -0
  321. package/src/Chat/ChatLayout.test.tsx +28 -0
  322. package/src/Chat/ChatLayout.tsx +19 -0
  323. package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
  324. package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
  325. package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
  326. package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
  327. package/src/Chat/ChatMessageBubble.spec.md +263 -0
  328. package/src/Chat/ChatMessageBubble.test.tsx +93 -0
  329. package/src/Chat/ChatMessageBubble.tsx +8 -3
  330. package/src/Chat/ChatMessageList.spec.md +222 -0
  331. package/src/Chat/ChatMessageList.test.tsx +64 -2
  332. package/src/Chat/ChatMessageList.tsx +4 -3
  333. package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
  334. package/src/Chat/ChatMessageMetadata.spec.md +220 -0
  335. package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
  336. package/src/Chat/ChatMessageMetadata.tsx +13 -13
  337. package/src/Chat/ChatSendButton.doc.mjs +32 -0
  338. package/src/Chat/ChatSendButton.spec.md +227 -0
  339. package/src/Chat/ChatSendButton.test.tsx +128 -8
  340. package/src/Chat/ChatSendButton.tsx +6 -4
  341. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
  342. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
  343. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
  344. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
  345. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
  346. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
  347. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
  348. package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +1522 -0
  349. package/src/Chat/index.ts +1 -0
  350. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  351. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  352. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  353. package/src/Citation/Citation.doc.mjs +2 -2
  354. package/src/Citation/Citation.test.tsx +17 -0
  355. package/src/Citation/Citation.tsx +4 -2
  356. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  357. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  358. package/src/CodeBlock/highlightRanges.test.ts +143 -1
  359. package/src/CodeBlock/highlightRanges.ts +15 -5
  360. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  361. package/src/Collapsible/Collapsible.test.tsx +21 -0
  362. package/src/Collapsible/Collapsible.tsx +5 -0
  363. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  364. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  365. package/src/DateInput/DateInput.tsx +6 -1
  366. package/src/DateInput/NativeDateField.tsx +24 -8
  367. package/src/DateInput/TouchDateField.tsx +6 -2
  368. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  369. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  370. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  371. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  372. package/src/Dialog/Dialog.doc.mjs +2 -0
  373. package/src/Dialog/DialogHeader.doc.mjs +22 -0
  374. package/src/Dialog/DialogHeader.test.tsx +167 -3
  375. package/src/Dialog/DialogHeader.tsx +37 -6
  376. package/src/Dialog/modules/DialogHeader.spec.md +101 -42
  377. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
  378. package/src/Field/InputClearButton.test.tsx +17 -3
  379. package/src/Field/InputClearButton.tsx +5 -2
  380. package/src/Field/PanelSearchInput.tsx +5 -2
  381. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  382. package/src/FileInput/FileInput.tsx +11 -2
  383. package/src/Icon/Icon.doc.mjs +7 -5
  384. package/src/Icon/Icon.spec.md +81 -40
  385. package/src/Icon/Icon.test.tsx +6 -3
  386. package/src/Icon/Icon.tsx +20 -68
  387. package/src/Icon/IconDefaultSizeContext.ts +23 -0
  388. package/src/Icon/IconSize.stylex.ts +70 -0
  389. package/src/IconButton/IconButton.doc.mjs +3 -3
  390. package/src/IconButton/IconButton.test.tsx +40 -0
  391. package/src/Item/Item.doc.mjs +6 -2
  392. package/src/Item/Item.tsx +9 -3
  393. package/src/Kbd/Kbd.doc.mjs +3 -3
  394. package/src/Kbd/Kbd.test.tsx +57 -1
  395. package/src/Kbd/Kbd.tsx +57 -37
  396. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  397. package/src/Link/Link.doc.mjs +17 -4
  398. package/src/Link/Link.test.tsx +24 -0
  399. package/src/Link/Link.tsx +5 -0
  400. package/src/Link/LinkProvider.doc.mjs +2 -2
  401. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
  402. package/src/Link/useLinkComponent.test.tsx +333 -0
  403. package/src/Link/useLinkComponent.ts +29 -57
  404. package/src/List/List.doc.mjs +6 -0
  405. package/src/List/List.spec.md +200 -0
  406. package/src/List/List.test.tsx +117 -1
  407. package/src/List/List.tsx +22 -2
  408. package/src/List/ListContext.tsx +3 -1
  409. package/src/List/ListItem.doc.mjs +2 -2
  410. package/src/List/ListItem.source-build.test.mjs +41 -0
  411. package/src/List/ListItem.tsx +26 -2
  412. package/src/Markdown/Markdown.doc.mjs +300 -2
  413. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  414. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  415. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  416. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  417. package/src/Markdown/Markdown.public.test.ts +130 -3
  418. package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
  419. package/src/Markdown/Markdown.spec.md +210 -45
  420. package/src/Markdown/Markdown.test.tsx +87 -3
  421. package/src/Markdown/Markdown.tsx +414 -135
  422. package/src/Markdown/ast.test.ts +307 -0
  423. package/src/Markdown/ast.ts +337 -0
  424. package/src/Markdown/incremental.test.ts +43 -0
  425. package/src/Markdown/index.ts +37 -1
  426. package/src/Markdown/modules/remark.spec.md +269 -0
  427. package/src/Markdown/modules/softBreaks.spec.md +137 -0
  428. package/src/Markdown/parser/index.ts +64 -0
  429. package/src/Markdown/parser.public.test.ts +103 -0
  430. package/src/Markdown/parser.test.ts +38 -6
  431. package/src/Markdown/parser.ts +1221 -242
  432. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  433. package/src/Markdown/plugins/frontmatter.ts +198 -0
  434. package/src/Markdown/plugins/index.ts +54 -0
  435. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  436. package/src/Markdown/plugins/protocol.ts +1618 -0
  437. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  438. package/src/Markdown/plugins/semanticFence.ts +424 -0
  439. package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
  440. package/src/Markdown/plugins/softBreaks.ts +169 -0
  441. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  442. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  443. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  444. package/src/Markdown/plugins/textTransform.ts +554 -0
  445. package/src/Markdown/remark.test.tsx +1877 -0
  446. package/src/Markdown/remark.ts +1574 -0
  447. package/src/Markdown/url.ts +36 -0
  448. package/src/Markdown/utils.ts +13 -1
  449. package/src/MetadataList/MetadataList.test.tsx +4 -2
  450. package/src/MetadataList/MetadataList.tsx +3 -3
  451. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  452. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  453. package/src/NumberInput/NumberInput.tsx +6 -1
  454. package/src/Outline/Outline.doc.mjs +7 -5
  455. package/src/Outline/Outline.spec.md +1 -1
  456. package/src/Outline/index.ts +1 -0
  457. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  458. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  459. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  460. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  461. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  462. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  463. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
  464. package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
  465. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  466. package/src/ProgressBar/ProgressBar.tsx +59 -72
  467. package/src/RadioList/RadioList.doc.mjs +11 -0
  468. package/src/RadioList/RadioList.test.tsx +32 -0
  469. package/src/RadioList/RadioListItem.tsx +25 -1
  470. package/src/ScrollableArea/ScrollableArea.tsx +1 -0
  471. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  472. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  473. package/src/SegmentedControl/SegmentedControl.test.tsx +61 -0
  474. package/src/SegmentedControl/SegmentedControl.tsx +2 -1
  475. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  476. package/src/Selector/Selector.doc.mjs +3 -3
  477. package/src/Selector/Selector.spec.md +51 -49
  478. package/src/Selector/Selector.test.tsx +61 -55
  479. package/src/Selector/Selector.tsx +34 -15
  480. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  481. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  482. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  483. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  484. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  485. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
  486. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  487. package/src/SideNav/SideNav.doc.mjs +1 -1
  488. package/src/SideNav/SideNav.test.tsx +10 -0
  489. package/src/SideNav/SideNav.tsx +14 -2
  490. package/src/Slider/Slider.doc.mjs +17 -3
  491. package/src/Slider/Slider.test.tsx +221 -0
  492. package/src/Slider/Slider.tsx +40 -9
  493. package/src/Spinner/Spinner.test.tsx +4 -0
  494. package/src/Spinner/Spinner.tsx +31 -18
  495. package/src/Switch/Switch.doc.mjs +11 -0
  496. package/src/Switch/Switch.test.tsx +16 -0
  497. package/src/Switch/Switch.tsx +28 -0
  498. package/src/TabList/Tab.tsx +35 -7
  499. package/src/TabList/TabList.doc.mjs +11 -0
  500. package/src/TabList/TabList.spec.md +204 -0
  501. package/src/TabList/TabList.test.tsx +66 -0
  502. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  503. package/src/Table/Table.doc.mjs +1 -1
  504. package/src/Table/Table.spec.md +54 -40
  505. package/src/Table/Table.test.tsx +83 -10
  506. package/src/Table/Table.tsx +30 -22
  507. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  508. package/src/TextArea/TextArea.tsx +6 -1
  509. package/src/TextInput/TextInput.test.tsx +20 -1
  510. package/src/TextInput/TextInput.tsx +6 -1
  511. package/src/TimeInput/TimeInput.tsx +6 -1
  512. package/src/Timer/Timer.doc.mjs +183 -0
  513. package/src/Timer/Timer.spec.md +215 -0
  514. package/src/Timer/Timer.test.tsx +302 -0
  515. package/src/Timer/Timer.tsx +258 -0
  516. package/src/Timer/index.ts +11 -0
  517. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  518. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  519. package/src/ToggleButton/ToggleButton.tsx +26 -33
  520. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  521. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  522. package/src/Token/Token.doc.mjs +2 -2
  523. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  524. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  525. package/src/TopNav/TopNavMenu.test.tsx +30 -0
  526. package/src/TopNav/TopNavMenu.tsx +7 -2
  527. package/src/TreeList/TreeList.doc.mjs +3 -3
  528. package/src/TreeList/TreeList.spec.md +123 -75
  529. package/src/TreeList/TreeList.test.tsx +29 -0
  530. package/src/TreeList/TreeList.tsx +3 -0
  531. package/src/TreeList/TreeListItem.tsx +15 -1
  532. package/src/TreeList/TreeListTypes.ts +11 -1
  533. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  534. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  535. package/src/Typeahead/Typeahead.test.tsx +53 -0
  536. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  537. package/src/__tests__/pressState.ts +93 -0
  538. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  539. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  540. package/src/hooks/useClickableContainer.test.tsx +236 -0
  541. package/src/hooks/useClickableContainer.ts +20 -4
  542. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  543. package/src/hooks/useScrollableArea.test.tsx +59 -1
  544. package/src/hooks/useScrollableArea.ts +34 -10
  545. package/src/index.ts +3 -0
  546. package/src/inputFontFloor.test.ts +106 -0
  547. package/src/tailwind-theme.css +0 -6
  548. package/src/theme/Theme.test.tsx +32 -0
  549. package/src/theme/declarationBoundary.test.ts +126 -0
  550. package/src/theme/declarationBoundary.ts +347 -0
  551. package/src/theme/derivedVarRegistry.test.ts +7 -0
  552. package/src/theme/derivedVarRegistry.ts +1 -0
  553. package/src/theme/generateThemeRules.test.ts +526 -1
  554. package/src/theme/generateThemeRules.ts +228 -50
  555. package/src/theme/tokenValueCompat.test.ts +4 -1
  556. package/src/utils/interactionOverlay.stylex.ts +20 -0
  557. package/src/utils/safeUrl.test.ts +88 -0
  558. package/src/utils/safeUrl.ts +85 -0
@@ -2,45 +2,79 @@
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 {
13
+ getMarkdownAstLegacyCodeLanguage,
14
+ markMarkdownAstLegacyCodeLanguage,
15
+ } from './ast';
16
+ import type {
17
+ MarkdownAstBlockContent,
18
+ MarkdownAstList,
19
+ MarkdownAstListItem,
20
+ MarkdownAstPhrasingContent,
21
+ MarkdownAstPosition,
22
+ MarkdownAstRoot,
23
+ MarkdownAstTableCell,
24
+ MarkdownAstTableRow,
25
+ } from './ast';
26
+ import {
27
+ applyMarkdownTransforms,
28
+ freezeMarkdownPluginData,
29
+ isMarkdownPluginData,
30
+ prepareMarkdownPlugins,
31
+ reportMarkdownPluginFailure,
32
+ } from './plugins/protocol';
33
+ import type {
34
+ MarkdownExtensionNode,
35
+ MarkdownExtensionsOf,
36
+ MarkdownPluginData,
37
+ MarkdownPluginEntry,
38
+ PreparedMarkdownPlugins,
39
+ PreparedSyntaxContribution,
40
+ } from './plugins/protocol';
41
+ import {isSafeMarkdownParserUrl} from './url';
42
+
13
43
  // ---------------------------------------------------------------------------
14
44
  // Types
15
45
  // ---------------------------------------------------------------------------
16
46
 
17
47
  /** Nodes returned by default and legacy parser calls. */
18
- export type InlineNode =
48
+ export type InlineNode<Extension extends MarkdownExtensionNode = never> =
19
49
  | {type: 'text'; content: string}
20
- | {type: 'bold'; children: InlineNode[]}
21
- | {type: 'italic'; children: InlineNode[]}
22
- | {type: 'strikethrough'; children: InlineNode[]}
50
+ | {type: 'bold'; children: InlineNode<Extension>[]}
51
+ | {type: 'italic'; children: InlineNode<Extension>[]}
52
+ | {type: 'strikethrough'; children: InlineNode<Extension>[]}
23
53
  | {type: 'code'; content: string}
24
- | {type: 'link'; href: string; children: InlineNode[]}
54
+ | {type: 'link'; href: string; children: InlineNode<Extension>[]}
25
55
  | {type: 'image'; src: string; alt: string}
26
56
  | {type: 'citation'; sourceId: string}
27
- | {type: 'break'};
57
+ | {type: 'break'}
58
+ | Extract<Extension, {display: 'inline'}>;
28
59
 
29
60
  /** The additional inline node returned only when parsing with `math: true`. */
30
61
  export type MathInlineNode = {type: 'math'; value: string};
31
62
 
32
63
  /** Nodes returned by an explicitly math-enabled inline parse. */
33
- export type InlineNodeWithMath =
64
+ export type InlineNodeWithMath<
65
+ Extension extends MarkdownExtensionNode = never,
66
+ > =
34
67
  | {type: 'text'; content: string}
35
- | {type: 'bold'; children: InlineNodeWithMath[]}
36
- | {type: 'italic'; children: InlineNodeWithMath[]}
37
- | {type: 'strikethrough'; children: InlineNodeWithMath[]}
68
+ | {type: 'bold'; children: InlineNodeWithMath<Extension>[]}
69
+ | {type: 'italic'; children: InlineNodeWithMath<Extension>[]}
70
+ | {type: 'strikethrough'; children: InlineNodeWithMath<Extension>[]}
38
71
  | {type: 'code'; content: string}
39
72
  | MathInlineNode
40
- | {type: 'link'; href: string; children: InlineNodeWithMath[]}
73
+ | {type: 'link'; href: string; children: InlineNodeWithMath<Extension>[]}
41
74
  | {type: 'image'; src: string; alt: string}
42
75
  | {type: 'citation'; sourceId: string}
43
- | {type: 'break'};
76
+ | {type: 'break'}
77
+ | Extract<Extension, {display: 'inline'}>;
44
78
 
45
79
  type BlockMetadata = {
46
80
  /**
@@ -50,11 +84,15 @@ type BlockMetadata = {
50
84
  range?: SourceRange;
51
85
  };
52
86
 
53
- type LegacyBlockNodeKind =
54
- | {type: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; children: InlineNode[]}
55
- | {type: 'paragraph'; children: InlineNode[]}
87
+ type LegacyBlockNodeKind<Extension extends MarkdownExtensionNode = never> =
88
+ | {
89
+ type: 'heading';
90
+ level: 1 | 2 | 3 | 4 | 5 | 6;
91
+ children: InlineNode<Extension>[];
92
+ }
93
+ | {type: 'paragraph'; children: InlineNode<Extension>[]}
56
94
  | {type: 'codeblock'; language: string; content: string}
57
- | {type: 'blockquote'; children: BlockNode[]}
95
+ | {type: 'blockquote'; children: BlockNode<Extension>[]}
58
96
  | {
59
97
  type: 'list';
60
98
  ordered: boolean;
@@ -62,33 +100,35 @@ type LegacyBlockNodeKind =
62
100
  /** Ordered-list marker delimiter ('.' or ')'). Undefined for bullets. */
63
101
  delimiter?: '.' | ')';
64
102
  loose?: boolean;
65
- items: ListItemNode[];
103
+ items: ListItemNode<Extension>[];
66
104
  }
67
105
  | {
68
106
  type: 'table';
69
- headers: TableCellNode[];
107
+ headers: TableCellNode<Extension>[];
70
108
  alignments: TableAlignment[];
71
- rows: TableCellNode[][];
109
+ rows: TableCellNode<Extension>[][];
72
110
  }
73
111
  | {type: 'hr'}
74
- | {type: 'image'; src: string; alt: string};
112
+ | {type: 'image'; src: string; alt: string}
113
+ | Extract<Extension, {display: 'block'}>;
75
114
 
76
115
  /** Blocks returned by default and legacy parser calls. */
77
- export type BlockNode = LegacyBlockNodeKind & BlockMetadata;
116
+ export type BlockNode<Extension extends MarkdownExtensionNode = never> =
117
+ LegacyBlockNodeKind<Extension> & BlockMetadata;
78
118
 
79
119
  /** The additional block returned only when parsing with `math: true`. */
80
120
  export type MathBlockNode = {type: 'math'; value: string} & BlockMetadata;
81
121
 
82
- type MathEnabledBlockNodeKind =
122
+ type MathEnabledBlockNodeKind<Extension extends MarkdownExtensionNode = never> =
83
123
  | {
84
124
  type: 'heading';
85
125
  level: 1 | 2 | 3 | 4 | 5 | 6;
86
- children: InlineNodeWithMath[];
126
+ children: InlineNodeWithMath<Extension>[];
87
127
  }
88
- | {type: 'paragraph'; children: InlineNodeWithMath[]}
128
+ | {type: 'paragraph'; children: InlineNodeWithMath<Extension>[]}
89
129
  | {type: 'codeblock'; language: string; content: string}
90
130
  | MathBlockNode
91
- | {type: 'blockquote'; children: BlockNodeWithMath[]}
131
+ | {type: 'blockquote'; children: BlockNodeWithMath<Extension>[]}
92
132
  | {
93
133
  type: 'list';
94
134
  ordered: boolean;
@@ -96,19 +136,21 @@ type MathEnabledBlockNodeKind =
96
136
  /** Ordered-list marker delimiter ('.' or ')'). Undefined for bullets. */
97
137
  delimiter?: '.' | ')';
98
138
  loose?: boolean;
99
- items: ListItemNodeWithMath[];
139
+ items: ListItemNodeWithMath<Extension>[];
100
140
  }
101
141
  | {
102
142
  type: 'table';
103
- headers: TableCellNodeWithMath[];
143
+ headers: TableCellNodeWithMath<Extension>[];
104
144
  alignments: TableAlignment[];
105
- rows: TableCellNodeWithMath[][];
145
+ rows: TableCellNodeWithMath<Extension>[][];
106
146
  }
107
147
  | {type: 'hr'}
108
- | {type: 'image'; src: string; alt: string};
148
+ | {type: 'image'; src: string; alt: string}
149
+ | Extract<Extension, {display: 'block'}>;
109
150
 
110
151
  /** Blocks returned by an explicitly math-enabled block parse. */
111
- export type BlockNodeWithMath = MathEnabledBlockNodeKind & BlockMetadata;
152
+ export type BlockNodeWithMath<Extension extends MarkdownExtensionNode = never> =
153
+ MathEnabledBlockNodeKind<Extension> & BlockMetadata;
112
154
 
113
155
  /**
114
156
  * Where a block sits in the source string handed to `parseMarkdown`:
@@ -121,15 +163,288 @@ export type BlockNodeWithMath = MathEnabledBlockNodeKind & BlockMetadata;
121
163
  */
122
164
  export type SourceRange = {readonly start: number; readonly end: number};
123
165
 
124
- export type ListItemNode = {checked?: boolean; children: BlockNode[]};
125
- type ListItemNodeWithMath = {
166
+ export type ListItemNode<Extension extends MarkdownExtensionNode = never> = {
167
+ checked?: boolean;
168
+ children: BlockNode<Extension>[];
169
+ };
170
+ type ListItemNodeWithMath<Extension extends MarkdownExtensionNode = never> = {
126
171
  checked?: boolean;
127
- children: BlockNodeWithMath[];
172
+ children: BlockNodeWithMath<Extension>[];
173
+ };
174
+ export type TableCellNode<Extension extends MarkdownExtensionNode = never> = {
175
+ children: InlineNode<Extension>[];
176
+ };
177
+ type TableCellNodeWithMath<Extension extends MarkdownExtensionNode = never> = {
178
+ children: InlineNodeWithMath<Extension>[];
128
179
  };
129
- export type TableCellNode = {children: InlineNode[]};
130
- type TableCellNodeWithMath = {children: InlineNodeWithMath[]};
131
180
  export type TableAlignment = 'left' | 'center' | 'right' | null;
132
181
 
182
+ type RuntimeExtensionNode =
183
+ | MarkdownExtensionNode<string, string, MarkdownPluginData, 'inline'>
184
+ | MarkdownExtensionNode<string, string, MarkdownPluginData, 'block'>;
185
+ type RuntimeInlineNode = InlineNodeWithMath<RuntimeExtensionNode>;
186
+ type RuntimeBlockNode = BlockNodeWithMath<RuntimeExtensionNode>;
187
+
188
+ type LegacyProjectionCache = WeakMap<object, object>;
189
+
190
+ function projectRange(
191
+ position: MarkdownAstPosition | undefined,
192
+ ): BlockMetadata {
193
+ const start = position?.start.offset;
194
+ const end = position?.end.offset;
195
+ return start == null || end == null ? {} : {range: {start, end}};
196
+ }
197
+
198
+ /**
199
+ * Canonical nodes always carry Core-authored positions; the released block
200
+ * shape exposes them as `range` only when the caller passed `sourceRanges`,
201
+ * so this projection stays byte-for-byte what it has always returned.
202
+ *
203
+ * An extension node is the one kind the projection passes through instead of
204
+ * rebuilding field by field, so it is the one kind that could carry a
205
+ * canonical `position` out into released output. Strip it unless the caller
206
+ * asked for provenance, in which case the node keeps exactly the `position`
207
+ * it has always exposed.
208
+ */
209
+ function projectExtensionNode<Node extends RuntimeExtensionNode>(
210
+ node: Node,
211
+ withRanges: boolean,
212
+ ): Node {
213
+ if (withRanges || node.position === undefined) {
214
+ return node;
215
+ }
216
+ const {position: _canonicalPosition, ...released} = node;
217
+ return released as Node;
218
+ }
219
+
220
+ function projectInlineNode(
221
+ node: MarkdownAstPhrasingContent<RuntimeExtensionNode>,
222
+ cache: LegacyProjectionCache,
223
+ withRanges: boolean,
224
+ ): RuntimeInlineNode {
225
+ const cached = cache.get(node);
226
+ if (cached != null) {
227
+ return cached as RuntimeInlineNode;
228
+ }
229
+ let projected: RuntimeInlineNode;
230
+ switch (node.type) {
231
+ case 'text':
232
+ projected = {type: 'text', content: node.value};
233
+ break;
234
+ case 'strong':
235
+ projected = {
236
+ type: 'bold',
237
+ children: node.children.map(child =>
238
+ projectInlineNode(child, cache, withRanges),
239
+ ),
240
+ };
241
+ break;
242
+ case 'emphasis':
243
+ projected = {
244
+ type: 'italic',
245
+ children: node.children.map(child =>
246
+ projectInlineNode(child, cache, withRanges),
247
+ ),
248
+ };
249
+ break;
250
+ case 'delete':
251
+ projected = {
252
+ type: 'strikethrough',
253
+ children: node.children.map(child =>
254
+ projectInlineNode(child, cache, withRanges),
255
+ ),
256
+ };
257
+ break;
258
+ case 'inlineCode':
259
+ projected = {type: 'code', content: node.value};
260
+ break;
261
+ case 'inlineMath':
262
+ projected = {type: 'math', value: node.value};
263
+ break;
264
+ case 'link':
265
+ projected = {
266
+ type: 'link',
267
+ href: node.url,
268
+ children: node.children.map(child =>
269
+ projectInlineNode(child, cache, withRanges),
270
+ ),
271
+ };
272
+ break;
273
+ case 'image':
274
+ projected = {type: 'image', src: node.url, alt: node.alt};
275
+ break;
276
+ case 'citation':
277
+ projected = {type: 'citation', sourceId: node.sourceId};
278
+ break;
279
+ case 'break':
280
+ projected = {type: 'break'};
281
+ break;
282
+ case 'extension':
283
+ projected = projectExtensionNode(node, withRanges);
284
+ break;
285
+ }
286
+ cache.set(node, projected);
287
+ return projected;
288
+ }
289
+
290
+ function projectTableCell(
291
+ node: MarkdownAstTableCell<RuntimeExtensionNode>,
292
+ cache: LegacyProjectionCache,
293
+ withRanges: boolean,
294
+ ): TableCellNodeWithMath<RuntimeExtensionNode> {
295
+ const cached = cache.get(node);
296
+ if (cached != null) {
297
+ return cached as TableCellNodeWithMath<RuntimeExtensionNode>;
298
+ }
299
+ const projected = {
300
+ children: node.children.map(child =>
301
+ projectInlineNode(child, cache, withRanges),
302
+ ),
303
+ };
304
+ cache.set(node, projected);
305
+ return projected;
306
+ }
307
+
308
+ function projectListItem(
309
+ node: MarkdownAstListItem<RuntimeExtensionNode>,
310
+ cache: LegacyProjectionCache,
311
+ withRanges: boolean,
312
+ ): ListItemNodeWithMath<RuntimeExtensionNode> {
313
+ const cached = cache.get(node);
314
+ if (cached != null) {
315
+ return cached as ListItemNodeWithMath<RuntimeExtensionNode>;
316
+ }
317
+ const projected = {
318
+ checked: node.checked,
319
+ children: node.children.map(child =>
320
+ projectBlockNode(child, cache, withRanges),
321
+ ),
322
+ };
323
+ cache.set(node, projected);
324
+ return projected;
325
+ }
326
+
327
+ function projectBlockNode(
328
+ node: MarkdownAstBlockContent<RuntimeExtensionNode>,
329
+ cache: LegacyProjectionCache,
330
+ withRanges: boolean,
331
+ ): RuntimeBlockNode {
332
+ const cached = cache.get(node);
333
+ if (cached != null) {
334
+ return cached as RuntimeBlockNode;
335
+ }
336
+ const metadata = withRanges ? projectRange(node.position) : {};
337
+ let projected: RuntimeBlockNode;
338
+ switch (node.type) {
339
+ case 'heading':
340
+ projected = {
341
+ type: 'heading',
342
+ level: node.depth,
343
+ children: node.children.map(child =>
344
+ projectInlineNode(child, cache, withRanges),
345
+ ),
346
+ ...metadata,
347
+ };
348
+ break;
349
+ case 'paragraph':
350
+ projected = {
351
+ type: 'paragraph',
352
+ children: node.children.map(child =>
353
+ projectInlineNode(child, cache, withRanges),
354
+ ),
355
+ ...metadata,
356
+ };
357
+ break;
358
+ case 'code':
359
+ projected = {
360
+ type: 'codeblock',
361
+ language: getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext',
362
+ content: node.value,
363
+ ...metadata,
364
+ };
365
+ break;
366
+ case 'math':
367
+ projected = {type: 'math', value: node.value, ...metadata};
368
+ break;
369
+ case 'blockquote':
370
+ projected = {
371
+ type: 'blockquote',
372
+ children: node.children.map(child =>
373
+ projectBlockNode(child, cache, withRanges),
374
+ ),
375
+ ...metadata,
376
+ };
377
+ break;
378
+ case 'list':
379
+ projected = {
380
+ type: 'list',
381
+ ordered: node.ordered,
382
+ start: node.start,
383
+ delimiter: node.delimiter,
384
+ loose: node.spread,
385
+ items: node.children.map(item =>
386
+ projectListItem(item, cache, withRanges),
387
+ ),
388
+ ...metadata,
389
+ };
390
+ break;
391
+ case 'table': {
392
+ const [header = {type: 'tableRow' as const, children: []}, ...rows] =
393
+ node.children;
394
+ projected = {
395
+ type: 'table',
396
+ headers: header.children.map(cell =>
397
+ projectTableCell(cell, cache, withRanges),
398
+ ),
399
+ alignments: [...node.align],
400
+ rows: rows.map(row =>
401
+ row.children.map(cell => projectTableCell(cell, cache, withRanges)),
402
+ ),
403
+ ...metadata,
404
+ };
405
+ break;
406
+ }
407
+ case 'thematicBreak':
408
+ projected = {type: 'hr', ...metadata};
409
+ break;
410
+ case 'image':
411
+ projected = {type: 'image', alt: node.alt, src: node.url, ...metadata};
412
+ break;
413
+ case 'extension':
414
+ projected = projectExtensionNode(node, withRanges);
415
+ break;
416
+ }
417
+ cache.set(node, projected);
418
+ return projected;
419
+ }
420
+
421
+ function projectInlineNodes(
422
+ nodes: ReadonlyArray<MarkdownAstPhrasingContent<RuntimeExtensionNode>>,
423
+ withRanges: boolean,
424
+ cache: LegacyProjectionCache = new WeakMap(),
425
+ ): RuntimeInlineNode[] {
426
+ return nodes.map(node => projectInlineNode(node, cache, withRanges));
427
+ }
428
+
429
+ function projectMarkdownRoot(
430
+ root: MarkdownAstRoot<RuntimeExtensionNode>,
431
+ withRanges: boolean,
432
+ cache: LegacyProjectionCache = new WeakMap(),
433
+ ): RuntimeBlockNode[] {
434
+ return root.children.map(node => projectBlockNode(node, cache, withRanges));
435
+ }
436
+
437
+ /** Whether a caller asked for the released `range` field on its blocks. */
438
+ function wantsLegacyRanges(
439
+ arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
440
+ ): boolean {
441
+ return (
442
+ arg != null &&
443
+ typeof (arg as {has?: unknown}).has !== 'function' &&
444
+ (arg as RuntimeParseOptions).sourceRanges === true
445
+ );
446
+ }
447
+
133
448
  // ---------------------------------------------------------------------------
134
449
  // Parse options
135
450
  // ---------------------------------------------------------------------------
@@ -141,10 +456,14 @@ export type TableAlignment = 'left' | 'center' | 'right' | null;
141
456
  * `parseMarkdownIncremental` functions also accept the legacy
142
457
  * `ReadonlySet<string>` shape as the second argument.
143
458
  */
144
- type CommonParseOptions = {
459
+ type CommonParseOptions<
460
+ Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
461
+ > = {
145
462
  /** Set of citation source ids — `[id]` / `【id】` markers in this set
146
463
  * become citation nodes instead of plain text / links. */
147
464
  sourceIds?: ReadonlySet<string>;
465
+ /** Ordered opt-in Markdown extensions. */
466
+ plugins?: Plugins;
148
467
  /**
149
468
  * Autolink mode. When set to `'gfm'`, the parser turns bare
150
469
  * `https?://…` / `www.…` URLs, `<URL>` / `<email>` angle-bracket
@@ -170,22 +489,56 @@ type CommonParseOptions = {
170
489
  };
171
490
 
172
491
  /** Options for default and legacy parser results. */
173
- export type ParseOptions = CommonParseOptions & {math?: false | undefined};
492
+ export type ParseOptions<
493
+ Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
494
+ > = CommonParseOptions<Plugins> & {math?: false | undefined};
174
495
 
175
496
  /**
176
497
  * Options that explicitly parse `$…$` and `$$…$$` into math-enabled result
177
498
  * unions. Keeping this separate prevents a legacy `ParseOptions` annotation
178
499
  * from silently widening an exhaustive node switch.
179
500
  */
180
- export type MathParseOptions = CommonParseOptions & {math: true};
501
+ export type MathParseOptions<
502
+ Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
503
+ > = CommonParseOptions<Plugins> & {math: true};
504
+
505
+ export type IncrementalParseOptions<
506
+ Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
507
+ > = ParseOptions<Plugins> & {
508
+ /** False while more source may arrive; true for the terminal snapshot. */
509
+ readonly isFinal?: boolean;
510
+ };
181
511
 
182
- type RuntimeParseOptions = CommonParseOptions & {math?: boolean};
512
+ export type IncrementalMathParseOptions<
513
+ Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
514
+ > = MathParseOptions<Plugins> & {
515
+ /** False while more source may arrive; true for the terminal snapshot. */
516
+ readonly isFinal?: boolean;
517
+ };
518
+
519
+ type RuntimeParseOptions = CommonParseOptions<
520
+ ReadonlyArray<MarkdownPluginEntry>
521
+ > & {
522
+ math?: boolean;
523
+ isFinal?: boolean;
524
+ };
183
525
 
184
526
  type ResolvedOptions = {
185
527
  readonly sourceIds: ReadonlySet<string> | undefined;
186
528
  readonly autolink: 'gfm' | undefined;
187
529
  readonly math?: boolean;
530
+ /** Whether the caller asked for the released `range` projection. */
188
531
  readonly sourceRanges?: boolean;
532
+ /**
533
+ * Whether this parse authors canonical `position` offsets on its blocks.
534
+ * Internal only, and independent of `sourceRanges`: the canonical tree
535
+ * transforms observe always carries provenance, while `sourceRanges` alone
536
+ * decides whether the released projection exposes it as `range`.
537
+ */
538
+ readonly astPositions?: boolean;
539
+ readonly plugins?: PreparedMarkdownPlugins;
540
+ readonly isFinal: boolean;
541
+ readonly allowBlockSyntax?: boolean;
189
542
  /**
190
543
  * Offset of this parse's input within the document the ranges are reported
191
544
  * against. Internal only — the incremental parser parses slices and needs
@@ -201,13 +554,42 @@ type ResolvedOptions = {
201
554
  readonly linkDefs?: ReadonlyMap<string, string>;
202
555
  };
203
556
 
204
- const EMPTY_OPTS: ResolvedOptions = {sourceIds: undefined, autolink: undefined};
557
+ /**
558
+ * Every resolved options object is built here, so all of them share one key
559
+ * set in one order — and therefore one hidden class. The parser reads these
560
+ * fields in its hottest loops; letting an omitted-options parse and an
561
+ * options-bag parse produce differently-shaped objects makes those reads
562
+ * polymorphic in any process that does both, which is exactly what a caller
563
+ * comparing an empty pipeline against a configured one does.
564
+ */
565
+ function makeResolvedOptions(
566
+ fields: Partial<ResolvedOptions> & {readonly isFinal: boolean},
567
+ ): ResolvedOptions {
568
+ return {
569
+ sourceIds: fields.sourceIds,
570
+ autolink: fields.autolink,
571
+ math: fields.math,
572
+ sourceRanges: fields.sourceRanges,
573
+ astPositions: fields.astPositions ?? true,
574
+ plugins: fields.plugins,
575
+ isFinal: fields.isFinal,
576
+ allowBlockSyntax: fields.allowBlockSyntax ?? true,
577
+ baseOffset: fields.baseOffset,
578
+ linkDefs: fields.linkDefs,
579
+ };
580
+ }
581
+
582
+ const EMPTY_OPTS: ResolvedOptions = makeResolvedOptions({isFinal: true});
583
+ const EMPTY_INCREMENTAL_OPTS: ResolvedOptions = makeResolvedOptions({
584
+ isFinal: false,
585
+ });
205
586
 
206
587
  function resolveOptions(
207
588
  arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
589
+ incremental = false,
208
590
  ): ResolvedOptions {
209
591
  if (arg == null) {
210
- return EMPTY_OPTS;
592
+ return incremental ? EMPTY_INCREMENTAL_OPTS : EMPTY_OPTS;
211
593
  }
212
594
  // Duck-type the legacy `ReadonlySet<string>` form: any object whose
213
595
  // `.has` is callable is treated as the legacy sourceIds set. This is
@@ -215,15 +597,23 @@ function resolveOptions(
215
597
  // or polyfilled `ReadonlySet` implementations as a `ParseOptions` bag
216
598
  // and silently lose citation resolution.
217
599
  if (typeof (arg as {has?: unknown}).has === 'function') {
218
- return {sourceIds: arg as ReadonlySet<string>, autolink: undefined};
600
+ return makeResolvedOptions({
601
+ sourceIds: arg as ReadonlySet<string>,
602
+ isFinal: !incremental,
603
+ });
219
604
  }
220
605
  const opts = arg as RuntimeParseOptions;
221
- return {
606
+ return makeResolvedOptions({
222
607
  sourceIds: opts.sourceIds,
223
608
  autolink: opts.autolink,
224
609
  math: opts.math === true ? true : undefined,
225
610
  sourceRanges: opts.sourceRanges,
226
- };
611
+ plugins:
612
+ opts.plugins != null && opts.plugins.length > 0
613
+ ? prepareMarkdownPlugins(opts.plugins)
614
+ : undefined,
615
+ isFinal: incremental ? opts.isFinal === true : true,
616
+ });
227
617
  }
228
618
 
229
619
  // ---------------------------------------------------------------------------
@@ -520,6 +910,15 @@ function extractLinkDefinitions(
520
910
  return {defs, cleaned, lineMap};
521
911
  }
522
912
 
913
+ /** Order-independent signature of a citation-source set, for cache checks. */
914
+ function sourceIdsSignature(
915
+ sourceIds: ReadonlySet<string> | undefined,
916
+ ): string {
917
+ return sourceIds == null || sourceIds.size === 0
918
+ ? ''
919
+ : [...sourceIds].sort().join('\u0000');
920
+ }
921
+
523
922
  /** Order-independent signature of a link-definition set, for cache checks. */
524
923
  function linkDefsSignature(defs: ReadonlyMap<string, string>): string {
525
924
  if (defs.size === 0) {
@@ -537,12 +936,20 @@ function linkDefsSignature(defs: ReadonlyMap<string, string>): string {
537
936
  * Returns the node plus the index just past the reference, or null when it is
538
937
  * not a resolvable reference (caller falls through to literal handling).
539
938
  */
939
+ function protectedInlineOptions(opts: ResolvedOptions): ResolvedOptions {
940
+ return opts.plugins == null ? opts : {...opts, plugins: undefined};
941
+ }
942
+
540
943
  function matchReferenceLink(
541
944
  text: string,
542
945
  start: number,
543
946
  linkDefs: ReadonlyMap<string, string>,
544
947
  opts: ResolvedOptions,
545
- ): {node: InlineNodeWithMath; end: number} | null {
948
+ context: 'default' | 'tableCell',
949
+ ): {
950
+ node: MarkdownAstPhrasingContent<RuntimeExtensionNode>;
951
+ end: number;
952
+ } | null {
546
953
  const textClose = text.indexOf(']', start + 1);
547
954
  if (textClose === -1) {
548
955
  return null;
@@ -558,9 +965,17 @@ function matchReferenceLink(
558
965
  // matches nothing.
559
966
  const label = rawLabel === '' ? linkText : rawLabel;
560
967
  const href = linkDefs.get(normalizeLinkLabel(label));
561
- if (href != null && isSafeUrl(href)) {
968
+ if (href != null && isSafeMarkdownParserUrl(href)) {
562
969
  return {
563
- node: {type: 'link', href, children: parseInlineImpl(linkText, opts)},
970
+ node: {
971
+ type: 'link',
972
+ url: href,
973
+ children: parseInlineImpl(
974
+ linkText,
975
+ protectedInlineOptions(opts),
976
+ context,
977
+ ),
978
+ },
564
979
  end: labelClose + 1,
565
980
  };
566
981
  }
@@ -573,11 +988,19 @@ function matchReferenceLink(
573
988
  return null;
574
989
  }
575
990
  const href = linkDefs.get(normalizeLinkLabel(linkText));
576
- if (href == null || !isSafeUrl(href)) {
991
+ if (href == null || !isSafeMarkdownParserUrl(href)) {
577
992
  return null;
578
993
  }
579
994
  return {
580
- node: {type: 'link', href, children: parseInlineImpl(linkText, opts)},
995
+ node: {
996
+ type: 'link',
997
+ url: href,
998
+ children: parseInlineImpl(
999
+ linkText,
1000
+ protectedInlineOptions(opts),
1001
+ context,
1002
+ ),
1003
+ },
581
1004
  end: textClose + 1,
582
1005
  };
583
1006
  }
@@ -587,7 +1010,10 @@ function matchReferenceImage(
587
1010
  text: string,
588
1011
  start: number,
589
1012
  linkDefs: ReadonlyMap<string, string>,
590
- ): {node: InlineNodeWithMath; end: number} | null {
1013
+ ): {
1014
+ node: MarkdownAstPhrasingContent<RuntimeExtensionNode>;
1015
+ end: number;
1016
+ } | null {
591
1017
  const altClose = text.indexOf(']', start + 2);
592
1018
  if (altClose === -1) {
593
1019
  return null;
@@ -599,8 +1025,8 @@ function matchReferenceImage(
599
1025
  const rawLabel = text.slice(altClose + 2, labelClose);
600
1026
  const label = rawLabel === '' ? alt : rawLabel;
601
1027
  const src = linkDefs.get(normalizeLinkLabel(label));
602
- if (src != null && isSafeUrl(src)) {
603
- return {node: {type: 'image', src, alt}, end: labelClose + 1};
1028
+ if (src != null && isSafeMarkdownParserUrl(src)) {
1029
+ return {node: {type: 'image', url: src, alt}, end: labelClose + 1};
604
1030
  }
605
1031
  // No match — fall back to a shortcut `![alt]`.
606
1032
  }
@@ -609,10 +1035,10 @@ function matchReferenceImage(
609
1035
  return null;
610
1036
  }
611
1037
  const src = linkDefs.get(normalizeLinkLabel(alt));
612
- if (src == null || !isSafeUrl(src)) {
1038
+ if (src == null || !isSafeMarkdownParserUrl(src)) {
613
1039
  return null;
614
1040
  }
615
- return {node: {type: 'image', src, alt}, end: altClose + 1};
1041
+ return {node: {type: 'image', url: src, alt}, end: altClose + 1};
616
1042
  }
617
1043
 
618
1044
  // ---------------------------------------------------------------------------
@@ -701,30 +1127,6 @@ function findInlineMathEnd(text: string, start: number): number {
701
1127
  }
702
1128
 
703
1129
  // ---------------------------------------------------------------------------
704
- // URL scheme sanitization
705
- // ---------------------------------------------------------------------------
706
-
707
- /**
708
- * Reject URLs with dangerous schemes (javascript:, vbscript:, data:) that
709
- * could execute arbitrary code when rendered as link hrefs or image srcs.
710
- * Returns true if the URL is safe to use, false otherwise.
711
- */
712
- function isSafeUrl(url: string): boolean {
713
- // Trim and collapse whitespace/control chars that browsers tolerate but
714
- // could bypass a naive prefix check (e.g. "java\nscript:alert(1)").
715
- // eslint-disable-next-line no-control-regex -- control chars are the bypass
716
- const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
717
- const lower = normalized.toLowerCase();
718
- if (
719
- lower.startsWith('javascript:') ||
720
- lower.startsWith('vbscript:') ||
721
- lower.startsWith('data:text/html')
722
- ) {
723
- return false;
724
- }
725
- return true;
726
- }
727
-
728
1130
  // ---------------------------------------------------------------------------
729
1131
  // Inline parser
730
1132
  // ---------------------------------------------------------------------------
@@ -778,20 +1180,246 @@ function matchBracketCitation(
778
1180
  return {sourceId: id, end: closeIndex + 1};
779
1181
  }
780
1182
 
1183
+ type ExtensionMatch =
1184
+ | {readonly status: 'none'}
1185
+ | {readonly status: 'defer'}
1186
+ | {
1187
+ readonly status: 'match';
1188
+ readonly node: RuntimeExtensionNode;
1189
+ readonly end: number;
1190
+ };
1191
+
1192
+ function currentLineStart(source: string, offset: number): number {
1193
+ return source.lastIndexOf('\n', offset - 1) + 1;
1194
+ }
1195
+
1196
+ function matchExtensionSyntax(
1197
+ source: string,
1198
+ offset: number,
1199
+ context: 'inline' | 'block',
1200
+ opts: ResolvedOptions,
1201
+ ): ExtensionMatch {
1202
+ const byFirstCharacter =
1203
+ context === 'inline'
1204
+ ? opts.plugins?.inlineByFirstCharacter
1205
+ : opts.allowBlockSyntax === false
1206
+ ? undefined
1207
+ : opts.plugins?.blockByFirstCharacter;
1208
+ const candidates = byFirstCharacter?.get(source[offset]);
1209
+ if (candidates == null) {
1210
+ return {status: 'none'};
1211
+ }
1212
+
1213
+ const seen = new Set<PreparedSyntaxContribution>();
1214
+ for (const candidate of candidates) {
1215
+ if (seen.has(candidate)) {
1216
+ continue;
1217
+ }
1218
+ seen.add(candidate);
1219
+ let fullPrefix = false;
1220
+ for (const prefix of candidate.contribution.startsWith) {
1221
+ if (source.startsWith(prefix, offset)) {
1222
+ fullPrefix = true;
1223
+ break;
1224
+ }
1225
+ if (!opts.isFinal) {
1226
+ const remaining = source.slice(offset);
1227
+ if (remaining.length < prefix.length && prefix.startsWith(remaining)) {
1228
+ return {status: 'defer'};
1229
+ }
1230
+ }
1231
+ }
1232
+ if (!fullPrefix) {
1233
+ continue;
1234
+ }
1235
+
1236
+ const end = Math.min(
1237
+ source.length,
1238
+ offset + candidate.contribution.maxSpan,
1239
+ );
1240
+ let result: ReturnType<typeof candidate.contribution.tokenize>;
1241
+ try {
1242
+ result = candidate.contribution.tokenize({
1243
+ source: end === source.length ? source : source.slice(0, end),
1244
+ offset,
1245
+ end,
1246
+ isFinal: opts.isFinal,
1247
+ context,
1248
+ lineStart: currentLineStart(source, offset),
1249
+ column: offset - currentLineStart(source, offset),
1250
+ });
1251
+ } catch (error) {
1252
+ reportMarkdownPluginFailure(candidate.pluginName, 'syntax', error);
1253
+ continue;
1254
+ }
1255
+ if (
1256
+ result == null ||
1257
+ typeof result !== 'object' ||
1258
+ typeof (result as {then?: unknown}).then === 'function'
1259
+ ) {
1260
+ reportMarkdownPluginFailure(
1261
+ candidate.pluginName,
1262
+ 'syntax',
1263
+ new TypeError('Tokenizer returned an invalid result'),
1264
+ );
1265
+ continue;
1266
+ }
1267
+ if (result.status === 'no-match') {
1268
+ continue;
1269
+ }
1270
+ if (result.status === 'defer') {
1271
+ return !opts.isFinal &&
1272
+ end === source.length &&
1273
+ source.length - offset < candidate.contribution.maxSpan
1274
+ ? {status: 'defer'}
1275
+ : {status: 'none'};
1276
+ }
1277
+ if (
1278
+ result.status !== 'match' ||
1279
+ !Number.isInteger(result.end) ||
1280
+ result.end <= offset ||
1281
+ result.end > end ||
1282
+ result.node.type !== 'extension' ||
1283
+ result.node.plugin !== candidate.pluginName ||
1284
+ result.node.display !== context ||
1285
+ result.node.name.trim() === '' ||
1286
+ !isMarkdownPluginData(result.node.data) ||
1287
+ !opts.plugins?.renderers.has(
1288
+ `${candidate.pluginName}\0${result.node.name}`,
1289
+ )
1290
+ ) {
1291
+ reportMarkdownPluginFailure(
1292
+ candidate.pluginName,
1293
+ 'syntax',
1294
+ new TypeError('Tokenizer returned an invalid extension node'),
1295
+ );
1296
+ continue;
1297
+ }
1298
+ const rawNode = result.node as typeof result.node & {
1299
+ readonly source?: unknown;
1300
+ readonly position?: unknown;
1301
+ };
1302
+ const {
1303
+ source: _ignoredSource,
1304
+ position: _ignoredPosition,
1305
+ ...safeNode
1306
+ } = rawNode;
1307
+ const absoluteStart = (opts.baseOffset ?? 0) + offset;
1308
+ const absoluteEnd = (opts.baseOffset ?? 0) + result.end;
1309
+ return {
1310
+ status: 'match',
1311
+ end: result.end,
1312
+ node: Object.freeze({
1313
+ ...safeNode,
1314
+ data: freezeMarkdownPluginData(result.node.data),
1315
+ source: source.slice(offset, result.end),
1316
+ ...(opts.sourceRanges && context === 'block'
1317
+ ? {
1318
+ position: Object.freeze({
1319
+ start: Object.freeze({offset: absoluteStart}),
1320
+ end: Object.freeze({offset: absoluteEnd}),
1321
+ }),
1322
+ }
1323
+ : null),
1324
+ }) as RuntimeExtensionNode,
1325
+ };
1326
+ }
1327
+ return {status: 'none'};
1328
+ }
1329
+
1330
+ type ParseOptionsWithoutPlugins = Omit<ParseOptions, 'plugins'>;
1331
+ type MathParseOptionsWithoutPlugins = Omit<MathParseOptions, 'plugins'>;
1332
+ type IncrementalParseOptionsWithoutPlugins = Omit<
1333
+ IncrementalParseOptions,
1334
+ 'plugins'
1335
+ >;
1336
+ type IncrementalMathParseOptionsWithoutPlugins = Omit<
1337
+ IncrementalMathParseOptions,
1338
+ 'plugins'
1339
+ >;
1340
+
781
1341
  export function parseInline(
782
1342
  text: string,
783
1343
  sourceIds?: ReadonlySet<string>,
784
1344
  ): InlineNode[];
785
1345
  export function parseInline(
786
1346
  text: string,
787
- options: MathParseOptions,
1347
+ options: MathParseOptionsWithoutPlugins,
788
1348
  ): InlineNodeWithMath[];
789
- export function parseInline(text: string, options: ParseOptions): InlineNode[];
1349
+ export function parseInline(
1350
+ text: string,
1351
+ options: ParseOptionsWithoutPlugins,
1352
+ ): InlineNode[];
1353
+ export function parseInline<
1354
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
1355
+ >(
1356
+ text: string,
1357
+ options: ParseOptionsWithoutPlugins & {plugins: Plugins},
1358
+ ): InlineNode<MarkdownExtensionsOf<Plugins>>[];
1359
+ export function parseInline<
1360
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
1361
+ >(
1362
+ text: string,
1363
+ options: MathParseOptionsWithoutPlugins & {plugins: Plugins},
1364
+ ): InlineNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
790
1365
  export function parseInline(
791
1366
  text: string,
792
1367
  arg?: ReadonlySet<string> | RuntimeParseOptions,
793
- ): InlineNodeWithMath[] {
794
- return parseInlineEntry(text, resolveOptions(arg));
1368
+ ): RuntimeInlineNode[] {
1369
+ return projectInlineNodes(
1370
+ parseInlineAstRuntime(text, arg),
1371
+ wantsLegacyRanges(arg),
1372
+ );
1373
+ }
1374
+
1375
+ /**
1376
+ * Parse inline Markdown into the canonical immutable Astryx AST.
1377
+ *
1378
+ * Unlike `parseInline`, this returns MDAST-aligned node names and fields. Math
1379
+ * nodes are present only when `math: true`, and plugin extension nodes are
1380
+ * inferred from the ordered `plugins` tuple.
1381
+ */
1382
+ export function parseInlineAst(
1383
+ text: string,
1384
+ sourceIds?: ReadonlySet<string>,
1385
+ ): MarkdownAstPhrasingContent[];
1386
+ export function parseInlineAst(
1387
+ text: string,
1388
+ options: ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins,
1389
+ ): MarkdownAstPhrasingContent[];
1390
+ export function parseInlineAst<
1391
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
1392
+ >(
1393
+ text: string,
1394
+ options: (ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins) & {
1395
+ plugins: Plugins;
1396
+ },
1397
+ ): MarkdownAstPhrasingContent<MarkdownExtensionsOf<Plugins>>[];
1398
+ export function parseInlineAst(
1399
+ text: string,
1400
+ arg?: ReadonlySet<string> | RuntimeParseOptions,
1401
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
1402
+ return parseInlineAstRuntime(text, arg);
1403
+ }
1404
+
1405
+ function parseInlineAstRuntime(
1406
+ text: string,
1407
+ arg?: ReadonlySet<string> | RuntimeParseOptions,
1408
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
1409
+ const opts = resolveOptions(arg);
1410
+ const nodes = parseInlineEntry(text, opts);
1411
+ if ((opts.plugins?.transforms.length ?? 0) === 0) {
1412
+ return nodes;
1413
+ }
1414
+ const transformed = applyMarkdownTransforms(
1415
+ {type: 'root', children: [{type: 'paragraph', children: nodes}]},
1416
+ opts.plugins,
1417
+ text,
1418
+ opts.isFinal,
1419
+ 'inline',
1420
+ );
1421
+ const paragraph = transformed.children[0];
1422
+ return paragraph?.type === 'paragraph' ? [...paragraph.children] : nodes;
795
1423
  }
796
1424
 
797
1425
  /**
@@ -806,22 +1434,32 @@ export function parseInline(
806
1434
  function parseInlineEntry(
807
1435
  text: string,
808
1436
  opts: ResolvedOptions,
809
- ): InlineNodeWithMath[] {
810
- const nodes = parseInlineImpl(text, opts);
1437
+ context: 'default' | 'tableCell' = 'default',
1438
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
1439
+ const nodes = parseInlineImpl(text, opts, context);
811
1440
  return opts.autolink === 'gfm' ? transformAutolinks(nodes) : nodes;
812
1441
  }
813
1442
 
814
1443
  function parseInlineImpl(
815
1444
  text: string,
816
1445
  opts: ResolvedOptions,
817
- ): InlineNodeWithMath[] {
818
- const nodes: InlineNodeWithMath[] = [];
1446
+ context: 'default' | 'tableCell' = 'default',
1447
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
1448
+ const nodes: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
1449
+ // Only a plugin that actually contributes INLINE syntax may cost anything
1450
+ // per source position. A transform-only list contributes none, so it takes
1451
+ // the same path as an omitted or empty one: no candidate probe per
1452
+ // position, and no map lookup per character in the plain-text scan below.
1453
+ const inlineExtensionStarts =
1454
+ opts.plugins != null && opts.plugins.inlineByFirstCharacter.size > 0
1455
+ ? opts.plugins.inlineByFirstCharacter
1456
+ : undefined;
819
1457
  let i = 0;
820
1458
 
821
1459
  while (i < text.length) {
822
1460
  // --- Escape ---
823
1461
  if (text[i] === '\\' && i + 1 < text.length) {
824
- nodes.push({type: 'text', content: text[i + 1]});
1462
+ nodes.push({type: 'text', value: text[i + 1]});
825
1463
  i += 2;
826
1464
  continue;
827
1465
  }
@@ -832,7 +1470,13 @@ function parseInlineImpl(
832
1470
  const openIndex = i + tickCount;
833
1471
  const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
834
1472
  if (closeIndex !== -1) {
835
- nodes.push({type: 'code', content: text.slice(openIndex, closeIndex)});
1473
+ nodes.push({
1474
+ type: 'inlineCode',
1475
+ value:
1476
+ context === 'tableCell'
1477
+ ? text.slice(openIndex, closeIndex).replace(/\\\|/g, '|')
1478
+ : text.slice(openIndex, closeIndex),
1479
+ });
836
1480
  i = closeIndex + tickCount;
837
1481
  continue;
838
1482
  }
@@ -842,7 +1486,10 @@ function parseInlineImpl(
842
1486
  if (opts.math && text[i] === '$') {
843
1487
  const closeIndex = findInlineMathEnd(text, i);
844
1488
  if (closeIndex !== -1) {
845
- nodes.push({type: 'math', value: text.slice(i + 1, closeIndex)});
1489
+ nodes.push({
1490
+ type: 'inlineMath',
1491
+ value: text.slice(i + 1, closeIndex),
1492
+ });
846
1493
  i = closeIndex + 1;
847
1494
  continue;
848
1495
  }
@@ -865,13 +1512,13 @@ function parseInlineImpl(
865
1512
  const srcClose = findClosingParen(text, altClose + 2);
866
1513
  if (srcClose !== -1) {
867
1514
  const src = text.slice(altClose + 2, srcClose);
868
- if (!isSafeUrl(src)) {
1515
+ if (!isSafeMarkdownParserUrl(src)) {
869
1516
  // Dangerous scheme — emit as plain text.
870
- nodes.push({type: 'text', content: text.slice(i, srcClose + 1)});
1517
+ nodes.push({type: 'text', value: text.slice(i, srcClose + 1)});
871
1518
  } else {
872
1519
  nodes.push({
873
1520
  type: 'image',
874
- src,
1521
+ url: src,
875
1522
  alt: text.slice(i + 2, altClose),
876
1523
  });
877
1524
  }
@@ -908,14 +1555,18 @@ function parseInlineImpl(
908
1555
  const urlClose = findClosingParen(text, textClose + 2);
909
1556
  if (urlClose !== -1) {
910
1557
  const href = text.slice(textClose + 2, urlClose);
911
- if (!isSafeUrl(href)) {
1558
+ if (!isSafeMarkdownParserUrl(href)) {
912
1559
  // Dangerous scheme — emit as plain text instead of a link.
913
- nodes.push({type: 'text', content: text.slice(i, urlClose + 1)});
1560
+ nodes.push({type: 'text', value: text.slice(i, urlClose + 1)});
914
1561
  } else {
915
1562
  nodes.push({
916
1563
  type: 'link',
917
- href,
918
- children: parseInlineImpl(text.slice(i + 1, textClose), opts),
1564
+ url: href,
1565
+ children: parseInlineImpl(
1566
+ text.slice(i + 1, textClose),
1567
+ protectedInlineOptions(opts),
1568
+ context,
1569
+ ),
919
1570
  });
920
1571
  }
921
1572
  i = urlClose + 1;
@@ -926,7 +1577,7 @@ function parseInlineImpl(
926
1577
 
927
1578
  // --- Reference link [text][label] / [text][] / [text] ---
928
1579
  if (opts.linkDefs != null && text[i] === '[') {
929
- const ref = matchReferenceLink(text, i, opts.linkDefs, opts);
1580
+ const ref = matchReferenceLink(text, i, opts.linkDefs, opts, context);
930
1581
  if (ref) {
931
1582
  nodes.push(ref.node);
932
1583
  i = ref.end;
@@ -950,11 +1601,15 @@ function parseInlineImpl(
950
1601
  (!isUnderscore || !isWordChar(text[closeIndex + 3]))
951
1602
  ) {
952
1603
  nodes.push({
953
- type: 'bold',
1604
+ type: 'strong',
954
1605
  children: [
955
1606
  {
956
- type: 'italic',
957
- children: parseInlineImpl(text.slice(i + 3, closeIndex), opts),
1607
+ type: 'emphasis',
1608
+ children: parseInlineImpl(
1609
+ text.slice(i + 3, closeIndex),
1610
+ opts,
1611
+ context,
1612
+ ),
958
1613
  },
959
1614
  ],
960
1615
  });
@@ -980,8 +1635,12 @@ function parseInlineImpl(
980
1635
  (!isUnderscore || !isWordChar(text[closeIndex + 2]))
981
1636
  ) {
982
1637
  nodes.push({
983
- type: 'bold',
984
- children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
1638
+ type: 'strong',
1639
+ children: parseInlineImpl(
1640
+ text.slice(i + 2, closeIndex),
1641
+ opts,
1642
+ context,
1643
+ ),
985
1644
  });
986
1645
  i = closeIndex + 2;
987
1646
  continue;
@@ -994,8 +1653,12 @@ function parseInlineImpl(
994
1653
  const closeIndex = text.indexOf('~~', i + 2);
995
1654
  if (closeIndex !== -1) {
996
1655
  nodes.push({
997
- type: 'strikethrough',
998
- children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
1656
+ type: 'delete',
1657
+ children: parseInlineImpl(
1658
+ text.slice(i + 2, closeIndex),
1659
+ opts,
1660
+ context,
1661
+ ),
999
1662
  });
1000
1663
  i = closeIndex + 2;
1001
1664
  continue;
@@ -1015,8 +1678,12 @@ function parseInlineImpl(
1015
1678
  (!isUnderscore || !isWordChar(text[closeIndex + 1]))
1016
1679
  ) {
1017
1680
  nodes.push({
1018
- type: 'italic',
1019
- children: parseInlineImpl(text.slice(i + 1, closeIndex), opts),
1681
+ type: 'emphasis',
1682
+ children: parseInlineImpl(
1683
+ text.slice(i + 1, closeIndex),
1684
+ opts,
1685
+ context,
1686
+ ),
1020
1687
  });
1021
1688
  i = closeIndex + 1;
1022
1689
  continue;
@@ -1024,12 +1691,29 @@ function parseInlineImpl(
1024
1691
  }
1025
1692
  }
1026
1693
 
1694
+ // --- Extension syntax (built-ins and protected contexts win) ---
1695
+ if (inlineExtensionStarts !== undefined) {
1696
+ const extension = matchExtensionSyntax(text, i, 'inline', opts);
1697
+ if (extension.status === 'match') {
1698
+ nodes.push(
1699
+ extension.node as Extract<RuntimeExtensionNode, {display: 'inline'}>,
1700
+ );
1701
+ i = extension.end;
1702
+ continue;
1703
+ }
1704
+ if (extension.status === 'defer') {
1705
+ break;
1706
+ }
1707
+ }
1708
+
1027
1709
  // --- Plain text (with line-break detection) ---
1028
1710
  let end = i + 1;
1029
1711
  while (
1030
1712
  end < text.length &&
1031
1713
  !'*_~`[!\\\n\u3010'.includes(text[end]) &&
1032
- !(opts.math && text[end] === '$')
1714
+ !(opts.math && text[end] === '$') &&
1715
+ (inlineExtensionStarts === undefined ||
1716
+ !inlineExtensionStarts.has(text[end]))
1033
1717
  ) {
1034
1718
  end++;
1035
1719
  }
@@ -1043,9 +1727,12 @@ function parseInlineImpl(
1043
1727
  if (trimmed.length > 0) {
1044
1728
  const last = nodes[nodes.length - 1];
1045
1729
  if (last?.type === 'text') {
1046
- last.content += trimmed;
1730
+ nodes[nodes.length - 1] = {
1731
+ ...last,
1732
+ value: last.value + trimmed,
1733
+ };
1047
1734
  } else {
1048
- nodes.push({type: 'text', content: trimmed});
1735
+ nodes.push({type: 'text', value: trimmed});
1049
1736
  }
1050
1737
  }
1051
1738
  nodes.push({type: 'break'});
@@ -1056,9 +1743,9 @@ function parseInlineImpl(
1056
1743
 
1057
1744
  const last = nodes[nodes.length - 1];
1058
1745
  if (last?.type === 'text') {
1059
- last.content += content;
1746
+ nodes[nodes.length - 1] = {...last, value: last.value + content};
1060
1747
  } else {
1061
- nodes.push({type: 'text', content});
1748
+ nodes.push({type: 'text', value: content});
1062
1749
  }
1063
1750
  i = end;
1064
1751
  }
@@ -1183,7 +1870,7 @@ function scanAutolinksInText(text: string): AutolinkMatch[] {
1183
1870
  while ((m = re.exec(text)) !== null) {
1184
1871
  const url = m[1];
1185
1872
  // Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
1186
- if (!isSafeUrl(url)) {
1873
+ if (!isSafeMarkdownParserUrl(url)) {
1187
1874
  continue;
1188
1875
  }
1189
1876
  matches.push({
@@ -1303,26 +1990,28 @@ function scanAutolinksInText(text: string): AutolinkMatch[] {
1303
1990
  * Split a text-node `content` string into a sequence of text + link nodes
1304
1991
  * based on autolink matches.
1305
1992
  */
1306
- function splitTextOnAutolinks(content: string): InlineNodeWithMath[] {
1993
+ function splitTextOnAutolinks(
1994
+ content: string,
1995
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
1307
1996
  const matches = scanAutolinksInText(content);
1308
1997
  if (matches.length === 0) {
1309
- return [{type: 'text', content}];
1998
+ return [{type: 'text', value: content}];
1310
1999
  }
1311
- const out: InlineNodeWithMath[] = [];
2000
+ const out: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
1312
2001
  let cursor = 0;
1313
2002
  for (const m of matches) {
1314
2003
  if (m.start > cursor) {
1315
- out.push({type: 'text', content: content.slice(cursor, m.start)});
2004
+ out.push({type: 'text', value: content.slice(cursor, m.start)});
1316
2005
  }
1317
2006
  out.push({
1318
2007
  type: 'link',
1319
- href: m.href,
1320
- children: [{type: 'text', content: m.display}],
2008
+ url: m.href,
2009
+ children: [{type: 'text', value: m.display}],
1321
2010
  });
1322
2011
  cursor = m.end;
1323
2012
  }
1324
2013
  if (cursor < content.length) {
1325
- out.push({type: 'text', content: content.slice(cursor)});
2014
+ out.push({type: 'text', value: content.slice(cursor)});
1326
2015
  }
1327
2016
  return out;
1328
2017
  }
@@ -1335,23 +2024,25 @@ function splitTextOnAutolinks(content: string): InlineNodeWithMath[] {
1335
2024
  * `image` alt text, `citation`, or `break`. Runs only on the outermost
1336
2025
  * block's inline tree (see `parseInlineEntry`).
1337
2026
  */
1338
- function transformAutolinks(nodes: InlineNodeWithMath[]): InlineNodeWithMath[] {
1339
- const out: InlineNodeWithMath[] = [];
2027
+ function transformAutolinks(
2028
+ nodes: ReadonlyArray<MarkdownAstPhrasingContent<RuntimeExtensionNode>>,
2029
+ ): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
2030
+ const out: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
1340
2031
  for (const node of nodes) {
1341
2032
  if (node.type === 'text') {
1342
- const split = splitTextOnAutolinks(node.content);
2033
+ const split = splitTextOnAutolinks(node.value);
1343
2034
  for (const seg of split) {
1344
2035
  const last = out[out.length - 1];
1345
2036
  if (seg.type === 'text' && last?.type === 'text') {
1346
- last.content += seg.content;
2037
+ out[out.length - 1] = {...last, value: last.value + seg.value};
1347
2038
  } else {
1348
2039
  out.push(seg);
1349
2040
  }
1350
2041
  }
1351
2042
  } else if (
1352
- node.type === 'bold' ||
1353
- node.type === 'italic' ||
1354
- node.type === 'strikethrough'
2043
+ node.type === 'strong' ||
2044
+ node.type === 'emphasis' ||
2045
+ node.type === 'delete'
1355
2046
  ) {
1356
2047
  out.push({...node, children: transformAutolinks(node.children)});
1357
2048
  } else {
@@ -1434,7 +2125,16 @@ function isTableSeparator(line: string): boolean {
1434
2125
  * offset into it would not address the document.
1435
2126
  */
1436
2127
  function nested(opts: ResolvedOptions): ResolvedOptions {
1437
- return opts.sourceRanges ? {...opts, sourceRanges: false} : opts;
2128
+ const nestedOptions =
2129
+ opts.allowBlockSyntax === false ? opts : {...opts, allowBlockSyntax: false};
2130
+ return nestedOptions.sourceRanges || nestedOptions.astPositions
2131
+ ? {...nestedOptions, sourceRanges: false, astPositions: false}
2132
+ : nestedOptions;
2133
+ }
2134
+
2135
+ function blockExtensionColumn(line: string): number | null {
2136
+ const indentation = line.length - line.trimStart().length;
2137
+ return indentation <= 3 ? indentation : null;
1438
2138
  }
1439
2139
 
1440
2140
  /**
@@ -1510,10 +2210,13 @@ function parseTable(
1510
2210
  lines: string[],
1511
2211
  lineIndex: number,
1512
2212
  opts: ResolvedOptions,
1513
- ): {node: BlockNodeWithMath; nextIndex: number} {
1514
- const headers: TableCellNodeWithMath[] = splitTableRow(lines[lineIndex]).map(
1515
- cell => ({children: parseInlineEntry(cell, opts)}),
1516
- );
2213
+ ): {node: MarkdownAstBlockContent<RuntimeExtensionNode>; nextIndex: number} {
2214
+ const headers: MarkdownAstTableCell<RuntimeExtensionNode>[] = splitTableRow(
2215
+ lines[lineIndex],
2216
+ ).map(cell => ({
2217
+ type: 'tableCell',
2218
+ children: parseInlineEntry(cell, opts, 'tableCell'),
2219
+ }));
1517
2220
  const alignments: TableAlignment[] = splitTableRow(lines[lineIndex + 1]).map(
1518
2221
  cell => {
1519
2222
  const trimmed = cell.trim();
@@ -1528,22 +2231,32 @@ function parseTable(
1528
2231
  : null;
1529
2232
  },
1530
2233
  );
1531
- const rows: TableCellNodeWithMath[][] = [];
2234
+ const rows: MarkdownAstTableRow<RuntimeExtensionNode>[] = [];
1532
2235
  let rowIndex = lineIndex + 2;
1533
2236
  while (
1534
2237
  rowIndex < lines.length &&
1535
2238
  lines[rowIndex].includes('|') &&
1536
2239
  lines[rowIndex].trim() !== ''
1537
2240
  ) {
1538
- rows.push(
1539
- splitTableRow(lines[rowIndex]).map(cell => ({
1540
- children: parseInlineEntry(cell, opts),
2241
+ rows.push({
2242
+ type: 'tableRow',
2243
+ children: splitTableRow(lines[rowIndex]).map(cell => ({
2244
+ type: 'tableCell',
2245
+ children: parseInlineEntry(cell, opts, 'tableCell'),
1541
2246
  })),
1542
- );
2247
+ });
1543
2248
  rowIndex++;
1544
2249
  }
2250
+ const header: MarkdownAstTableRow<RuntimeExtensionNode> = {
2251
+ type: 'tableRow',
2252
+ children: headers,
2253
+ };
1545
2254
  return {
1546
- node: {type: 'table', headers, alignments, rows},
2255
+ node: {
2256
+ type: 'table',
2257
+ align: alignments,
2258
+ children: [header, ...rows],
2259
+ },
1547
2260
  nextIndex: rowIndex,
1548
2261
  };
1549
2262
  }
@@ -1553,8 +2266,8 @@ function parseList(
1553
2266
  startIndex: number,
1554
2267
  ordered: boolean,
1555
2268
  opts: ResolvedOptions,
1556
- ): {node: BlockNodeWithMath; nextIndex: number} {
1557
- const items: ListItemNodeWithMath[] = [];
2269
+ ): {node: MarkdownAstBlockContent<RuntimeExtensionNode>; nextIndex: number} {
2270
+ const items: MarkdownAstListItem<RuntimeExtensionNode>[] = [];
1558
2271
  const baseIndent = getIndent(lines[startIndex]);
1559
2272
  // Ordered lists may use either '.' or ')' as the marker delimiter
1560
2273
  // (CommonMark 5.2). Capture which one this list starts with so its items
@@ -1608,7 +2321,11 @@ function parseList(
1608
2321
  itemText += '\n' + deindented.join('\n');
1609
2322
  }
1610
2323
 
1611
- items.push({checked, children: parseMarkdownImpl(itemText, nested(opts))});
2324
+ items.push({
2325
+ type: 'listItem',
2326
+ checked,
2327
+ children: parseMarkdownImpl(itemText, nested(opts)),
2328
+ });
1612
2329
 
1613
2330
  // CommonMark loose list: blank line(s) between items of the same style
1614
2331
  // and indent still form one list. Skip the blanks and continue if the
@@ -1626,17 +2343,15 @@ function parseList(
1626
2343
  index = lookahead;
1627
2344
  }
1628
2345
  }
1629
- return {
1630
- node: {
1631
- type: 'list',
1632
- ordered,
1633
- start,
1634
- delimiter: ordered ? (delim as '.' | ')') : undefined,
1635
- loose: loose || undefined,
1636
- items,
1637
- },
1638
- nextIndex: index,
2346
+ const node: MarkdownAstList<RuntimeExtensionNode> = {
2347
+ type: 'list',
2348
+ ordered,
2349
+ start,
2350
+ delimiter: ordered ? (delim as '.' | ')') : undefined,
2351
+ spread: loose || undefined,
2352
+ children: items,
1639
2353
  };
2354
+ return {node, nextIndex: index};
1640
2355
  }
1641
2356
 
1642
2357
  // ---------------------------------------------------------------------------
@@ -1649,23 +2364,89 @@ export function parseMarkdown(
1649
2364
  ): BlockNode[];
1650
2365
  export function parseMarkdown(
1651
2366
  input: string,
1652
- options: MathParseOptions,
2367
+ options: MathParseOptionsWithoutPlugins,
1653
2368
  ): BlockNodeWithMath[];
1654
2369
  export function parseMarkdown(
1655
2370
  input: string,
1656
- options: ParseOptions,
2371
+ options: ParseOptionsWithoutPlugins,
1657
2372
  ): BlockNode[];
2373
+ export function parseMarkdown<
2374
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
2375
+ >(
2376
+ input: string,
2377
+ options: ParseOptionsWithoutPlugins & {plugins: Plugins},
2378
+ ): BlockNode<MarkdownExtensionsOf<Plugins>>[];
2379
+ export function parseMarkdown<
2380
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
2381
+ >(
2382
+ input: string,
2383
+ options: MathParseOptionsWithoutPlugins & {plugins: Plugins},
2384
+ ): BlockNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
1658
2385
  export function parseMarkdown(
1659
2386
  input: string,
1660
2387
  arg?: ReadonlySet<string> | RuntimeParseOptions,
1661
- ): BlockNodeWithMath[] {
1662
- return parseMarkdownImpl(input, resolveOptions(arg));
2388
+ ): RuntimeBlockNode[] {
2389
+ return projectMarkdownRoot(
2390
+ parseMarkdownAstInternal(input, arg, true),
2391
+ wantsLegacyRanges(arg),
2392
+ );
2393
+ }
2394
+
2395
+ /**
2396
+ * Parse block Markdown into the canonical immutable Astryx AST.
2397
+ *
2398
+ * Unlike `parseMarkdown`, this returns the MDAST-aligned root used by Markdown,
2399
+ * transforms, and Outline. Math nodes are present only when `math: true`, and
2400
+ * plugin extension nodes are inferred from the ordered `plugins` tuple.
2401
+ */
2402
+ export function parseMarkdownAst(
2403
+ input: string,
2404
+ sourceIds?: ReadonlySet<string>,
2405
+ ): MarkdownAstRoot;
2406
+ export function parseMarkdownAst(
2407
+ input: string,
2408
+ options: ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins,
2409
+ ): MarkdownAstRoot;
2410
+ export function parseMarkdownAst<
2411
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
2412
+ >(
2413
+ input: string,
2414
+ options: (ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins) & {
2415
+ plugins: Plugins;
2416
+ },
2417
+ ): MarkdownAstRoot<MarkdownExtensionsOf<Plugins>>;
2418
+ export function parseMarkdownAst(
2419
+ input: string,
2420
+ arg?: ReadonlySet<string> | RuntimeParseOptions,
2421
+ ): MarkdownAstRoot<RuntimeExtensionNode> {
2422
+ return parseMarkdownAstInternal(input, arg, true);
2423
+ }
2424
+
2425
+ /** @internal Full-parse entry point with streaming finality for Outline. */
2426
+ export function parseMarkdownAstInternal(
2427
+ input: string,
2428
+ arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
2429
+ isFinal: boolean,
2430
+ ): MarkdownAstRoot<RuntimeExtensionNode> {
2431
+ const resolved = resolveOptions(arg);
2432
+ const opts = isFinal ? resolved : {...resolved, isFinal: false};
2433
+ const root: MarkdownAstRoot<RuntimeExtensionNode> = {
2434
+ type: 'root',
2435
+ children: parseMarkdownImpl(input, opts),
2436
+ };
2437
+ return applyMarkdownTransforms(
2438
+ root,
2439
+ opts.plugins,
2440
+ input,
2441
+ opts.isFinal,
2442
+ 'block',
2443
+ );
1663
2444
  }
1664
2445
 
1665
2446
  function parseMarkdownImpl(
1666
2447
  input: string,
1667
2448
  baseOpts: ResolvedOptions,
1668
- ): BlockNodeWithMath[] {
2449
+ ): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
1669
2450
  // Collect this input's link reference definitions and strip their lines,
1670
2451
  // then merge them with any definitions inherited from an enclosing parse
1671
2452
  // (the incremental parser passes the whole document's definitions in; a
@@ -1686,18 +2467,35 @@ function parseMarkdownImpl(
1686
2467
  const opts: ResolvedOptions =
1687
2468
  linkDefs != null ? {...baseOpts, linkDefs} : baseOpts;
1688
2469
  const lines = cleaned.split('\n');
1689
- const blocks: BlockNodeWithMath[] = [];
2470
+ const hasBlockExtensionSyntax =
2471
+ opts.allowBlockSyntax !== false &&
2472
+ (opts.plugins?.blockByFirstCharacter.size ?? 0) > 0;
2473
+ // Derived from the already-split lines rather than a second character
2474
+ // scan: every line contributes its own length plus the newline it ended on.
2475
+ const lineOffsets = [0];
2476
+ if (opts.astPositions === true || hasBlockExtensionSyntax) {
2477
+ let offset = 0;
2478
+ for (let index = 0; index < lines.length; index++) {
2479
+ offset += lines[index].length + 1;
2480
+ lineOffsets.push(offset);
2481
+ }
2482
+ }
2483
+ const blockExtensionMatches = new Map<number, ExtensionMatch>();
2484
+ const blocks: MarkdownAstBlockContent<RuntimeExtensionNode>[] = [];
1690
2485
  // The line each block started on, parallel to `blocks`. Only collected when
1691
2486
  // ranges were asked for; a block's end is resolved after the loop, since the
1692
2487
  // branch that produced it has already moved `index` past whatever it read.
1693
- const blockStartLines: number[] | null = opts.sourceRanges ? [] : null;
2488
+ const blockStartLines: number[] | null =
2489
+ opts.astPositions === true ? [] : null;
1694
2490
  // Set only by a block that consumes blank lines as content, where the
1695
2491
  // positional end derivation would trim them away.
1696
- const blockEndLines: (number | undefined)[] | null = opts.sourceRanges
1697
- ? []
1698
- : null;
2492
+ const blockEndLines: (number | undefined)[] | null =
2493
+ opts.astPositions === true ? [] : null;
1699
2494
  let blockStartLine = 0;
1700
- const pushBlock = (node: BlockNodeWithMath, endLine?: number) => {
2495
+ const pushBlock = (
2496
+ node: MarkdownAstBlockContent<RuntimeExtensionNode>,
2497
+ endLine?: number,
2498
+ ) => {
1701
2499
  blocks.push(node);
1702
2500
  blockStartLines?.push(blockStartLine);
1703
2501
  blockEndLines?.push(endLine);
@@ -1713,10 +2511,16 @@ function parseMarkdownImpl(
1713
2511
  }
1714
2512
 
1715
2513
  // --- Fenced code block ---
1716
- const fenceMatch = line.match(/^(`{3,}|~{3,})(\w*)/);
2514
+ const fenceMatch = line.match(/^(`{3,}|~{3,})/);
1717
2515
  if (fenceMatch) {
1718
2516
  const fence = fenceMatch[1];
1719
- const language = fenceMatch[2] || 'plaintext';
2517
+ const info = line.slice(fence.length);
2518
+ const language = info.match(/^(\S+)/)?.[1] ?? null;
2519
+ const legacyLanguage = info.match(/^(\w*)/)?.[1] || null;
2520
+ const meta =
2521
+ language == null
2522
+ ? undefined
2523
+ : info.slice(language.length).trim() || undefined;
1720
2524
  const codeLines: string[] = [];
1721
2525
  index++;
1722
2526
  while (index < lines.length && !lines[index].startsWith(fence)) {
@@ -1728,7 +2532,15 @@ function parseMarkdownImpl(
1728
2532
  // can end on them, so it states its own end rather than letting the
1729
2533
  // positional derivation trim them off.
1730
2534
  pushBlock(
1731
- {type: 'codeblock', language, content: codeLines.join('\n')},
2535
+ markMarkdownAstLegacyCodeLanguage(
2536
+ {
2537
+ type: 'code',
2538
+ lang: language,
2539
+ ...(meta == null ? {} : {meta}),
2540
+ value: codeLines.join('\n'),
2541
+ },
2542
+ legacyLanguage,
2543
+ ),
1732
2544
  Math.min(index, lines.length) - 1,
1733
2545
  );
1734
2546
  continue;
@@ -1752,7 +2564,7 @@ function parseMarkdownImpl(
1752
2564
  if (headingMatch) {
1753
2565
  pushBlock({
1754
2566
  type: 'heading',
1755
- level: headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6,
2567
+ depth: headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6,
1756
2568
  children: parseInlineEntry(headingMatch[2], opts),
1757
2569
  });
1758
2570
  index++;
@@ -1761,7 +2573,7 @@ function parseMarkdownImpl(
1761
2573
 
1762
2574
  // --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
1763
2575
  if (isHorizontalRule(line)) {
1764
- pushBlock({type: 'hr'});
2576
+ pushBlock({type: 'thematicBreak'});
1765
2577
  index++;
1766
2578
  continue;
1767
2579
  }
@@ -1773,9 +2585,9 @@ function parseMarkdownImpl(
1773
2585
  if (
1774
2586
  imageMatch &&
1775
2587
  line.trim() === imageMatch[0] &&
1776
- isSafeUrl(imageMatch[2])
2588
+ isSafeMarkdownParserUrl(imageMatch[2])
1777
2589
  ) {
1778
- pushBlock({type: 'image', alt: imageMatch[1], src: imageMatch[2]});
2590
+ pushBlock({type: 'image', alt: imageMatch[1], url: imageMatch[2]});
1779
2591
  index++;
1780
2592
  continue;
1781
2593
  }
@@ -1825,16 +2637,80 @@ function parseMarkdownImpl(
1825
2637
  continue;
1826
2638
  }
1827
2639
 
2640
+ // --- Extension block syntax (built-in blocks take precedence) ---
2641
+ if (hasBlockExtensionSyntax) {
2642
+ const extensionColumn = blockExtensionColumn(line);
2643
+ const extensionOffset =
2644
+ extensionColumn == null
2645
+ ? lineOffsets[index]
2646
+ : lineOffsets[index] + extensionColumn;
2647
+ const extension =
2648
+ blockExtensionMatches.get(extensionOffset) ??
2649
+ (extensionColumn == null
2650
+ ? {status: 'none' as const}
2651
+ : matchExtensionSyntax(cleaned, extensionOffset, 'block', opts));
2652
+ blockExtensionMatches.delete(extensionOffset);
2653
+ if (extension.status === 'match') {
2654
+ const consumedEnd = extension.end;
2655
+ const consumed = cleaned.slice(lineOffsets[index], consumedEnd);
2656
+ if (
2657
+ consumedEnd < cleaned.length &&
2658
+ cleaned[consumedEnd] !== '\n' &&
2659
+ cleaned[consumedEnd - 1] !== '\n'
2660
+ ) {
2661
+ reportMarkdownPluginFailure(
2662
+ extension.node.plugin,
2663
+ 'syntax',
2664
+ new TypeError('Block syntax must consume complete lines'),
2665
+ );
2666
+ } else {
2667
+ const newlineCount = consumed.split('\n').length - 1;
2668
+ const consumedLines = Math.max(
2669
+ 1,
2670
+ newlineCount + (consumed.endsWith('\n') ? 0 : 1),
2671
+ );
2672
+ pushBlock(
2673
+ extension.node as Extract<RuntimeExtensionNode, {display: 'block'}>,
2674
+ index + consumedLines - 1,
2675
+ );
2676
+ index += consumedLines;
2677
+ continue;
2678
+ }
2679
+ }
2680
+ if (extension.status === 'defer') {
2681
+ break;
2682
+ }
2683
+ }
2684
+
1828
2685
  // --- Paragraph ---
1829
2686
  const paraLines: string[] = [line];
1830
2687
  index++;
1831
- while (
1832
- index < lines.length &&
1833
- !isBlockStart(lines[index]) &&
1834
- !(opts.math && matchDisplayMathBlock(lines, index) != null) &&
1835
- lines[index].trim() !== ''
1836
- ) {
1837
- paraLines.push(lines[index]);
2688
+ while (index < lines.length) {
2689
+ const nextLine = lines[index];
2690
+ if (
2691
+ isBlockStart(nextLine) ||
2692
+ (opts.math && matchDisplayMathBlock(lines, index) != null) ||
2693
+ nextLine.trim() === ''
2694
+ ) {
2695
+ break;
2696
+ }
2697
+ if (hasBlockExtensionSyntax) {
2698
+ const extensionColumn = blockExtensionColumn(nextLine);
2699
+ if (extensionColumn != null) {
2700
+ const extensionOffset = lineOffsets[index] + extensionColumn;
2701
+ const extension = matchExtensionSyntax(
2702
+ cleaned,
2703
+ extensionOffset,
2704
+ 'block',
2705
+ opts,
2706
+ );
2707
+ if (extension.status !== 'none') {
2708
+ blockExtensionMatches.set(extensionOffset, extension);
2709
+ break;
2710
+ }
2711
+ }
2712
+ }
2713
+ paraLines.push(nextLine);
1838
2714
  index++;
1839
2715
  }
1840
2716
  pushBlock({
@@ -1866,7 +2742,7 @@ function parseMarkdownImpl(
1866
2742
  * `lineMap` says which input line each surviving line came from.
1867
2743
  */
1868
2744
  function stampSourceRanges(
1869
- blocks: BlockNodeWithMath[],
2745
+ blocks: MarkdownAstBlockContent<RuntimeExtensionNode>[],
1870
2746
  blockStartLines: number[],
1871
2747
  blockEndLines: (number | undefined)[],
1872
2748
  lines: string[],
@@ -1903,7 +2779,13 @@ function stampSourceRanges(
1903
2779
  // range that dropped it would slice to something that re-parses
1904
2780
  // differently.
1905
2781
  const end = lineStart(endLine) + lines[endLine].length;
1906
- blocks[i] = {...blocks[i], range: {start: lineStart(startLine), end}};
2782
+ blocks[i] = {
2783
+ ...blocks[i],
2784
+ position: {
2785
+ start: {offset: lineStart(startLine)},
2786
+ end: {offset: end},
2787
+ },
2788
+ };
1907
2789
  }
1908
2790
  }
1909
2791
 
@@ -1938,6 +2820,10 @@ export interface IncrementalState<MathEnabled extends boolean = false> {
1938
2820
  * lack the ranges the caller now asks for, or carry ones it did not.
1939
2821
  */
1940
2822
  sourceRanges?: boolean;
2823
+ /** Identity of citation sources used by settled nodes. */
2824
+ sourceIdsKey?: string;
2825
+ /** Ordered syntax-only identity used by settled nodes. */
2826
+ pluginSyntaxIdentity?: string;
1941
2827
  /**
1942
2828
  * Signature of the link reference definitions the cached `settledBlocks`
1943
2829
  * were parsed with. Definitions are document-global and typically arrive
@@ -1968,6 +2854,13 @@ type IncrementalCache = {
1968
2854
  /** The effective document-global definitions used by slice parses. */
1969
2855
  linkDefs: ReadonlyMap<string, string>;
1970
2856
  linkDefsKey: string;
2857
+ /** Canonical settled blocks shared by rendering and compatibility projection. */
2858
+ settledAstBlocks: MarkdownAstBlockContent<RuntimeExtensionNode>[];
2859
+ settledRevision: number;
2860
+ projectedRevision: number;
2861
+ projectedSettledBlocks: RuntimeBlockNode[];
2862
+ /** Preserves released settled-node identity across projected snapshots. */
2863
+ projectionCache: LegacyProjectionCache;
1971
2864
  work: IncrementalWork;
1972
2865
  };
1973
2866
 
@@ -1986,6 +2879,11 @@ function makeIncrementalCache(
1986
2879
  tailLinkDefs: new Map(),
1987
2880
  linkDefs: defs,
1988
2881
  linkDefsKey: linkDefsSignature(defs),
2882
+ settledAstBlocks: [],
2883
+ settledRevision: 0,
2884
+ projectedRevision: -1,
2885
+ projectedSettledBlocks: [],
2886
+ projectionCache: new WeakMap(),
1989
2887
  work: {
1990
2888
  splitCharacters: 0,
1991
2889
  boundaryLines: 0,
@@ -2470,7 +3368,7 @@ function trimUnsettledStructural(text: string): string {
2470
3368
  * rather than into the slice.
2471
3369
  */
2472
3370
  function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
2473
- return opts.sourceRanges ? {...opts, baseOffset: offset} : opts;
3371
+ return opts.astPositions === true ? {...opts, baseOffset: offset} : opts;
2474
3372
  }
2475
3373
 
2476
3374
  /**
@@ -2481,9 +3379,9 @@ function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
2481
3379
  * lists even though the full-text parser joins them per CommonMark §5.3.
2482
3380
  */
2483
3381
  function mergeSettledBlocks(
2484
- prev: BlockNodeWithMath[],
2485
- delta: BlockNodeWithMath[],
2486
- ): BlockNodeWithMath[] {
3382
+ prev: MarkdownAstBlockContent<RuntimeExtensionNode>[],
3383
+ delta: MarkdownAstBlockContent<RuntimeExtensionNode>[],
3384
+ ): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
2487
3385
  if (prev.length === 0 || delta.length === 0) {
2488
3386
  return [...prev, ...delta];
2489
3387
  }
@@ -2495,17 +3393,21 @@ function mergeSettledBlocks(
2495
3393
  prevLast.ordered === deltaFirst.ordered &&
2496
3394
  prevLast.delimiter === deltaFirst.delimiter
2497
3395
  ) {
2498
- const merged: BlockNodeWithMath = {
3396
+ const merged: MarkdownAstBlockContent<RuntimeExtensionNode> = {
2499
3397
  type: 'list',
2500
3398
  ordered: prevLast.ordered,
2501
3399
  start: prevLast.start,
2502
3400
  delimiter: prevLast.delimiter,
2503
- loose: true,
2504
- items: [...prevLast.items, ...deltaFirst.items],
2505
- // One list now, so one range: from where the first half started to
2506
- // where the second half ended.
2507
- ...(prevLast.range != null && deltaFirst.range != null
2508
- ? {range: {start: prevLast.range.start, end: deltaFirst.range.end}}
3401
+ spread: true,
3402
+ children: [...prevLast.children, ...deltaFirst.children],
3403
+ // One list now, so one position spans both halves.
3404
+ ...(prevLast.position != null && deltaFirst.position != null
3405
+ ? {
3406
+ position: {
3407
+ start: prevLast.position.start,
3408
+ end: deltaFirst.position.end,
3409
+ },
3410
+ }
2509
3411
  : null),
2510
3412
  };
2511
3413
  return [...prev.slice(0, -1), merged, ...delta.slice(1)];
@@ -2515,15 +3417,15 @@ function mergeSettledBlocks(
2515
3417
 
2516
3418
  /** Append a newly-settled slice without copying the already-settled prefix. */
2517
3419
  function appendSettledBlocks(
2518
- prev: BlockNodeWithMath[],
2519
- delta: BlockNodeWithMath[],
2520
- ): void {
3420
+ prev: MarkdownAstBlockContent<RuntimeExtensionNode>[],
3421
+ delta: MarkdownAstBlockContent<RuntimeExtensionNode>[],
3422
+ ): boolean {
2521
3423
  if (delta.length === 0) {
2522
- return;
3424
+ return false;
2523
3425
  }
2524
3426
  if (prev.length === 0) {
2525
3427
  prev.push(...delta);
2526
- return;
3428
+ return false;
2527
3429
  }
2528
3430
  const prevLast = prev[prev.length - 1];
2529
3431
  const deltaFirst = delta[0];
@@ -2538,16 +3440,22 @@ function appendSettledBlocks(
2538
3440
  ordered: prevLast.ordered,
2539
3441
  start: prevLast.start,
2540
3442
  delimiter: prevLast.delimiter,
2541
- loose: true,
2542
- items: [...prevLast.items, ...deltaFirst.items],
2543
- ...(prevLast.range != null && deltaFirst.range != null
2544
- ? {range: {start: prevLast.range.start, end: deltaFirst.range.end}}
3443
+ spread: true,
3444
+ children: [...prevLast.children, ...deltaFirst.children],
3445
+ ...(prevLast.position != null && deltaFirst.position != null
3446
+ ? {
3447
+ position: {
3448
+ start: prevLast.position.start,
3449
+ end: deltaFirst.position.end,
3450
+ },
3451
+ }
2545
3452
  : null),
2546
3453
  };
2547
3454
  prev.push(...delta.slice(1));
2548
- return;
3455
+ return true;
2549
3456
  }
2550
3457
  prev.push(...delta);
3458
+ return false;
2551
3459
  }
2552
3460
 
2553
3461
  function sameUnsettledDefinitions(
@@ -2577,12 +3485,19 @@ function resetIncrementalCache(
2577
3485
  state.settledBlocks = [];
2578
3486
  state.settledUpTo = 0;
2579
3487
  state.linkDefsKey = undefined;
3488
+ state.sourceIdsKey = undefined;
3489
+ state.pluginSyntaxIdentity = undefined;
2580
3490
  state.math = undefined;
2581
3491
  cache.settledEnd = 0;
2582
3492
  cache.settledLinkDefs.clear();
2583
3493
  cache.tailLinkDefs = new Map();
2584
3494
  cache.linkDefs = new Map();
2585
3495
  cache.linkDefsKey = '';
3496
+ cache.settledAstBlocks = [];
3497
+ cache.settledRevision++;
3498
+ cache.projectedRevision = -1;
3499
+ cache.projectedSettledBlocks = [];
3500
+ cache.projectionCache = new WeakMap();
2586
3501
  cache.work = {
2587
3502
  splitCharacters: 0,
2588
3503
  boundaryLines: 0,
@@ -2610,40 +3525,102 @@ export function parseMarkdownIncremental(
2610
3525
  export function parseMarkdownIncremental(
2611
3526
  input: string,
2612
3527
  state: IncrementalState<true>,
2613
- options: MathParseOptions,
3528
+ options: IncrementalMathParseOptionsWithoutPlugins,
2614
3529
  ): BlockNodeWithMath[];
2615
3530
  export function parseMarkdownIncremental(
2616
3531
  input: string,
2617
3532
  state: IncrementalState<false>,
2618
- options: ParseOptions,
3533
+ options: IncrementalParseOptionsWithoutPlugins,
2619
3534
  ): BlockNode[];
3535
+ export function parseMarkdownIncremental<
3536
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
3537
+ >(
3538
+ input: string,
3539
+ state: IncrementalState<false>,
3540
+ options: IncrementalParseOptionsWithoutPlugins & {plugins: Plugins},
3541
+ ): BlockNode<MarkdownExtensionsOf<Plugins>>[];
3542
+ export function parseMarkdownIncremental<
3543
+ const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
3544
+ >(
3545
+ input: string,
3546
+ state: IncrementalState<true>,
3547
+ options: IncrementalMathParseOptionsWithoutPlugins & {plugins: Plugins},
3548
+ ): BlockNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
2620
3549
  export function parseMarkdownIncremental(
2621
3550
  input: string,
2622
3551
  state: IncrementalState<boolean>,
2623
3552
  arg?: ReadonlySet<string> | RuntimeParseOptions,
2624
- ): BlockNodeWithMath[] {
2625
- const opts = resolveOptions(arg);
3553
+ ): RuntimeBlockNode[] {
3554
+ const withRanges = wantsLegacyRanges(arg);
3555
+ const root = parseMarkdownAstIncremental(input, state, arg);
3556
+ const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
3557
+ const projected = projectMarkdownRoot(
3558
+ root,
3559
+ withRanges,
3560
+ cache.projectionCache,
3561
+ );
3562
+ if (cache.projectedRevision !== cache.settledRevision) {
3563
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
3564
+ projectBlockNode(block, cache.projectionCache, withRanges),
3565
+ );
3566
+ cache.projectedRevision = cache.settledRevision;
3567
+ }
3568
+ state.settledBlocks =
3569
+ cache.projectedSettledBlocks as typeof state.settledBlocks;
3570
+ return projected;
3571
+ }
3572
+
3573
+ /** @internal Canonical incremental parse used by Markdown rendering. */
3574
+ export function parseMarkdownAstIncremental(
3575
+ input: string,
3576
+ state: IncrementalState<boolean>,
3577
+ arg?: ReadonlySet<string> | RuntimeParseOptions,
3578
+ ): MarkdownAstRoot<RuntimeExtensionNode> {
3579
+ const opts = resolveOptions(arg, true);
3580
+ const root: MarkdownAstRoot<RuntimeExtensionNode> = {
3581
+ type: 'root',
3582
+ children: parseMarkdownIncrementalAstBlocks(input, state, opts),
3583
+ };
3584
+ return applyMarkdownTransforms(
3585
+ root,
3586
+ opts.plugins,
3587
+ input,
3588
+ opts.isFinal,
3589
+ 'block',
3590
+ );
3591
+ }
3592
+
3593
+ function parseMarkdownIncrementalAstBlocks(
3594
+ input: string,
3595
+ state: IncrementalState<boolean>,
3596
+ opts: ResolvedOptions,
3597
+ ): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
2626
3598
  const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
2627
3599
  let reparseSettled = false;
2628
3600
 
3601
+ const nextSourceIdsKey = sourceIdsSignature(opts.sourceIds);
3602
+ const nextPluginSyntaxIdentity = opts.plugins?.syntaxIdentity ?? '';
2629
3603
  // Invalidate cache when an option that changes parsed nodes flips — cached
2630
3604
  // settled blocks were parsed with the previous setting and would otherwise
2631
3605
  // be reused unchanged.
2632
3606
  if (
2633
3607
  state.autolink !== opts.autolink ||
2634
3608
  state.math !== opts.math ||
2635
- Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)
3609
+ Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges) ||
3610
+ state.sourceIdsKey !== nextSourceIdsKey ||
3611
+ state.pluginSyntaxIdentity !== nextPluginSyntaxIdentity
2636
3612
  ) {
2637
3613
  reparseSettled = true;
2638
3614
  state.autolink = opts.autolink;
2639
3615
  state.math = opts.math;
2640
3616
  state.sourceRanges = opts.sourceRanges;
3617
+ state.sourceIdsKey = nextSourceIdsKey;
3618
+ state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
2641
3619
  }
2642
3620
  if (input === '') {
2643
3621
  resetIncrementalCache(state, cache);
2644
3622
  return [];
2645
3623
  }
2646
-
2647
3624
  // The settled prefix is only reusable while the input still contains it
2648
3625
  // verbatim. Lengths alone cannot tell: a same-length or longer replacement
2649
3626
  // (new args after reusing a state) disagrees with the prefix without ever
@@ -2660,6 +3637,8 @@ export function parseMarkdownIncremental(
2660
3637
  state.autolink = opts.autolink;
2661
3638
  state.math = opts.math;
2662
3639
  state.sourceRanges = opts.sourceRanges;
3640
+ state.sourceIdsKey = nextSourceIdsKey;
3641
+ state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
2663
3642
  }
2664
3643
 
2665
3644
  // The recurring parse costs are confined to the mutable suffix: splitting,
@@ -2744,28 +3723,52 @@ export function parseMarkdownIncremental(
2744
3723
  ? trimOpenDisplayMath(unsettledRaw)
2745
3724
  : trimUnsettledStructural(unsettledRaw);
2746
3725
 
3726
+ const legacyRanges = opts.sourceRanges === true;
2747
3727
  let parsedSettledBlocks = 0;
2748
3728
  if (reparseSettled) {
2749
- state.settledBlocks = state.settledText
3729
+ cache.settledAstBlocks = state.settledText
2750
3730
  ? parseMarkdownImpl(state.settledText, parseOpts)
2751
3731
  : [];
2752
- parsedSettledBlocks = state.settledBlocks.length;
3732
+ parsedSettledBlocks = cache.settledAstBlocks.length;
3733
+ cache.settledRevision++;
3734
+ cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
3735
+ projectBlockNode(block, cache.projectionCache, legacyRanges),
3736
+ );
3737
+ cache.projectedRevision = cache.settledRevision;
2753
3738
  } else if (settledDelta !== '') {
2754
3739
  const deltaBlocks = parseMarkdownImpl(
2755
3740
  settledDelta,
2756
3741
  atOffset(parseOpts, oldSettledEnd),
2757
3742
  );
2758
- appendSettledBlocks(state.settledBlocks, deltaBlocks);
3743
+ const mergeIndex = cache.settledAstBlocks.length - 1;
3744
+ const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
2759
3745
  parsedSettledBlocks = deltaBlocks.length;
3746
+ cache.settledRevision++;
3747
+ const projectedDelta = deltaBlocks.map(block =>
3748
+ projectBlockNode(block, cache.projectionCache, legacyRanges),
3749
+ );
3750
+ if (mergedList) {
3751
+ const canonicalMerged = cache.settledAstBlocks[mergeIndex];
3752
+ const projectedMerged = projectBlockNode(
3753
+ canonicalMerged,
3754
+ cache.projectionCache,
3755
+ legacyRanges,
3756
+ );
3757
+ cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
3758
+ cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
3759
+ } else {
3760
+ cache.projectedSettledBlocks.push(...projectedDelta);
3761
+ }
3762
+ cache.projectedRevision = cache.settledRevision;
2760
3763
  }
2761
3764
 
2762
3765
  // The unsettled tail is trimmed before parsing, so its offset in the
2763
3766
  // document is where that trimmed text actually starts — not the boundary,
2764
- // which is a line index. If it somehow can't be located, parse it without
2765
- // ranges rather than report wrong ones. Only worth searching for when
2766
- // ranges were asked for: this runs on every streamed chunk.
3767
+ // which is a line index. If it somehow cannot be located, parse it without
3768
+ // positions rather than report wrong ones. The search starts at the
3769
+ // settled end, so it scans the tail and never the prefix.
2767
3770
  const unsettledStart =
2768
- unsettledText && opts.sourceRanges
3771
+ unsettledText && opts.astPositions === true
2769
3772
  ? input.indexOf(unsettledText, cache.settledEnd)
2770
3773
  : -1;
2771
3774
  const unsettledBlocks = unsettledText
@@ -2790,7 +3793,7 @@ export function parseMarkdownIncremental(
2790
3793
  definitionCharacters: settledDelta.length + unsettledInput.length,
2791
3794
  renderedBlocks: parsedSettledBlocks + unsettledBlocks.length,
2792
3795
  };
2793
- return mergeSettledBlocks(state.settledBlocks, unsettledBlocks);
3796
+ return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
2794
3797
  }
2795
3798
 
2796
3799
  // ---------------------------------------------------------------------------
@@ -2801,30 +3804,6 @@ export function parseMarkdownIncremental(
2801
3804
  // derives its item ids from the same functions, so outline hash links always
2802
3805
  // resolve to a rendered heading by construction.
2803
3806
 
2804
- /** Flatten inline nodes into their plain text content. */
2805
- export function inlineText(nodes: ReadonlyArray<InlineNodeWithMath>): string {
2806
- return nodes
2807
- .map(node => {
2808
- switch (node.type) {
2809
- case 'text':
2810
- case 'code':
2811
- case 'math':
2812
- return node.type === 'math' ? node.value : node.content;
2813
- case 'bold':
2814
- case 'italic':
2815
- case 'strikethrough':
2816
- case 'link':
2817
- return inlineText(node.children);
2818
- case 'image':
2819
- return node.alt;
2820
- case 'citation':
2821
- case 'break':
2822
- return '';
2823
- }
2824
- })
2825
- .join('');
2826
- }
2827
-
2828
3807
  /** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
2829
3808
  export function slugify(value: string): string {
2830
3809
  return value