@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4

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 (305) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +2 -2
  10. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  12. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  13. package/dist/Collapsible/Collapsible.js +6 -1
  14. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  15. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  16. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  17. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  18. package/dist/DateInput/DateInput.d.ts.map +1 -1
  19. package/dist/DateInput/DateInput.js +4 -4
  20. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  21. package/dist/DateInput/NativeDateField.js +16 -16
  22. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  23. package/dist/DateInput/TouchDateField.js +2 -2
  24. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
  25. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  26. package/dist/DateRangeInput/DateRangeInput.js +20 -13
  27. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  28. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  29. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  30. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  31. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  32. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  33. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  34. package/dist/Dialog/DialogHeader.d.ts +1 -1
  35. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  36. package/dist/Dialog/DialogHeader.js +10 -7
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +1 -1
  39. package/dist/FileInput/FileInput.d.ts.map +1 -1
  40. package/dist/FileInput/FileInput.js +16 -10
  41. package/dist/Kbd/Kbd.d.ts +5 -3
  42. package/dist/Kbd/Kbd.d.ts.map +1 -1
  43. package/dist/Kbd/Kbd.js +36 -42
  44. package/dist/Link/Link.d.ts.map +1 -1
  45. package/dist/Link/Link.js +6 -2
  46. package/dist/Markdown/Markdown.d.ts +18 -4
  47. package/dist/Markdown/Markdown.d.ts.map +1 -1
  48. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  49. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  50. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  51. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  52. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  53. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  54. package/dist/Markdown/Markdown.js +311 -114
  55. package/dist/Markdown/ast.d.ts +162 -0
  56. package/dist/Markdown/ast.d.ts.map +1 -0
  57. package/dist/Markdown/ast.js +74 -0
  58. package/dist/Markdown/index.d.ts +5 -3
  59. package/dist/Markdown/index.d.ts.map +1 -1
  60. package/dist/Markdown/index.js +3 -2
  61. package/dist/Markdown/parser/index.d.ts +10 -0
  62. package/dist/Markdown/parser/index.d.ts.map +1 -0
  63. package/dist/Markdown/parser/index.js +10 -0
  64. package/dist/Markdown/parser.d.ts +220 -38
  65. package/dist/Markdown/parser.d.ts.map +1 -1
  66. package/dist/Markdown/parser.js +1072 -188
  67. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  68. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  69. package/dist/Markdown/plugins/frontmatter.js +132 -0
  70. package/dist/Markdown/plugins/index.d.ts +16 -0
  71. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  72. package/dist/Markdown/plugins/index.js +13 -0
  73. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  74. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  75. package/dist/Markdown/plugins/protocol.js +957 -0
  76. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  77. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  78. package/dist/Markdown/plugins/semanticFence.js +213 -0
  79. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  80. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  81. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  82. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  83. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  84. package/dist/Markdown/plugins/textTransform.js +374 -0
  85. package/dist/Markdown/remark.d.ts +196 -0
  86. package/dist/Markdown/remark.d.ts.map +1 -0
  87. package/dist/Markdown/remark.js +1062 -0
  88. package/dist/Markdown/url.d.ts +4 -0
  89. package/dist/Markdown/url.d.ts.map +1 -0
  90. package/dist/Markdown/url.js +25 -0
  91. package/dist/Markdown/utils.d.ts +4 -2
  92. package/dist/Markdown/utils.d.ts.map +1 -1
  93. package/dist/Markdown/utils.js +2 -1
  94. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  95. package/dist/NumberInput/NumberInput.js +4 -4
  96. package/dist/Outline/index.d.ts +1 -0
  97. package/dist/Outline/index.d.ts.map +1 -1
  98. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  99. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  100. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  101. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  102. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  103. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  104. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  105. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  106. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  107. package/dist/ProgressBar/ProgressBar.js +36 -31
  108. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  109. package/dist/RadioList/RadioListItem.js +13 -1
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  112. package/dist/Selector/Selector.d.ts.map +1 -1
  113. package/dist/Selector/Selector.js +1 -1
  114. package/dist/SideNav/SideNav.d.ts +2 -1
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +9 -3
  117. package/dist/Slider/Slider.d.ts.map +1 -1
  118. package/dist/Slider/Slider.js +19 -10
  119. package/dist/Spinner/Spinner.d.ts +1 -1
  120. package/dist/Spinner/Spinner.d.ts.map +1 -1
  121. package/dist/Spinner/Spinner.js +45 -21
  122. package/dist/Switch/Switch.d.ts.map +1 -1
  123. package/dist/Switch/Switch.js +11 -0
  124. package/dist/TabList/Tab.d.ts +1 -1
  125. package/dist/TabList/Tab.d.ts.map +1 -1
  126. package/dist/TabList/Tab.js +20 -7
  127. package/dist/TextArea/TextArea.d.ts.map +1 -1
  128. package/dist/TextArea/TextArea.js +1 -1
  129. package/dist/TextInput/TextInput.d.ts.map +1 -1
  130. package/dist/TextInput/TextInput.js +2 -2
  131. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  132. package/dist/TimeInput/TimeInput.js +4 -4
  133. package/dist/ToggleButton/ToggleButton.d.ts +2 -1
  134. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  135. package/dist/ToggleButton/ToggleButton.js +7 -1
  136. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  137. package/dist/Typeahead/BaseTypeahead.js +16 -7
  138. package/dist/astryx.css +19 -8
  139. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  140. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  141. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  142. package/dist/hooks/useScrollableArea.d.ts +6 -2
  143. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  144. package/dist/hooks/useScrollableArea.js +17 -5
  145. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  146. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  147. package/dist/utils/interactionOverlay.stylex.js +9 -0
  148. package/locales/en.json +16 -0
  149. package/locales/pseudo.json +12 -0
  150. package/package.json +22 -5
  151. package/scripts/agent-doc-state.mjs +1 -1
  152. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  153. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  154. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  155. package/src/BottomSheet/BottomSheet.tsx +3 -1
  156. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  157. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  158. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  159. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  160. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  161. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  162. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  163. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  164. package/src/Chat/ChatComposerInput.doc.mjs +3 -3
  165. package/src/Chat/ChatComposerInput.tsx +10 -2
  166. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  167. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  168. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  169. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  170. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  171. package/src/Collapsible/Collapsible.test.tsx +21 -0
  172. package/src/Collapsible/Collapsible.tsx +5 -0
  173. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  174. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  175. package/src/DateInput/DateInput.tsx +6 -1
  176. package/src/DateInput/NativeDateField.tsx +24 -8
  177. package/src/DateInput/TouchDateField.tsx +6 -2
  178. package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
  179. package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
  180. package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
  181. package/src/DateRangeInput/DateRangeInput.tsx +35 -21
  182. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  183. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  184. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  185. package/src/Dialog/Dialog.doc.mjs +3 -0
  186. package/src/Dialog/Dialog.spec.md +1 -1
  187. package/src/Dialog/DialogHeader.doc.mjs +38 -0
  188. package/src/Dialog/DialogHeader.test.tsx +49 -0
  189. package/src/Dialog/DialogHeader.tsx +23 -4
  190. package/src/Dialog/modules/DialogHeader.spec.md +152 -0
  191. package/src/Field/PanelSearchInput.tsx +5 -2
  192. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  193. package/src/FileInput/FileInput.doc.mjs +2 -0
  194. package/src/FileInput/FileInput.spec.md +199 -0
  195. package/src/FileInput/FileInput.test.tsx +14 -0
  196. package/src/FileInput/FileInput.tsx +24 -5
  197. package/src/Kbd/Kbd.doc.mjs +3 -3
  198. package/src/Kbd/Kbd.test.tsx +57 -1
  199. package/src/Kbd/Kbd.tsx +57 -37
  200. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  201. package/src/Link/Link.doc.mjs +11 -0
  202. package/src/Link/Link.test.tsx +24 -0
  203. package/src/Link/Link.tsx +5 -0
  204. package/src/Markdown/Markdown.doc.mjs +439 -42
  205. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  206. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  207. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  208. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  209. package/src/Markdown/Markdown.public.test.ts +280 -0
  210. package/src/Markdown/Markdown.spec.md +304 -72
  211. package/src/Markdown/Markdown.test.tsx +118 -3
  212. package/src/Markdown/Markdown.tsx +489 -129
  213. package/src/Markdown/ast.test.ts +307 -0
  214. package/src/Markdown/ast.ts +337 -0
  215. package/src/Markdown/incremental.test.ts +187 -7
  216. package/src/Markdown/index.ts +43 -1
  217. package/src/Markdown/modules/remark.spec.md +269 -0
  218. package/src/Markdown/parser/index.ts +64 -0
  219. package/src/Markdown/parser.perf.test.ts +3 -1
  220. package/src/Markdown/parser.public.test.ts +103 -0
  221. package/src/Markdown/parser.test.ts +122 -0
  222. package/src/Markdown/parser.ts +1721 -248
  223. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  224. package/src/Markdown/plugins/frontmatter.ts +198 -0
  225. package/src/Markdown/plugins/index.ts +53 -0
  226. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  227. package/src/Markdown/plugins/protocol.ts +1618 -0
  228. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  229. package/src/Markdown/plugins/semanticFence.ts +424 -0
  230. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  231. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  232. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  233. package/src/Markdown/plugins/textTransform.ts +554 -0
  234. package/src/Markdown/remark.test.tsx +1877 -0
  235. package/src/Markdown/remark.ts +1574 -0
  236. package/src/Markdown/url.ts +30 -0
  237. package/src/Markdown/utils.ts +19 -1
  238. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  239. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  240. package/src/NumberInput/NumberInput.tsx +6 -1
  241. package/src/Outline/Outline.doc.mjs +7 -5
  242. package/src/Outline/Outline.spec.md +1 -1
  243. package/src/Outline/index.ts +1 -0
  244. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  245. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  246. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  247. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  248. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  249. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  250. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  251. package/src/ProgressBar/ProgressBar.tsx +59 -72
  252. package/src/RadioList/RadioList.doc.mjs +11 -0
  253. package/src/RadioList/RadioList.test.tsx +32 -0
  254. package/src/RadioList/RadioListItem.tsx +25 -1
  255. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  256. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  257. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  258. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  259. package/src/Selector/Selector.spec.md +12 -9
  260. package/src/Selector/Selector.tsx +6 -1
  261. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  262. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  263. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  264. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  265. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  266. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  267. package/src/SideNav/SideNav.doc.mjs +1 -1
  268. package/src/SideNav/SideNav.test.tsx +10 -0
  269. package/src/SideNav/SideNav.tsx +14 -2
  270. package/src/Slider/Slider.doc.mjs +27 -0
  271. package/src/Slider/Slider.spec.md +61 -47
  272. package/src/Slider/Slider.test.tsx +146 -0
  273. package/src/Slider/Slider.tsx +37 -13
  274. package/src/Spinner/Spinner.doc.mjs +6 -3
  275. package/src/Spinner/Spinner.test.tsx +41 -0
  276. package/src/Spinner/Spinner.tsx +62 -32
  277. package/src/Switch/Switch.doc.mjs +11 -0
  278. package/src/Switch/Switch.test.tsx +16 -0
  279. package/src/Switch/Switch.tsx +28 -0
  280. package/src/TabList/Tab.tsx +35 -7
  281. package/src/TabList/TabList.doc.mjs +11 -0
  282. package/src/TabList/TabList.test.tsx +66 -0
  283. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  284. package/src/TextArea/TextArea.tsx +6 -1
  285. package/src/TextInput/TextInput.test.tsx +20 -1
  286. package/src/TextInput/TextInput.tsx +6 -1
  287. package/src/TimeInput/TimeInput.tsx +6 -1
  288. package/src/ToggleButton/ToggleButton.test.tsx +133 -0
  289. package/src/ToggleButton/ToggleButton.tsx +9 -2
  290. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  291. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  292. package/src/TreeList/TreeList.spec.md +123 -75
  293. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  294. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  295. package/src/Typeahead/Typeahead.test.tsx +53 -0
  296. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  297. package/src/__tests__/pressState.ts +93 -0
  298. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  299. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  300. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  301. package/src/hooks/useScrollableArea.test.tsx +59 -1
  302. package/src/hooks/useScrollableArea.ts +34 -10
  303. package/src/inputFontFloor.test.ts +106 -0
  304. package/src/theme/derivedVarRegistry.test.ts +6 -4
  305. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -4,11 +4,11 @@
4
4
 
5
5
  /**
6
6
  * @file Markdown.tsx
7
- * @input Markdown string, parser AST types
8
- * @output Exports Markdown component and MarkdownProps
7
+ * @input Markdown string, canonical AST, optional plugins and custom renderers
8
+ * @output Exports Markdown component, MarkdownProps, and renderer contracts
9
9
  * @position Core implementation; renders markdown as Astryx components
10
10
  */
11
- import { useMemo, useRef } from 'react';
11
+ import { Component, Suspense, useMemo, useRef } from 'react';
12
12
  import { Fragment } from 'react';
13
13
  import * as stylex from '@stylexjs/stylex';
14
14
  import "../theme/tokens.stylex.js";
@@ -31,7 +31,11 @@ import { computeBoundaries, computeSegments } from "./streaming.js";
31
31
  import { useTheme } from "../theme/useTheme.js";
32
32
  import { Citation } from "../Citation/Citation.js";
33
33
  import { useLinkComponent } from "../Link/useLinkComponent.js";
34
- import { parseInline, parseMarkdown, parseMarkdownIncremental, createIncrementalState, trimStreamingArtifacts, inlineText, slugify, uniqueSlug } from "./parser.js";
34
+ import { parseInlineAst, parseMarkdownAst, parseMarkdownAstIncremental, createIncrementalState, trimStreamingArtifacts, slugify, uniqueSlug } from "./parser.js";
35
+ import { getMarkdownAstLegacyCodeLanguage, markdownAstText } from "./ast.js";
36
+ import { applyMarkdownTransforms, getMarkdownExtensionRenderer, markdownExtensionText, prepareMarkdownPlugins, reportMarkdownPluginFailure } from "./plugins/protocol.js";
37
+ import { getMarkdownFenceProposal } from "./plugins/semanticFence.js";
38
+ import { sanitizeMarkdownUrl } from "./url.js";
35
39
  import { themeProps } from "../utils/themeProps.js";
36
40
  import { useTranslator } from "../i18n/index.js";
37
41
 
@@ -42,7 +46,7 @@ import { useTranslator } from "../i18n/index.js";
42
46
  /**
43
47
  * A plugin that transforms text patterns into custom React elements
44
48
  * inside Markdown. Applied to parsed text nodes only — code blocks,
45
- * inline code, and other non-prose contexts are unaffected.
49
+ * inline code, math, and other non-prose contexts are unaffected.
46
50
  *
47
51
  * Follows Lexical's TextMatchTransformer architecture:
48
52
  * - `pattern` for initial regex matching
@@ -331,17 +335,20 @@ function countInlineTextLength(nodes) {
331
335
  for (const node of nodes) {
332
336
  switch (node.type) {
333
337
  case 'text':
334
- len += node.content.length;
338
+ len += node.value.length;
335
339
  break;
336
- case 'code':
337
- len += node.content.length;
340
+ case 'inlineCode':
341
+ len += node.value.length;
342
+ break;
343
+ case 'inlineMath':
344
+ len += node.value.length;
338
345
  break;
339
346
  case 'image':
340
347
  len += node.alt.length;
341
348
  break;
342
- case 'bold':
343
- case 'italic':
344
- case 'strikethrough':
349
+ case 'strong':
350
+ case 'emphasis':
351
+ case 'delete':
345
352
  case 'link':
346
353
  len += countInlineTextLength(node.children);
347
354
  break;
@@ -351,6 +358,9 @@ function countInlineTextLength(nodes) {
351
358
  case 'citation':
352
359
  len += 1;
353
360
  break;
361
+ case 'extension':
362
+ len += node.source?.length ?? 0;
363
+ break;
354
364
  }
355
365
  }
356
366
  return len;
@@ -367,28 +377,31 @@ function countBlockTextLength(nodes) {
367
377
  case 'paragraph':
368
378
  len += countInlineTextLength(node.children);
369
379
  break;
370
- case 'codeblock':
371
- len += node.content.length;
380
+ case 'code':
381
+ len += node.value.length;
382
+ break;
383
+ case 'math':
384
+ len += node.value.length;
372
385
  break;
373
386
  case 'blockquote':
374
387
  len += countBlockTextLength(node.children);
375
388
  break;
376
389
  case 'list':
377
- for (const item of node.items) {
390
+ for (const item of node.children) {
378
391
  len += countBlockTextLength(item.children);
379
392
  }
380
393
  break;
381
394
  case 'table':
382
- for (const h of node.headers) {
383
- len += countInlineTextLength(h.children);
384
- }
385
- for (const row of node.rows) {
386
- for (const cell of row) {
395
+ for (const row of node.children) {
396
+ for (const cell of row.children) {
387
397
  len += countInlineTextLength(cell.children);
388
398
  }
389
399
  }
390
400
  break;
391
- case 'hr':
401
+ case 'extension':
402
+ len += node.source?.length ?? 0;
403
+ break;
404
+ case 'thematicBreak':
392
405
  break;
393
406
  case 'image':
394
407
  len += node.alt.length;
@@ -407,26 +420,6 @@ const headingStyles = {
407
420
  };
408
421
 
409
422
  // ---------------------------------------------------------------------------
410
- // URL sanitization — block dangerous protocols
411
- // ---------------------------------------------------------------------------
412
-
413
- const DANGEROUS_URL_PATTERN = /^(javascript|data|vbscript):/i;
414
- function sanitizeUrl(url) {
415
- // Strip control characters before testing, the same normalization the
416
- // parser's isSafeUrl applies — the anchored pattern must see the URL the
417
- // way a browser will. Return the normalized value so the stripped
418
- // characters don't ride along into an attribute or component override.
419
- // eslint-disable-next-line no-control-regex -- control chars are the bypass
420
- const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
421
- if (normalized.length === 0) {
422
- return null;
423
- }
424
- if (DANGEROUS_URL_PATTERN.test(normalized)) {
425
- return null;
426
- }
427
- return normalized;
428
- }
429
-
430
423
  // ---------------------------------------------------------------------------
431
424
  // Inline plugin matching
432
425
  // ---------------------------------------------------------------------------
@@ -518,6 +511,42 @@ function applyInlinePlugins(text, plugins) {
518
511
  }
519
512
  return segments;
520
513
  }
514
+ class MarkdownPluginBoundary extends Component {
515
+ state = {
516
+ failed: false,
517
+ resetKey: this.props.resetKey,
518
+ resetRenderer: this.props.resetRenderer
519
+ };
520
+ static getDerivedStateFromError() {
521
+ return {
522
+ failed: true
523
+ };
524
+ }
525
+ static getDerivedStateFromProps(props, state) {
526
+ return props.resetKey === state.resetKey && props.resetRenderer === state.resetRenderer ? null : {
527
+ failed: false,
528
+ resetKey: props.resetKey,
529
+ resetRenderer: props.resetRenderer
530
+ };
531
+ }
532
+ componentDidCatch(error) {
533
+ reportMarkdownPluginFailure(this.props.pluginName, 'render', error);
534
+ }
535
+ render() {
536
+ return this.state.failed ? this.props.fallback : this.props.children;
537
+ }
538
+ }
539
+ function useStableMarkdownSyntaxEntries(plugins) {
540
+ const identity = plugins?.syntaxIdentity ?? '';
541
+ const syntaxEntriesRef = useRef(undefined);
542
+ if (syntaxEntriesRef.current == null || syntaxEntriesRef.current.identity !== identity) {
543
+ syntaxEntriesRef.current = {
544
+ identity,
545
+ entries: plugins == null || identity === '' ? undefined : plugins.syntaxEntries
546
+ };
547
+ }
548
+ return syntaxEntriesRef.current.entries;
549
+ }
521
550
 
522
551
  // ---------------------------------------------------------------------------
523
552
  // Inline renderer
@@ -565,12 +594,12 @@ function getCitationNumber(ctx, sourceId) {
565
594
  }
566
595
  return num;
567
596
  }
568
- function renderInline(node, index, onLinkClick, cursor, citationCtx, linkComponent = 'a', inlinePlugins, components) {
597
+ function renderInline(node, index, onLinkClick, cursor, citationCtx, linkComponent = 'a', inlinePlugins, components, preparedPlugins) {
569
598
  switch (node.type) {
570
599
  case 'text':
571
600
  {
572
601
  if (inlinePlugins && inlinePlugins.length > 0) {
573
- const segments = applyInlinePlugins(node.content, inlinePlugins);
602
+ const segments = applyInlinePlugins(node.value, inlinePlugins);
574
603
  // If no plugin matched, fall through to the normal path
575
604
  // O(1) guard: applyInlinePlugins returns a single text segment when
576
605
  // nothing matched — skip the plugin path entirely in that case.
@@ -593,52 +622,64 @@ function renderInline(node, index, onLinkClick, cursor, citationCtx, linkCompone
593
622
  }, index);
594
623
  }
595
624
  }
596
- return wrapTextWithFade(node.content, cursor, index);
625
+ return wrapTextWithFade(node.value, cursor, index);
597
626
  }
598
- case 'bold':
627
+ case 'strong':
599
628
  return /*#__PURE__*/_jsx("strong", {
600
629
  ...{
601
630
  className: "x2mo6ok"
602
631
  },
603
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
632
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
604
633
  }, index);
605
- case 'italic':
634
+ case 'emphasis':
606
635
  return /*#__PURE__*/_jsx("em", {
607
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
636
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
608
637
  }, index);
609
- case 'strikethrough':
638
+ case 'delete':
610
639
  return /*#__PURE__*/_jsx("del", {
611
640
  ...{
612
641
  className: "xv1l7n4"
613
642
  },
614
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
643
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
615
644
  }, index);
616
- case 'code':
645
+ case 'inlineCode':
617
646
  {
618
647
  // Track code content length for cursor but don't split inside code
619
- cursor.offset += node.content.length;
648
+ cursor.offset += node.value.length;
620
649
  const InlineCodeComp = components?.inlineCode;
621
650
  const codeEl = InlineCodeComp ? /*#__PURE__*/_jsx(InlineCodeComp, {
622
- children: node.content
651
+ children: node.value
623
652
  }, index) : /*#__PURE__*/_jsx(Code, {
624
- children: node.content
653
+ children: node.value
625
654
  }, index);
626
655
  return codeEl;
627
656
  }
657
+ case 'inlineMath':
658
+ {
659
+ const MathComp = components?.math;
660
+ if (MathComp == null) {
661
+ return wrapTextWithFade(`$${node.value}$`, cursor, index);
662
+ }
663
+ cursor.offset += node.value.length;
664
+ return /*#__PURE__*/_jsx(MathComp, {
665
+ value: node.value,
666
+ display: "inline"
667
+ }, index);
668
+ }
628
669
  case 'link':
629
670
  {
630
- const safeHref = sanitizeUrl(node.href);
671
+ const safeHref = sanitizeMarkdownUrl(node.url);
631
672
  if (safeHref == null) {
632
673
  // Unsafe URL — render as plain text
633
674
  return /*#__PURE__*/_jsx("span", {
634
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
675
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
635
676
  }, index);
636
677
  }
637
678
  const LinkComp = components?.link;
638
679
  if (LinkComp) {
639
680
  return /*#__PURE__*/_jsx(LinkComp, {
640
681
  href: safeHref,
641
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
682
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
642
683
  }, index);
643
684
  }
644
685
  const isExternal = safeHref.startsWith('https://') || safeHref.startsWith('http://');
@@ -662,12 +703,12 @@ function renderInline(node, index, onLinkClick, cursor, citationCtx, linkCompone
662
703
  ...{
663
704
  className: "xjse4m1 x1bvjpef"
664
705
  },
665
- children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
706
+ children: node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
666
707
  }, index);
667
708
  }
668
709
  case 'image':
669
710
  {
670
- const safeSrc = sanitizeUrl(node.src);
711
+ const safeSrc = sanitizeMarkdownUrl(node.url);
671
712
  if (safeSrc == null) {
672
713
  return /*#__PURE__*/_jsxs("span", {
673
714
  children: ["[", node.alt, "]"]
@@ -688,6 +729,33 @@ function renderInline(node, index, onLinkClick, cursor, citationCtx, linkCompone
688
729
  }
689
730
  }, index);
690
731
  }
732
+ case 'extension':
733
+ {
734
+ const renderer = getMarkdownExtensionRenderer(preparedPlugins, node);
735
+ const fallback = wrapTextWithFade(node.source ?? markdownExtensionText(preparedPlugins, node), cursor, index);
736
+ if (renderer == null) {
737
+ return fallback;
738
+ }
739
+ let rendered;
740
+ try {
741
+ rendered = renderer.render({
742
+ node
743
+ });
744
+ } catch (error) {
745
+ reportMarkdownPluginFailure(node.plugin, 'render', error);
746
+ return fallback;
747
+ }
748
+ return /*#__PURE__*/_jsx(MarkdownPluginBoundary, {
749
+ pluginName: node.plugin,
750
+ resetKey: node,
751
+ resetRenderer: renderer.render,
752
+ fallback: fallback,
753
+ children: /*#__PURE__*/_jsx(Suspense, {
754
+ fallback: fallback,
755
+ children: rendered
756
+ })
757
+ }, index);
758
+ }
691
759
  case 'break':
692
760
  cursor.offset += 1;
693
761
  return /*#__PURE__*/_jsx("br", {}, index);
@@ -728,10 +796,11 @@ function getElementSpacing(node, density) {
728
796
  const compact = density === 'compact';
729
797
  switch (node.type) {
730
798
  case 'heading':
731
- return node.level <= 3 ? compact ? styles.spacingHeadingMajorCompact : styles.spacingHeadingMajorDefault : compact ? styles.spacingHeadingMinorCompact : styles.spacingHeadingMinorDefault;
799
+ return node.depth <= 3 ? compact ? styles.spacingHeadingMajorCompact : styles.spacingHeadingMajorDefault : compact ? styles.spacingHeadingMinorCompact : styles.spacingHeadingMinorDefault;
732
800
  case 'paragraph':
733
801
  return compact ? styles.spacingParagraphCompact : styles.spacingParagraphDefault;
734
- case 'codeblock':
802
+ case 'code':
803
+ case 'math':
735
804
  return compact ? styles.spacingCodeblockCompact : styles.spacingCodeblockDefault;
736
805
  case 'blockquote':
737
806
  return compact ? styles.spacingBlockquoteCompact : styles.spacingBlockquoteDefault;
@@ -739,7 +808,9 @@ function getElementSpacing(node, density) {
739
808
  return compact ? styles.spacingListCompact : styles.spacingListDefault;
740
809
  case 'table':
741
810
  return compact ? styles.spacingTableCompact : styles.spacingTableDefault;
742
- case 'hr':
811
+ case 'extension':
812
+ return compact ? styles.spacingParagraphCompact : styles.spacingParagraphDefault;
813
+ case 'thematicBreak':
743
814
  return compact ? styles.spacingHrCompact : styles.spacingHrDefault;
744
815
  case 'image':
745
816
  return compact ? styles.spacingImageCompact : styles.spacingImageDefault;
@@ -755,11 +826,16 @@ function getElementSpacing(node, density) {
755
826
  * Buckets: ≤6 chars → 60px, 7–15 → 80px, >15 → 120px.
756
827
  */
757
828
  function computeTableColumnMinWidths(node) {
758
- return node.headers.map((h, colIdx) => {
759
- let maxLen = countInlineTextLength(h.children);
760
- for (const row of node.rows) {
761
- if (row[colIdx]) {
762
- const len = countInlineTextLength(row[colIdx].children);
829
+ const [header, ...rows] = node.children;
830
+ if (header == null) {
831
+ return [];
832
+ }
833
+ return header.children.map((cell, colIdx) => {
834
+ let maxLen = countInlineTextLength(cell.children);
835
+ for (const row of rows) {
836
+ const rowCell = row.children[colIdx];
837
+ if (rowCell != null) {
838
+ const len = countInlineTextLength(rowCell.children);
763
839
  if (len > maxLen) {
764
840
  maxLen = len;
765
841
  }
@@ -768,7 +844,7 @@ function computeTableColumnMinWidths(node) {
768
844
  return maxLen <= 6 ? 60 : maxLen <= 15 ? 80 : 120;
769
845
  });
770
846
  }
771
- function renderBlock(node, index, blockCount, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent = 'a', inlinePlugins, components, t, headingIdMap) {
847
+ function renderBlock(node, index, blockCount, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent = 'a', inlinePlugins, components, preparedPlugins, t, headingIdMap) {
772
848
  const blockAlignMargin = BLOCK_ALIGN_MARGIN[contentAlign];
773
849
  const blockAlignStyle = blockAlignMargin != null ? dynamicStyles.blockAlign(blockAlignMargin) : null;
774
850
  const spacing = getElementSpacing(node, density);
@@ -777,8 +853,8 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
777
853
  switch (node.type) {
778
854
  case 'heading':
779
855
  {
780
- const level = Math.min(node.level + headingLevelStart - 1, 6);
781
- const headingChildren = node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components));
856
+ const level = Math.min(node.depth + headingLevelStart - 1, 6);
857
+ const headingChildren = node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins));
782
858
  // Only top-level headings get an id: the map is built from the same
783
859
  // traversal parseOutlineFromMarkdown uses (which skips headings nested
784
860
  // in blockquotes / list items), so rendered ids and outline ids stay
@@ -804,7 +880,7 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
804
880
  }
805
881
  case 'paragraph':
806
882
  {
807
- const paraChildren = node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components));
883
+ const paraChildren = node.children.map((c, i) => renderInline(c, i, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins));
808
884
  const ParagraphComp = components?.paragraph;
809
885
  if (ParagraphComp) {
810
886
  return /*#__PURE__*/_jsx(ParagraphComp, {
@@ -828,34 +904,79 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
828
904
  children: paraChildren
829
905
  }, index);
830
906
  }
831
- case 'codeblock':
907
+ case 'code':
832
908
  {
833
909
  // Track codeblock content in cursor for accurate character counting
834
- cursor.offset += node.content.length;
910
+ cursor.offset += node.value.length;
911
+ const language = getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext';
835
912
  const CodeBlockComp = components?.code;
836
913
  if (CodeBlockComp) {
837
914
  return /*#__PURE__*/_jsx(CodeBlockComp, {
838
- code: node.content,
839
- language: node.language
915
+ code: node.value,
916
+ language: language
840
917
  }, index);
841
918
  }
842
- return /*#__PURE__*/_jsx("div", {
919
+ const fallback = /*#__PURE__*/_jsx("div", {
843
920
  ...mergeProps(themeProps('markdown-codeblock', {
844
921
  density
845
922
  }), stylex.props(spacing, styles.codeBlockWrapper, isFirst && styles.noMarginBlockStart, isLast && styles.noMarginBlockEnd)),
846
923
  children: /*#__PURE__*/_jsx(CodeBlock, {
847
- code: node.content,
848
- language: node.language,
924
+ code: node.value,
925
+ language: language,
849
926
  isCollapsible: true,
850
927
  xstyle: [contentWidthValue != null ? dynamicStyles.blockWidth(contentWidthValue) : undefined, blockAlignStyle]
851
928
  })
852
929
  }, index);
930
+ const proposal = getMarkdownFenceProposal(node);
931
+ if (proposal == null) {
932
+ return fallback;
933
+ }
934
+ const renderer = getMarkdownExtensionRenderer(preparedPlugins, proposal.node);
935
+ if (renderer == null) {
936
+ return fallback;
937
+ }
938
+ try {
939
+ const rendered = renderer.render({
940
+ node: proposal.node
941
+ });
942
+ if (rendered == null || typeof rendered === 'boolean') {
943
+ return fallback;
944
+ }
945
+ return /*#__PURE__*/_jsx(MarkdownPluginBoundary, {
946
+ pluginName: proposal.node.plugin,
947
+ resetKey: proposal.node,
948
+ resetRenderer: renderer.render,
949
+ fallback: fallback,
950
+ children: /*#__PURE__*/_jsx(Suspense, {
951
+ fallback: fallback,
952
+ children: rendered
953
+ })
954
+ }, index);
955
+ } catch (error) {
956
+ reportMarkdownPluginFailure(proposal.node.plugin, 'render', error);
957
+ return fallback;
958
+ }
959
+ }
960
+ case 'math':
961
+ {
962
+ cursor.offset += node.value.length;
963
+ const MathComp = components?.math;
964
+ if (MathComp == null) {
965
+ return /*#__PURE__*/_jsx("div", {
966
+ role: "paragraph",
967
+ children: `$$${node.value}$$`
968
+ }, index);
969
+ }
970
+ return /*#__PURE__*/_jsx(MathComp, {
971
+ value: node.value,
972
+ display: "block"
973
+ }, index);
853
974
  }
854
975
  case 'blockquote':
855
976
  {
856
977
  const BlockquoteComp = components?.blockquote;
857
978
  if (BlockquoteComp) {
858
- const bqC = node.children.map((c, i) => renderBlock(c, i, node.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, t));
979
+ const bqC = node.children.map((c, i) => renderBlock(c, i, node.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, preparedPlugins, t));
859
980
  return /*#__PURE__*/_jsx(BlockquoteComp, {
860
981
  children: bqC
861
982
  }, index);
@@ -865,16 +986,16 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
865
986
  density
866
987
  }),
867
988
  xstyle: [spacing, contentWidthValue != null ? dynamicStyles.proseWidth(contentWidthValue) : undefined, contentAlign !== 'start' ? dynamicStyles.proseAlign(ALIGN_MARGIN[contentAlign]) : undefined, isFirst && styles.noMarginBlockStart, isLast && styles.noMarginBlockEnd],
868
- children: node.children.map((c, i) => renderBlock(c, i, node.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, t))
989
+ children: node.children.map((c, i) => renderBlock(c, i, node.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, preparedPlugins, t))
869
990
  }, index);
870
991
  }
871
992
  case 'list':
872
993
  {
873
994
  // Detect task lists: all items have a checked state
874
- const isTaskList = node.items.length > 0 && node.items.every(item => item.checked != null);
995
+ const isTaskList = node.children.length > 0 && node.children.every(item => item.checked != null);
875
996
  if (isTaskList) {
876
997
  // Extract labels from task items — render as rich inline content
877
- const checkedValues = node.items.map((item, i) => ({
998
+ const checkedValues = node.children.map((item, i) => ({
878
999
  item,
879
1000
  key: `task-${i}`
880
1001
  })).filter(({
@@ -893,13 +1014,13 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
893
1014
  xstyle: styles.blockIndent,
894
1015
  isReadOnly: true,
895
1016
  density: "compact",
896
- children: node.items.map((item, i) => {
1017
+ children: node.children.map((item, i) => {
897
1018
  const firstChild = item.children[0];
898
1019
  const isInline = item.children.length === 1 && firstChild?.type === 'paragraph';
899
1020
  const label = isInline ? /*#__PURE__*/_jsx(_Fragment, {
900
- children: firstChild.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
1021
+ children: firstChild.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
901
1022
  }) : /*#__PURE__*/_jsx(_Fragment, {
902
- children: item.children.map((c, j) => renderBlock(c, j, item.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, t))
1023
+ children: item.children.map((c, j) => renderBlock(c, j, item.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, preparedPlugins, t))
903
1024
  });
904
1025
  return /*#__PURE__*/_jsx(CheckboxListItem
905
1026
  // eslint-disable-next-line @eslint-react/no-array-index-key -- markdown task items are rendered from positional AST nodes
@@ -920,7 +1041,7 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
920
1041
  density: "compact",
921
1042
  start: node.ordered ? node.start : undefined,
922
1043
  xstyle: styles.blockIndent,
923
- children: node.items.map((item, i) => {
1044
+ children: node.children.map((item, i) => {
924
1045
  const firstChild = item.children[0];
925
1046
  const isInline = item.children.length === 1 && firstChild?.type === 'paragraph';
926
1047
 
@@ -928,9 +1049,9 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
928
1049
  // whole item as a block instead of fading individual text spans.
929
1050
 
930
1051
  const label = isInline ? /*#__PURE__*/_jsx(_Fragment, {
931
- children: firstChild.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
1052
+ children: firstChild.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
932
1053
  }) : /*#__PURE__*/_jsx(_Fragment, {
933
- children: item.children.map((c, j) => renderBlock(c, j, item.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, t))
1054
+ children: item.children.map((c, j) => renderBlock(c, j, item.children.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidthValue, contentAlign, linkComponent, inlinePlugins, components, preparedPlugins, t))
934
1055
  });
935
1056
  return /*#__PURE__*/_jsx(ListItem
936
1057
  // eslint-disable-next-line @eslint-react/no-array-index-key -- markdown list items are rendered from positional AST nodes
@@ -944,6 +1065,7 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
944
1065
  case 'table':
945
1066
  {
946
1067
  const colMinWidths = computeTableColumnMinWidths(node);
1068
+ const [header, ...rows] = node.children;
947
1069
  return /*#__PURE__*/_jsx("div", {
948
1070
  // Keyboard-focusable so keyboard users can scroll a horizontally
949
1071
  // overflowing GFM table. Uses role="group" (not "region") so
@@ -960,20 +1082,20 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
960
1082
  textOverflow: "wrap",
961
1083
  children: [/*#__PURE__*/_jsx(TableHeader, {
962
1084
  children: /*#__PURE__*/_jsx(TableRow, {
963
- children: node.headers.map((h, i) => /*#__PURE__*/_jsx(TableHeaderCell
1085
+ children: header?.children.map((h, i) => /*#__PURE__*/_jsx(TableHeaderCell
964
1086
  // eslint-disable-next-line @eslint-react/no-array-index-key -- markdown table columns are positional by definition
965
1087
  , {
966
- xstyle: [dynamicStyles.cellMinWidth(`${colMinWidths[i]}px`), node.alignments[i] === 'center' && cellAlignStyles.center, node.alignments[i] === 'right' && cellAlignStyles.end],
967
- children: h.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
1088
+ xstyle: [dynamicStyles.cellMinWidth(`${colMinWidths[i]}px`), node.align[i] === 'center' && cellAlignStyles.center, node.align[i] === 'right' && cellAlignStyles.end],
1089
+ children: h.children.map((c, j) => renderInline(c, j, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
968
1090
  }, i))
969
1091
  })
970
1092
  }), /*#__PURE__*/_jsx(TableBody, {
971
- children: node.rows.map((row, i) => {
972
- const cells = row.map((cell, j) => /*#__PURE__*/_jsx(TableCell
1093
+ children: rows.map((row, i) => {
1094
+ const cells = row.children.map((cell, j) => /*#__PURE__*/_jsx(TableCell
973
1095
  // eslint-disable-next-line @eslint-react/no-array-index-key -- markdown table cells are positional by row and column
974
1096
  , {
975
- xstyle: [node.alignments[j] === 'center' && cellAlignStyles.center, node.alignments[j] === 'right' && cellAlignStyles.end],
976
- children: cell.children.map((c, k) => renderInline(c, k, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components))
1097
+ xstyle: [node.align[j] === 'center' && cellAlignStyles.center, node.align[j] === 'right' && cellAlignStyles.end],
1098
+ children: cell.children.map((c, k) => renderInline(c, k, onLinkClick, cursor, citationCtx, linkComponent, inlinePlugins, components, preparedPlugins))
977
1099
  }, j));
978
1100
  return /*#__PURE__*/_jsx(TableRow
979
1101
  // eslint-disable-next-line @eslint-react/no-array-index-key -- markdown table rows are rendered from positional AST nodes
@@ -985,7 +1107,37 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
985
1107
  })
986
1108
  }, index);
987
1109
  }
988
- case 'hr':
1110
+ case 'extension':
1111
+ {
1112
+ const renderer = getMarkdownExtensionRenderer(preparedPlugins, node);
1113
+ const fallbackText = node.source ?? markdownExtensionText(preparedPlugins, node);
1114
+ const fallback = wrapTextWithFade(fallbackText, cursor, index);
1115
+ let content = fallback;
1116
+ if (renderer != null) {
1117
+ try {
1118
+ const rendered = renderer.render({
1119
+ node
1120
+ });
1121
+ content = /*#__PURE__*/_jsx(MarkdownPluginBoundary, {
1122
+ pluginName: node.plugin,
1123
+ resetKey: node,
1124
+ resetRenderer: renderer.render,
1125
+ fallback: fallback,
1126
+ children: /*#__PURE__*/_jsx(Suspense, {
1127
+ fallback: fallback,
1128
+ children: rendered
1129
+ })
1130
+ });
1131
+ } catch (error) {
1132
+ reportMarkdownPluginFailure(node.plugin, 'render', error);
1133
+ }
1134
+ }
1135
+ return /*#__PURE__*/_jsx("div", {
1136
+ ...stylex.props(spacing, contentWidthValue != null ? dynamicStyles.proseWidth(contentWidthValue) : null, contentAlign !== 'start' ? dynamicStyles.proseAlign(ALIGN_MARGIN[contentAlign]) : null, isFirst && styles.noMarginBlockStart, isLast && styles.noMarginBlockEnd),
1137
+ children: content
1138
+ }, index);
1139
+ }
1140
+ case 'thematicBreak':
989
1141
  {
990
1142
  const HrComp = components?.hr;
991
1143
  if (HrComp) {
@@ -999,7 +1151,7 @@ function renderBlock(node, index, blockCount, density, headingLevelStart, onLink
999
1151
  }
1000
1152
  case 'image':
1001
1153
  {
1002
- const safeSrc = sanitizeUrl(node.src);
1154
+ const safeSrc = sanitizeMarkdownUrl(node.url);
1003
1155
  if (safeSrc == null) {
1004
1156
  return /*#__PURE__*/_jsxs("div", {
1005
1157
  ...mergeProps(themeProps('markdown-image', {
@@ -1059,6 +1211,7 @@ export function Markdown({
1059
1211
  contentWidth = 680,
1060
1212
  contentAlign = 'start',
1061
1213
  components,
1214
+ plugins,
1062
1215
  inlinePlugins,
1063
1216
  autolink,
1064
1217
  xstyle,
@@ -1071,23 +1224,45 @@ export function Markdown({
1071
1224
  const LinkComponent = useLinkComponent();
1072
1225
  // Derive the set of source IDs for the parser (stable across renders when sources don't change)
1073
1226
  const sourceIds = useMemo(() => sources ? new Set(Object.keys(sources)) : undefined, [sources]);
1074
- const parseOptions = useMemo(() => ({
1227
+ const preparedPlugins = useMemo(() => {
1228
+ if (plugins == null) {
1229
+ return undefined;
1230
+ }
1231
+ try {
1232
+ return prepareMarkdownPlugins(plugins);
1233
+ } catch (error) {
1234
+ reportMarkdownPluginFailure('configuration', 'transform', error);
1235
+ return undefined;
1236
+ }
1237
+ }, [plugins]);
1238
+ const syntaxPlugins = useStableMarkdownSyntaxEntries(preparedPlugins);
1239
+ const hasMathRenderer = components?.math != null;
1240
+ const legacyParseOptions = useMemo(() => ({
1075
1241
  sourceIds,
1076
- autolink
1077
- }), [sourceIds, autolink]);
1242
+ autolink,
1243
+ plugins: syntaxPlugins
1244
+ }), [sourceIds, autolink, syntaxPlugins]);
1245
+ const mathParseOptions = useMemo(() => ({
1246
+ sourceIds,
1247
+ autolink,
1248
+ math: true,
1249
+ plugins: syntaxPlugins
1250
+ }), [sourceIds, autolink, syntaxPlugins]);
1078
1251
 
1079
1252
  // Smooth bursty streamed chunks into a steady character-by-character reveal.
1080
1253
  // When not streaming, the hook returns children unchanged (no-op).
1081
1254
  const smoothedText = useStreamingText(children, isStreaming);
1082
1255
  const incrementalStateRef = useRef(createIncrementalState());
1083
- // Reset incremental cache when the autolink option toggles — cached
1084
- // settled blocks were parsed with the previous setting.
1256
+ // Reset incremental cache when parser-affecting component options toggle —
1257
+ // cached settled blocks were parsed with the previous setting.
1085
1258
  const prevAutolinkRef = useRef(autolink);
1086
- if (prevAutolinkRef.current !== autolink) {
1259
+ const prevMathRef = useRef(hasMathRenderer);
1260
+ if (prevAutolinkRef.current !== autolink || prevMathRef.current !== hasMathRenderer) {
1087
1261
  incrementalStateRef.current = createIncrementalState();
1088
1262
  prevAutolinkRef.current = autolink;
1263
+ prevMathRef.current = hasMathRenderer;
1089
1264
  }
1090
- const blocks = useMemo(() => {
1265
+ const parsedBlocks = useMemo(() => {
1091
1266
  if (display === 'inline') {
1092
1267
  return [];
1093
1268
  }
@@ -1096,11 +1271,17 @@ export function Markdown({
1096
1271
  incrementalStateRef.current = createIncrementalState();
1097
1272
  return [];
1098
1273
  }
1099
- const input = trimStreamingArtifacts(smoothedText);
1100
- return parseMarkdownIncremental(input, incrementalStateRef.current, parseOptions);
1274
+ const options = hasMathRenderer ? mathParseOptions : legacyParseOptions;
1275
+ const input = trimStreamingArtifacts(smoothedText, options);
1276
+ return (hasMathRenderer ? parseMarkdownAstIncremental(input, incrementalStateRef.current, mathParseOptions) : parseMarkdownAstIncremental(input, incrementalStateRef.current, legacyParseOptions)).children;
1101
1277
  }
1102
- return parseMarkdown(children, parseOptions);
1103
- }, [display, smoothedText, children, isStreaming, parseOptions]);
1278
+ return (hasMathRenderer ? parseMarkdownAst(children, mathParseOptions) : parseMarkdownAst(children, legacyParseOptions)).children;
1279
+ }, [display, smoothedText, children, isStreaming, hasMathRenderer, mathParseOptions, legacyParseOptions]);
1280
+ const transformSource = isStreaming ? trimStreamingArtifacts(smoothedText, hasMathRenderer ? mathParseOptions : legacyParseOptions) : children;
1281
+ const blocks = useMemo(() => applyMarkdownTransforms({
1282
+ type: 'root',
1283
+ children: parsedBlocks
1284
+ }, preparedPlugins, transformSource, !isStreaming, 'block').children, [parsedBlocks, preparedPlugins, transformSource, isStreaming]);
1104
1285
 
1105
1286
  // Assign each top-level heading the slug that parseOutlineFromMarkdown
1106
1287
  // would derive for it, so Outline hash links built from the same source
@@ -1116,19 +1297,35 @@ export function Markdown({
1116
1297
  const counts = new Map();
1117
1298
  for (const block of blocks) {
1118
1299
  if (block.type === 'heading') {
1119
- const label = inlineText(block.children).trim();
1300
+ const label = markdownAstText(block.children, node => markdownExtensionText(preparedPlugins, node)).trim();
1120
1301
  map.set(block, uniqueSlug(slugify(label), counts));
1121
1302
  }
1122
1303
  }
1123
1304
  return map;
1124
- }, [display, blocks]);
1305
+ }, [display, blocks, preparedPlugins]);
1306
+ const parsedInlineNodes = useMemo(() => {
1307
+ if (display !== 'inline') {
1308
+ return [];
1309
+ }
1310
+ const options = hasMathRenderer ? mathParseOptions : legacyParseOptions;
1311
+ const input = isStreaming ? trimStreamingArtifacts(smoothedText, options) : children;
1312
+ return hasMathRenderer ? parseInlineAst(input, mathParseOptions) : parseInlineAst(input, legacyParseOptions);
1313
+ }, [display, smoothedText, children, isStreaming, hasMathRenderer, mathParseOptions, legacyParseOptions]);
1314
+ const inlineTransformSource = isStreaming ? trimStreamingArtifacts(smoothedText, hasMathRenderer ? mathParseOptions : legacyParseOptions) : children;
1125
1315
  const inlineNodes = useMemo(() => {
1126
1316
  if (display !== 'inline') {
1127
1317
  return [];
1128
1318
  }
1129
- const input = isStreaming ? trimStreamingArtifacts(smoothedText) : children;
1130
- return parseInline(input, parseOptions);
1131
- }, [display, smoothedText, children, isStreaming, parseOptions]);
1319
+ const transformed = applyMarkdownTransforms({
1320
+ type: 'root',
1321
+ children: [{
1322
+ type: 'paragraph',
1323
+ children: parsedInlineNodes
1324
+ }]
1325
+ }, preparedPlugins, inlineTransformSource, !isStreaming, 'inline');
1326
+ const paragraph = transformed.children[0];
1327
+ return paragraph?.type === 'paragraph' ? [...paragraph.children] : parsedInlineNodes;
1328
+ }, [display, parsedInlineNodes, preparedPlugins, inlineTransformSource, isStreaming]);
1132
1329
 
1133
1330
  // Track recent boundaries for stacked fade-in animation.
1134
1331
  // The number of spans needed = ceil(animationDuration / tickInterval).
@@ -1180,7 +1377,7 @@ export function Markdown({
1180
1377
  ...mergeProps(themeProps('markdown', {
1181
1378
  density
1182
1379
  }), stylex.props(styles.root, styles.inlineRoot, xstyle), className, style),
1183
- children: inlineNodes.map((node, i) => renderInline(node, i, onLinkClick, cursor, citationCtx, LinkComponent, inlinePlugins, components))
1380
+ children: inlineNodes.map((node, i) => renderInline(node, i, onLinkClick, cursor, citationCtx, LinkComponent, inlinePlugins, components, preparedPlugins))
1184
1381
  });
1185
1382
 
1186
1383
  // Store current inline nodes for next render's boundary calculation.
@@ -1199,7 +1396,7 @@ export function Markdown({
1199
1396
  ...mergeProps(themeProps('markdown', {
1200
1397
  density
1201
1398
  }), stylex.props(styles.root, xstyle), className, style),
1202
- children: blocks.map((block, i) => renderBlock(block, i, blocks.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidth ? typeof contentWidth === 'number' ? `${contentWidth}px` : contentWidth : null, contentAlign, LinkComponent, inlinePlugins, components, t, headingIdMap))
1399
+ children: blocks.map((block, i) => renderBlock(block, i, blocks.length, density, headingLevelStart, onLinkClick, cursor, citationCtx, contentWidth ? typeof contentWidth === 'number' ? `${contentWidth}px` : contentWidth : null, contentAlign, LinkComponent, inlinePlugins, components, preparedPlugins, t, headingIdMap))
1203
1400
  });
1204
1401
 
1205
1402
  // Store current blocks for next render's boundary calculation.