@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
@@ -0,0 +1,1618 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file protocol.ts
5
+ * @input Markdown plugin definitions, syntax tokenizers, immutable transforms, and renderers
6
+ * @output Public plugin protocol plus validated internal preparation and execution
7
+ * @position Shared Markdown extension boundary consumed by the parser and renderer
8
+ */
9
+
10
+ import type React from 'react';
11
+ import {devError, warnOnce} from '../../utils/devWarning';
12
+ import {isSafeMarkdownParserUrl} from '../url';
13
+ import type {
14
+ MarkdownAstDataValue,
15
+ MarkdownAstExtensionNode,
16
+ MarkdownAstNodeBase,
17
+ MarkdownAstPosition,
18
+ MarkdownAstRoot,
19
+ } from '../ast';
20
+
21
+ export type MarkdownPluginData = MarkdownAstDataValue;
22
+
23
+ export type MarkdownExtensionNode<
24
+ PluginName extends string = string,
25
+ NodeName extends string = string,
26
+ Data extends MarkdownPluginData = MarkdownPluginData,
27
+ Display extends 'inline' | 'block' = 'inline' | 'block',
28
+ > = MarkdownAstExtensionNode<PluginName, NodeName, Data, Display>;
29
+
30
+ export function isMarkdownExtensionNode<Node extends MarkdownExtensionNode>(
31
+ node: MarkdownAstNodeBase & {readonly type: string},
32
+ plugin: Node['plugin'],
33
+ name: Node['name'],
34
+ ): node is Node {
35
+ return (
36
+ node.type === 'extension' &&
37
+ (node as MarkdownExtensionNode).plugin === plugin &&
38
+ (node as MarkdownExtensionNode).name === name
39
+ );
40
+ }
41
+
42
+ export interface MarkdownTokenizerInput {
43
+ readonly source: string;
44
+ /** UTF-16 offset into `source`. */
45
+ readonly offset: number;
46
+ /** Exclusive UTF-16 bound visible to this tokenizer. */
47
+ readonly end: number;
48
+ readonly isFinal: boolean;
49
+ readonly context: 'inline' | 'block';
50
+ readonly lineStart: number;
51
+ readonly column: number;
52
+ }
53
+
54
+ type ExtensionNodeWithoutProvenance<Node extends MarkdownExtensionNode> = Omit<
55
+ Node,
56
+ 'source' | 'position'
57
+ >;
58
+
59
+ export type MarkdownTokenizeResult<Node extends MarkdownExtensionNode> =
60
+ | {readonly status: 'no-match'}
61
+ | {readonly status: 'defer'}
62
+ | {
63
+ readonly status: 'match';
64
+ readonly end: number;
65
+ readonly node: ExtensionNodeWithoutProvenance<Node>;
66
+ };
67
+
68
+ export interface MarkdownSyntaxContribution<
69
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
70
+ > {
71
+ readonly startsWith: readonly [string, ...string[]];
72
+ /** Maximum UTF-16 span Core exposes from a candidate offset. */
73
+ readonly maxSpan: number;
74
+ tokenize(input: MarkdownTokenizerInput): MarkdownTokenizeResult<Node>;
75
+ }
76
+
77
+ type ExtensionForDisplay<
78
+ Node extends MarkdownExtensionNode,
79
+ Display extends 'inline' | 'block',
80
+ > = Display extends Node['display']
81
+ ? Node & {readonly display: Display}
82
+ : never;
83
+
84
+ export interface MarkdownSyntaxCapability<
85
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
86
+ > {
87
+ readonly inline?: readonly [
88
+ MarkdownSyntaxContribution<ExtensionForDisplay<Node, 'inline'>>,
89
+ ...MarkdownSyntaxContribution<ExtensionForDisplay<Node, 'inline'>>[],
90
+ ];
91
+ readonly block?: readonly [
92
+ MarkdownSyntaxContribution<ExtensionForDisplay<Node, 'block'>>,
93
+ ...MarkdownSyntaxContribution<ExtensionForDisplay<Node, 'block'>>[],
94
+ ];
95
+ }
96
+
97
+ export interface MarkdownTransformContext {
98
+ readonly source: string;
99
+ readonly isFinal: boolean;
100
+ readonly display: 'inline' | 'block';
101
+ report(message: string): void;
102
+ }
103
+
104
+ const markdownTransformPluginName = Symbol('MarkdownTransformPluginName');
105
+ const markdownTransformHasRenderer = Symbol('MarkdownTransformHasRenderer');
106
+ const markdownTransformSourceUnchanged = Symbol(
107
+ 'MarkdownTransformSourceUnchanged',
108
+ );
109
+
110
+ type InternalMarkdownTransformContext = MarkdownTransformContext & {
111
+ readonly [markdownTransformPluginName]: string;
112
+ readonly [markdownTransformHasRenderer]: (nodeName: string) => boolean;
113
+ readonly [markdownTransformSourceUnchanged]: boolean;
114
+ };
115
+
116
+ /** @internal Reads the owning plugin name without expanding the public context. */
117
+ export function getMarkdownTransformPluginName(
118
+ context: MarkdownTransformContext,
119
+ ): string | undefined {
120
+ return (context as Partial<InternalMarkdownTransformContext>)[
121
+ markdownTransformPluginName
122
+ ];
123
+ }
124
+
125
+ /**
126
+ * @internal The ownership facts a Core helper needs to validate the nodes a
127
+ * caller's callback handed it, without seeing the rest of the pipeline.
128
+ */
129
+ export interface MarkdownHelperOwnership {
130
+ readonly pluginName: string;
131
+ readonly hasRenderer: (nodeName: string) => boolean;
132
+ /** Whether source text still describes the current transform input. */
133
+ readonly sourceUnchanged: boolean;
134
+ }
135
+
136
+ /** @internal Reads this transform's ownership, when Core is running it. */
137
+ export function getMarkdownHelperOwnership(
138
+ context: MarkdownTransformContext,
139
+ ): MarkdownHelperOwnership | undefined {
140
+ const internal = context as Partial<InternalMarkdownTransformContext>;
141
+ const pluginName = internal[markdownTransformPluginName];
142
+ const hasRenderer = internal[markdownTransformHasRenderer];
143
+ const sourceUnchanged = internal[markdownTransformSourceUnchanged];
144
+ return pluginName === undefined ||
145
+ hasRenderer === undefined ||
146
+ sourceUnchanged === undefined
147
+ ? undefined
148
+ : {pluginName, hasRenderer, sourceUnchanged};
149
+ }
150
+
151
+ declare const markdownTransformNode: unique symbol;
152
+
153
+ export interface MarkdownTransform<Node extends MarkdownExtensionNode = never> {
154
+ (
155
+ document: MarkdownAstRoot<MarkdownExtensionNode>,
156
+ context: MarkdownTransformContext,
157
+ ): MarkdownAstRoot<MarkdownExtensionNode>;
158
+ readonly [markdownTransformNode]?: Node;
159
+ }
160
+
161
+ const markdownTransformClaim = Symbol('MarkdownTransformClaim');
162
+ const markdownTransformTrusted = Symbol('MarkdownTransformTrusted');
163
+
164
+ interface ClaimAwareMarkdownTransform<
165
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
166
+ > extends MarkdownTransform<Node> {
167
+ readonly [markdownTransformClaim]?: (source: string) => boolean;
168
+ }
169
+
170
+ interface TrustAwareMarkdownTransform extends MarkdownTransform<never> {
171
+ readonly [markdownTransformTrusted]?: true;
172
+ }
173
+
174
+ /**
175
+ * @internal Marks a Core-authored transform whose OUTPUT STRUCTURE is entirely
176
+ * Core-computed — it never inserts a caller-supplied node, never edits an
177
+ * existing node's meaning, provenance, or ownership, and never mutates its
178
+ * input. Core then skips the validation and immutability guards it applies to
179
+ * plugin-authored output, whose purpose is to contain untrusted structure.
180
+ *
181
+ * Only helpers in this package may be marked, and only when every node they
182
+ * emit is built here from validated inputs. A helper that inserts anything the
183
+ * caller supplied — the text helper's replacement nodes, for example — is NOT
184
+ * trusted: Core must still validate the tree it returns.
185
+ */
186
+ export function markMarkdownTransformTrusted(
187
+ transform: MarkdownTransform<never>,
188
+ ): MarkdownTransform<never> {
189
+ Object.defineProperty(transform, markdownTransformTrusted, {
190
+ configurable: false,
191
+ enumerable: false,
192
+ value: true,
193
+ writable: false,
194
+ });
195
+ return transform;
196
+ }
197
+
198
+ /** @internal Registers a cheap source-level claim check for a helper transform. */
199
+ export function markMarkdownTransformClaim<
200
+ Node extends MarkdownExtensionNode = never,
201
+ >(
202
+ transform: MarkdownTransform<Node>,
203
+ claims: (source: string) => boolean,
204
+ ): MarkdownTransform<Node> {
205
+ Object.defineProperty(transform, markdownTransformClaim, {
206
+ configurable: false,
207
+ enumerable: false,
208
+ value: claims,
209
+ writable: false,
210
+ });
211
+ return transform;
212
+ }
213
+
214
+ export interface MarkdownExtensionRenderer<Node extends MarkdownExtensionNode> {
215
+ /** Pure render callback. Hooks belong in components returned by this callback. */
216
+ readonly render: (props: {node: Node}) => React.ReactNode;
217
+ readonly toText: (node: Node) => string;
218
+ }
219
+
220
+ export type MarkdownExtensionRenderers<Node extends MarkdownExtensionNode> =
221
+ Readonly<{
222
+ [NodeName in Node['name']]: MarkdownExtensionRenderer<
223
+ Extract<Node, {name: NodeName}>
224
+ >;
225
+ }>;
226
+
227
+ interface MarkdownPluginDefinitionBase<Name extends string> {
228
+ readonly name: Name;
229
+ readonly apiVersion: 1;
230
+ }
231
+
232
+ export interface MarkdownSyntaxPluginDefinition<
233
+ Name extends string,
234
+ Node extends MarkdownExtensionNode<Name>,
235
+ > extends MarkdownPluginDefinitionBase<Name> {
236
+ readonly parseKey: string;
237
+ readonly syntax: MarkdownSyntaxCapability<Node>;
238
+ readonly transform?: MarkdownTransform<Node>;
239
+ readonly renderers: MarkdownExtensionRenderers<Node>;
240
+ }
241
+
242
+ export type MarkdownTransformPluginDefinition<
243
+ Name extends string,
244
+ Node extends MarkdownExtensionNode<Name> = never,
245
+ > = MarkdownPluginDefinitionBase<Name> & {
246
+ readonly transform: MarkdownTransform<Node>;
247
+ readonly parseKey?: never;
248
+ readonly syntax?: never;
249
+ } & ([Node] extends [never]
250
+ ? {readonly renderers?: never}
251
+ : {readonly renderers: MarkdownExtensionRenderers<Node>});
252
+
253
+ export type MarkdownPluginDefinition<
254
+ Name extends string,
255
+ Node extends MarkdownExtensionNode<Name> = never,
256
+ > =
257
+ | MarkdownSyntaxPluginDefinition<Name, Node>
258
+ | MarkdownTransformPluginDefinition<Name, Node>;
259
+
260
+ declare const markdownPluginNode: unique symbol;
261
+
262
+ /** Opaque, covariant entry safe to store in heterogeneous plugin lists. */
263
+ export interface MarkdownPluginEntry<
264
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
265
+ > {
266
+ readonly name: string;
267
+ readonly apiVersion: 1;
268
+ readonly [markdownPluginNode]: Node;
269
+ }
270
+
271
+ export type MarkdownNodeOf<Entry> =
272
+ Entry extends MarkdownPluginEntry<infer Node> ? Node : never;
273
+
274
+ export type MarkdownExtensionsOf<
275
+ Plugins extends ReadonlyArray<MarkdownPluginEntry>,
276
+ > = MarkdownNodeOf<Plugins[number]>;
277
+
278
+ const MARKDOWN_PLUGIN_BRAND = '@astryxdesign/core/MarkdownPluginEntry';
279
+ const markdownPluginDefinition = Symbol.for(MARKDOWN_PLUGIN_BRAND);
280
+
281
+ interface MarkdownPluginBrand<
282
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
283
+ > {
284
+ readonly kind: typeof MARKDOWN_PLUGIN_BRAND;
285
+ readonly apiVersion: 1;
286
+ readonly definition: MarkdownPluginDefinition<string, Node>;
287
+ }
288
+
289
+ interface InternalMarkdownPluginEntry<
290
+ Node extends MarkdownExtensionNode = MarkdownExtensionNode,
291
+ > extends MarkdownPluginEntry<Node> {
292
+ readonly [markdownPluginDefinition]: MarkdownPluginBrand<Node>;
293
+ }
294
+
295
+ function getMarkdownPluginDefinition(
296
+ publicEntry: MarkdownPluginEntry,
297
+ ): MarkdownPluginDefinition<string, MarkdownExtensionNode> {
298
+ if (
299
+ publicEntry == null ||
300
+ typeof publicEntry !== 'object' ||
301
+ Object.getPrototypeOf(publicEntry) !== Object.prototype
302
+ ) {
303
+ fail('entries must come from a compatible createMarkdownPlugin()');
304
+ }
305
+ const entry = publicEntry as Partial<InternalMarkdownPluginEntry>;
306
+ const brand = entry[markdownPluginDefinition];
307
+ const definition = brand?.definition;
308
+ if (
309
+ Reflect.ownKeys(entry).length !== 3 ||
310
+ typeof entry.name !== 'string' ||
311
+ entry.apiVersion !== 1 ||
312
+ !Object.isFrozen(entry) ||
313
+ brand == null ||
314
+ typeof brand !== 'object' ||
315
+ Object.getPrototypeOf(brand) !== Object.prototype ||
316
+ Reflect.ownKeys(brand).length !== 3 ||
317
+ !Object.isFrozen(brand) ||
318
+ brand.kind !== MARKDOWN_PLUGIN_BRAND ||
319
+ brand.apiVersion !== 1 ||
320
+ definition == null ||
321
+ typeof definition !== 'object' ||
322
+ !Object.isFrozen(definition) ||
323
+ definition.name !== entry.name ||
324
+ definition.apiVersion !== entry.apiVersion
325
+ ) {
326
+ fail('entries must come from a compatible createMarkdownPlugin()');
327
+ }
328
+ return definition;
329
+ }
330
+
331
+ function deepFreezeConfig<T>(value: T, seen: Set<object> = new Set()): T {
332
+ if (value != null && typeof value === 'object' && !seen.has(value)) {
333
+ seen.add(value);
334
+ for (const nested of Object.values(value)) {
335
+ deepFreezeConfig(nested, seen);
336
+ }
337
+ Object.freeze(value);
338
+ }
339
+ return value;
340
+ }
341
+
342
+ function fail(message: string): never {
343
+ throw new Error(`Markdown plugin: ${message}`);
344
+ }
345
+
346
+ function validateSyntax(
347
+ pluginName: string,
348
+ syntax: MarkdownSyntaxCapability,
349
+ ): void {
350
+ const contributions = [...(syntax.inline ?? []), ...(syntax.block ?? [])];
351
+ if (contributions.length === 0) {
352
+ fail(`"${pluginName}" syntax must declare an inline or block contribution`);
353
+ }
354
+ for (const contribution of contributions) {
355
+ if (!Number.isFinite(contribution.maxSpan) || contribution.maxSpan <= 0) {
356
+ fail(`"${pluginName}" syntax maxSpan must be positive and finite`);
357
+ }
358
+ for (const prefix of contribution.startsWith) {
359
+ if (prefix === '' || prefix.length > contribution.maxSpan) {
360
+ fail(`"${pluginName}" syntax prefixes must be non-empty and bounded`);
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ export function createMarkdownPlugin<
367
+ const Name extends string,
368
+ const Node extends MarkdownExtensionNode<Name>,
369
+ >(
370
+ definition: MarkdownSyntaxPluginDefinition<Name, Node>,
371
+ ): MarkdownPluginEntry<Node>;
372
+ export function createMarkdownPlugin<const Name extends string>(
373
+ definition: MarkdownTransformPluginDefinition<Name, never>,
374
+ ): MarkdownPluginEntry<never>;
375
+ export function createMarkdownPlugin<
376
+ const Name extends string,
377
+ const Node extends MarkdownExtensionNode<Name>,
378
+ >(
379
+ definition: MarkdownTransformPluginDefinition<Name, Node>,
380
+ ): MarkdownPluginEntry<Node>;
381
+ export function createMarkdownPlugin(
382
+ definition:
383
+ | MarkdownPluginDefinition<string, MarkdownExtensionNode>
384
+ | MarkdownTransformPluginDefinition<string, never>,
385
+ ): MarkdownPluginEntry {
386
+ if (definition.name.trim() === '') {
387
+ fail('name must be a non-empty string');
388
+ }
389
+ if (definition.apiVersion !== 1) {
390
+ fail(`"${definition.name}" uses unsupported apiVersion`);
391
+ }
392
+ if (
393
+ typeof definition.transform !== 'undefined' &&
394
+ typeof definition.transform !== 'function'
395
+ ) {
396
+ fail(`"${definition.name}" transform must be a function`);
397
+ }
398
+ if ('syntax' in definition && definition.syntax != null) {
399
+ if (definition.parseKey.trim() === '') {
400
+ fail(`"${definition.name}" parseKey must be non-empty`);
401
+ }
402
+ validateSyntax(definition.name, definition.syntax);
403
+ if (Object.keys(definition.renderers).length === 0) {
404
+ fail(`"${definition.name}" syntax must provide renderers`);
405
+ }
406
+ } else if (definition.transform == null) {
407
+ fail(`"${definition.name}" must declare syntax or transform`);
408
+ }
409
+
410
+ const frozenDefinition = deepFreezeConfig(definition);
411
+ const brand = Object.freeze({
412
+ kind: MARKDOWN_PLUGIN_BRAND,
413
+ apiVersion: 1 as const,
414
+ definition: frozenDefinition,
415
+ });
416
+ return Object.freeze({
417
+ name: frozenDefinition.name,
418
+ apiVersion: 1 as const,
419
+ [markdownPluginDefinition]: brand,
420
+ }) as unknown as MarkdownPluginEntry;
421
+ }
422
+
423
+ export interface PreparedSyntaxContribution {
424
+ readonly pluginName: string;
425
+ readonly display: 'inline' | 'block';
426
+ readonly contribution: MarkdownSyntaxContribution;
427
+ }
428
+
429
+ interface PreparedTransform {
430
+ readonly pluginName: string;
431
+ readonly transform: MarkdownTransform<MarkdownExtensionNode>;
432
+ readonly claims?: (source: string) => boolean;
433
+ /** Core-authored helper whose output needs no plugin-output validation. */
434
+ readonly trusted: boolean;
435
+ }
436
+
437
+ interface PreparedRenderer {
438
+ readonly pluginName: string;
439
+ readonly renderer: MarkdownExtensionRenderer<MarkdownExtensionNode>;
440
+ }
441
+
442
+ export interface PreparedMarkdownPlugins {
443
+ readonly entries: ReadonlyArray<MarkdownPluginEntry>;
444
+ readonly syntaxEntries: ReadonlyArray<MarkdownPluginEntry>;
445
+ readonly inlineByFirstCharacter: ReadonlyMap<
446
+ string,
447
+ ReadonlyArray<PreparedSyntaxContribution>
448
+ >;
449
+ readonly blockByFirstCharacter: ReadonlyMap<
450
+ string,
451
+ ReadonlyArray<PreparedSyntaxContribution>
452
+ >;
453
+ readonly transforms: ReadonlyArray<PreparedTransform>;
454
+ /** Whether any transform is plugin-authored and needs Core's guards. */
455
+ readonly hasUntrustedTransform: boolean;
456
+ readonly syntaxIdentity: string;
457
+ readonly renderers: ReadonlyMap<string, PreparedRenderer>;
458
+ }
459
+
460
+ const preparedPluginLists = new WeakMap<
461
+ ReadonlyArray<MarkdownPluginEntry>,
462
+ PreparedMarkdownPlugins
463
+ >();
464
+
465
+ function appendMapValue<T>(map: Map<string, T[]>, key: string, value: T): void {
466
+ const current = map.get(key);
467
+ if (current == null) {
468
+ map.set(key, [value]);
469
+ } else {
470
+ current.push(value);
471
+ }
472
+ }
473
+
474
+ function syntaxOnlyEntry(
475
+ publicEntry: MarkdownPluginEntry,
476
+ ): MarkdownPluginEntry {
477
+ const entry = publicEntry as InternalMarkdownPluginEntry;
478
+ const definition = getMarkdownPluginDefinition(publicEntry);
479
+ if (definition.transform == null) {
480
+ return publicEntry;
481
+ }
482
+ const syntaxDefinition = Object.freeze({...definition, transform: undefined});
483
+ return Object.freeze({
484
+ name: entry.name,
485
+ apiVersion: entry.apiVersion,
486
+ [markdownPluginDefinition]: Object.freeze({
487
+ kind: MARKDOWN_PLUGIN_BRAND,
488
+ apiVersion: 1 as const,
489
+ definition: syntaxDefinition,
490
+ }),
491
+ }) as unknown as MarkdownPluginEntry;
492
+ }
493
+
494
+ export function prepareMarkdownPlugins(
495
+ plugins: ReadonlyArray<MarkdownPluginEntry>,
496
+ ): PreparedMarkdownPlugins | undefined {
497
+ if (plugins.length === 0) {
498
+ return undefined;
499
+ }
500
+ const cached = preparedPluginLists.get(plugins);
501
+ if (cached != null) {
502
+ return cached;
503
+ }
504
+
505
+ const names = new Set<string>();
506
+ const inlineByFirstCharacter = new Map<
507
+ string,
508
+ PreparedSyntaxContribution[]
509
+ >();
510
+ const blockByFirstCharacter = new Map<string, PreparedSyntaxContribution[]>();
511
+ const transforms: PreparedTransform[] = [];
512
+ const renderers = new Map<string, PreparedRenderer>();
513
+ const syntaxIdentity: string[] = [];
514
+ const entries: InternalMarkdownPluginEntry[] = [];
515
+ const syntaxEntries: MarkdownPluginEntry[] = [];
516
+
517
+ for (const publicEntry of plugins) {
518
+ const definition = getMarkdownPluginDefinition(publicEntry);
519
+ const entry = publicEntry as InternalMarkdownPluginEntry;
520
+ if (names.has(definition.name)) {
521
+ fail(`duplicate name "${definition.name}"`);
522
+ }
523
+ names.add(definition.name);
524
+ entries.push(entry);
525
+
526
+ if (definition.transform != null) {
527
+ transforms.push({
528
+ pluginName: definition.name,
529
+ transform: definition.transform,
530
+ claims: (definition.transform as ClaimAwareMarkdownTransform)[
531
+ markdownTransformClaim
532
+ ],
533
+ trusted:
534
+ (definition.transform as TrustAwareMarkdownTransform)[
535
+ markdownTransformTrusted
536
+ ] === true,
537
+ });
538
+ }
539
+ if (definition.renderers != null) {
540
+ for (const [nodeName, renderer] of Object.entries(definition.renderers)) {
541
+ renderers.set(`${definition.name}\0${nodeName}`, {
542
+ pluginName: definition.name,
543
+ renderer: renderer,
544
+ });
545
+ }
546
+ }
547
+ if ('syntax' in definition && definition.syntax != null) {
548
+ syntaxEntries.push(syntaxOnlyEntry(publicEntry));
549
+ syntaxIdentity.push(
550
+ `${definition.name}\0${definition.apiVersion}\0${definition.parseKey}`,
551
+ );
552
+ for (const contribution of definition.syntax.inline ?? []) {
553
+ const prepared: PreparedSyntaxContribution = {
554
+ pluginName: definition.name,
555
+ display: 'inline',
556
+ contribution,
557
+ };
558
+ for (const firstCharacter of new Set(
559
+ contribution.startsWith.map(prefix => prefix[0]),
560
+ )) {
561
+ appendMapValue(inlineByFirstCharacter, firstCharacter, prepared);
562
+ }
563
+ }
564
+ for (const contribution of definition.syntax.block ?? []) {
565
+ const prepared: PreparedSyntaxContribution = {
566
+ pluginName: definition.name,
567
+ display: 'block',
568
+ contribution,
569
+ };
570
+ for (const firstCharacter of new Set(
571
+ contribution.startsWith.map(prefix => prefix[0]),
572
+ )) {
573
+ appendMapValue(blockByFirstCharacter, firstCharacter, prepared);
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ const prepared: PreparedMarkdownPlugins = {
580
+ entries,
581
+ syntaxEntries,
582
+ inlineByFirstCharacter,
583
+ blockByFirstCharacter,
584
+ transforms,
585
+ hasUntrustedTransform: transforms.some(entry => !entry.trusted),
586
+ syntaxIdentity: syntaxIdentity.join('\u0001'),
587
+ renderers,
588
+ };
589
+ preparedPluginLists.set(plugins, prepared);
590
+ return prepared;
591
+ }
592
+
593
+ export function isMarkdownPluginData(
594
+ value: unknown,
595
+ seen: Set<object> = new Set(),
596
+ ): value is MarkdownPluginData {
597
+ if (
598
+ value == null ||
599
+ typeof value === 'boolean' ||
600
+ typeof value === 'string'
601
+ ) {
602
+ return true;
603
+ }
604
+ if (typeof value === 'number') {
605
+ return Number.isFinite(value);
606
+ }
607
+ if (typeof value !== 'object' || seen.has(value)) {
608
+ return false;
609
+ }
610
+ seen.add(value);
611
+ let valid: boolean;
612
+ if (Array.isArray(value)) {
613
+ valid = value.every(item => isMarkdownPluginData(item, seen));
614
+ } else if (Object.getPrototypeOf(value) !== Object.prototype) {
615
+ valid = false;
616
+ } else {
617
+ valid = Object.values(value).every(item =>
618
+ isMarkdownPluginData(item, seen),
619
+ );
620
+ }
621
+ seen.delete(value);
622
+ return valid;
623
+ }
624
+
625
+ export function freezeMarkdownPluginData<T extends MarkdownPluginData>(
626
+ value: T,
627
+ ): T {
628
+ if (value != null && typeof value === 'object' && !Object.isFrozen(value)) {
629
+ for (const nested of Object.values(value)) {
630
+ freezeMarkdownPluginData(nested);
631
+ }
632
+ Object.freeze(value);
633
+ }
634
+ return value;
635
+ }
636
+
637
+ function positionKey(position: MarkdownAstPosition | undefined): string | null {
638
+ const start = position?.start.offset;
639
+ const end = position?.end.offset;
640
+ return start == null || end == null ? null : `${start}:${end}`;
641
+ }
642
+
643
+ interface CodeInternalProperties {
644
+ readonly descriptors: ReadonlyArray<readonly [symbol, PropertyDescriptor]>;
645
+ }
646
+
647
+ function codeSourceSignature(node: Record<string, unknown>): string {
648
+ return JSON.stringify([
649
+ node.lang,
650
+ node.meta,
651
+ node.value,
652
+ node.position ?? null,
653
+ ]);
654
+ }
655
+
656
+ function collectCodeInternalProperties(
657
+ root: MarkdownAstRoot<MarkdownExtensionNode>,
658
+ ): Map<string, CodeInternalProperties[]> {
659
+ const properties = new Map<string, CodeInternalProperties[]>();
660
+ const visit = (node: MarkdownAstNodeBase & {readonly type: string}): void => {
661
+ if (node.type === 'code') {
662
+ const descriptors = Object.getOwnPropertySymbols(node)
663
+ .map(
664
+ symbol =>
665
+ [symbol, Object.getOwnPropertyDescriptor(node, symbol)] as const,
666
+ )
667
+ .filter(
668
+ (entry): entry is readonly [symbol, PropertyDescriptor] =>
669
+ entry[1]?.enumerable === true,
670
+ );
671
+ if (descriptors.length > 0) {
672
+ const key = codeSourceSignature(
673
+ node as unknown as Record<string, unknown>,
674
+ );
675
+ properties.set(key, [...(properties.get(key) ?? []), {descriptors}]);
676
+ }
677
+ }
678
+ if ('children' in node && Array.isArray(node.children)) {
679
+ for (const child of node.children) {
680
+ if (child != null && typeof child === 'object') {
681
+ visit(child as MarkdownAstNodeBase & {readonly type: string});
682
+ }
683
+ }
684
+ }
685
+ };
686
+ visit(root);
687
+ return properties;
688
+ }
689
+
690
+ function restoreCodeInternalProperties(
691
+ previous: MarkdownAstRoot<MarkdownExtensionNode>,
692
+ next: MarkdownAstRoot<MarkdownExtensionNode>,
693
+ ): MarkdownAstRoot<MarkdownExtensionNode> {
694
+ const properties = collectCodeInternalProperties(previous);
695
+ if (properties.size === 0) {
696
+ return next;
697
+ }
698
+
699
+ const visit = (
700
+ node: MarkdownAstNodeBase & {readonly type: string},
701
+ ): MarkdownAstNodeBase & {readonly type: string} => {
702
+ let current = node;
703
+ if (node.type === 'code') {
704
+ const key = codeSourceSignature(
705
+ node as unknown as Record<string, unknown>,
706
+ );
707
+ const queue = properties.get(key);
708
+ const internal = queue?.shift();
709
+ const shouldRestore = internal?.descriptors.some(
710
+ ([symbol, descriptor]) => {
711
+ const current = Object.getOwnPropertyDescriptor(node, symbol);
712
+ return (
713
+ current == null ||
714
+ current.value !== descriptor.value ||
715
+ current.get !== descriptor.get ||
716
+ current.set !== descriptor.set ||
717
+ current.enumerable !== descriptor.enumerable ||
718
+ current.configurable !== descriptor.configurable ||
719
+ current.writable !== descriptor.writable
720
+ );
721
+ },
722
+ );
723
+ if (internal != null && shouldRestore === true) {
724
+ const clone = {...node};
725
+ for (const [symbol, descriptor] of internal.descriptors) {
726
+ Object.defineProperty(clone, symbol, descriptor);
727
+ }
728
+ current = clone;
729
+ }
730
+ }
731
+ if ('children' in current && Array.isArray(current.children)) {
732
+ const currentChildren = current.children as ReadonlyArray<
733
+ MarkdownAstNodeBase & {readonly type: string}
734
+ >;
735
+ const children = currentChildren.map(child => visit(child));
736
+ if (children.some((child, index) => child !== currentChildren[index])) {
737
+ const parent: MarkdownAstNodeBase & {
738
+ readonly type: string;
739
+ readonly children: ReadonlyArray<
740
+ MarkdownAstNodeBase & {readonly type: string}
741
+ >;
742
+ } = {...current, children};
743
+ current = parent;
744
+ }
745
+ }
746
+ return current;
747
+ };
748
+
749
+ return visit(next) as MarkdownAstRoot<MarkdownExtensionNode>;
750
+ }
751
+
752
+ interface SourceInvariant {
753
+ readonly type: string;
754
+ readonly depth?: unknown;
755
+ readonly ordered?: unknown;
756
+ readonly start?: unknown;
757
+ readonly delimiter?: unknown;
758
+ readonly plugin?: unknown;
759
+ readonly name?: unknown;
760
+ readonly display?: unknown;
761
+ }
762
+
763
+ function collectSourceInvariants(
764
+ root: MarkdownAstRoot<MarkdownExtensionNode>,
765
+ ): Map<string, SourceInvariant[]> {
766
+ const positions = new Map<string, SourceInvariant[]>();
767
+ const visit = (node: MarkdownAstNodeBase & {readonly type: string}): void => {
768
+ const key = positionKey(node.position);
769
+ if (key != null) {
770
+ const record = node as unknown as Record<string, unknown>;
771
+ const invariant = {
772
+ type: node.type,
773
+ depth: record.depth,
774
+ ordered: record.ordered,
775
+ start: record.start,
776
+ delimiter: record.delimiter,
777
+ plugin: record.plugin,
778
+ name: record.name,
779
+ display: record.display,
780
+ };
781
+ positions.set(key, [...(positions.get(key) ?? []), invariant]);
782
+ }
783
+ if ('children' in node && Array.isArray(node.children)) {
784
+ for (const child of node.children) {
785
+ if (child != null && typeof child === 'object') {
786
+ visit(child as MarkdownAstNodeBase & {readonly type: string});
787
+ }
788
+ }
789
+ }
790
+ };
791
+ visit(root);
792
+ return positions;
793
+ }
794
+
795
+ function extensionSignature(node: Record<string, unknown>): string {
796
+ return JSON.stringify([
797
+ node.plugin,
798
+ node.name,
799
+ node.display,
800
+ node.data,
801
+ node.source,
802
+ positionKey(node.position as MarkdownAstPosition | undefined),
803
+ ]);
804
+ }
805
+
806
+ interface ExistingExtension {
807
+ readonly plugin: string;
808
+ count: number;
809
+ }
810
+
811
+ function collectExtensionSignatures(
812
+ root: MarkdownAstRoot<MarkdownExtensionNode>,
813
+ ): Map<string, ExistingExtension> {
814
+ const signatures = new Map<string, ExistingExtension>();
815
+ const visit = (node: MarkdownAstNodeBase & {readonly type: string}): void => {
816
+ if (node.type === 'extension') {
817
+ const signature = extensionSignature(
818
+ node as unknown as Record<string, unknown>,
819
+ );
820
+ const existing = signatures.get(signature);
821
+ signatures.set(signature, {
822
+ plugin: String((node as unknown as Record<string, unknown>).plugin),
823
+ count: (existing?.count ?? 0) + 1,
824
+ });
825
+ }
826
+ if ('children' in node && Array.isArray(node.children)) {
827
+ for (const child of node.children) {
828
+ if (child != null && typeof child === 'object') {
829
+ visit(child as MarkdownAstNodeBase & {readonly type: string});
830
+ }
831
+ }
832
+ }
833
+ };
834
+ visit(root);
835
+ return signatures;
836
+ }
837
+
838
+ const sourceHeadingMarker = Symbol('MarkdownSourceHeading');
839
+
840
+ interface SourceHeadingMarker {
841
+ readonly depth: number;
842
+ }
843
+
844
+ function markSourceHeadings(
845
+ root: MarkdownAstRoot<MarkdownExtensionNode>,
846
+ ): Set<SourceHeadingMarker> {
847
+ const markers = new Set<SourceHeadingMarker>();
848
+ const visit = (node: MarkdownAstNodeBase & {readonly type: string}): void => {
849
+ if (node.type === 'heading' && !(sourceHeadingMarker in node)) {
850
+ Object.defineProperty(node, sourceHeadingMarker, {
851
+ configurable: false,
852
+ enumerable: true,
853
+ value: Object.freeze({
854
+ depth: (node as unknown as {readonly depth: number}).depth,
855
+ } satisfies SourceHeadingMarker),
856
+ writable: false,
857
+ });
858
+ }
859
+ if (node.type === 'heading') {
860
+ markers.add(
861
+ (
862
+ node as unknown as {
863
+ readonly [sourceHeadingMarker]: SourceHeadingMarker;
864
+ }
865
+ )[sourceHeadingMarker],
866
+ );
867
+ }
868
+ if ('children' in node && Array.isArray(node.children)) {
869
+ for (const child of node.children) {
870
+ if (child != null && typeof child === 'object') {
871
+ visit(child as MarkdownAstNodeBase & {readonly type: string});
872
+ }
873
+ }
874
+ }
875
+ };
876
+ visit(root);
877
+ return markers;
878
+ }
879
+
880
+ const PHRASING_TYPES = new Set([
881
+ 'text',
882
+ 'strong',
883
+ 'emphasis',
884
+ 'delete',
885
+ 'inlineCode',
886
+ 'inlineMath',
887
+ 'break',
888
+ 'link',
889
+ 'image',
890
+ 'citation',
891
+ ]);
892
+ const BLOCK_TYPES = new Set([
893
+ 'heading',
894
+ 'paragraph',
895
+ 'code',
896
+ 'math',
897
+ 'blockquote',
898
+ 'list',
899
+ 'table',
900
+ 'thematicBreak',
901
+ 'image',
902
+ ]);
903
+ const PHRASING_PARENTS = new Set([
904
+ 'heading',
905
+ 'paragraph',
906
+ 'strong',
907
+ 'emphasis',
908
+ 'delete',
909
+ 'link',
910
+ 'tableCell',
911
+ ]);
912
+ const BLOCK_PARENTS = new Set(['root', 'blockquote', 'listItem']);
913
+ const CHILD_PARENT_TYPES = new Set([
914
+ ...PHRASING_PARENTS,
915
+ ...BLOCK_PARENTS,
916
+ 'list',
917
+ 'table',
918
+ 'tableRow',
919
+ ]);
920
+
921
+ function validateAst(
922
+ root: unknown,
923
+ pluginNames: ReadonlySet<string>,
924
+ rendererKeys: ReadonlySet<string>,
925
+ sourcePositions: Map<string, SourceInvariant[]>,
926
+ sourceHeadingMarkers: ReadonlySet<SourceHeadingMarker>,
927
+ activePluginName: string,
928
+ existingExtensions: Map<string, ExistingExtension>,
929
+ display: 'inline' | 'block',
930
+ ): root is MarkdownAstRoot<MarkdownExtensionNode> {
931
+ if (root == null || typeof root !== 'object') {
932
+ return false;
933
+ }
934
+ const seen = new Set<object>();
935
+ const seenHeadingMarkers = new Set<SourceHeadingMarker>();
936
+ let count = 0;
937
+ const visit = (
938
+ value: unknown,
939
+ parent: string | null,
940
+ insideLink: boolean,
941
+ ): boolean => {
942
+ if (value == null || typeof value !== 'object' || seen.has(value)) {
943
+ return false;
944
+ }
945
+ if (++count > 100_000) {
946
+ return false;
947
+ }
948
+ seen.add(value);
949
+ const node = value as Record<string, unknown>;
950
+ const type = node.type;
951
+ if (typeof type !== 'string') {
952
+ return false;
953
+ }
954
+ if (node.data !== undefined && !isMarkdownPluginData(node.data)) {
955
+ return false;
956
+ }
957
+
958
+ if (parent != null) {
959
+ const extensionDisplay = type === 'extension' ? node.display : undefined;
960
+ const phrasing =
961
+ PHRASING_TYPES.has(type) || extensionDisplay === 'inline';
962
+ const block = BLOCK_TYPES.has(type) || extensionDisplay === 'block';
963
+ if (
964
+ (PHRASING_PARENTS.has(parent) && !phrasing) ||
965
+ (BLOCK_PARENTS.has(parent) && !block) ||
966
+ (insideLink && type === 'link') ||
967
+ (parent === 'list' && type !== 'listItem') ||
968
+ (parent === 'table' && type !== 'tableRow') ||
969
+ (parent === 'tableRow' && type !== 'tableCell')
970
+ ) {
971
+ return false;
972
+ }
973
+ }
974
+
975
+ const position = node.position as MarkdownAstPosition | undefined;
976
+ const key = positionKey(position);
977
+ if (position != null) {
978
+ const start = position.start?.offset;
979
+ const end = position.end?.offset;
980
+ if (
981
+ !Number.isInteger(start) ||
982
+ !Number.isInteger(end) ||
983
+ (start as number) < 0 ||
984
+ (end as number) < (start as number)
985
+ ) {
986
+ return false;
987
+ }
988
+ }
989
+ if (key != null) {
990
+ const invariants = sourcePositions.get(key) ?? [];
991
+ const invariantIndex = invariants.findIndex(
992
+ invariant =>
993
+ invariant.type === type &&
994
+ invariant.depth === node.depth &&
995
+ invariant.ordered === node.ordered &&
996
+ invariant.start === node.start &&
997
+ invariant.delimiter === node.delimiter &&
998
+ invariant.plugin === node.plugin &&
999
+ invariant.name === node.name &&
1000
+ invariant.display === node.display,
1001
+ );
1002
+ if (invariantIndex < 0) {
1003
+ return false;
1004
+ }
1005
+ invariants.splice(invariantIndex, 1);
1006
+ }
1007
+ if (type !== 'extension' && node.source !== undefined) {
1008
+ return false;
1009
+ }
1010
+
1011
+ switch (type) {
1012
+ case 'root':
1013
+ if (parent != null) {
1014
+ return false;
1015
+ }
1016
+ break;
1017
+ case 'heading': {
1018
+ if (![1, 2, 3, 4, 5, 6].includes(node.depth as number)) {
1019
+ return false;
1020
+ }
1021
+ const marker = (
1022
+ node as unknown as {
1023
+ readonly [sourceHeadingMarker]?: SourceHeadingMarker;
1024
+ }
1025
+ )[sourceHeadingMarker];
1026
+ if (
1027
+ marker != null &&
1028
+ (!sourceHeadingMarkers.has(marker) ||
1029
+ marker.depth !== node.depth ||
1030
+ seenHeadingMarkers.has(marker))
1031
+ ) {
1032
+ return false;
1033
+ }
1034
+ if (marker != null) {
1035
+ seenHeadingMarkers.add(marker);
1036
+ }
1037
+ break;
1038
+ }
1039
+ case 'text':
1040
+ case 'inlineCode':
1041
+ case 'inlineMath':
1042
+ case 'math':
1043
+ if (typeof node.value !== 'string') {
1044
+ return false;
1045
+ }
1046
+ break;
1047
+ case 'code':
1048
+ if (
1049
+ typeof node.value !== 'string' ||
1050
+ (node.lang !== null && typeof node.lang !== 'string') ||
1051
+ (node.meta !== undefined && typeof node.meta !== 'string')
1052
+ ) {
1053
+ return false;
1054
+ }
1055
+ break;
1056
+ case 'link':
1057
+ if (
1058
+ typeof node.url !== 'string' ||
1059
+ !isSafeMarkdownParserUrl(node.url)
1060
+ ) {
1061
+ return false;
1062
+ }
1063
+ break;
1064
+ case 'image':
1065
+ if (
1066
+ typeof node.url !== 'string' ||
1067
+ !isSafeMarkdownParserUrl(node.url) ||
1068
+ typeof node.alt !== 'string'
1069
+ ) {
1070
+ return false;
1071
+ }
1072
+ break;
1073
+ case 'citation':
1074
+ if (typeof node.sourceId !== 'string') {
1075
+ return false;
1076
+ }
1077
+ break;
1078
+ case 'list':
1079
+ if (
1080
+ typeof node.ordered !== 'boolean' ||
1081
+ (node.start != null && !Number.isFinite(node.start)) ||
1082
+ (node.delimiter != null &&
1083
+ node.delimiter !== '.' &&
1084
+ node.delimiter !== ')')
1085
+ ) {
1086
+ return false;
1087
+ }
1088
+ break;
1089
+ case 'table':
1090
+ if (
1091
+ !Array.isArray(node.align) ||
1092
+ !node.align.every(
1093
+ value =>
1094
+ value === null ||
1095
+ value === 'left' ||
1096
+ value === 'center' ||
1097
+ value === 'right',
1098
+ )
1099
+ ) {
1100
+ return false;
1101
+ }
1102
+ break;
1103
+ case 'extension': {
1104
+ if (
1105
+ typeof node.plugin !== 'string' ||
1106
+ !pluginNames.has(node.plugin) ||
1107
+ typeof node.name !== 'string' ||
1108
+ (node.display !== 'inline' && node.display !== 'block') ||
1109
+ !isMarkdownPluginData(node.data) ||
1110
+ !rendererKeys.has(`${node.plugin}\0${node.name}`)
1111
+ ) {
1112
+ return false;
1113
+ }
1114
+ const signature = extensionSignature(node);
1115
+ const existing = existingExtensions.get(signature);
1116
+ if (existing != null && existing.count > 0) {
1117
+ existing.count--;
1118
+ } else if (
1119
+ node.plugin !== activePluginName ||
1120
+ node.source !== undefined ||
1121
+ node.position !== undefined
1122
+ ) {
1123
+ return false;
1124
+ }
1125
+ break;
1126
+ }
1127
+ case 'strong':
1128
+ case 'emphasis':
1129
+ case 'delete':
1130
+ case 'break':
1131
+ case 'paragraph':
1132
+ case 'blockquote':
1133
+ case 'listItem':
1134
+ case 'tableRow':
1135
+ case 'tableCell':
1136
+ case 'thematicBreak':
1137
+ break;
1138
+ default:
1139
+ return false;
1140
+ }
1141
+
1142
+ if (CHILD_PARENT_TYPES.has(type)) {
1143
+ if (!Array.isArray(node.children)) {
1144
+ return false;
1145
+ }
1146
+ for (const child of node.children) {
1147
+ if (!visit(child, type, insideLink || type === 'link')) {
1148
+ return false;
1149
+ }
1150
+ }
1151
+ } else if ('children' in node) {
1152
+ return false;
1153
+ }
1154
+ return true;
1155
+ };
1156
+ const candidate = root as MarkdownAstRoot<MarkdownExtensionNode>;
1157
+ return (
1158
+ visit(candidate, null, false) &&
1159
+ candidate.type === 'root' &&
1160
+ Array.from(existingExtensions.values()).every(
1161
+ extension =>
1162
+ extension.plugin === activePluginName || extension.count === 0,
1163
+ ) &&
1164
+ (display === 'block' ||
1165
+ (candidate.children.length === 1 &&
1166
+ candidate.children[0]?.type === 'paragraph'))
1167
+ );
1168
+ }
1169
+
1170
+ /**
1171
+ * Deeply freezes a Markdown tree.
1172
+ *
1173
+ * No memo set and no cycle set. Both were measured against this parser's own
1174
+ * fixture and together cost four times the freezing they were guarding: a
1175
+ * full freeze of a 12k-node tree runs at 19 percent of the parse that built
1176
+ * it, a WeakSet memo takes that to 47 percent, and a cycle set to 75. Their
1177
+ * absence is safe, not merely cheap: every tree reaching here is acyclic
1178
+ * (the parser builds no cycles, and `validateAst` rejects any plugin tree
1179
+ * that contains one), and `Object.freeze` on an already-frozen node is a
1180
+ * no-op, so re-walking a shared subtree is correct — just not free.
1181
+ *
1182
+ * Deliberately NOT skipping nodes that look frozen: a node a plugin froze
1183
+ * itself may still hold mutable children, and an undecorated sibling a
1184
+ * trusted helper carried over by identity must be frozen like any other.
1185
+ */
1186
+ function freezeAstNode(value: unknown): void {
1187
+ if (value == null || typeof value !== 'object') {
1188
+ return;
1189
+ }
1190
+ if (Array.isArray(value)) {
1191
+ for (let index = 0; index < value.length; index++) {
1192
+ freezeAstNode(value[index]);
1193
+ }
1194
+ } else {
1195
+ // `for…in` over a plain AST node rather than Object.values: the entries
1196
+ // array is the dominant remaining cost of the walk.
1197
+ for (const key in value) {
1198
+ freezeAstNode((value as Record<string, unknown>)[key]);
1199
+ }
1200
+ }
1201
+ Object.freeze(value);
1202
+ }
1203
+
1204
+ function freezeAst<T>(value: T): T {
1205
+ freezeAstNode(value);
1206
+ return value;
1207
+ }
1208
+
1209
+ const reportedProductionPluginFailures = new Set<string>();
1210
+
1211
+ /**
1212
+ * Freezes only what `next` does not share with `previous`.
1213
+ *
1214
+ * Valid ONLY when no plugin-authored transform runs in this pipeline. The
1215
+ * nodes it skips are the ones a Core helper carried over untouched, and with
1216
+ * every transform Core-authored there is no actor that could mutate them:
1217
+ * they are left in exactly the state an omitted or empty plugin list leaves
1218
+ * them in, which is the state rendering already observes for every
1219
+ * zero-plugin document today. The moment one untrusted transform exists,
1220
+ * `applyMarkdownTransforms` uses the full `freezeAst` instead, before that
1221
+ * transform observes anything — a shared, undecorated sibling included.
1222
+ */
1223
+ function freezeBuiltAst<T>(previous: unknown, next: T): T {
1224
+ if (next == null || typeof next !== 'object' || next === previous) {
1225
+ return next;
1226
+ }
1227
+ const before =
1228
+ previous != null && typeof previous === 'object'
1229
+ ? (previous as Record<string, unknown>)
1230
+ : undefined;
1231
+ if (Array.isArray(next)) {
1232
+ const beforeArray = Array.isArray(previous) ? previous : undefined;
1233
+ for (let index = 0; index < next.length; index++) {
1234
+ freezeBuiltAst(beforeArray?.[index], next[index]);
1235
+ }
1236
+ } else {
1237
+ for (const key in next) {
1238
+ freezeBuiltAst(before?.[key], (next as Record<string, unknown>)[key]);
1239
+ }
1240
+ }
1241
+ Object.freeze(next);
1242
+ return next;
1243
+ }
1244
+
1245
+ const HELPER_PHRASING_TYPES = new Set([
1246
+ 'text',
1247
+ 'strong',
1248
+ 'emphasis',
1249
+ 'delete',
1250
+ 'inlineCode',
1251
+ 'inlineMath',
1252
+ 'break',
1253
+ 'link',
1254
+ 'image',
1255
+ 'citation',
1256
+ 'extension',
1257
+ ]);
1258
+
1259
+ /**
1260
+ * @internal Validates and freezes phrasing nodes a Core helper's caller just
1261
+ * authored, so the helper may put them straight into its output tree.
1262
+ * applying exactly the rules `validateAst` applies to newly-introduced nodes:
1263
+ * representable typed data only, no authored provenance, no foreign extension
1264
+ * ownership, no unrenderable extension, revalidated destinations, no nested
1265
+ * link, no block content, no cycles, and a bounded node count.
1266
+ *
1267
+ * A helper that validates and freezes its callback's output this way has
1268
+ * nothing left for Core to check, because every other node it returns is one
1269
+ * Core already validated. That is what lets such a helper run on the trusted
1270
+ * path while keeping FR10 and FR11 intact.
1271
+ *
1272
+ * Returns the frozen nodes, or throws — the caller reports the failure
1273
+ * through the ordinary transform failure path.
1274
+ */
1275
+ export function adoptMarkdownHelperNode(
1276
+ node: unknown,
1277
+ ownership: MarkdownHelperOwnership,
1278
+ insideLink = false,
1279
+ ): void {
1280
+ adoptMarkdownHelperNodes(singleNodeScratch(node), ownership, insideLink);
1281
+ }
1282
+
1283
+ /**
1284
+ * A one-element view for the single-node case, reused between calls so the
1285
+ * common replacement costs no array. Refilled on entry and never held past
1286
+ * the synchronous validation below, and a callback that re-enters the
1287
+ * helper gets a fresh array rather than disturbing an outer walk.
1288
+ */
1289
+ let scratchInUse = false;
1290
+ const reusableScratch: unknown[] = [undefined];
1291
+ function singleNodeScratch(node: unknown): unknown[] {
1292
+ if (scratchInUse) {
1293
+ return [node];
1294
+ }
1295
+ reusableScratch[0] = node;
1296
+ return reusableScratch;
1297
+ }
1298
+
1299
+ export function adoptMarkdownHelperNodes(
1300
+ nodes: ReadonlyArray<unknown>,
1301
+ ownership: MarkdownHelperOwnership,
1302
+ insideLink = false,
1303
+ ): void {
1304
+ // The overwhelmingly common replacement is one flat text node. Checking
1305
+ // that shape directly avoids the general walk's setup entirely, and this
1306
+ // runs once per match.
1307
+ if (nodes.length === 1) {
1308
+ const only = nodes[0];
1309
+ if (
1310
+ only != null &&
1311
+ typeof only === 'object' &&
1312
+ (only as {type?: unknown}).type === 'text' &&
1313
+ typeof (only as {value?: unknown}).value === 'string'
1314
+ ) {
1315
+ let extra = false;
1316
+ for (const key in only) {
1317
+ if (key !== 'type' && key !== 'value') {
1318
+ extra = true;
1319
+ break;
1320
+ }
1321
+ }
1322
+ if (!extra) {
1323
+ return;
1324
+ }
1325
+ }
1326
+ }
1327
+ // Cycle tracking is allocated only once a node with children appears: the
1328
+ // common replacement is a flat node, and this runs once per match.
1329
+ let seen: Set<object> | undefined;
1330
+ let count = 0;
1331
+ const visit = (value: unknown, withinLink: boolean): boolean => {
1332
+ if (value == null || typeof value !== 'object' || seen?.has(value)) {
1333
+ return false;
1334
+ }
1335
+ if (++count > 10_000) {
1336
+ return false;
1337
+ }
1338
+ if (Array.isArray((value as {children?: unknown}).children)) {
1339
+ seen ??= new Set<object>();
1340
+ seen.add(value);
1341
+ }
1342
+ const node = value as Record<string, unknown>;
1343
+ const type = node.type;
1344
+ if (typeof type !== 'string' || !HELPER_PHRASING_TYPES.has(type)) {
1345
+ return false;
1346
+ }
1347
+ // A helper's callback authors synthetic nodes only: provenance stays
1348
+ // Core's to assign, so neither field may be present.
1349
+ if (node.position !== undefined) {
1350
+ return false;
1351
+ }
1352
+ if (type !== 'extension' && node.source !== undefined) {
1353
+ return false;
1354
+ }
1355
+ if (node.data !== undefined && !isMarkdownPluginData(node.data)) {
1356
+ return false;
1357
+ }
1358
+ switch (type) {
1359
+ case 'text':
1360
+ case 'inlineCode':
1361
+ case 'inlineMath':
1362
+ if (typeof node.value !== 'string') {
1363
+ return false;
1364
+ }
1365
+ break;
1366
+ case 'link':
1367
+ if (
1368
+ withinLink ||
1369
+ typeof node.url !== 'string' ||
1370
+ !isSafeMarkdownParserUrl(node.url)
1371
+ ) {
1372
+ return false;
1373
+ }
1374
+ break;
1375
+ case 'image':
1376
+ if (
1377
+ typeof node.url !== 'string' ||
1378
+ !isSafeMarkdownParserUrl(node.url) ||
1379
+ typeof node.alt !== 'string'
1380
+ ) {
1381
+ return false;
1382
+ }
1383
+ break;
1384
+ case 'citation':
1385
+ if (typeof node.sourceId !== 'string') {
1386
+ return false;
1387
+ }
1388
+ break;
1389
+ case 'extension':
1390
+ if (
1391
+ node.plugin !== ownership.pluginName ||
1392
+ typeof node.name !== 'string' ||
1393
+ !ownership.hasRenderer(node.name) ||
1394
+ node.display !== 'inline' ||
1395
+ node.source !== undefined ||
1396
+ !isMarkdownPluginData(node.data)
1397
+ ) {
1398
+ return false;
1399
+ }
1400
+ break;
1401
+ case 'strong':
1402
+ case 'emphasis':
1403
+ case 'delete':
1404
+ case 'break':
1405
+ break;
1406
+ }
1407
+ if (type === 'strong' || type === 'emphasis' || type === 'delete') {
1408
+ if (!Array.isArray(node.children)) {
1409
+ return false;
1410
+ }
1411
+ for (const child of node.children) {
1412
+ if (!visit(child, withinLink)) {
1413
+ return false;
1414
+ }
1415
+ }
1416
+ } else if (type === 'link') {
1417
+ if (!Array.isArray(node.children)) {
1418
+ return false;
1419
+ }
1420
+ for (const child of node.children) {
1421
+ if (!visit(child, true)) {
1422
+ return false;
1423
+ }
1424
+ }
1425
+ } else if ('children' in node) {
1426
+ return false;
1427
+ }
1428
+ return true;
1429
+ };
1430
+ for (const node of nodes) {
1431
+ if (!visit(node, insideLink)) {
1432
+ throw new TypeError(
1433
+ 'Markdown helper callback returned an unrepresentable node',
1434
+ );
1435
+ }
1436
+ }
1437
+ // Frozen only once every node passed, so a rejected batch leaves the
1438
+ // caller's objects exactly as they were. Freezing is what makes adopting
1439
+ // a caller's object safe instead of copying it: the object is now part of
1440
+ // an immutable tree, and a later write by whoever still holds a reference
1441
+ // cannot reach into the document.
1442
+ const reentrant = nodes !== reusableScratch;
1443
+ if (!reentrant) {
1444
+ scratchInUse = true;
1445
+ }
1446
+ try {
1447
+ for (const node of nodes) {
1448
+ freezeAstNode(node);
1449
+ }
1450
+ } finally {
1451
+ if (!reentrant) {
1452
+ scratchInUse = false;
1453
+ reusableScratch[0] = undefined;
1454
+ }
1455
+ }
1456
+ }
1457
+
1458
+ export function reportMarkdownPluginFailure(
1459
+ pluginName: string,
1460
+ phase: 'syntax' | 'transform' | 'render',
1461
+ error: unknown,
1462
+ ): void {
1463
+ const key = `markdown-plugin:${pluginName}:${phase}`;
1464
+ const message = `plugin "${pluginName}" failed in ${phase}; rendered readable fallback.`;
1465
+ if (process.env.NODE_ENV === 'production') {
1466
+ if (!reportedProductionPluginFailures.has(key)) {
1467
+ reportedProductionPluginFailures.add(key);
1468
+ devError('Markdown', message);
1469
+ }
1470
+ return;
1471
+ }
1472
+ warnOnce(key, 'Markdown', message, error);
1473
+ }
1474
+
1475
+ export function applyMarkdownTransforms<Node extends MarkdownExtensionNode>(
1476
+ root: MarkdownAstRoot<Node>,
1477
+ plugins: PreparedMarkdownPlugins | undefined,
1478
+ source: string,
1479
+ isFinal: boolean,
1480
+ display: 'inline' | 'block',
1481
+ ): MarkdownAstRoot<Node> {
1482
+ if (plugins == null || plugins.transforms.length === 0) {
1483
+ return root;
1484
+ }
1485
+ let pluginNames: Set<string> | undefined;
1486
+ let rendererKeys: Set<string> | undefined;
1487
+ let document: MarkdownAstRoot<Node> = root;
1488
+ // Heading identity is stamped on the mutable parser tree, before anything
1489
+ // freezes it, and only when a plugin-authored transform will actually be
1490
+ // validated against it. An all-trusted pipeline never pays for it.
1491
+ const sourceHeadingMarkers = plugins.hasUntrustedTransform
1492
+ ? markSourceHeadings(root)
1493
+ : undefined;
1494
+ let guarded = false;
1495
+ let priorTransformChangedTree = false;
1496
+ /** A trusted helper has produced a tree nothing has frozen yet. */
1497
+ let deferredFreeze = false;
1498
+ for (const prepared of plugins.transforms) {
1499
+ if (!priorTransformChangedTree && prepared.claims?.(source) === false) {
1500
+ continue;
1501
+ }
1502
+ // A plugin-authored transform observes deeply frozen input and has its
1503
+ // output validated. Both guards exist to contain untrusted structure, so
1504
+ // a Core-authored trusted helper needs neither — and a pipeline of only
1505
+ // trusted helpers never pays for them at all. Every untrusted transform
1506
+ // still receives a frozen tree, including one that follows a trusted
1507
+ // helper, whose output is not frozen on the way out.
1508
+ if (!prepared.trusted) {
1509
+ if (!guarded) {
1510
+ pluginNames = new Set(plugins.entries.map(entry => entry.name));
1511
+ rendererKeys = new Set(plugins.renderers.keys());
1512
+ guarded = true;
1513
+ }
1514
+ // Deeply freezes the whole tree, including anything a trusted helper
1515
+ // left shared and mutable, before plugin-authored code observes it.
1516
+ // Subtrees already frozen by an earlier pass are skipped.
1517
+ document = freezeAst(document);
1518
+ deferredFreeze = false;
1519
+ }
1520
+ try {
1521
+ const context: InternalMarkdownTransformContext = {
1522
+ source,
1523
+ isFinal,
1524
+ display,
1525
+ report(message) {
1526
+ reportMarkdownPluginFailure(
1527
+ prepared.pluginName,
1528
+ 'transform',
1529
+ message,
1530
+ );
1531
+ },
1532
+ [markdownTransformPluginName]: prepared.pluginName,
1533
+ [markdownTransformHasRenderer]: (nodeName: string) =>
1534
+ plugins.renderers.has(`${prepared.pluginName}\u0000${nodeName}`),
1535
+ [markdownTransformSourceUnchanged]: !priorTransformChangedTree,
1536
+ };
1537
+ const next = prepared.transform(document, context) as unknown;
1538
+ if (
1539
+ next != null &&
1540
+ typeof next === 'object' &&
1541
+ typeof (next as {then?: unknown}).then === 'function'
1542
+ ) {
1543
+ void Promise.resolve(next).catch(() => {});
1544
+ throw new TypeError('Async Markdown transforms are not supported');
1545
+ }
1546
+ if (next === document) {
1547
+ continue;
1548
+ }
1549
+ if (prepared.trusted) {
1550
+ priorTransformChangedTree = true;
1551
+ // No freeze here. Nothing untrusted can observe this tree yet —
1552
+ // only another Core helper, which never mutates its input — so the
1553
+ // pipeline freezes once, below, before anything else sees it. That
1554
+ // freeze is a FULL walk, never one that stops at nodes merely
1555
+ // shared with an earlier unfrozen tree: an undecorated sibling a
1556
+ // helper carried over by identity must be frozen too.
1557
+ deferredFreeze = true;
1558
+ document = next as MarkdownAstRoot<Node>;
1559
+ continue;
1560
+ }
1561
+ const positions = collectSourceInvariants(document);
1562
+ const existingExtensions = collectExtensionSignatures(document);
1563
+ if (
1564
+ !validateAst(
1565
+ next,
1566
+ pluginNames as ReadonlySet<string>,
1567
+ rendererKeys as ReadonlySet<string>,
1568
+ positions,
1569
+ sourceHeadingMarkers as ReadonlySet<SourceHeadingMarker>,
1570
+ prepared.pluginName,
1571
+ existingExtensions,
1572
+ display,
1573
+ )
1574
+ ) {
1575
+ throw new TypeError('Transform returned an invalid Markdown document');
1576
+ }
1577
+ priorTransformChangedTree = true;
1578
+ document = freezeAst(
1579
+ restoreCodeInternalProperties(document, next),
1580
+ ) as MarkdownAstRoot<Node>;
1581
+ } catch (error) {
1582
+ reportMarkdownPluginFailure(prepared.pluginName, 'transform', error);
1583
+ }
1584
+ }
1585
+ if (!deferredFreeze) {
1586
+ return document;
1587
+ }
1588
+ // Freeze what the helpers built. With a plugin-authored transform in the
1589
+ // list the whole tree is frozen, because that plugin may observe any part
1590
+ // of it; with only Core helpers, the untouched remainder is left exactly
1591
+ // as a zero-plugin parse leaves it.
1592
+ return plugins.hasUntrustedTransform
1593
+ ? freezeAst(document)
1594
+ : freezeBuiltAst(root, document);
1595
+ }
1596
+
1597
+ export function getMarkdownExtensionRenderer(
1598
+ plugins: PreparedMarkdownPlugins | undefined,
1599
+ node: MarkdownExtensionNode,
1600
+ ): MarkdownExtensionRenderer<MarkdownExtensionNode> | undefined {
1601
+ return plugins?.renderers.get(`${node.plugin}\0${node.name}`)?.renderer;
1602
+ }
1603
+
1604
+ export function markdownExtensionText(
1605
+ plugins: PreparedMarkdownPlugins | undefined,
1606
+ node: MarkdownExtensionNode,
1607
+ ): string {
1608
+ const renderer = getMarkdownExtensionRenderer(plugins, node);
1609
+ if (renderer == null) {
1610
+ return node.source ?? '';
1611
+ }
1612
+ try {
1613
+ return renderer.toText(node);
1614
+ } catch (error) {
1615
+ reportMarkdownPluginFailure(node.plugin, 'render', error);
1616
+ return node.source ?? '';
1617
+ }
1618
+ }