@localix-ai/flowdown 0.0.0-stage → 0.5.1

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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -2
  3. package/dist/browser/file-actions.js +80 -0
  4. package/dist/browser/file-actions.js.map +1 -0
  5. package/dist/core/block-facts.js +72 -0
  6. package/dist/core/block-facts.js.map +1 -0
  7. package/dist/core/blocks.d.ts +25 -0
  8. package/dist/core/blocks.js +464 -0
  9. package/dist/core/blocks.js.map +1 -0
  10. package/dist/core/bounded-cache.js +42 -0
  11. package/dist/core/bounded-cache.js.map +1 -0
  12. package/dist/core/continues.js +15 -0
  13. package/dist/core/continues.js.map +1 -0
  14. package/dist/core/dollars.d.ts +12 -0
  15. package/dist/core/dollars.js +88 -0
  16. package/dist/core/dollars.js.map +1 -0
  17. package/dist/core/fence-scan.js +62 -0
  18. package/dist/core/fence-scan.js.map +1 -0
  19. package/dist/core/file-tree-parse.d.ts +26 -0
  20. package/dist/core/file-tree-parse.js +51 -0
  21. package/dist/core/file-tree-parse.js.map +1 -0
  22. package/dist/core/forms-html.js +63 -0
  23. package/dist/core/forms-html.js.map +1 -0
  24. package/dist/core/forms.d.ts +28 -0
  25. package/dist/core/forms.js +23 -0
  26. package/dist/core/forms.js.map +1 -0
  27. package/dist/core/headings.d.ts +11 -0
  28. package/dist/core/headings.js +74 -0
  29. package/dist/core/headings.js.map +1 -0
  30. package/dist/core/heal.d.ts +11 -0
  31. package/dist/core/heal.js +220 -0
  32. package/dist/core/heal.js.map +1 -0
  33. package/dist/core/load-once.js +23 -0
  34. package/dist/core/load-once.js.map +1 -0
  35. package/dist/core/math-delimiters.js +33 -0
  36. package/dist/core/math-delimiters.js.map +1 -0
  37. package/dist/core/math-syntax.js +56 -0
  38. package/dist/core/math-syntax.js.map +1 -0
  39. package/dist/core/prose.d.ts +10 -0
  40. package/dist/core/prose.js +77 -0
  41. package/dist/core/prose.js.map +1 -0
  42. package/dist/core/remark-answer-blocks.d.ts +5 -0
  43. package/dist/core/remark-answer-blocks.js +172 -0
  44. package/dist/core/remark-answer-blocks.js.map +1 -0
  45. package/dist/core/remark-answer-containers.js +299 -0
  46. package/dist/core/remark-answer-containers.js.map +1 -0
  47. package/dist/core/sanitize-schema.d.ts +18 -0
  48. package/dist/core/sanitize-schema.js +62 -0
  49. package/dist/core/sanitize-schema.js.map +1 -0
  50. package/dist/core/syntax.d.ts +8 -0
  51. package/dist/core/syntax.js +32 -0
  52. package/dist/core/syntax.js.map +1 -0
  53. package/dist/dialect.d.ts +92 -0
  54. package/dist/dialect.js +176 -0
  55. package/dist/dialect.js.map +1 -0
  56. package/dist/document/fence.d.ts +25 -0
  57. package/dist/document/fence.js +102 -0
  58. package/dist/document/fence.js.map +1 -0
  59. package/dist/document/index.d.ts +46 -0
  60. package/dist/document/index.js +66 -0
  61. package/dist/document/index.js.map +1 -0
  62. package/dist/document/table.d.ts +11 -0
  63. package/dist/document/table.js +35 -0
  64. package/dist/document/table.js.map +1 -0
  65. package/dist/dom/index.d.ts +21 -0
  66. package/dist/dom/index.js +141 -0
  67. package/dist/dom/index.js.map +1 -0
  68. package/dist/dom/patch.js +104 -0
  69. package/dist/dom/patch.js.map +1 -0
  70. package/dist/element/index.d.ts +35 -0
  71. package/dist/element/index.js +64 -0
  72. package/dist/element/index.js.map +1 -0
  73. package/dist/engine/blocks.js +785 -0
  74. package/dist/engine/blocks.js.map +1 -0
  75. package/dist/engine/entities.js +83 -0
  76. package/dist/engine/entities.js.map +1 -0
  77. package/dist/engine/entity-core.js +9 -0
  78. package/dist/engine/entity-core.js.map +1 -0
  79. package/dist/engine/entity-table.js +9 -0
  80. package/dist/engine/entity-table.js.map +1 -0
  81. package/dist/engine/html.d.ts +7 -0
  82. package/dist/engine/html.js +207 -0
  83. package/dist/engine/html.js.map +1 -0
  84. package/dist/engine/index.d.ts +7 -0
  85. package/dist/engine/index.js +74 -0
  86. package/dist/engine/index.js.map +1 -0
  87. package/dist/engine/inlines.js +887 -0
  88. package/dist/engine/inlines.js.map +1 -0
  89. package/dist/engine/types.d.ts +109 -0
  90. package/dist/engine/url.d.ts +11 -0
  91. package/dist/engine/url.js +16 -0
  92. package/dist/engine/url.js.map +1 -0
  93. package/dist/engine/visit.d.ts +10 -0
  94. package/dist/engine/visit.js +29 -0
  95. package/dist/engine/visit.js.map +1 -0
  96. package/dist/highlight/highlight.d.ts +4 -0
  97. package/dist/highlight/highlight.js +144 -0
  98. package/dist/highlight/highlight.js.map +1 -0
  99. package/dist/highlight/index.d.ts +4 -0
  100. package/dist/highlight/index.js +18 -0
  101. package/dist/highlight/index.js.map +1 -0
  102. package/dist/html/index.d.ts +4 -0
  103. package/dist/html/index.js +544 -0
  104. package/dist/html/index.js.map +1 -0
  105. package/dist/index.d.ts +27 -0
  106. package/dist/index.js +13 -0
  107. package/dist/math/index.d.ts +17 -0
  108. package/dist/math/index.js +20 -0
  109. package/dist/math/index.js.map +1 -0
  110. package/dist/math/typeset.d.ts +12 -0
  111. package/dist/math/typeset.js +74 -0
  112. package/dist/math/typeset.js.map +1 -0
  113. package/dist/mermaid/diagram.d.ts +8 -0
  114. package/dist/mermaid/diagram.js +114 -0
  115. package/dist/mermaid/diagram.js.map +1 -0
  116. package/dist/mermaid/drawing.js +139 -0
  117. package/dist/mermaid/drawing.js.map +1 -0
  118. package/dist/mermaid/icons.js +16 -0
  119. package/dist/mermaid/icons.js.map +1 -0
  120. package/dist/mermaid/index.d.ts +13 -0
  121. package/dist/mermaid/index.js +3 -0
  122. package/dist/mermaid/loader.d.ts +5 -0
  123. package/dist/mermaid/loader.js +18 -0
  124. package/dist/mermaid/loader.js.map +1 -0
  125. package/dist/mermaid/look.d.ts +16 -0
  126. package/dist/mermaid/look.js +167 -0
  127. package/dist/mermaid/look.js.map +1 -0
  128. package/dist/mermaid/normalize-svg.js +44 -0
  129. package/dist/mermaid/normalize-svg.js.map +1 -0
  130. package/dist/mermaid/renderer.d.ts +22 -0
  131. package/dist/mermaid/renderer.js +132 -0
  132. package/dist/mermaid/renderer.js.map +1 -0
  133. package/dist/mermaid/viewer.js +309 -0
  134. package/dist/mermaid/viewer.js.map +1 -0
  135. package/dist/prompt/index.d.ts +19 -0
  136. package/dist/prompt/index.js +54 -0
  137. package/dist/prompt/index.js.map +1 -0
  138. package/dist/react/callout.d.ts +9 -0
  139. package/dist/react/callout.js +12 -0
  140. package/dist/react/callout.js.map +1 -0
  141. package/dist/react/code-block.d.ts +18 -0
  142. package/dist/react/code-block.js +94 -0
  143. package/dist/react/code-block.js.map +1 -0
  144. package/dist/react/context.d.ts +18 -0
  145. package/dist/react/context.js +17 -0
  146. package/dist/react/context.js.map +1 -0
  147. package/dist/react/draw.d.ts +2 -0
  148. package/dist/react/draw.js +17 -0
  149. package/dist/react/draw.js.map +1 -0
  150. package/dist/react/file-tree.d.ts +16 -0
  151. package/dist/react/file-tree.js +69 -0
  152. package/dist/react/file-tree.js.map +1 -0
  153. package/dist/react/growing-code.js +36 -0
  154. package/dist/react/growing-code.js.map +1 -0
  155. package/dist/react/helpers.d.ts +6 -0
  156. package/dist/react/helpers.js +17 -0
  157. package/dist/react/helpers.js.map +1 -0
  158. package/dist/react/icons.js +23 -0
  159. package/dist/react/icons.js.map +1 -0
  160. package/dist/react/labels.js +9 -0
  161. package/dist/react/labels.js.map +1 -0
  162. package/dist/react/markdown.d.ts +78 -0
  163. package/dist/react/markdown.js +206 -0
  164. package/dist/react/markdown.js.map +1 -0
  165. package/dist/react/render.d.ts +8 -0
  166. package/dist/react/render.js +41 -0
  167. package/dist/react/render.js.map +1 -0
  168. package/dist/react/scrolls.js +62 -0
  169. package/dist/react/scrolls.js.map +1 -0
  170. package/dist/react/table-rows.js +66 -0
  171. package/dist/react/table-rows.js.map +1 -0
  172. package/dist/react/table.js +22 -0
  173. package/dist/react/table.js.map +1 -0
  174. package/dist/react/tabs.d.ts +25 -0
  175. package/dist/react/tabs.js +58 -0
  176. package/dist/react/tabs.js.map +1 -0
  177. package/dist/react/use-smooth-text.d.ts +6 -0
  178. package/dist/react/use-smooth-text.js +157 -0
  179. package/dist/react/use-smooth-text.js.map +1 -0
  180. package/dist/react/use-stage.d.ts +9 -0
  181. package/dist/react/use-stage.js +57 -0
  182. package/dist/react/use-stage.js.map +1 -0
  183. package/dist/remark.d.ts +20 -0
  184. package/dist/remark.js +10 -0
  185. package/dist/render/factory.d.ts +15 -0
  186. package/dist/render/labels.d.ts +46 -0
  187. package/dist/render/labels.js +46 -0
  188. package/dist/render/labels.js.map +1 -0
  189. package/dist/render/tree.js +314 -0
  190. package/dist/render/tree.js.map +1 -0
  191. package/dist/render/view.d.ts +58 -0
  192. package/dist/render/view.js +306 -0
  193. package/dist/render/view.js.map +1 -0
  194. package/dist/render/views.d.ts +16 -0
  195. package/dist/render/views.js +261 -0
  196. package/dist/render/views.js.map +1 -0
  197. package/dist/render/vnode.d.ts +21 -0
  198. package/dist/render/vnode.js +194 -0
  199. package/dist/render/vnode.js.map +1 -0
  200. package/dist/static/index.d.ts +28 -0
  201. package/dist/static/index.js +27 -0
  202. package/dist/static/index.js.map +1 -0
  203. package/dist/stream.d.ts +12 -0
  204. package/dist/stream.js +3 -0
  205. package/dist/styles.css +1362 -0
  206. package/dist/tui/code.d.ts +5 -0
  207. package/dist/tui/code.js +240 -0
  208. package/dist/tui/code.js.map +1 -0
  209. package/dist/tui/index.d.ts +54 -0
  210. package/dist/tui/index.js +99 -0
  211. package/dist/tui/index.js.map +1 -0
  212. package/dist/tui/math.js +242 -0
  213. package/dist/tui/math.js.map +1 -0
  214. package/dist/tui/printable.d.ts +12 -0
  215. package/dist/tui/printable.js +31 -0
  216. package/dist/tui/printable.js.map +1 -0
  217. package/dist/tui/render.d.ts +12 -0
  218. package/dist/tui/render.js +524 -0
  219. package/dist/tui/render.js.map +1 -0
  220. package/dist/tui/style.d.ts +35 -0
  221. package/dist/tui/style.js +77 -0
  222. package/dist/tui/style.js.map +1 -0
  223. package/dist/tui/table.js +73 -0
  224. package/dist/tui/table.js.map +1 -0
  225. package/dist/tui/width.d.ts +2 -0
  226. package/dist/tui/width.js +133 -0
  227. package/dist/tui/width.js.map +1 -0
  228. package/dist/tui/wrap.d.ts +8 -0
  229. package/dist/tui/wrap.js +191 -0
  230. package/dist/tui/wrap.js.map +1 -0
  231. package/package.json +128 -4
@@ -0,0 +1,20 @@
1
+ import { MATH_INLINE, eachFormula } from "../core/math-syntax.js";
2
+ import { typesetMath } from "./typeset.js";
3
+ // src/math/index.ts
4
+ /** The math stage: what `<Markdown math>` takes. */
5
+ const math = {
6
+ name: "math",
7
+ mathFences: true,
8
+ inline: MATH_INLINE,
9
+ transform(root) {
10
+ eachFormula(root, (node, display) => {
11
+ node.data = {
12
+ ...node.data,
13
+ ...typesetMath(node.value ?? "", display)
14
+ };
15
+ });
16
+ }
17
+ };
18
+ export { math as default, math, typesetMath };
19
+
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/math/index.ts"],"sourcesContent":["/**\n * `@localix-ai/flowdown/math`: `$…$`, `$$…$$` and a ```` ```math ```` fence typeset with KaTeX.\n *\n * Its own entry so that only a page that imports it bundles KaTeX (a quarter of a megabyte). Pass\n * it to `<Markdown math={…}>` directly, or as a loader so it is fetched the first time a document\n * holds a formula:\n *\n * <Markdown math={() => import('@localix-ai/flowdown/math')}>\n *\n * KaTeX's stylesheet is the page's to add (`import 'katex/dist/katex.min.css'`): a bundler-only\n * CSS import inside a library breaks the hosts that do not run one.\n */\nimport type { Extension } from '../engine/types'\nimport { eachFormula, MATH_INLINE } from '../core/math-syntax'\nimport { typesetMath } from './typeset'\n\nexport { typesetMath, type Typeset } from './typeset'\n\n/** The math stage: what `<Markdown math>` takes. */\nexport const math: Extension = {\n name: 'math',\n mathFences: true,\n inline: MATH_INLINE,\n transform(root) {\n eachFormula(root, (node, display) => {\n node.data = { ...node.data, ...typesetMath(node.value ?? '', display) }\n })\n },\n}\n\nexport default math\n"],"mappings":";;;;AAmBA,MAAa,OAAkB;CAC7B,MAAM;CACN,YAAY;CACZ,QAAQ;CACR,UAAU,MAAM;EACd,YAAY,OAAO,MAAM,YAAY;GACnC,KAAK,OAAO;IAAE,GAAG,KAAK;IAAM,GAAG,YAAY,KAAK,SAAS,IAAI,OAAO;GAAE;EACxE,CAAC;CACH;AACF"}
@@ -0,0 +1,12 @@
1
+ /** What the renderer draws a formula from: the element KaTeX wraps it in, and the HTML inside. */
2
+ export interface Typeset {
3
+ hName: 'span';
4
+ hProperties: {
5
+ className: string;
6
+ innerHTML: string;
7
+ title?: string;
8
+ style?: Record<string, string>;
9
+ };
10
+ }
11
+ /** One expression typeset: from the cache when it was set before. */
12
+ export declare function typesetMath(source: string, displayMode: boolean): Typeset;
@@ -0,0 +1,74 @@
1
+ import { createBoundedCache } from "../core/bounded-cache.js";
2
+ import katex from "katex";
3
+ // src/math/typeset.ts
4
+ const cache = createBoundedCache({
5
+ maxEntries: 1e3,
6
+ maxWeight: 6e6,
7
+ weigh: (entry, key) => entry.hProperties.innerHTML.length + key.length
8
+ });
9
+ function renderMath(source, displayMode) {
10
+ try {
11
+ return katex.renderToString(source, {
12
+ displayMode,
13
+ throwOnError: true
14
+ });
15
+ } catch {
16
+ return katex.renderToString(source, {
17
+ displayMode,
18
+ throwOnError: false,
19
+ strict: "ignore"
20
+ });
21
+ }
22
+ }
23
+ const UNESCAPES = {
24
+ "&amp;": "&",
25
+ "&lt;": "<",
26
+ "&gt;": ">",
27
+ "&quot;": "\"",
28
+ "&#x27;": "'"
29
+ };
30
+ const unescape = (text) => text.replace(/&(?:amp|lt|gt|quot|#x27);/g, (entity) => UNESCAPES[entity]);
31
+ /** KaTeX answers with one `<span>`: its attributes go on the element, its inside is the HTML. */
32
+ const OUTER = /^<span((?: [a-z-]+="[^"]*")*)>([\s\S]*)<\/span>$/;
33
+ function split(html) {
34
+ const outer = OUTER.exec(html);
35
+ if (!outer) return {
36
+ hName: "span",
37
+ hProperties: {
38
+ className: "katex",
39
+ innerHTML: html
40
+ }
41
+ };
42
+ const properties = {
43
+ className: "",
44
+ innerHTML: outer[2]
45
+ };
46
+ for (const [, name, value] of outer[1].matchAll(/ ([a-z-]+)="([^"]*)"/g)) if (name === "class") properties.className = value;
47
+ else if (name === "title") properties.title = unescape(value);
48
+ else if (name === "style") {
49
+ const style = {};
50
+ for (const rule of unescape(value).split(";")) {
51
+ const colon = rule.indexOf(":");
52
+ if (colon === -1) continue;
53
+ const property = rule.slice(0, colon).trim().replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
54
+ style[property] = rule.slice(colon + 1).trim();
55
+ }
56
+ properties.style = style;
57
+ }
58
+ return {
59
+ hName: "span",
60
+ hProperties: properties
61
+ };
62
+ }
63
+ /** One expression typeset: from the cache when it was set before. */
64
+ function typesetMath(source, displayMode) {
65
+ const key = `${displayMode ? "display" : "inline"}\0${source}`;
66
+ const hit = cache.get(key);
67
+ if (hit) return hit;
68
+ const typeset = split(renderMath(source, displayMode));
69
+ cache.set(key, typeset);
70
+ return typeset;
71
+ }
72
+ export { typesetMath };
73
+
74
+ //# sourceMappingURL=typeset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeset.js","names":[],"sources":["../../src/math/typeset.ts"],"sourcesContent":["// A formula typeset by KaTeX, remembered by its source.\n//\n// A streaming reply is read again on every token, and typesetting is the expensive part of a\n// formula: thirty of them cost about 12 ms a pass against half a millisecond for the rest of the\n// Markdown. A finished formula never changes, so what KaTeX made of it is kept and reused: only\n// an expression not seen before pays KaTeX.\nimport katex from 'katex'\nimport { createBoundedCache } from '../core/bounded-cache'\n\n/** What the renderer draws a formula from: the element KaTeX wraps it in, and the HTML inside. */\nexport interface Typeset {\n hName: 'span'\n hProperties: {\n className: string\n innerHTML: string\n title?: string\n style?: Record<string, string>\n }\n}\n\nconst cache = createBoundedCache<string, Typeset>({\n maxEntries: 1_000,\n // Characters of KaTeX HTML: a formula is a few KB, so this holds a long math-heavy session.\n maxWeight: 6_000_000,\n weigh: (entry, key) => entry.hProperties.innerHTML.length + key.length,\n})\n\nfunction renderMath(source: string, displayMode: boolean): string {\n try {\n return katex.renderToString(source, { displayMode, throwOnError: true })\n } catch {\n // KaTeX's own fallback: the source, marked as an error, instead of a thrown render.\n return katex.renderToString(source, { displayMode, throwOnError: false, strict: 'ignore' })\n }\n}\n\nconst UNESCAPES: Record<string, string> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&#x27;': \"'\",\n}\nconst unescape = (text: string): string =>\n text.replace(/&(?:amp|lt|gt|quot|#x27);/g, (entity) => UNESCAPES[entity] as string)\n\n/** KaTeX answers with one `<span>`: its attributes go on the element, its inside is the HTML. */\nconst OUTER = /^<span((?: [a-z-]+=\"[^\"]*\")*)>([\\s\\S]*)<\\/span>$/\n\nfunction split(html: string): Typeset {\n const outer = OUTER.exec(html)\n if (!outer) return { hName: 'span', hProperties: { className: 'katex', innerHTML: html } }\n const properties: Typeset['hProperties'] = { className: '', innerHTML: outer[2] as string }\n for (const [, name, value] of (outer[1] as string).matchAll(/ ([a-z-]+)=\"([^\"]*)\"/g)) {\n if (name === 'class') properties.className = value as string\n else if (name === 'title') properties.title = unescape(value as string)\n else if (name === 'style') {\n const style: Record<string, string> = {}\n for (const rule of unescape(value as string).split(';')) {\n const colon = rule.indexOf(':')\n if (colon === -1) continue\n const property = rule\n .slice(0, colon)\n .trim()\n .replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())\n style[property] = rule.slice(colon + 1).trim()\n }\n properties.style = style\n }\n }\n return { hName: 'span', hProperties: properties }\n}\n\n/** One expression typeset: from the cache when it was set before. */\nexport function typesetMath(source: string, displayMode: boolean): Typeset {\n const key = `${displayMode ? 'display' : 'inline'}\\0${source}`\n const hit = cache.get(key)\n if (hit) return hit\n const typeset = split(renderMath(source, displayMode))\n cache.set(key, typeset)\n return typeset\n}\n"],"mappings":";;;AAoBA,MAAM,QAAQ,mBAAoC;CAChD,YAAY;CAEZ,WAAW;CACX,QAAQ,OAAO,QAAQ,MAAM,YAAY,UAAU,SAAS,IAAI;AAClE,CAAC;AAED,SAAS,WAAW,QAAgB,aAA8B;CAChE,IAAI;EACF,OAAO,MAAM,eAAe,QAAQ;GAAE;GAAa,cAAc;EAAK,CAAC;CACzE,QAAQ;EAEN,OAAO,MAAM,eAAe,QAAQ;GAAE;GAAa,cAAc;GAAO,QAAQ;EAAS,CAAC;CAC5F;AACF;AAEA,MAAM,YAAoC;CACxC,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,UAAU;AACZ;AACA,MAAM,YAAY,SAChB,KAAK,QAAQ,+BAA+B,WAAW,UAAU,OAAiB;;AAGpF,MAAM,QAAQ;AAEd,SAAS,MAAM,MAAuB;CACpC,MAAM,QAAQ,MAAM,KAAK,IAAI;CAC7B,IAAI,CAAC,OAAO,OAAO;EAAE,OAAO;EAAQ,aAAa;GAAE,WAAW;GAAS,WAAW;EAAK;CAAE;CACzF,MAAM,aAAqC;EAAE,WAAW;EAAI,WAAW,MAAM;CAAa;CAC1F,KAAK,MAAM,GAAG,MAAM,UAAW,MAAM,EAAE,CAAY,SAAS,uBAAuB,GACjF,IAAI,SAAS,SAAS,WAAW,YAAY;MACxC,IAAI,SAAS,SAAS,WAAW,QAAQ,SAAS,KAAe;MACjE,IAAI,SAAS,SAAS;EACzB,MAAM,QAAgC,CAAC;EACvC,KAAK,MAAM,QAAQ,SAAS,KAAe,CAAC,CAAC,MAAM,GAAG,GAAG;GACvD,MAAM,QAAQ,KAAK,QAAQ,GAAG;GAC9B,IAAI,UAAU,IAAI;GAClB,MAAM,WAAW,KACd,MAAM,GAAG,KAAK,CAAC,CACf,KAAK,CAAC,CACN,QAAQ,cAAc,GAAG,WAAmB,OAAO,YAAY,CAAC;GACnE,MAAM,YAAY,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;EAC/C;EACA,WAAW,QAAQ;CACrB;CAEF,OAAO;EAAE,OAAO;EAAQ,aAAa;CAAW;AAClD;;AAGA,SAAgB,YAAY,QAAgB,aAA+B;CACzE,MAAM,MAAM,GAAG,cAAc,YAAY,SAAS,IAAI;CACtD,MAAM,MAAM,MAAM,IAAI,GAAG;CACzB,IAAI,KAAK,OAAO;CAChB,MAAM,UAAU,MAAM,WAAW,QAAQ,WAAW,CAAC;CACrD,MAAM,IAAI,KAAK,OAAO;CACtB,OAAO;AACT"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A ```mermaid fence. Drawn when the page gave `<Markdown>` a Mermaid loader, in the colours of the
3
+ * Markdown around it; until then, without one, while its fence is still being written, or when the
4
+ * source does not parse, it is shown as the source it is, with a line saying why.
5
+ */
6
+ export declare function Diagram({ source }: {
7
+ source: string;
8
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | Iterable<import("react").ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { GrowingFence, useRenderContext } from "../react/context.js";
3
+ import { useLabels } from "../react/labels.js";
4
+ import { react } from "../react/render.js";
5
+ import { diagramSource } from "../render/views.js";
6
+ import { loadMermaid } from "./loader.js";
7
+ import { useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ // src/mermaid/diagram.tsx
10
+ let drawing = null;
11
+ const loadDrawing = () => import("./drawing.js").then((module) => {
12
+ drawing = module;
13
+ return module;
14
+ });
15
+ /**
16
+ * A counter that moves whenever the page may have changed its colours: the system scheme, or a
17
+ * theme class or attribute on the root element. A diagram is drawn in resolved colours (`look.ts`),
18
+ * so it has to be drawn again when they change; the renderer's cache makes a redraw in a look it
19
+ * has seen free.
20
+ */
21
+ let schemeVersion = 0;
22
+ function subscribeScheme(notify) {
23
+ const bump = () => {
24
+ schemeVersion += 1;
25
+ notify();
26
+ };
27
+ const media = window.matchMedia?.("(prefers-color-scheme: dark)");
28
+ media?.addEventListener("change", bump);
29
+ const observer = new MutationObserver(bump);
30
+ observer.observe(document.documentElement, {
31
+ attributes: true,
32
+ attributeFilter: [
33
+ "class",
34
+ "data-theme",
35
+ "style"
36
+ ]
37
+ });
38
+ return () => {
39
+ media?.removeEventListener("change", bump);
40
+ observer.disconnect();
41
+ };
42
+ }
43
+ const useSchemeVersion = () => useSyncExternalStore(subscribeScheme, () => schemeVersion, () => 0);
44
+ /**
45
+ * A ```mermaid fence. Drawn when the page gave `<Markdown>` a Mermaid loader, in the colours of the
46
+ * Markdown around it; until then, without one, while its fence is still being written, or when the
47
+ * source does not parse, it is shown as the source it is, with a line saying why.
48
+ */
49
+ function Diagram({ source }) {
50
+ const labels = useLabels();
51
+ const { mermaid } = useRenderContext();
52
+ const growing = useContext(GrowingFence);
53
+ const scheme = useSchemeVersion();
54
+ const figure = useRef(null);
55
+ const [state, setState] = useState(null);
56
+ useEffect(() => {
57
+ const root = figure.current;
58
+ if (!mermaid || growing || !root) return;
59
+ const controller = new AbortController();
60
+ const failed = (error) => {
61
+ if (controller.signal.aborted) return;
62
+ const reason = error instanceof Error ? error.message : String(error);
63
+ setState({
64
+ source,
65
+ error: reason.split("\n")[0] || reason
66
+ });
67
+ };
68
+ const draw = ({ resolveMermaidLook, pageMermaidRenderer }) => {
69
+ if (controller.signal.aborted) return;
70
+ const renderer = mermaid === "page" ? pageMermaidRenderer() : mermaid;
71
+ const look = resolveMermaidLook(root);
72
+ const ready = renderer.peek(source, look);
73
+ if (ready) {
74
+ setState({
75
+ source,
76
+ svg: ready
77
+ });
78
+ return;
79
+ }
80
+ renderer.render(source, look, controller.signal).then((svg) => {
81
+ if (!controller.signal.aborted) setState({
82
+ source,
83
+ svg
84
+ });
85
+ }, failed);
86
+ };
87
+ if (drawing) draw(drawing);
88
+ else {
89
+ if (mermaid === "page") loadMermaid().catch(() => {});
90
+ loadDrawing().then(draw, failed);
91
+ }
92
+ return () => controller.abort();
93
+ }, [
94
+ mermaid,
95
+ growing,
96
+ source,
97
+ scheme
98
+ ]);
99
+ const drawn = state?.source === source ? state : null;
100
+ if (drawn?.svg && drawing) {
101
+ const { DrawnDiagram } = drawing;
102
+ return /* @__PURE__ */ jsx(DrawnDiagram, {
103
+ figure,
104
+ svg: drawn.svg,
105
+ source,
106
+ toolbar: !growing
107
+ });
108
+ }
109
+ const note = drawn?.error ? labels.diagramFailed(drawn.error) : mermaid ? labels.drawingDiagram : null;
110
+ return diagramSource(react, source, note, Boolean(drawn?.error), { ref: figure });
111
+ }
112
+ export { Diagram };
113
+
114
+ //# sourceMappingURL=diagram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagram.js","names":[],"sources":["../../src/mermaid/diagram.tsx"],"sourcesContent":["'use client'\nimport { useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport { GrowingFence, useRenderContext } from '../react/context'\nimport { useLabels } from '../react/labels'\nimport { react } from '../react/render'\nimport { diagramSource } from '../render/views'\nimport type * as DrawingModule from './drawing'\nimport { loadMermaid } from './loader'\n\n/**\n * What draws a diagram (its toolbar, its colours, the renderer) is a chunk of its own: a reply\n * with no diagram, or a page that gave no Mermaid loader, never loads it. Once here it is used at\n * once, so a diagram drawn before (a row scrolled back into a virtualized chat) does not flash.\n */\ntype Drawing = typeof DrawingModule\nlet drawing: Drawing | null = null\nconst loadDrawing = () =>\n import('./drawing').then((module) => {\n drawing = module\n return module\n })\n\n/**\n * A counter that moves whenever the page may have changed its colours: the system scheme, or a\n * theme class or attribute on the root element. A diagram is drawn in resolved colours (`look.ts`),\n * so it has to be drawn again when they change; the renderer's cache makes a redraw in a look it\n * has seen free.\n */\nlet schemeVersion = 0\nfunction subscribeScheme(notify: () => void) {\n const bump = () => {\n schemeVersion += 1\n notify()\n }\n const media = window.matchMedia?.('(prefers-color-scheme: dark)')\n media?.addEventListener('change', bump)\n const observer = new MutationObserver(bump)\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'data-theme', 'style'],\n })\n return () => {\n media?.removeEventListener('change', bump)\n observer.disconnect()\n }\n}\nconst useSchemeVersion = () =>\n useSyncExternalStore(\n subscribeScheme,\n () => schemeVersion,\n () => 0,\n )\n\ntype DrawState = { source: string; svg?: string; error?: string }\n\n/**\n * A ```mermaid fence. Drawn when the page gave `<Markdown>` a Mermaid loader, in the colours of the\n * Markdown around it; until then, without one, while its fence is still being written, or when the\n * source does not parse, it is shown as the source it is, with a line saying why.\n */\nexport function Diagram({ source }: { source: string }) {\n const labels = useLabels()\n const { mermaid } = useRenderContext()\n const growing = useContext(GrowingFence)\n const scheme = useSchemeVersion()\n const figure = useRef<HTMLElement>(null)\n const [state, setState] = useState<DrawState | null>(null)\n\n useEffect(() => {\n const root = figure.current\n if (!mermaid || growing || !root) return\n const controller = new AbortController()\n const failed = (error: unknown) => {\n if (controller.signal.aborted) return\n const reason = error instanceof Error ? error.message : String(error)\n setState({ source, error: reason.split('\\n')[0] || reason })\n }\n const draw = ({ resolveMermaidLook, pageMermaidRenderer }: Drawing) => {\n if (controller.signal.aborted) return\n const renderer = mermaid === 'page' ? pageMermaidRenderer() : mermaid\n const look = resolveMermaidLook(root)\n const ready = renderer.peek(source, look)\n if (ready) {\n setState({ source, svg: ready })\n return\n }\n renderer.render(source, look, controller.signal).then((svg) => {\n if (!controller.signal.aborted) setState({ source, svg })\n }, failed)\n }\n if (drawing) draw(drawing)\n else {\n // The engine is the long fetch: it starts now, beside the chunk that will ask for it.\n if (mermaid === 'page') loadMermaid().catch(() => {})\n loadDrawing().then(draw, failed)\n }\n return () => controller.abort()\n // `scheme` is not read here: it changes when the page's colours do, and the diagram is drawn\n // again in the new ones.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [mermaid, growing, source, scheme])\n\n const drawn = state?.source === source ? state : null\n if (drawn?.svg && drawing) {\n const { DrawnDiagram } = drawing\n return <DrawnDiagram figure={figure} svg={drawn.svg} source={source} toolbar={!growing} />\n }\n const note = drawn?.error\n ? labels.diagramFailed(drawn.error)\n : mermaid\n ? labels.drawingDiagram\n : null\n return diagramSource(react, source, note, Boolean(drawn?.error), { ref: figure })\n}\n"],"mappings":";;;;;;;;;AAeA,IAAI,UAA0B;AAC9B,MAAM,oBACJ,OAAO,eAAY,CAAC,MAAM,WAAW;CACnC,UAAU;CACV,OAAO;AACT,CAAC;;;;;;;AAQH,IAAI,gBAAgB;AACpB,SAAS,gBAAgB,QAAoB;CAC3C,MAAM,aAAa;EACjB,iBAAiB;EACjB,OAAO;CACT;CACA,MAAM,QAAQ,OAAO,aAAa,8BAA8B;CAChE,OAAO,iBAAiB,UAAU,IAAI;CACtC,MAAM,WAAW,IAAI,iBAAiB,IAAI;CAC1C,SAAS,QAAQ,SAAS,iBAAiB;EACzC,YAAY;EACZ,iBAAiB;GAAC;GAAS;GAAc;EAAO;CAClD,CAAC;CACD,aAAa;EACX,OAAO,oBAAoB,UAAU,IAAI;EACzC,SAAS,WAAW;CACtB;AACF;AACA,MAAM,yBACJ,qBACE,uBACM,qBACA,CACR;;;;;;AASF,SAAgB,QAAQ,EAAE,UAA8B;CACtD,MAAM,SAAS,UAAU;CACzB,MAAM,EAAE,YAAY,iBAAiB;CACrC,MAAM,UAAU,WAAW,YAAY;CACvC,MAAM,SAAS,iBAAiB;CAChC,MAAM,SAAS,OAAoB,IAAI;CACvC,MAAM,CAAC,OAAO,YAAY,SAA2B,IAAI;CAEzD,gBAAgB;EACd,MAAM,OAAO,OAAO;EACpB,IAAI,CAAC,WAAW,WAAW,CAAC,MAAM;EAClC,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,UAAU,UAAmB;GACjC,IAAI,WAAW,OAAO,SAAS;GAC/B,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACpE,SAAS;IAAE;IAAQ,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,MAAM;GAAO,CAAC;EAC7D;EACA,MAAM,QAAQ,EAAE,oBAAoB,0BAAmC;GACrE,IAAI,WAAW,OAAO,SAAS;GAC/B,MAAM,WAAW,YAAY,SAAS,oBAAoB,IAAI;GAC9D,MAAM,OAAO,mBAAmB,IAAI;GACpC,MAAM,QAAQ,SAAS,KAAK,QAAQ,IAAI;GACxC,IAAI,OAAO;IACT,SAAS;KAAE;KAAQ,KAAK;IAAM,CAAC;IAC/B;GACF;GACA,SAAS,OAAO,QAAQ,MAAM,WAAW,MAAM,CAAC,CAAC,MAAM,QAAQ;IAC7D,IAAI,CAAC,WAAW,OAAO,SAAS,SAAS;KAAE;KAAQ;IAAI,CAAC;GAC1D,GAAG,MAAM;EACX;EACA,IAAI,SAAS,KAAK,OAAO;OACpB;GAEH,IAAI,YAAY,QAAQ,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC;GACpD,YAAY,CAAC,CAAC,KAAK,MAAM,MAAM;EACjC;EACA,aAAa,WAAW,MAAM;CAIhC,GAAG;EAAC;EAAS;EAAS;EAAQ;CAAM,CAAC;CAErC,MAAM,QAAQ,OAAO,WAAW,SAAS,QAAQ;CACjD,IAAI,OAAO,OAAO,SAAS;EACzB,MAAM,EAAE,iBAAiB;EACzB,OAAO,oBAAC,cAAD;GAAsB;GAAQ,KAAK,MAAM;GAAa;GAAQ,SAAS,CAAC;EAAU,CAAA;CAC3F;CACA,MAAM,OAAO,OAAO,QAChB,OAAO,cAAc,MAAM,KAAK,IAChC,UACE,OAAO,iBACP;CACN,OAAO,cAAc,OAAO,QAAQ,MAAM,QAAQ,OAAO,KAAK,GAAG,EAAE,KAAK,OAAO,CAAC;AAClF"}
@@ -0,0 +1,139 @@
1
+ "use client";
2
+ import { copyText, downloadFile } from "../browser/file-actions.js";
3
+ import { CheckIcon, CopyIcon, DownloadIcon } from "../react/icons.js";
4
+ import { useLabels } from "../react/labels.js";
5
+ import { DiagramIcon, EnlargeIcon, SourceIcon } from "./icons.js";
6
+ import { resolveMermaidLook } from "./look.js";
7
+ import { pageMermaidRenderer } from "./renderer.js";
8
+ import { Suspense, lazy, useEffect, useRef, useState } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ // src/mermaid/drawing.tsx
11
+ /**
12
+ * The drawn half of a diagram: its toolbar, the colours it is drawn in and the page's renderer.
13
+ * A chunk of its own, fetched with the Mermaid engine the first time a page draws a diagram: a
14
+ * page whose replies hold none never loads it.
15
+ */
16
+ /**
17
+ * The enlarged viewer is its own chunk: most diagrams are never enlarged, so its pan and zoom
18
+ * load on the first Enlarge (or when the pointer reaches the button), not with the renderer.
19
+ */
20
+ const loadViewer = () => import("./viewer.js");
21
+ const DiagramViewer = lazy(() => loadViewer().then((module) => ({ default: module.DiagramViewer })));
22
+ /**
23
+ * The drawn `<svg>` as a standalone file: serialized from the page, so it is what the reader sees.
24
+ * The page sizes it by CSS; the file gets its own size from its `viewBox`.
25
+ */
26
+ function serializeSvg(svg) {
27
+ const copy = svg.cloneNode(true);
28
+ const box = copy.getAttribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
29
+ if (box && box.length === 4 && box.every(Number.isFinite)) {
30
+ copy.setAttribute("width", String(box[2]));
31
+ copy.setAttribute("height", String(box[3]));
32
+ copy.style.removeProperty("max-width");
33
+ }
34
+ const text = new XMLSerializer().serializeToString(copy);
35
+ return text.includes("xmlns=") ? text : text.replace("<svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"");
36
+ }
37
+ /** How long "Copied" stays before the button reads "Copy source" again. */
38
+ const COPIED_MS = 1600;
39
+ /**
40
+ * A drawn diagram and its toolbar: enlarge it, swap it for its source and back, copy the source,
41
+ * save the picture. The toolbar shows on hover and on focus, and always under a finger.
42
+ */
43
+ function DrawnDiagram({ figure, svg, source, toolbar }) {
44
+ const labels = useLabels();
45
+ const canvas = useRef(null);
46
+ const enlargeButton = useRef(null);
47
+ const [showSource, setShowSource] = useState(false);
48
+ const [enlarged, setEnlarged] = useState(false);
49
+ const [copied, setCopied] = useState(false);
50
+ const timer = useRef(void 0);
51
+ useEffect(() => () => clearTimeout(timer.current), []);
52
+ const copy = () => {
53
+ copyText(source).then((done) => {
54
+ if (!done) return;
55
+ setCopied(true);
56
+ clearTimeout(timer.current);
57
+ timer.current = setTimeout(() => setCopied(false), COPIED_MS);
58
+ });
59
+ };
60
+ const download = () => {
61
+ const drawn = canvas.current?.querySelector("svg");
62
+ downloadFile(drawn ? serializeSvg(drawn) : svg, "diagram.svg", "image/svg+xml");
63
+ };
64
+ const close = () => {
65
+ setEnlarged(false);
66
+ enlargeButton.current?.focus();
67
+ };
68
+ const Copied = copied ? CheckIcon : CopyIcon;
69
+ const Swap = showSource ? DiagramIcon : SourceIcon;
70
+ const swapLabel = showSource ? labels.showDiagram : labels.showSource;
71
+ return /* @__PURE__ */ jsxs("figure", {
72
+ ref: figure,
73
+ "aria-label": labels.diagram,
74
+ className: "fd-diagram",
75
+ "data-view": showSource ? "source" : void 0,
76
+ children: [
77
+ toolbar && /* @__PURE__ */ jsxs("div", {
78
+ className: "fd-diagram-actions",
79
+ children: [
80
+ /* @__PURE__ */ jsx("button", {
81
+ ref: enlargeButton,
82
+ type: "button",
83
+ className: "fd-diagram-action",
84
+ "aria-label": labels.enlarge,
85
+ title: labels.enlarge,
86
+ "aria-haspopup": "dialog",
87
+ onClick: () => setEnlarged(true),
88
+ onPointerEnter: () => void loadViewer(),
89
+ onFocus: () => void loadViewer(),
90
+ children: /* @__PURE__ */ jsx(EnlargeIcon, {})
91
+ }),
92
+ /* @__PURE__ */ jsx("button", {
93
+ type: "button",
94
+ className: "fd-diagram-action",
95
+ "aria-label": swapLabel,
96
+ title: swapLabel,
97
+ onClick: () => setShowSource((shown) => !shown),
98
+ children: /* @__PURE__ */ jsx(Swap, {})
99
+ }),
100
+ /* @__PURE__ */ jsx("button", {
101
+ type: "button",
102
+ className: "fd-diagram-action",
103
+ "aria-label": copied ? labels.copied : labels.copySource,
104
+ title: copied ? labels.copied : labels.copySource,
105
+ "data-copied": copied || void 0,
106
+ onClick: copy,
107
+ children: /* @__PURE__ */ jsx(Copied, {})
108
+ }),
109
+ /* @__PURE__ */ jsx("button", {
110
+ type: "button",
111
+ className: "fd-diagram-action",
112
+ "aria-label": labels.downloadSvg,
113
+ title: labels.downloadSvg,
114
+ onClick: download,
115
+ children: /* @__PURE__ */ jsx(DownloadIcon, {})
116
+ })
117
+ ]
118
+ }),
119
+ showSource ? /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", {
120
+ className: "language-mermaid",
121
+ children: source
122
+ }) }) : /* @__PURE__ */ jsx("div", {
123
+ ref: canvas,
124
+ className: "fd-diagram-canvas",
125
+ dangerouslySetInnerHTML: { __html: svg }
126
+ }),
127
+ enlarged && /* @__PURE__ */ jsx(Suspense, {
128
+ fallback: null,
129
+ children: /* @__PURE__ */ jsx(DiagramViewer, {
130
+ svg,
131
+ onClose: close
132
+ })
133
+ })
134
+ ]
135
+ });
136
+ }
137
+ export { DrawnDiagram, pageMermaidRenderer, resolveMermaidLook };
138
+
139
+ //# sourceMappingURL=drawing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawing.js","names":[],"sources":["../../src/mermaid/drawing.tsx"],"sourcesContent":["'use client'\n/**\n * The drawn half of a diagram: its toolbar, the colours it is drawn in and the page's renderer.\n * A chunk of its own, fetched with the Mermaid engine the first time a page draws a diagram: a\n * page whose replies hold none never loads it.\n */\nimport { lazy, Suspense, useEffect, useRef, useState, type Ref } from 'react'\nimport { copyText, downloadFile } from '../browser/file-actions'\nimport { CheckIcon, CopyIcon, DownloadIcon } from '../react/icons'\nimport { useLabels } from '../react/labels'\nimport { DiagramIcon, EnlargeIcon, SourceIcon } from './icons'\n\nexport { resolveMermaidLook } from './look'\nexport { pageMermaidRenderer } from './renderer'\n\n/**\n * The enlarged viewer is its own chunk: most diagrams are never enlarged, so its pan and zoom\n * load on the first Enlarge (or when the pointer reaches the button), not with the renderer.\n */\nconst loadViewer = () => import('./viewer')\nconst DiagramViewer = lazy(() => loadViewer().then((module) => ({ default: module.DiagramViewer })))\n\n/**\n * The drawn `<svg>` as a standalone file: serialized from the page, so it is what the reader sees.\n * The page sizes it by CSS; the file gets its own size from its `viewBox`.\n */\nfunction serializeSvg(svg: SVGSVGElement): string {\n const copy = svg.cloneNode(true) as SVGSVGElement\n const box = copy\n .getAttribute('viewBox')\n ?.trim()\n .split(/[\\s,]+/)\n .map(Number)\n if (box && box.length === 4 && box.every(Number.isFinite)) {\n copy.setAttribute('width', String(box[2]))\n copy.setAttribute('height', String(box[3]))\n copy.style.removeProperty('max-width')\n }\n const text = new XMLSerializer().serializeToString(copy)\n return text.includes('xmlns=')\n ? text\n : text.replace('<svg', '<svg xmlns=\"http://www.w3.org/2000/svg\"')\n}\n\n/** How long \"Copied\" stays before the button reads \"Copy source\" again. */\nconst COPIED_MS = 1_600\n\n/**\n * A drawn diagram and its toolbar: enlarge it, swap it for its source and back, copy the source,\n * save the picture. The toolbar shows on hover and on focus, and always under a finger.\n */\nexport function DrawnDiagram({\n figure,\n svg,\n source,\n toolbar,\n}: {\n /** Not `ref`: React 18 does not pass one to a function component. */\n figure: Ref<HTMLElement>\n svg: string\n source: string\n toolbar: boolean\n}) {\n const labels = useLabels()\n const canvas = useRef<HTMLDivElement>(null)\n const enlargeButton = useRef<HTMLButtonElement>(null)\n const [showSource, setShowSource] = useState(false)\n const [enlarged, setEnlarged] = useState(false)\n const [copied, setCopied] = useState(false)\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined)\n useEffect(() => () => clearTimeout(timer.current), [])\n\n const copy = () => {\n void copyText(source).then((done) => {\n if (!done) return\n setCopied(true)\n clearTimeout(timer.current)\n timer.current = setTimeout(() => setCopied(false), COPIED_MS)\n })\n }\n const download = () => {\n // The picture on the page when it is shown (what the reader sees), else the one drawn.\n const drawn = canvas.current?.querySelector('svg')\n downloadFile(drawn ? serializeSvg(drawn) : svg, 'diagram.svg', 'image/svg+xml')\n }\n const close = () => {\n setEnlarged(false)\n enlargeButton.current?.focus()\n }\n const Copied = copied ? CheckIcon : CopyIcon\n const Swap = showSource ? DiagramIcon : SourceIcon\n const swapLabel = showSource ? labels.showDiagram : labels.showSource\n\n return (\n <figure\n ref={figure}\n aria-label={labels.diagram}\n className=\"fd-diagram\"\n data-view={showSource ? 'source' : undefined}\n >\n {toolbar && (\n <div className=\"fd-diagram-actions\">\n <button\n ref={enlargeButton}\n type=\"button\"\n className=\"fd-diagram-action\"\n aria-label={labels.enlarge}\n title={labels.enlarge}\n aria-haspopup=\"dialog\"\n onClick={() => setEnlarged(true)}\n onPointerEnter={() => void loadViewer()}\n onFocus={() => void loadViewer()}\n >\n <EnlargeIcon />\n </button>\n <button\n type=\"button\"\n className=\"fd-diagram-action\"\n aria-label={swapLabel}\n title={swapLabel}\n onClick={() => setShowSource((shown) => !shown)}\n >\n <Swap />\n </button>\n <button\n type=\"button\"\n className=\"fd-diagram-action\"\n aria-label={copied ? labels.copied : labels.copySource}\n title={copied ? labels.copied : labels.copySource}\n data-copied={copied || undefined}\n onClick={copy}\n >\n <Copied />\n </button>\n <button\n type=\"button\"\n className=\"fd-diagram-action\"\n aria-label={labels.downloadSvg}\n title={labels.downloadSvg}\n onClick={download}\n >\n <DownloadIcon />\n </button>\n </div>\n )}\n {showSource ? (\n <pre>\n <code className=\"language-mermaid\">{source}</code>\n </pre>\n ) : (\n <div ref={canvas} className=\"fd-diagram-canvas\" dangerouslySetInnerHTML={{ __html: svg }} />\n )}\n {enlarged && (\n <Suspense fallback={null}>\n <DiagramViewer svg={svg} onClose={close} />\n </Suspense>\n )}\n </figure>\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { icon } from "../react/icons.js";
2
+ // src/mermaid/icons.tsx
3
+ /**
4
+ * The diagram's own glyphs, drawn like the renderer's others (`react/icons.tsx`): one path each.
5
+ * Outlines from Lucide (https://lucide.dev), ISC License, Copyright (c) Lucide Contributors.
6
+ */
7
+ const EnlargeIcon = icon("M15 3h6v6M21 3l-7 7M3 21l7-7M9 21H3v-6");
8
+ const SourceIcon = icon("m18 16 4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16");
9
+ const DiagramIcon = icon("M5 3h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM7 11v4a2 2 0 0 0 2 2h4M15 13h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2z");
10
+ const PlusIcon = icon("M5 12h14M12 5v14");
11
+ const MinusIcon = icon("M5 12h14");
12
+ const FitIcon = icon("M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2");
13
+ const CloseIcon = icon("M18 6 6 18M6 6l12 12");
14
+ export { CloseIcon, DiagramIcon, EnlargeIcon, FitIcon, MinusIcon, PlusIcon, SourceIcon };
15
+
16
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","names":[],"sources":["../../src/mermaid/icons.tsx"],"sourcesContent":["import { icon } from '../react/icons'\n\n/**\n * The diagram's own glyphs, drawn like the renderer's others (`react/icons.tsx`): one path each.\n * Outlines from Lucide (https://lucide.dev), ISC License, Copyright (c) Lucide Contributors.\n */\nexport const EnlargeIcon = icon('M15 3h6v6M21 3l-7 7M3 21l7-7M9 21H3v-6')\n\nexport const SourceIcon = icon('m18 16 4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16')\n\nexport const DiagramIcon = icon(\n 'M5 3h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM7 11v4a2 2 0 0 0 2 2h4M15 13h4a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2z',\n)\n\nexport const PlusIcon = icon('M5 12h14M12 5v14')\n\nexport const MinusIcon = icon('M5 12h14')\n\nexport const FitIcon = icon(\n 'M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2',\n)\n\nexport const CloseIcon = icon('M18 6 6 18M6 6l12 12')\n"],"mappings":";;;;;;AAMA,MAAa,cAAc,KAAK,wCAAwC;AAExE,MAAa,aAAa,KAAK,0CAA0C;AAEzE,MAAa,cAAc,KACzB,uKACF;AAEA,MAAa,WAAW,KAAK,kBAAkB;AAE/C,MAAa,YAAY,KAAK,UAAU;AAExC,MAAa,UAAU,KACrB,gGACF;AAEA,MAAa,YAAY,KAAK,sBAAsB"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@localix-ai/flowdown/mermaid`: what a page that draws ```mermaid fences itself needs, with
3
+ * `components.diagram`: the page's one queue and cache of drawn diagrams, and the colours of
4
+ * the tokens around an element.
5
+ *
6
+ * Mermaid is one global engine, so every diagram on a page goes through `mermaidRendererFor`:
7
+ * the same renderer `<Markdown mermaid>` draws with, which lays out one diagram at a time and
8
+ * keeps what it drew. The engine itself is the loader's: nothing here imports it.
9
+ */
10
+ import { MermaidEngine, MermaidRenderer, mermaidRendererFor } from "./renderer.js";
11
+ import { MermaidLook, resolveMermaidLook } from "./look.js";
12
+ import { MermaidLoader } from "./loader.js";
13
+ export { type MermaidEngine, type MermaidLoader, type MermaidLook, type MermaidRenderer, mermaidRendererFor, resolveMermaidLook };
@@ -0,0 +1,3 @@
1
+ import { mermaidRendererFor } from "./renderer.js";
2
+ import { resolveMermaidLook } from "./look.js";
3
+ export { mermaidRendererFor, resolveMermaidLook };
@@ -0,0 +1,5 @@
1
+ import { MermaidEngine } from "./renderer.js";
2
+ /** What `<Markdown mermaid={…}>` takes: a loader for the engine, `() => import('mermaid')`. */
3
+ export type MermaidLoader = () => Promise<MermaidEngine | {
4
+ default: MermaidEngine;
5
+ }>;
@@ -0,0 +1,18 @@
1
+ import { loadOnce } from "../core/load-once.js";
2
+ // src/mermaid/loader.ts
3
+ let latest = null;
4
+ /**
5
+ * The loader the page's engine comes from: the one passed most recently. A loader written inline
6
+ * (a new function on each render) changes nothing once the engine is here.
7
+ */
8
+ function setMermaidLoader(load) {
9
+ latest = load;
10
+ }
11
+ /** The page's Mermaid engine, fetched once; a failed fetch is tried again by the next diagram. */
12
+ const loadMermaid = loadOnce(() => {
13
+ if (!latest) return Promise.reject(/* @__PURE__ */ new Error("No Mermaid loader was given"));
14
+ return latest().then((module) => "default" in module ? module.default : module);
15
+ });
16
+ export { loadMermaid, setMermaidLoader };
17
+
18
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../../src/mermaid/loader.ts"],"sourcesContent":["import { loadOnce } from '../core/load-once'\nimport type { MermaidEngine } from './renderer'\n\n/** What `<Markdown mermaid={…}>` takes: a loader for the engine, `() => import('mermaid')`. */\nexport type MermaidLoader = () => Promise<MermaidEngine | { default: MermaidEngine }>\n\nlet latest: MermaidLoader | null = null\n\n/**\n * The loader the page's engine comes from: the one passed most recently. A loader written inline\n * (a new function on each render) changes nothing once the engine is here.\n */\nexport function setMermaidLoader(load: MermaidLoader): void {\n latest = load\n}\n\n/** The page's Mermaid engine, fetched once; a failed fetch is tried again by the next diagram. */\nexport const loadMermaid = loadOnce<MermaidEngine>(() => {\n if (!latest) return Promise.reject(new Error('No Mermaid loader was given'))\n return latest().then((module) => ('default' in module ? module.default : module))\n})\n"],"mappings":";;AAMA,IAAI,SAA+B;;;;;AAMnC,SAAgB,iBAAiB,MAA2B;CAC1D,SAAS;AACX;;AAGA,MAAa,cAAc,eAA8B;CACvD,IAAI,CAAC,QAAQ,OAAO,QAAQ,uBAAO,IAAI,MAAM,6BAA6B,CAAC;CAC3E,OAAO,OAAO,CAAC,CAAC,MAAM,WAAY,aAAa,SAAS,OAAO,UAAU,MAAO;AAClF,CAAC"}
@@ -0,0 +1,16 @@
1
+ /** The Mermaid configuration a diagram is drawn with, and the identity it is cached under. */
2
+ export interface MermaidLook {
3
+ key: string;
4
+ config: Record<string, unknown>;
5
+ }
6
+ /**
7
+ * Mermaid's own themes are fixed palettes that match no page around them, so a diagram is drawn
8
+ * with Mermaid's `base` theme fed from the renderer's tokens (`--fd-*`, `styles.css`), read on
9
+ * the element the diagram sits in: a page that recolours the Markdown recolours its diagrams.
10
+ *
11
+ * Mermaid derives shades with a colour library that cannot read `oklch()`, `color-mix()` or a
12
+ * system colour, so each token is resolved by the browser (a probe element inside the root) and
13
+ * rasterized to sRGB hex on a 1×1 canvas. A "veil", the text colour at a few percent over the
14
+ * ground, is composited there too, since Mermaid needs an opaque colour where the page uses alpha.
15
+ */
16
+ export declare function resolveMermaidLook(root: HTMLElement): MermaidLook;