@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,17 @@
1
+ import { parse } from "../engine/index.js";
2
+ import { render } from "./render.js";
3
+ // src/react/draw.ts
4
+ /** One text read and drawn: a whole document, or one of its blocks. */
5
+ function draw(source, offset, headingIds, options) {
6
+ return render(parse(source, { extensions: options.extensions }), {
7
+ components: options.components,
8
+ urlTransform: options.urlTransform,
9
+ special: options.special,
10
+ labels: options.labels,
11
+ offset,
12
+ headingIds
13
+ });
14
+ }
15
+ export { draw };
16
+
17
+ //# sourceMappingURL=draw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draw.js","names":[],"sources":["../../src/react/draw.ts"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { parse } from '../engine'\nimport type { Extension } from '../engine/types'\nimport type { UrlTransform } from '../engine/url'\nimport { render, type Components, type RenderOptions } from './render'\n\n/** What a block needs besides its text; one object per document, so blocks can compare it. */\nexport interface BlockOptions {\n components: Components\n /** The syntax every block is read with: the dialect, the stages that are there, the page's. */\n extensions: Extension[]\n urlTransform: UrlTransform\n special: RenderOptions['special']\n /** The page draws code itself (`components.pre` or `.code`): a growing fence goes to it too. */\n ownCode: boolean\n /** The page draws tables itself, or its extensions read them: a table is then parsed whole. */\n ownTable: boolean\n /** The names of a task list's boxes. */\n labels?: RenderOptions['labels']\n}\n\n/** One text read and drawn: a whole document, or one of its blocks. */\nexport function draw(\n source: string,\n offset: number,\n headingIds: ReadonlyMap<number, string> | null,\n options: BlockOptions,\n): ReactNode {\n return render(parse(source, { extensions: options.extensions }), {\n components: options.components,\n urlTransform: options.urlTransform,\n special: options.special,\n labels: options.labels,\n offset,\n headingIds,\n })\n}\n"],"mappings":";;;;AAsBA,SAAgB,KACd,QACA,QACA,YACA,SACW;CACX,OAAO,OAAO,MAAM,QAAQ,EAAE,YAAY,QAAQ,WAAW,CAAC,GAAG;EAC/D,YAAY,QAAQ;EACpB,cAAc,QAAQ;EACtB,SAAS,QAAQ;EACjB,QAAQ,QAAQ;EAChB;EACA;CACF,CAAC;AACH"}
@@ -0,0 +1,16 @@
1
+ import { TreeRow } from "../core/file-tree-parse.js";
2
+ import { ComponentType, ReactNode, SVGProps } from "react";
3
+ /** The glyph drawn before a row; a host can colour files by type (`getIcon`). */
4
+ export type TreeIcon = ComponentType<SVGProps<SVGSVGElement>>;
5
+ export interface FileTreeProps {
6
+ /** The fence's body: `├──` / `└──` lines. */
7
+ source: string;
8
+ getIcon?: (row: TreeRow, open: boolean) => TreeIcon;
9
+ }
10
+ /**
11
+ * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. A
12
+ * folder folds and unfolds on click, and the tree is one Tab stop walked with the arrow keys (the
13
+ * WAI-ARIA tree pattern), so a long layout can be read one branch at a time.
14
+ */
15
+ export declare function FileTree({ source, getIcon }: FileTreeProps): ReactNode;
16
+ export type { TreeRow };
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import { parseFileTree } from "../core/file-tree-parse.js";
3
+ import { fileTree } from "../render/views.js";
4
+ import { useLabels } from "./labels.js";
5
+ import { react } from "./render.js";
6
+ import { createElement, useMemo, useRef, useState } from "react";
7
+ // src/react/file-tree.tsx
8
+ /**
9
+ * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. A
10
+ * folder folds and unfolds on click, and the tree is one Tab stop walked with the arrow keys (the
11
+ * WAI-ARIA tree pattern), so a long layout can be read one branch at a time.
12
+ */
13
+ function FileTree({ source, getIcon }) {
14
+ const labels = useLabels();
15
+ const rows = useMemo(() => parseFileTree(source), [source]);
16
+ const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
17
+ const [focused, setFocused] = useState(null);
18
+ const elements = useRef(/* @__PURE__ */ new Map());
19
+ if (rows.length === 0) return null;
20
+ const toggle = (path) => setCollapsed((current) => {
21
+ const next = new Set(current);
22
+ if (next.has(path)) next.delete(path);
23
+ else next.add(path);
24
+ return next;
25
+ });
26
+ const focus = (row) => {
27
+ if (row) elements.current.get(row.path)?.focus();
28
+ };
29
+ const onKeyDown = (event, row, visible) => {
30
+ const index = visible.indexOf(row);
31
+ const open = row.folder && !collapsed.has(row.path);
32
+ const parent = row.parent === null ? void 0 : rows[row.parent];
33
+ const action = {
34
+ ArrowDown: () => focus(visible[index + 1]),
35
+ ArrowUp: () => focus(visible[index - 1]),
36
+ Home: () => focus(visible[0]),
37
+ End: () => focus(visible.at(-1)),
38
+ ArrowRight: () => open ? focus(visible[index + 1]) : row.folder && toggle(row.path),
39
+ ArrowLeft: () => open ? toggle(row.path) : focus(parent),
40
+ Enter: () => row.folder && toggle(row.path),
41
+ " ": () => row.folder && toggle(row.path)
42
+ }[event.key];
43
+ if (!action) return;
44
+ event.preventDefault();
45
+ event.stopPropagation();
46
+ action();
47
+ };
48
+ return fileTree(react, {
49
+ rows,
50
+ collapsed,
51
+ focused,
52
+ ...getIcon ? { icon: (row, open) => createElement(getIcon(row, open), { className: "fd-tree-icon" }) } : null,
53
+ on: (row, visible) => ({
54
+ ref: (element) => {
55
+ if (element) elements.current.set(row.path, element);
56
+ else elements.current.delete(row.path);
57
+ },
58
+ onFocus: () => setFocused(row.path),
59
+ onClick: (event) => {
60
+ event.currentTarget.focus();
61
+ if (row.folder) toggle(row.path);
62
+ },
63
+ onKeyDown: (event) => onKeyDown(event, row, visible)
64
+ })
65
+ }, labels);
66
+ }
67
+ export { FileTree };
68
+
69
+ //# sourceMappingURL=file-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-tree.js","names":[],"sources":["../../src/react/file-tree.tsx"],"sourcesContent":["'use client'\nimport {\n createElement,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type SVGProps,\n} from 'react'\nimport { parseFileTree, type TreeRow } from '../core/file-tree-parse'\nimport { fileTree } from '../render/views'\nimport { useLabels } from './labels'\nimport { react } from './render'\n\nexport type { TreeRow } from '../core/file-tree-parse'\n\n/** The glyph drawn before a row; a host can colour files by type (`getIcon`). */\nexport type TreeIcon = ComponentType<SVGProps<SVGSVGElement>>\n\nexport interface FileTreeProps {\n /** The fence's body: `├──` / `└──` lines. */\n source: string\n getIcon?: (row: TreeRow, open: boolean) => TreeIcon\n}\n\n/**\n * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. A\n * folder folds and unfolds on click, and the tree is one Tab stop walked with the arrow keys (the\n * WAI-ARIA tree pattern), so a long layout can be read one branch at a time.\n */\nexport function FileTree({ source, getIcon }: FileTreeProps) {\n const labels = useLabels()\n const rows = useMemo(() => parseFileTree(source), [source])\n const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(() => new Set())\n const [focused, setFocused] = useState<string | null>(null)\n const elements = useRef(new Map<string, HTMLElement>())\n if (rows.length === 0) return null\n\n const toggle = (path: string) =>\n setCollapsed((current) => {\n const next = new Set(current)\n if (next.has(path)) next.delete(path)\n else next.add(path)\n return next\n })\n const focus = (row: TreeRow | undefined) => {\n if (row) elements.current.get(row.path)?.focus()\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>, row: TreeRow, visible: TreeRow[]) => {\n const index = visible.indexOf(row)\n const open = row.folder && !collapsed.has(row.path)\n const parent = row.parent === null ? undefined : rows[row.parent]\n const actions: Record<string, () => void> = {\n ArrowDown: () => focus(visible[index + 1]),\n ArrowUp: () => focus(visible[index - 1]),\n Home: () => focus(visible[0]),\n End: () => focus(visible.at(-1)),\n ArrowRight: () => (open ? focus(visible[index + 1]) : row.folder && toggle(row.path)),\n ArrowLeft: () => (open ? toggle(row.path) : focus(parent)),\n Enter: () => row.folder && toggle(row.path),\n ' ': () => row.folder && toggle(row.path),\n }\n const action = actions[event.key]\n if (!action) return\n // Handled here, so not also by the page: a board or a shortcut layer around the reply would\n // otherwise move a card or scroll on the same arrow press. Keys the tree leaves (Escape, Tab)\n // still reach it.\n event.preventDefault()\n event.stopPropagation()\n action()\n }\n\n return fileTree<ReactNode, never>(\n react as never,\n {\n rows,\n collapsed,\n focused,\n ...(getIcon\n ? {\n icon: (row: TreeRow, open: boolean) =>\n createElement(getIcon(row, open), { className: 'fd-tree-icon' }),\n }\n : null),\n on: (row, visible) => ({\n ref: (element: HTMLElement | null) => {\n if (element) elements.current.set(row.path, element)\n else elements.current.delete(row.path)\n },\n onFocus: () => setFocused(row.path),\n onClick: (event: MouseEvent<HTMLElement>) => {\n // Safari leaves a clicked row unfocused; the keys must carry on from it.\n event.currentTarget.focus()\n if (row.folder) toggle(row.path)\n },\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => onKeyDown(event, row, visible),\n }),\n },\n labels,\n )\n}\n"],"mappings":";;;;;;;;;;;;AAiCA,SAAgB,SAAS,EAAE,QAAQ,WAA0B;CAC3D,MAAM,SAAS,UAAU;CACzB,MAAM,OAAO,cAAc,cAAc,MAAM,GAAG,CAAC,MAAM,CAAC;CAC1D,MAAM,CAAC,WAAW,gBAAgB,+BAAoC,IAAI,IAAI,CAAC;CAC/E,MAAM,CAAC,SAAS,cAAc,SAAwB,IAAI;CAC1D,MAAM,WAAW,uBAAO,IAAI,IAAyB,CAAC;CACtD,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,MAAM,UAAU,SACd,cAAc,YAAY;EACxB,MAAM,OAAO,IAAI,IAAI,OAAO;EAC5B,IAAI,KAAK,IAAI,IAAI,GAAG,KAAK,OAAO,IAAI;OAC/B,KAAK,IAAI,IAAI;EAClB,OAAO;CACT,CAAC;CACH,MAAM,SAAS,QAA6B;EAC1C,IAAI,KAAK,SAAS,QAAQ,IAAI,IAAI,IAAI,CAAC,EAAE,MAAM;CACjD;CAEA,MAAM,aAAa,OAAmC,KAAc,YAAuB;EACzF,MAAM,QAAQ,QAAQ,QAAQ,GAAG;EACjC,MAAM,OAAO,IAAI,UAAU,CAAC,UAAU,IAAI,IAAI,IAAI;EAClD,MAAM,SAAS,IAAI,WAAW,OAAO,KAAA,IAAY,KAAK,IAAI;EAW1D,MAAM,SAAS;GATb,iBAAiB,MAAM,QAAQ,QAAQ,EAAE;GACzC,eAAe,MAAM,QAAQ,QAAQ,EAAE;GACvC,YAAY,MAAM,QAAQ,EAAE;GAC5B,WAAW,MAAM,QAAQ,GAAG,EAAE,CAAC;GAC/B,kBAAmB,OAAO,MAAM,QAAQ,QAAQ,EAAE,IAAI,IAAI,UAAU,OAAO,IAAI,IAAI;GACnF,iBAAkB,OAAO,OAAO,IAAI,IAAI,IAAI,MAAM,MAAM;GACxD,aAAa,IAAI,UAAU,OAAO,IAAI,IAAI;GAC1C,WAAW,IAAI,UAAU,OAAO,IAAI,IAAI;EAErB,EAAE,MAAM;EAC7B,IAAI,CAAC,QAAQ;EAIb,MAAM,eAAe;EACrB,MAAM,gBAAgB;EACtB,OAAO;CACT;CAEA,OAAO,SACL,OACA;EACE;EACA;EACA;EACA,GAAI,UACA,EACE,OAAO,KAAc,SACnB,cAAc,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,eAAe,CAAC,EACnE,IACA;EACJ,KAAK,KAAK,aAAa;GACrB,MAAM,YAAgC;IACpC,IAAI,SAAS,SAAS,QAAQ,IAAI,IAAI,MAAM,OAAO;SAC9C,SAAS,QAAQ,OAAO,IAAI,IAAI;GACvC;GACA,eAAe,WAAW,IAAI,IAAI;GAClC,UAAU,UAAmC;IAE3C,MAAM,cAAc,MAAM;IAC1B,IAAI,IAAI,QAAQ,OAAO,IAAI,IAAI;GACjC;GACA,YAAY,UAAsC,UAAU,OAAO,KAAK,OAAO;EACjF;CACF,GACA,MACF;AACF"}
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { createCutter, createFenceMemory, growingFence } from "../document/fence.js";
3
+ import { CodeFrame } from "./code-block.js";
4
+ import { useRenderContext } from "./context.js";
5
+ import { memo, useMemo, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ // src/react/growing-code.tsx
8
+ const Piece = memo(function Piece({ text, language, highlight }) {
9
+ const html = language && highlight ? highlight(text, language) : null;
10
+ return html === null ? text : /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } });
11
+ }, (before, after) => before.language === after.language && before.highlight === after.highlight && (before.done && after.done && before.run === after.run || before.text === after.text));
12
+ function GrowingCode({ language, title, body }) {
13
+ const { highlight } = useRenderContext();
14
+ const known = useMemo(() => Boolean(language && highlight && highlight("", language) !== null), [language, highlight]);
15
+ const className = [language ? `language-${language}` : null, known ? "fd-highlighted" : null].filter(Boolean).join(" ");
16
+ const [cut] = useState(createCutter);
17
+ const { parts, run } = useMemo(() => cut(body), [cut, body]);
18
+ return /* @__PURE__ */ jsx(CodeFrame, {
19
+ language,
20
+ title,
21
+ text: body,
22
+ children: /* @__PURE__ */ jsxs("code", {
23
+ className: className || void 0,
24
+ children: [parts.map((text, index) => /* @__PURE__ */ jsx(Piece, {
25
+ text,
26
+ done: index < parts.length - 1,
27
+ run,
28
+ language: known ? language : void 0,
29
+ highlight
30
+ }, index)), !known && body ? "\n" : null]
31
+ })
32
+ });
33
+ }
34
+ export { GrowingCode, createFenceMemory, growingFence };
35
+
36
+ //# sourceMappingURL=growing-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"growing-code.js","names":[],"sources":["../../src/react/growing-code.tsx"],"sourcesContent":["'use client'\nimport { memo, useMemo, useState } from 'react'\nimport { createCutter } from '../document/fence'\nimport { CodeFrame } from './code-block'\nimport { useRenderContext, type Highlighter } from './context'\n\nexport { createFenceMemory, growingFence, type FenceMemory } from '../document/fence'\n\n/**\n * A code fence still being written, as an agent writing a file streams it: hundreds of lines,\n * one token at a time. Drawn through react-markdown, every token would parse, draw and highlight\n * the whole fence again. Here the code is cut into pieces of {@link LINES} lines; a piece that is\n * complete is drawn and highlighted once, and a token redraws only the last piece.\n *\n * A piece is highlighted on its own, so a construct that spans two pieces (a block comment, a\n * template string) can be coloured wrongly for the moment the fence is open. When it closes the\n * block is drawn as any finished block is, highlighted whole.\n */\ninterface PieceProps {\n text: string\n /** A complete piece of run `run`: its text does not change while the run lasts. */\n done: boolean\n run: number\n language: string | undefined\n highlight: Highlighter | null\n}\n\nconst Piece = memo(\n function Piece({ text, language, highlight }: PieceProps) {\n const html = language && highlight ? highlight(text, language) : null\n return html === null ? text : <span dangerouslySetInnerHTML={{ __html: html }} />\n },\n (before, after) =>\n before.language === after.language &&\n before.highlight === after.highlight &&\n ((before.done && after.done && before.run === after.run) || before.text === after.text),\n)\n\nexport function GrowingCode({\n language,\n title,\n body,\n}: {\n language: string | undefined\n title?: string\n body: string\n}) {\n const { highlight } = useRenderContext()\n // The class a finished block gets when its language is one the highlighter knows.\n const known = useMemo(\n () => Boolean(language && highlight && highlight('', language) !== null),\n [language, highlight],\n )\n const className = [language ? `language-${language}` : null, known ? 'fd-highlighted' : null]\n .filter(Boolean)\n .join(' ')\n const [cut] = useState(createCutter)\n const { parts, run } = useMemo(() => cut(body), [cut, body])\n return (\n <CodeFrame language={language} title={title} text={body}>\n <code className={className || undefined}>\n {parts.map((text, index) => (\n <Piece\n key={index}\n text={text}\n done={index < parts.length - 1}\n run={run}\n language={known ? language : undefined}\n highlight={highlight}\n />\n ))}\n {/* A finished block's text ends in a newline unless it is highlighted; so does this. */}\n {!known && body ? '\\n' : null}\n </code>\n </CodeFrame>\n )\n}\n"],"mappings":";;;;;;;AA2BA,MAAM,QAAQ,KACZ,SAAS,MAAM,EAAE,MAAM,UAAU,aAAyB;CACxD,MAAM,OAAO,YAAY,YAAY,UAAU,MAAM,QAAQ,IAAI;CACjE,OAAO,SAAS,OAAO,OAAO,oBAAC,QAAD,EAAM,yBAAyB,EAAE,QAAQ,KAAK,EAAI,CAAA;AAClF,IACC,QAAQ,UACP,OAAO,aAAa,MAAM,YAC1B,OAAO,cAAc,MAAM,cACzB,OAAO,QAAQ,MAAM,QAAQ,OAAO,QAAQ,MAAM,OAAQ,OAAO,SAAS,MAAM,KACtF;AAEA,SAAgB,YAAY,EAC1B,UACA,OACA,QAKC;CACD,MAAM,EAAE,cAAc,iBAAiB;CAEvC,MAAM,QAAQ,cACN,QAAQ,YAAY,aAAa,UAAU,IAAI,QAAQ,MAAM,IAAI,GACvE,CAAC,UAAU,SAAS,CACtB;CACA,MAAM,YAAY,CAAC,WAAW,YAAY,aAAa,MAAM,QAAQ,mBAAmB,IAAI,CAAC,CAC1F,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;CACX,MAAM,CAAC,OAAO,SAAS,YAAY;CACnC,MAAM,EAAE,OAAO,QAAQ,cAAc,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,CAAC;CAC3D,OACE,oBAAC,WAAD;EAAqB;EAAiB;EAAO,MAAM;EACjD,UAAA,qBAAC,QAAD;GAAM,WAAW,aAAa,KAAA;GAA9B,UAAA,CACG,MAAM,KAAK,MAAM,UAChB,oBAAC,OAAD;IAEQ;IACN,MAAM,QAAQ,MAAM,SAAS;IACxB;IACL,UAAU,QAAQ,WAAW,KAAA;IAClB;GACZ,GANM,KAMN,CACF,GAEA,CAAC,SAAS,OAAO,OAAO,IACrB;;CACG,CAAA;AAEf"}
@@ -0,0 +1,6 @@
1
+ import { getLanguage } from "../render/views.js";
2
+ import { ReactNode } from "react";
3
+ /** Flatten a markdown node's children to their plain text (elements → their own children). */
4
+ export declare function childrenToText(node: ReactNode): string;
5
+ export declare function isCodeBlock(children: ReactNode, className?: string): boolean;
6
+ export { getLanguage };
@@ -0,0 +1,17 @@
1
+ import { getLanguage } from "../render/views.js";
2
+ import { isValidElement } from "react";
3
+ // src/react/helpers.ts
4
+ /** Flatten a markdown node's children to their plain text (elements → their own children). */
5
+ function childrenToText(node) {
6
+ if (typeof node === "string") return node;
7
+ if (typeof node === "number") return String(node);
8
+ if (Array.isArray(node)) return node.map((child) => childrenToText(child)).join("");
9
+ if (isValidElement(node)) return childrenToText(node.props.children);
10
+ return "";
11
+ }
12
+ function isCodeBlock(children, className) {
13
+ return Boolean(getLanguage(className)) || childrenToText(children).includes("\n");
14
+ }
15
+ export { childrenToText, getLanguage, isCodeBlock };
16
+
17
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","names":[],"sources":["../../src/react/helpers.ts"],"sourcesContent":["import { isValidElement, type ReactNode } from 'react'\nimport { getLanguage } from '../render/views'\n\nexport { getLanguage }\n\n/** Flatten a markdown node's children to their plain text (elements → their own children). */\nexport function childrenToText(node: ReactNode): string {\n if (typeof node === 'string') return node\n if (typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map((child) => childrenToText(child as ReactNode)).join('')\n if (isValidElement(node)) {\n return childrenToText((node.props as { children?: ReactNode }).children)\n }\n return ''\n}\n\nexport function isCodeBlock(children: ReactNode, className?: string): boolean {\n return Boolean(getLanguage(className)) || childrenToText(children).includes('\\n')\n}\n"],"mappings":";;;;AAMA,SAAgB,eAAe,MAAyB;CACtD,IAAI,OAAO,SAAS,UAAU,OAAO;CACrC,IAAI,OAAO,SAAS,UAAU,OAAO,OAAO,IAAI;CAChD,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAK,KAAK,UAAU,eAAe,KAAkB,CAAC,CAAC,CAAC,KAAK,EAAE;CAC/F,IAAI,eAAe,IAAI,GACrB,OAAO,eAAgB,KAAK,MAAmC,QAAQ;CAEzE,OAAO;AACT;AAEA,SAAgB,YAAY,UAAqB,WAA6B;CAC5E,OAAO,QAAQ,YAAY,SAAS,CAAC,KAAK,eAAe,QAAQ,CAAC,CAAC,SAAS,IAAI;AAClF"}
@@ -0,0 +1,23 @@
1
+ import { OUTLINES, glyph } from "../render/views.js";
2
+ import { react } from "./render.js";
3
+ // src/react/icons.tsx
4
+ function icon(outline) {
5
+ return function Icon(props) {
6
+ return glyph(react, outline, props);
7
+ };
8
+ }
9
+ OUTLINES.info;
10
+ OUTLINES.tip;
11
+ OUTLINES.important;
12
+ OUTLINES.warning;
13
+ OUTLINES.caution;
14
+ OUTLINES.chevron;
15
+ OUTLINES.file;
16
+ OUTLINES.folder;
17
+ OUTLINES.folderOpen;
18
+ const CopyIcon = icon(OUTLINES.copy);
19
+ const CheckIcon = icon(OUTLINES.check);
20
+ const DownloadIcon = icon(OUTLINES.download);
21
+ export { CheckIcon, CopyIcon, DownloadIcon, icon };
22
+
23
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","names":[],"sources":["../../src/react/icons.tsx"],"sourcesContent":["import type { ReactElement, SVGProps } from 'react'\nimport type { Props } from '../render/factory'\nimport { glyph, OUTLINES } from '../render/views'\nimport { react } from './render'\n\n/** The renderer's glyphs (`render/views.ts`) as React components, for a page that reuses one. */\ntype IconProps = SVGProps<SVGSVGElement>\n\nexport function icon(outline: string) {\n return function Icon(props: IconProps) {\n return glyph(react, outline, props as Props) as ReactElement\n }\n}\n\nexport const InfoIcon = icon(OUTLINES.info)\n\nexport const TipIcon = icon(OUTLINES.tip)\n\nexport const ImportantIcon = icon(OUTLINES.important)\n\nexport const WarningIcon = icon(OUTLINES.warning)\n\nexport const CautionIcon = icon(OUTLINES.caution)\n\nexport const ChevronIcon = icon(OUTLINES.chevron)\n\nexport const FileIcon = icon(OUTLINES.file)\n\nexport const FolderIcon = icon(OUTLINES.folder)\n\nexport const FolderOpenIcon = icon(OUTLINES.folderOpen)\n\nexport const CopyIcon = icon(OUTLINES.copy)\n\nexport const CheckIcon = icon(OUTLINES.check)\n\nexport const DownloadIcon = icon(OUTLINES.download)\n"],"mappings":";;;AAQA,SAAgB,KAAK,SAAiB;CACpC,OAAO,SAAS,KAAK,OAAkB;EACrC,OAAO,MAAM,OAAO,SAAS,KAAc;CAC7C;AACF;AAE6B,SAAS;AAEV,SAAS;AAEH,SAAS;AAEX,SAAS;AAET,SAAS;AAET,SAAS;AAEZ,SAAS;AAEP,SAAS;AAEL,SAAS;AAE5C,MAAa,WAAW,KAAK,SAAS,IAAI;AAE1C,MAAa,YAAY,KAAK,SAAS,KAAK;AAE5C,MAAa,eAAe,KAAK,SAAS,QAAQ"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { DEFAULT_LABELS, mergeLabels } from "../render/labels.js";
3
+ import { createContext, useContext } from "react";
4
+ // src/react/labels.tsx
5
+ const LabelsContext = createContext(DEFAULT_LABELS);
6
+ const useLabels = () => useContext(LabelsContext);
7
+ export { DEFAULT_LABELS, LabelsContext, mergeLabels, useLabels };
8
+
9
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","names":[],"sources":["../../src/react/labels.tsx"],"sourcesContent":["'use client'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_LABELS, type MarkdownLabels } from '../render/labels'\n\nexport {\n DEFAULT_LABELS,\n mergeLabels,\n type LabelOverrides,\n type MarkdownLabels,\n} from '../render/labels'\n\nexport const LabelsContext = createContext<MarkdownLabels>(DEFAULT_LABELS)\n\nexport const useLabels = () => useContext(LabelsContext)\n"],"mappings":";;;;AAWA,MAAa,gBAAgB,cAA8B,cAAc;AAEzE,MAAa,kBAAkB,WAAW,aAAa"}
@@ -0,0 +1,78 @@
1
+ import { Extension } from "../engine/types.js";
2
+ import { MermaidLoader } from "../mermaid/loader.js";
3
+ import { MarkdownComponents } from "./context.js";
4
+ import { Highlighter } from "../render/views.js";
5
+ import { LabelOverrides } from "../render/labels.js";
6
+ import { UrlTransform } from "../engine/url.js";
7
+ import "./render.js";
8
+ import { StageOption } from "./use-stage.js";
9
+ import "./draw.js";
10
+ import { ReactNode } from "react";
11
+ export interface MarkdownProps {
12
+ /** The Markdown. */
13
+ children: string;
14
+ /**
15
+ * The text is a reply still arriving. Its unfinished end is completed so it does not flash raw
16
+ * syntax (`**bo` reads as bold, a fence still being written as code), and a caret follows it.
17
+ */
18
+ streaming?: boolean;
19
+ /**
20
+ * Show a streaming reply at an even pace instead of in the bursts it arrives in: whole words,
21
+ * a few frames behind the text, finishing at the same pace when the reply ends. `true`, or the
22
+ * time in milliseconds a burst takes to show (240 by default). Off by default.
23
+ */
24
+ smooth?: boolean | number;
25
+ /** The caret after streaming text; on by default while `streaming`. */
26
+ caret?: boolean;
27
+ /**
28
+ * Your own elements for any tag (`a`, `img`, `pre`, `table`, …) or the dialect's, and your own
29
+ * drawing of a ```mermaid fence (`diagram`) or a ```tree fence (`tree`).
30
+ */
31
+ components?: MarkdownComponents;
32
+ /**
33
+ * Your own syntax, after the dialect's: inline rules, fences by language, and passes over the
34
+ * tree, each run on every block. A node's position counts from the start of its block. Keep the
35
+ * list's reference stable (a module constant): a new list reads every block again.
36
+ */
37
+ extensions?: Extension[];
38
+ /**
39
+ * Give headings ids (`setup`, `setup-1`) to link to, the same ids `extractHeadings` returns.
40
+ * Each block's headings are read once, so it costs a streaming reply only its last block.
41
+ */
42
+ headingIds?: boolean;
43
+ /**
44
+ * Raw HTML in the text, parsed and sanitized. `true` (the default) fetches the parser the first
45
+ * time a document holds HTML, and leaves the tags out until it is there; pass `@localix-ai/flowdown/html`
46
+ * itself to have it in the first render (a server); `false` drops raw HTML.
47
+ */
48
+ html?: boolean | StageOption<Extension>;
49
+ /** Typeset `$…$`: `@localix-ai/flowdown/math`, or a loader for it. Off by default. */
50
+ math?: StageOption<Extension>;
51
+ /** Draw ```mermaid fences: `() => import('mermaid')`. Off by default: they show as code. */
52
+ mermaid?: MermaidLoader | null;
53
+ /** Highlight fenced code: `@localix-ai/flowdown/highlight`, or your own. Off by default. */
54
+ highlight?: Highlighter | null;
55
+ /** The words the renderer adds (callout names, button labels), for a page in another language. */
56
+ labels?: LabelOverrides;
57
+ /**
58
+ * Rewrite or refuse a link or image URL. Omitted (or null), relative URLs and http, https,
59
+ * mailto, irc, ircs and xmpp are kept and everything else is emptied (`javascript:`, `tel:` and
60
+ * `data:` among them).
61
+ */
62
+ urlTransform?: UrlTransform | null;
63
+ className?: string;
64
+ /**
65
+ * Leaves the `flowdown` class off the root element, so the package's stylesheet does not
66
+ * apply: for a small surface that draws every element with `components` at its own scale.
67
+ */
68
+ unstyled?: boolean;
69
+ }
70
+ /**
71
+ * Markdown with the flowdown dialect: GitHub Markdown, callouts, steps, tabs, stats, pros and cons,
72
+ * figures, file trees and, when the page supplies them, diagrams, formulas and highlighted code.
73
+ *
74
+ * The document is drawn as its top-level blocks, each remembered by its text: while a reply
75
+ * streams, the blocks above its end do not change, and only the last one is parsed and drawn
76
+ * again on each token.
77
+ */
78
+ export declare function Markdown({ children, streaming: arriving, smooth, caret: caretOption, components, extensions, headingIds, html, math: mathOption, mermaid, highlight, labels, urlTransform, className, unstyled }: MarkdownProps): ReactNode;
@@ -0,0 +1,206 @@
1
+ "use client";
2
+ import { useSmoothText } from "./use-smooth-text.js";
3
+ import { normalizeLineEndings } from "../core/blocks.js";
4
+ import { createDocument, usesHtml, usesMath, usesRareEntity } from "../document/index.js";
5
+ import { flowdownDialect } from "../core/remark-answer-blocks.js";
6
+ import "../dialect.js";
7
+ import { createFenceMemory, growingFence } from "../document/fence.js";
8
+ import { tableRows } from "../document/table.js";
9
+ import { addEntities, hasAllEntities } from "../engine/entities.js";
10
+ import { Diagram } from "../mermaid/diagram.js";
11
+ import { setMermaidLoader } from "../mermaid/loader.js";
12
+ import { mergeLabels } from "../render/labels.js";
13
+ import { Callout } from "./callout.js";
14
+ import { Code, CodeBlock } from "./code-block.js";
15
+ import { GrowingCode } from "./growing-code.js";
16
+ import { GrowingFence, RenderContextValue } from "./context.js";
17
+ import { FileTree } from "./file-tree.js";
18
+ import { LabelsContext } from "./labels.js";
19
+ import { defaultUrlTransform } from "../engine/url.js";
20
+ import { draw } from "./draw.js";
21
+ import { useStage } from "./use-stage.js";
22
+ import { Table } from "./table.js";
23
+ import { TableRows } from "./table-rows.js";
24
+ import { Tab, Tabs } from "./tabs.js";
25
+ import { Fragment, memo, useContext, useMemo, useState } from "react";
26
+ import { jsx, jsxs } from "react/jsx-runtime";
27
+ // src/react/markdown.tsx
28
+ /** The package's own HTML stage, fetched as a chunk of its own the first time it is needed. */
29
+ const loadHtml = () => import("../html/index.js");
30
+ /** Every HTML5 named reference: ten kilobytes fetched the first time a document uses a rare one. */
31
+ const loadEntities = () => import("../engine/entity-table.js").then((module) => {
32
+ addEntities(module.ENTITY_TABLE);
33
+ return module;
34
+ });
35
+ /** A page's own diagram, told whether its fence is the one still being written. */
36
+ function OwnDiagram({ as: Page, source }) {
37
+ return /* @__PURE__ */ jsx(Page, {
38
+ source,
39
+ growing: useContext(GrowingFence)
40
+ });
41
+ }
42
+ /**
43
+ * The elements a document is drawn with: the dialect's (callouts, tabs, code blocks), then the
44
+ * page's. The two fences that are not code (```mermaid, ```tree) are drawn by `special`, outside
45
+ * both `pre` and `code`, so a page's own code block never receives a diagram.
46
+ */
47
+ function buildComponents(overrides) {
48
+ const { diagram: PageDiagram, tree: PageTree, ...elements } = overrides ?? {};
49
+ return {
50
+ components: {
51
+ blockquote: Callout,
52
+ table: Table,
53
+ "answer-tabs": Tabs,
54
+ "answer-tab": Tab,
55
+ pre: CodeBlock,
56
+ code: Code,
57
+ ...elements
58
+ },
59
+ special: (language, source) => language === "mermaid" ? PageDiagram ? /* @__PURE__ */ jsx(OwnDiagram, {
60
+ as: PageDiagram,
61
+ source
62
+ }) : /* @__PURE__ */ jsx(Diagram, { source }) : PageTree ? /* @__PURE__ */ jsx(PageTree, { source }) : /* @__PURE__ */ jsx(FileTree, { source })
63
+ };
64
+ }
65
+ const Block = memo(function Block({ source: open, closer, offset, growing, headingIds, options }) {
66
+ const [memory] = useState(createFenceMemory);
67
+ const fence = growing && !options.ownCode ? growingFence(open, closer, memory) : null;
68
+ if (fence && fence.language !== "mermaid" && fence.language !== "tree" && !(fence.language === "math" && options.extensions?.some((stage) => stage.name === "math"))) return /* @__PURE__ */ jsx(GrowingCode, { ...fence });
69
+ const source = closer ? open + closer : open;
70
+ const table = options.ownTable ? null : tableRows(source);
71
+ if (table) return /* @__PURE__ */ jsx(TableRows, {
72
+ ...table,
73
+ options
74
+ });
75
+ return /* @__PURE__ */ jsx(GrowingFence.Provider, {
76
+ value: growing,
77
+ children: draw(source, offset, headingIds, options)
78
+ });
79
+ }, (before, after) => before.source === after.source && before.closer === after.closer && before.offset === after.offset && before.growing === after.growing && before.headingKey === after.headingKey && before.options === after.options);
80
+ const NO_EXTENSIONS = [];
81
+ /**
82
+ * How many blocks a group holds. Small enough that the group a reply is growing in is cheap to
83
+ * go through on every token, large enough that a long reply is few groups.
84
+ */
85
+ const GROUP = 32;
86
+ const headingKeyOf = (own) => own ? [...own].map(([at, id]) => `${at}:${id}`).join(";") : "";
87
+ /**
88
+ * The blocks at one run of places in the document. A block never moves to another group, so it
89
+ * keeps what it holds (an open tab, a drawn diagram) for as long as it is there. While a reply
90
+ * streams only its last group changes: React passes over each group above as one element, where
91
+ * a flat list had it compare every block of the reply on every token.
92
+ */
93
+ const BlockGroup = memo(function BlockGroup({ blocks, ids, from, to, closer, growing, options }) {
94
+ const drawn = [];
95
+ for (let index = from; index < to; index++) {
96
+ const block = blocks[index];
97
+ const own = ids?.[index] ?? null;
98
+ const last = index === to - 1;
99
+ drawn.push(/* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? "\n" : null, /* @__PURE__ */ jsx(Block, {
100
+ source: block.source,
101
+ closer: last ? closer : "",
102
+ offset: block.offset,
103
+ growing: last && growing,
104
+ headingIds: own && own.size > 0 ? own : null,
105
+ headingKey: headingKeyOf(own),
106
+ options
107
+ })] }, index));
108
+ }
109
+ return drawn;
110
+ }, (before, after) => {
111
+ if (before.from !== after.from || before.to !== after.to || before.closer !== after.closer || before.growing !== after.growing || before.options !== after.options || before.ids === null !== (after.ids === null)) return false;
112
+ for (let index = after.to - 1; index >= after.from; index--) {
113
+ const was = before.blocks[index];
114
+ const is = after.blocks[index];
115
+ if (was !== is && (was.source !== is.source || was.offset !== is.offset)) return false;
116
+ if (after.ids) {
117
+ const had = before.ids[index];
118
+ const has = after.ids[index];
119
+ if (had !== has && ((had?.size ?? 0) > 0 || (has?.size ?? 0) > 0)) {
120
+ if (headingKeyOf(had) !== headingKeyOf(has)) return false;
121
+ }
122
+ }
123
+ }
124
+ return true;
125
+ });
126
+ /**
127
+ * Markdown with the flowdown dialect: GitHub Markdown, callouts, steps, tabs, stats, pros and cons,
128
+ * figures, file trees and, when the page supplies them, diagrams, formulas and highlighted code.
129
+ *
130
+ * The document is drawn as its top-level blocks, each remembered by its text: while a reply
131
+ * streams, the blocks above its end do not change, and only the last one is parsed and drawn
132
+ * again on each token.
133
+ */
134
+ function Markdown({ children, streaming: arriving = false, smooth = false, caret: caretOption, components, extensions = NO_EXTENSIONS, headingIds = false, html = true, math: mathOption = null, mermaid = null, highlight = null, labels, urlTransform, className, unstyled = false }) {
135
+ const whole = normalizeLineEndings(children);
136
+ const [text, behind] = useSmoothText(whole, arriving && smooth !== false, typeof smooth === "number" ? smooth : 240);
137
+ const streaming = arriving || behind;
138
+ const caret = caretOption ?? streaming;
139
+ const [doc] = useState(createDocument);
140
+ const { blocks, closer, growing: endsInFence, headingIds: ids } = doc.update(text, {
141
+ streaming,
142
+ math: Boolean(mathOption),
143
+ headingIds
144
+ });
145
+ const math = useStage(mathOption, mathOption !== null && usesMath(blocks));
146
+ const htmlStage = useStage(html === true ? loadHtml : html === false ? null : html, html !== false && usesHtml(blocks));
147
+ const entities = useStage(loadEntities, !hasAllEntities() && usesRareEntity(blocks));
148
+ const labelValue = useMemo(() => mergeLabels(labels), [labels]);
149
+ const options = useMemo(() => ({
150
+ ...buildComponents(components),
151
+ labels: labelValue,
152
+ extensions: [
153
+ flowdownDialect,
154
+ ...math ? [math] : [],
155
+ ...extensions,
156
+ ...htmlStage ? [htmlStage] : []
157
+ ],
158
+ urlTransform: urlTransform ?? defaultUrlTransform,
159
+ ownCode: Boolean(components?.pre || components?.code),
160
+ ownTable: Boolean(components?.table || components?.thead || components?.tbody || extensions.length)
161
+ }), [
162
+ htmlStage,
163
+ math,
164
+ urlTransform,
165
+ components,
166
+ extensions,
167
+ entities,
168
+ labelValue
169
+ ]);
170
+ const context = useMemo(() => {
171
+ if (mermaid) setMermaidLoader(mermaid);
172
+ return {
173
+ highlight,
174
+ mermaid: mermaid ? "page" : null
175
+ };
176
+ }, [highlight, mermaid]);
177
+ const groups = [];
178
+ for (let from = 0; from < blocks.length; from += GROUP) {
179
+ const to = Math.min(from + GROUP, blocks.length);
180
+ const last = to === blocks.length;
181
+ groups.push(/* @__PURE__ */ jsx(BlockGroup, {
182
+ blocks,
183
+ ids,
184
+ from,
185
+ to,
186
+ closer: last ? closer : "",
187
+ growing: last && endsInFence,
188
+ options
189
+ }, from));
190
+ }
191
+ return /* @__PURE__ */ jsx(LabelsContext.Provider, {
192
+ value: labelValue,
193
+ children: /* @__PURE__ */ jsx(RenderContextValue.Provider, {
194
+ value: context,
195
+ children: /* @__PURE__ */ jsx("div", {
196
+ className: unstyled ? className : className ? `flowdown ${className}` : "flowdown",
197
+ "data-streaming": streaming || void 0,
198
+ "data-caret": streaming && caret || void 0,
199
+ children: groups
200
+ })
201
+ })
202
+ });
203
+ }
204
+ export { Markdown, buildComponents };
205
+
206
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","names":[],"sources":["../../src/react/markdown.tsx"],"sourcesContent":["'use client'\nimport { DEFAULT_CATCH_UP_MS, useSmoothText } from './use-smooth-text'\nimport { useContext, Fragment, memo, useMemo, useState, type ReactNode } from 'react'\nimport { normalizeLineEndings, type MarkdownBlock } from '../core/blocks'\nimport { createDocument, usesHtml, usesMath, usesRareEntity } from '../document'\nimport { flowdownDialect } from '../core/remark-answer-blocks'\nimport { DIAGRAM_FENCE_LANGUAGE } from '../dialect'\nimport { addEntities, hasAllEntities } from '../engine/entities'\nimport type { Extension } from '../engine/types'\nimport { Diagram } from '../mermaid/diagram'\nimport { setMermaidLoader, type MermaidLoader } from '../mermaid/loader'\nimport { Callout } from './callout'\nimport { Code, CodeBlock } from './code-block'\nimport { createFenceMemory, GrowingCode, growingFence } from './growing-code'\nimport {\n GrowingFence,\n RenderContextValue,\n type Highlighter,\n type MarkdownComponents,\n type RenderContext,\n} from './context'\nimport { FileTree } from './file-tree'\nimport { LabelsContext, mergeLabels, type LabelOverrides } from './labels'\nimport { defaultUrlTransform, type UrlTransform } from '../engine/url'\nimport { draw, type BlockOptions } from './draw'\nimport type { Components, RenderOptions } from './render'\nimport { useStage, type StageOption } from './use-stage'\nimport { Table } from './table'\nimport { TableRows, tableRows } from './table-rows'\nimport { Tab, Tabs } from './tabs'\n\nexport interface MarkdownProps {\n /** The Markdown. */\n children: string\n /**\n * The text is a reply still arriving. Its unfinished end is completed so it does not flash raw\n * syntax (`**bo` reads as bold, a fence still being written as code), and a caret follows it.\n */\n streaming?: boolean\n /**\n * Show a streaming reply at an even pace instead of in the bursts it arrives in: whole words,\n * a few frames behind the text, finishing at the same pace when the reply ends. `true`, or the\n * time in milliseconds a burst takes to show (240 by default). Off by default.\n */\n smooth?: boolean | number\n /** The caret after streaming text; on by default while `streaming`. */\n caret?: boolean\n /**\n * Your own elements for any tag (`a`, `img`, `pre`, `table`, …) or the dialect's, and your own\n * drawing of a ```mermaid fence (`diagram`) or a ```tree fence (`tree`).\n */\n components?: MarkdownComponents\n /**\n * Your own syntax, after the dialect's: inline rules, fences by language, and passes over the\n * tree, each run on every block. A node's position counts from the start of its block. Keep the\n * list's reference stable (a module constant): a new list reads every block again.\n */\n extensions?: Extension[]\n /**\n * Give headings ids (`setup`, `setup-1`) to link to, the same ids `extractHeadings` returns.\n * Each block's headings are read once, so it costs a streaming reply only its last block.\n */\n headingIds?: boolean\n /**\n * Raw HTML in the text, parsed and sanitized. `true` (the default) fetches the parser the first\n * time a document holds HTML, and leaves the tags out until it is there; pass `@localix-ai/flowdown/html`\n * itself to have it in the first render (a server); `false` drops raw HTML.\n */\n html?: boolean | StageOption<Extension>\n /** Typeset `$…$`: `@localix-ai/flowdown/math`, or a loader for it. Off by default. */\n math?: StageOption<Extension>\n /** Draw ```mermaid fences: `() => import('mermaid')`. Off by default: they show as code. */\n mermaid?: MermaidLoader | null\n /** Highlight fenced code: `@localix-ai/flowdown/highlight`, or your own. Off by default. */\n highlight?: Highlighter | null\n /** The words the renderer adds (callout names, button labels), for a page in another language. */\n labels?: LabelOverrides\n /**\n * Rewrite or refuse a link or image URL. Omitted (or null), relative URLs and http, https,\n * mailto, irc, ircs and xmpp are kept and everything else is emptied (`javascript:`, `tel:` and\n * `data:` among them).\n */\n urlTransform?: UrlTransform | null\n className?: string\n /**\n * Leaves the `flowdown` class off the root element, so the package's stylesheet does not\n * apply: for a small surface that draws every element with `components` at its own scale.\n */\n unstyled?: boolean\n}\n\ninterface BlockProps {\n source: string\n /**\n * The line that closes the fence the reply ends inside, on the last block only: kept beside\n * the text, not joined to it, so a long fence is not copied on every token.\n */\n closer: string\n offset: number\n growing: boolean\n /** The anchors of the headings inside this block, by their offset in the document. */\n headingIds: ReadonlyMap<number, string> | null\n /** The same anchors as text: the memo compares this, since the map is new on every render. */\n headingKey: string\n options: BlockOptions\n}\n\nexport { draw, type BlockOptions } from './draw'\n\n/** The package's own HTML stage, fetched as a chunk of its own the first time it is needed. */\nconst loadHtml = () => import('../html/index')\n\n/** Every HTML5 named reference: ten kilobytes fetched the first time a document uses a rare one. */\nconst loadEntities = () =>\n import('../engine/entity-table').then((module) => {\n addEntities(module.ENTITY_TABLE)\n return module\n })\n\n/** A page's own diagram, told whether its fence is the one still being written. */\nfunction OwnDiagram({\n as: Page,\n source,\n}: {\n as: NonNullable<MarkdownComponents['diagram']>\n source: string\n}) {\n return <Page source={source} growing={useContext(GrowingFence)} />\n}\n\n/**\n * The elements a document is drawn with: the dialect's (callouts, tabs, code blocks), then the\n * page's. The two fences that are not code (```mermaid, ```tree) are drawn by `special`, outside\n * both `pre` and `code`, so a page's own code block never receives a diagram.\n */\nexport function buildComponents(overrides: MarkdownComponents | undefined): {\n components: Components\n special: RenderOptions['special']\n} {\n const { diagram: PageDiagram, tree: PageTree, ...elements } = overrides ?? {}\n return {\n components: {\n blockquote: Callout,\n table: Table,\n 'answer-tabs': Tabs,\n 'answer-tab': Tab,\n pre: CodeBlock,\n code: Code,\n ...elements,\n } as Components,\n special: (language, source) =>\n language === DIAGRAM_FENCE_LANGUAGE ? (\n PageDiagram ? (\n <OwnDiagram as={PageDiagram} source={source} />\n ) : (\n <Diagram source={source} />\n )\n ) : PageTree ? (\n <PageTree source={source} />\n ) : (\n <FileTree source={source} />\n ),\n }\n}\n\nconst Block = memo(\n function Block({ source: open, closer, offset, growing, headingIds, options }: BlockProps) {\n const [memory] = useState(createFenceMemory)\n const fence = growing && !options.ownCode ? growingFence(open, closer, memory) : null\n if (\n fence &&\n fence.language !== DIAGRAM_FENCE_LANGUAGE &&\n fence.language !== 'tree' &&\n // A `math` fence is a formula where the page typesets math, and code where it does not.\n !(fence.language === 'math' && options.extensions?.some((stage) => stage.name === 'math'))\n )\n return <GrowingCode {...fence} />\n const source = closer ? open + closer : open\n // A table that is the whole block is drawn row by row, unless the page reads tables itself\n // (its own elements or extensions see the table whole).\n const table = options.ownTable ? null : tableRows(source)\n if (table) return <TableRows {...table} options={options} />\n return (\n <GrowingFence.Provider value={growing}>\n {draw(source, offset, headingIds, options)}\n </GrowingFence.Provider>\n )\n },\n (before, after) =>\n before.source === after.source &&\n before.closer === after.closer &&\n before.offset === after.offset &&\n before.growing === after.growing &&\n before.headingKey === after.headingKey &&\n before.options === after.options,\n)\n\nconst NO_EXTENSIONS: Extension[] = []\n\n/**\n * How many blocks a group holds. Small enough that the group a reply is growing in is cheap to\n * go through on every token, large enough that a long reply is few groups.\n */\nconst GROUP = 32\n\nconst headingKeyOf = (own: ReadonlyMap<number, string> | null | undefined): string =>\n own ? [...own].map(([at, id]) => `${at}:${id}`).join(';') : ''\n\ninterface BlockGroupProps {\n /** The document's blocks and anchors; the group draws the ones from `from` up to `to`. */\n blocks: readonly MarkdownBlock[]\n ids: ReadonlyArray<ReadonlyMap<number, string>> | null\n from: number\n to: number\n /** The closing line and the growing fence of the document's last block, when it is in here. */\n closer: string\n growing: boolean\n options: BlockOptions\n}\n\n/**\n * The blocks at one run of places in the document. A block never moves to another group, so it\n * keeps what it holds (an open tab, a drawn diagram) for as long as it is there. While a reply\n * streams only its last group changes: React passes over each group above as one element, where\n * a flat list had it compare every block of the reply on every token.\n */\nconst BlockGroup = memo(\n function BlockGroup({ blocks, ids, from, to, closer, growing, options }: BlockGroupProps) {\n const drawn: ReactNode[] = []\n for (let index = from; index < to; index++) {\n const block = blocks[index]!\n // A block gets only its own anchors, keyed by their text: a map is new on every render,\n // and comparing it would redraw every block.\n const own = ids?.[index] ?? null\n const last = index === to - 1\n drawn.push(\n <Fragment key={index}>\n {/* One parse puts a line break between top-level blocks; so does this, so the text of\n the page (and a copy of it) is the same either way. */}\n {index > 0 ? '\\n' : null}\n <Block\n source={block.source}\n closer={last ? closer : ''}\n offset={block.offset}\n growing={last && growing}\n headingIds={own && own.size > 0 ? own : null}\n headingKey={headingKeyOf(own)}\n options={options}\n />\n </Fragment>,\n )\n }\n return drawn\n },\n (before, after) => {\n if (\n before.from !== after.from ||\n before.to !== after.to ||\n before.closer !== after.closer ||\n before.growing !== after.growing ||\n before.options !== after.options ||\n (before.ids === null) !== (after.ids === null)\n )\n return false\n // From the end: in the group a reply is growing in, the block that changed is the last one.\n for (let index = after.to - 1; index >= after.from; index--) {\n const was = before.blocks[index]!\n const is = after.blocks[index]!\n // The splitter hands back a block that did not change as the same object.\n if (was !== is && (was.source !== is.source || was.offset !== is.offset)) return false\n if (after.ids) {\n const had = before.ids![index]\n const has = after.ids[index]\n // Most blocks have no heading: two empty maps are not spelled out to be compared.\n if (had !== has && ((had?.size ?? 0) > 0 || (has?.size ?? 0) > 0)) {\n if (headingKeyOf(had) !== headingKeyOf(has)) return false\n }\n }\n }\n return true\n },\n)\n\n/**\n * Markdown with the flowdown dialect: GitHub Markdown, callouts, steps, tabs, stats, pros and cons,\n * figures, file trees and, when the page supplies them, diagrams, formulas and highlighted code.\n *\n * The document is drawn as its top-level blocks, each remembered by its text: while a reply\n * streams, the blocks above its end do not change, and only the last one is parsed and drawn\n * again on each token.\n */\nexport function Markdown({\n children,\n streaming: arriving = false,\n smooth = false,\n caret: caretOption,\n components,\n extensions = NO_EXTENSIONS,\n headingIds = false,\n html = true,\n math: mathOption = null,\n mermaid = null,\n highlight = null,\n labels,\n urlTransform,\n className,\n unstyled = false,\n}: MarkdownProps): ReactNode {\n // `\\r\\n` from a Windows file or a server that sends it reads as `\\n` everywhere below.\n const whole = normalizeLineEndings(children)\n // Paced, the reply is still \"streaming\" until all of it is shown, a moment after it ended.\n const [text, behind] = useSmoothText(\n whole,\n arriving && smooth !== false,\n typeof smooth === 'number' ? smooth : DEFAULT_CATCH_UP_MS,\n )\n const streaming = arriving || behind\n const caret = caretOption ?? streaming\n // One document per `<Markdown>`: it completes the unfinished end of a streaming reply, cuts the\n // text into blocks and remembers them, so on the next token only the end of the reply is read.\n // Blocks that did not change come back as the same objects.\n const [doc] = useState(createDocument)\n const {\n blocks,\n closer,\n growing: endsInFence,\n headingIds: ids,\n } = doc.update(text, { streaming, math: Boolean(mathOption), headingIds })\n\n const math = useStage(mathOption, mathOption !== null && usesMath(blocks))\n // Whether the document has HTML is decided for the whole of it, not per block: a tag still\n // being typed (`<div`) is its own block, and only the whole document says it is HTML.\n const htmlStage = useStage(\n html === true ? loadHtml : html === false ? null : html,\n html !== false && usesHtml(blocks),\n )\n // A name outside the common ones shows as written until the whole table is there.\n const entities = useStage(loadEntities, !hasAllEntities() && usesRareEntity(blocks))\n const labelValue = useMemo(() => mergeLabels(labels), [labels])\n const options = useMemo<BlockOptions>(\n () => ({\n ...buildComponents(components),\n labels: labelValue,\n // Raw HTML is made into elements last, so every pass before it sees the text as written.\n extensions: [\n flowdownDialect,\n ...(math ? [math] : []),\n ...extensions,\n ...(htmlStage ? [htmlStage] : []),\n ],\n urlTransform: urlTransform ?? defaultUrlTransform,\n ownCode: Boolean(components?.pre || components?.code),\n ownTable: Boolean(\n components?.table || components?.thead || components?.tbody || extensions.length,\n ),\n }),\n // `entities` is here so that blocks are read again once every name can be decoded.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n [htmlStage, math, urlTransform, components, extensions, entities, labelValue],\n )\n const context = useMemo<RenderContext>(() => {\n if (mermaid) setMermaidLoader(mermaid)\n return { highlight, mermaid: mermaid ? 'page' : null }\n }, [highlight, mermaid])\n const groups: ReactNode[] = []\n for (let from = 0; from < blocks.length; from += GROUP) {\n const to = Math.min(from + GROUP, blocks.length)\n const last = to === blocks.length\n groups.push(\n <BlockGroup\n key={from}\n blocks={blocks}\n ids={ids}\n from={from}\n to={to}\n closer={last ? closer : ''}\n growing={last && endsInFence}\n options={options}\n />,\n )\n }\n\n return (\n <LabelsContext.Provider value={labelValue}>\n <RenderContextValue.Provider value={context}>\n <div\n className={unstyled ? className : className ? `flowdown ${className}` : 'flowdown'}\n data-streaming={streaming || undefined}\n data-caret={(streaming && caret) || undefined}\n >\n {groups}\n </div>\n </RenderContextValue.Provider>\n </LabelsContext.Provider>\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Node } from "../engine/types.js";
2
+ import { ComponentType } from "react";
3
+ /** An element of the page's own for a tag: it gets the tag's props, and the node it draws. */
4
+ export type Components = { [Tag in keyof React.JSX.IntrinsicElements]?: ComponentType<React.JSX.IntrinsicElements[Tag] & {
5
+ node?: Node;
6
+ }>; } & {
7
+ [tag: string]: ComponentType<any> | undefined;
8
+ };
@@ -0,0 +1,41 @@
1
+ import { createRenderer } from "../render/tree.js";
2
+ import { Fragment, createElement, isValidElement } from "react";
3
+ // src/react/render.tsx
4
+ const read = (children, out) => {
5
+ for (const child of children) if (typeof child === "string") out.push(child);
6
+ else if (typeof child === "number") out.push(String(child));
7
+ else if (Array.isArray(child)) read(child, out);
8
+ else if (isValidElement(child)) {
9
+ const props = child.props;
10
+ const inner = props.children === void 0 || props.children === null ? [] : Array.isArray(props.children) ? props.children : [props.children];
11
+ if (child.type === Fragment) read(inner, out);
12
+ else out.push({
13
+ tag: child.type,
14
+ props,
15
+ children: inner
16
+ });
17
+ }
18
+ return out;
19
+ };
20
+ /** React's elements, as the factory every HTML renderer of the package is written against. */
21
+ const react = {
22
+ element(tag, props, children) {
23
+ if (props !== null && props.innerHTML !== void 0) {
24
+ const { innerHTML, ...rest } = props;
25
+ return createElement(tag, {
26
+ ...rest,
27
+ dangerouslySetInnerHTML: { __html: innerHTML }
28
+ });
29
+ }
30
+ return createElement(tag, props, ...children);
31
+ },
32
+ text: (value) => value,
33
+ fragment: (children) => createElement(Fragment, null, ...children),
34
+ read: (children) => read(children, [])
35
+ };
36
+ const draw = createRenderer(react);
37
+ /** Draws a tree. Every call gives new elements; the caller remembers them by the tree's text. */
38
+ const render = (root, options) => draw(root, options);
39
+ export { react, render };
40
+
41
+ //# sourceMappingURL=render.js.map