@localix-ai/flowdown 0.0.0-stage → 0.5.0

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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -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 +463 -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/dollars.d.ts +12 -0
  13. package/dist/core/dollars.js +88 -0
  14. package/dist/core/dollars.js.map +1 -0
  15. package/dist/core/fence-scan.js +53 -0
  16. package/dist/core/fence-scan.js.map +1 -0
  17. package/dist/core/file-tree-parse.d.ts +26 -0
  18. package/dist/core/file-tree-parse.js +51 -0
  19. package/dist/core/file-tree-parse.js.map +1 -0
  20. package/dist/core/forms-html.js +63 -0
  21. package/dist/core/forms-html.js.map +1 -0
  22. package/dist/core/forms.d.ts +28 -0
  23. package/dist/core/forms.js +23 -0
  24. package/dist/core/forms.js.map +1 -0
  25. package/dist/core/headings.d.ts +11 -0
  26. package/dist/core/headings.js +74 -0
  27. package/dist/core/headings.js.map +1 -0
  28. package/dist/core/heal.d.ts +11 -0
  29. package/dist/core/heal.js +220 -0
  30. package/dist/core/heal.js.map +1 -0
  31. package/dist/core/load-once.js +23 -0
  32. package/dist/core/load-once.js.map +1 -0
  33. package/dist/core/math-delimiters.js +33 -0
  34. package/dist/core/math-delimiters.js.map +1 -0
  35. package/dist/core/math-syntax.js +56 -0
  36. package/dist/core/math-syntax.js.map +1 -0
  37. package/dist/core/prose.d.ts +10 -0
  38. package/dist/core/prose.js +77 -0
  39. package/dist/core/prose.js.map +1 -0
  40. package/dist/core/remark-answer-blocks.d.ts +5 -0
  41. package/dist/core/remark-answer-blocks.js +172 -0
  42. package/dist/core/remark-answer-blocks.js.map +1 -0
  43. package/dist/core/remark-answer-containers.js +299 -0
  44. package/dist/core/remark-answer-containers.js.map +1 -0
  45. package/dist/core/sanitize-schema.d.ts +18 -0
  46. package/dist/core/sanitize-schema.js +62 -0
  47. package/dist/core/sanitize-schema.js.map +1 -0
  48. package/dist/core/syntax.d.ts +8 -0
  49. package/dist/core/syntax.js +32 -0
  50. package/dist/core/syntax.js.map +1 -0
  51. package/dist/dialect.d.ts +92 -0
  52. package/dist/dialect.js +176 -0
  53. package/dist/dialect.js.map +1 -0
  54. package/dist/document/fence.d.ts +25 -0
  55. package/dist/document/fence.js +101 -0
  56. package/dist/document/fence.js.map +1 -0
  57. package/dist/document/index.d.ts +46 -0
  58. package/dist/document/index.js +66 -0
  59. package/dist/document/index.js.map +1 -0
  60. package/dist/document/table.d.ts +11 -0
  61. package/dist/document/table.js +35 -0
  62. package/dist/document/table.js.map +1 -0
  63. package/dist/dom/index.d.ts +21 -0
  64. package/dist/dom/index.js +123 -0
  65. package/dist/dom/index.js.map +1 -0
  66. package/dist/dom/patch.js +104 -0
  67. package/dist/dom/patch.js.map +1 -0
  68. package/dist/element/index.d.ts +29 -0
  69. package/dist/element/index.js +64 -0
  70. package/dist/element/index.js.map +1 -0
  71. package/dist/engine/blocks.js +774 -0
  72. package/dist/engine/blocks.js.map +1 -0
  73. package/dist/engine/entities.js +83 -0
  74. package/dist/engine/entities.js.map +1 -0
  75. package/dist/engine/entity-core.js +9 -0
  76. package/dist/engine/entity-core.js.map +1 -0
  77. package/dist/engine/entity-table.js +9 -0
  78. package/dist/engine/entity-table.js.map +1 -0
  79. package/dist/engine/html.d.ts +7 -0
  80. package/dist/engine/html.js +207 -0
  81. package/dist/engine/html.js.map +1 -0
  82. package/dist/engine/index.d.ts +7 -0
  83. package/dist/engine/index.js +74 -0
  84. package/dist/engine/index.js.map +1 -0
  85. package/dist/engine/inlines.js +852 -0
  86. package/dist/engine/inlines.js.map +1 -0
  87. package/dist/engine/types.d.ts +109 -0
  88. package/dist/engine/url.d.ts +11 -0
  89. package/dist/engine/url.js +16 -0
  90. package/dist/engine/url.js.map +1 -0
  91. package/dist/engine/visit.d.ts +10 -0
  92. package/dist/engine/visit.js +29 -0
  93. package/dist/engine/visit.js.map +1 -0
  94. package/dist/highlight/highlight.d.ts +4 -0
  95. package/dist/highlight/highlight.js +144 -0
  96. package/dist/highlight/highlight.js.map +1 -0
  97. package/dist/highlight/index.d.ts +4 -0
  98. package/dist/highlight/index.js +18 -0
  99. package/dist/highlight/index.js.map +1 -0
  100. package/dist/html/index.d.ts +4 -0
  101. package/dist/html/index.js +544 -0
  102. package/dist/html/index.js.map +1 -0
  103. package/dist/index.d.ts +27 -0
  104. package/dist/index.js +13 -0
  105. package/dist/math/index.d.ts +17 -0
  106. package/dist/math/index.js +20 -0
  107. package/dist/math/index.js.map +1 -0
  108. package/dist/math/typeset.d.ts +12 -0
  109. package/dist/math/typeset.js +74 -0
  110. package/dist/math/typeset.js.map +1 -0
  111. package/dist/mermaid/diagram.d.ts +8 -0
  112. package/dist/mermaid/diagram.js +114 -0
  113. package/dist/mermaid/diagram.js.map +1 -0
  114. package/dist/mermaid/drawing.js +139 -0
  115. package/dist/mermaid/drawing.js.map +1 -0
  116. package/dist/mermaid/icons.js +16 -0
  117. package/dist/mermaid/icons.js.map +1 -0
  118. package/dist/mermaid/index.d.ts +13 -0
  119. package/dist/mermaid/index.js +3 -0
  120. package/dist/mermaid/loader.d.ts +5 -0
  121. package/dist/mermaid/loader.js +18 -0
  122. package/dist/mermaid/loader.js.map +1 -0
  123. package/dist/mermaid/look.d.ts +16 -0
  124. package/dist/mermaid/look.js +167 -0
  125. package/dist/mermaid/look.js.map +1 -0
  126. package/dist/mermaid/normalize-svg.js +44 -0
  127. package/dist/mermaid/normalize-svg.js.map +1 -0
  128. package/dist/mermaid/renderer.d.ts +22 -0
  129. package/dist/mermaid/renderer.js +132 -0
  130. package/dist/mermaid/renderer.js.map +1 -0
  131. package/dist/mermaid/viewer.js +309 -0
  132. package/dist/mermaid/viewer.js.map +1 -0
  133. package/dist/prompt/index.d.ts +19 -0
  134. package/dist/prompt/index.js +54 -0
  135. package/dist/prompt/index.js.map +1 -0
  136. package/dist/react/callout.d.ts +9 -0
  137. package/dist/react/callout.js +12 -0
  138. package/dist/react/callout.js.map +1 -0
  139. package/dist/react/code-block.d.ts +18 -0
  140. package/dist/react/code-block.js +81 -0
  141. package/dist/react/code-block.js.map +1 -0
  142. package/dist/react/context.d.ts +18 -0
  143. package/dist/react/context.js +17 -0
  144. package/dist/react/context.js.map +1 -0
  145. package/dist/react/draw.d.ts +2 -0
  146. package/dist/react/draw.js +16 -0
  147. package/dist/react/draw.js.map +1 -0
  148. package/dist/react/file-tree.d.ts +16 -0
  149. package/dist/react/file-tree.js +69 -0
  150. package/dist/react/file-tree.js.map +1 -0
  151. package/dist/react/growing-code.js +36 -0
  152. package/dist/react/growing-code.js.map +1 -0
  153. package/dist/react/helpers.d.ts +6 -0
  154. package/dist/react/helpers.js +17 -0
  155. package/dist/react/helpers.js.map +1 -0
  156. package/dist/react/icons.js +23 -0
  157. package/dist/react/icons.js.map +1 -0
  158. package/dist/react/labels.js +9 -0
  159. package/dist/react/labels.js.map +1 -0
  160. package/dist/react/markdown.d.ts +78 -0
  161. package/dist/react/markdown.js +204 -0
  162. package/dist/react/markdown.js.map +1 -0
  163. package/dist/react/render.d.ts +8 -0
  164. package/dist/react/render.js +41 -0
  165. package/dist/react/render.js.map +1 -0
  166. package/dist/react/table-rows.js +66 -0
  167. package/dist/react/table-rows.js.map +1 -0
  168. package/dist/react/table.js +36 -0
  169. package/dist/react/table.js.map +1 -0
  170. package/dist/react/tabs.d.ts +25 -0
  171. package/dist/react/tabs.js +58 -0
  172. package/dist/react/tabs.js.map +1 -0
  173. package/dist/react/use-smooth-text.d.ts +6 -0
  174. package/dist/react/use-smooth-text.js +156 -0
  175. package/dist/react/use-smooth-text.js.map +1 -0
  176. package/dist/react/use-stage.d.ts +9 -0
  177. package/dist/react/use-stage.js +57 -0
  178. package/dist/react/use-stage.js.map +1 -0
  179. package/dist/remark.d.ts +20 -0
  180. package/dist/remark.js +10 -0
  181. package/dist/render/factory.d.ts +15 -0
  182. package/dist/render/labels.d.ts +41 -0
  183. package/dist/render/labels.js +43 -0
  184. package/dist/render/labels.js.map +1 -0
  185. package/dist/render/tree.js +312 -0
  186. package/dist/render/tree.js.map +1 -0
  187. package/dist/render/view.d.ts +55 -0
  188. package/dist/render/view.js +302 -0
  189. package/dist/render/view.js.map +1 -0
  190. package/dist/render/views.d.ts +16 -0
  191. package/dist/render/views.js +261 -0
  192. package/dist/render/views.js.map +1 -0
  193. package/dist/render/vnode.d.ts +21 -0
  194. package/dist/render/vnode.js +194 -0
  195. package/dist/render/vnode.js.map +1 -0
  196. package/dist/static/index.d.ts +28 -0
  197. package/dist/static/index.js +27 -0
  198. package/dist/static/index.js.map +1 -0
  199. package/dist/stream.d.ts +12 -0
  200. package/dist/stream.js +3 -0
  201. package/dist/styles.css +1351 -0
  202. package/dist/tui/code.d.ts +5 -0
  203. package/dist/tui/code.js +240 -0
  204. package/dist/tui/code.js.map +1 -0
  205. package/dist/tui/index.d.ts +54 -0
  206. package/dist/tui/index.js +99 -0
  207. package/dist/tui/index.js.map +1 -0
  208. package/dist/tui/math.js +242 -0
  209. package/dist/tui/math.js.map +1 -0
  210. package/dist/tui/printable.d.ts +12 -0
  211. package/dist/tui/printable.js +31 -0
  212. package/dist/tui/printable.js.map +1 -0
  213. package/dist/tui/render.d.ts +12 -0
  214. package/dist/tui/render.js +524 -0
  215. package/dist/tui/render.js.map +1 -0
  216. package/dist/tui/style.d.ts +35 -0
  217. package/dist/tui/style.js +77 -0
  218. package/dist/tui/style.js.map +1 -0
  219. package/dist/tui/table.js +73 -0
  220. package/dist/tui/table.js.map +1 -0
  221. package/dist/tui/width.d.ts +2 -0
  222. package/dist/tui/width.js +133 -0
  223. package/dist/tui/width.js.map +1 -0
  224. package/dist/tui/wrap.d.ts +8 -0
  225. package/dist/tui/wrap.js +190 -0
  226. package/dist/tui/wrap.js.map +1 -0
  227. package/package.json +127 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"views.js","names":[],"sources":["../../src/render/views.ts"],"sourcesContent":["/**\n * What the dialect's own elements are made of, written once for every target that has elements.\n *\n * A view is a function of a {@link Factory} and of what the element shows right now: which tab is\n * open, whether the code was just copied. It keeps no state and listens to nothing: a target that\n * can (React, the DOM) holds the state and hands in the listeners, and one that cannot (a server)\n * draws the element as it first appears.\n */\nimport type { TreeRow } from '../core/file-tree-parse'\nimport { CALLOUT_KINDS, type CalloutKind } from '../dialect'\nimport type { Factory, Props } from './factory'\nimport type { MarkdownLabels } from './labels'\n\n/**\n * The few glyphs the renderer draws, inline. A dependency on an icon library for eleven outlines\n * would cost every consumer an install, and a theme cannot restyle an icon font as simply as an\n * SVG that strokes with `currentColor`.\n *\n * Each glyph is one `<path>`: its strokes are subpaths of the same outline, and its circle or\n * rounded square is written out as arcs. A reply with a dozen code blocks and callouts draws\n * dozens of icons, and every stroke that is its own element is one more node on the page.\n *\n * Outlines from Lucide (https://lucide.dev), ISC License, Copyright (c) Lucide Contributors.\n */\nexport const OUTLINES = {\n info: 'M2 12a10 10 0 1 0 20 0 10 10 0 1 0-20 0zM12 16v-4M12 8h.01',\n tip: 'M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5M9 18h6M10 22h4',\n important: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2zM12 7v2M12 13h.01',\n warning:\n 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4M12 17h.01',\n caution:\n 'M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586zM12 8v4M12 16h.01',\n chevron: 'm9 18 6-6-6-6',\n file: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM14 2v4a2 2 0 0 0 2 2h4',\n folder:\n 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z',\n folderOpen:\n 'm6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2',\n copy: 'M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2zM4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2',\n check: 'M20 6 9 17l-5-5',\n download: 'M12 15V3M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5',\n} as const\n\n/** One glyph. `props` go on its `<svg>` after its own. */\nexport const glyph = <E, C>(f: Factory<E, C>, outline: string, props?: Props | null): E =>\n f.element(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.75,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n focusable: 'false',\n ...props,\n },\n [f.element('path', { d: outline }, [])],\n )\n\n/** The language a `<code>`'s class names: `language-ts` is `ts`. */\nexport function getLanguage(className?: string): string | undefined {\n return /language-([^\\s]+)/.exec(className ?? '')?.[1]\n}\n\n/** The text of some children, whatever elements it is in. */\nexport function textOf<E, C>(f: Factory<E, C>, children: E[]): string {\n let out = ''\n for (const child of f.read(children))\n out += typeof child === 'string' ? child : textOf(f, child.children)\n return out\n}\n\n// A Record over the dialect's kinds, so a kind added there fails the type check here.\nconst CALLOUT_OUTLINES: Record<CalloutKind, string> = {\n note: OUTLINES.info,\n tip: OUTLINES.tip,\n important: OUTLINES.important,\n warning: OUTLINES.warning,\n caution: OUTLINES.caution,\n}\n\nfunction calloutKind(value: unknown): CalloutKind | null {\n return typeof value === 'string' && (CALLOUT_KINDS as readonly string[]).includes(value)\n ? (value as CalloutKind)\n : null\n}\n\n/**\n * A quote, or, when the dialect marked it, a typed callout (`> [!WARNING]`). An `aside` with\n * `role=\"note\"` rather than a styled quote: a warning is not a quotation, and assistive tech should\n * announce it as the aside it is, its kind first.\n *\n * The kind is carried by the glyph alone, in its colour. Its name is spoken, not printed, and a\n * title is printed only when the author wrote one (`> [!TIP] Faster builds`).\n */\nexport function callout<E, C>(\n f: Factory<E, C>,\n {\n node: _node,\n 'data-callout': marked,\n 'data-callout-title': title,\n className,\n cite,\n ...quoteProps\n }: Props,\n children: E[],\n labels: MarkdownLabels,\n): E {\n const kind = calloutKind(marked)\n if (!kind) return f.element('blockquote', { className, cite, ...quoteProps }, children)\n return f.element(\n 'aside',\n {\n ...quoteProps,\n className: className ? `fd-callout ${className as string}` : 'fd-callout',\n 'data-callout': kind,\n role: 'note',\n 'aria-label': title || labels.callouts[kind],\n },\n [\n glyph(f, CALLOUT_OUTLINES[kind], { className: 'fd-callout-icon' }),\n f.element('div', { className: 'fd-callout-body' }, [\n ...(title\n ? [f.element('div', { className: 'fd-callout-title' }, [f.text(title as string)])]\n : []),\n ...children,\n ]),\n ],\n )\n}\n\n/**\n * Turns a fence's code into highlighted HTML, or returns null to leave it plain. The HTML goes into\n * the page as is, so a highlighter must escape the code it was given (highlight.js and Shiki do);\n * `@localix-ai/flowdown/highlight` is one.\n */\nexport type Highlighter = (code: string, language: string) => string | null\n\n/**\n * Code, inline or in a fence. A fenced block is highlighted when the page gave a highlighter and\n * the fence names a language it knows; otherwise its text is printed as is.\n */\nexport function code<E, C>(\n f: Factory<E, C>,\n { node: _node, className, ...props }: Props,\n children: E[],\n highlight: Highlighter | null,\n): E {\n const language = getLanguage(className as string | undefined)\n const html =\n language && highlight ? highlight(textOf(f, children).replace(/\\n$/, ''), language) : null\n if (html === null) return f.element('code', { className, ...props }, children)\n return f.element(\n 'code',\n {\n className: [className, 'fd-highlighted'].filter(Boolean).join(' '),\n ...props,\n innerHTML: html,\n },\n [],\n )\n}\n\n/** What a code block's bar and buttons need of a `<pre>`'s children: its one `<code>`. */\nexport function readCodeBlock<E, C>(\n f: Factory<E, C>,\n children: E[],\n): { language: string | undefined; title: string | undefined; text: string } {\n const items = f.read(children)\n const only = items.length === 1 && typeof items[0] !== 'string' ? items[0]! : null\n return {\n language: getLanguage(only?.props.className as string | undefined),\n title: only?.props['data-title'] as string | undefined,\n text: only ? textOf(f, only.children).replace(/\\n$/, '') : '',\n }\n}\n\n/** The copy button of a code block, before and after a copy. */\nexport const copyButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n copied: boolean,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-copy',\n onClick,\n 'aria-label': copied ? labels.copied : labels.copy,\n title: copied ? labels.copied : labels.copy,\n 'data-copied': copied || undefined,\n },\n [glyph(f, copied ? OUTLINES.check : OUTLINES.copy)],\n )\n\n/** Saves the code as a file: named as its fence's title, or `code.<ext>` from its language. */\nexport const downloadButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-download',\n onClick,\n 'aria-label': labels.download,\n title: labels.download,\n },\n [glyph(f, OUTLINES.download)],\n )\n\n/** The bar of a code block: the file its fence names, its language, and the two buttons. */\nexport const codeBar = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n title: string | undefined,\n buttons: E[],\n): E =>\n f.element('figcaption', { className: 'fd-code-bar' }, [\n ...(title ? [f.element('span', { className: 'fd-code-title' }, [f.text(title)])] : []),\n ...(language ? [f.element('span', { className: 'fd-code-language' }, [f.text(language)])] : []),\n ...buttons,\n ])\n\n/**\n * The frame of a code block: the bar, then the `<pre>`. A fence that names its file\n * (` ```ts title=\"src/app.ts\" `) shows the file first, the language after it; a fence with no\n * language shows none.\n */\nexport const codeFrame = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n bar: E,\n preProps: Props | null | undefined,\n children: E[],\n): E =>\n f.element('figure', { className: 'fd-code', 'data-language': language }, [\n bar,\n f.element('pre', { ...preProps }, children),\n ])\n\n/** One variant of a `[!TABS]` form: its label and what it shows. */\nexport interface TabView<E> {\n label: string | undefined\n children: E[]\n}\n\n/** The variants among some children: every element, whatever is between them. */\nexport function readTabs<E, C>(f: Factory<E, C>, children: E[]): Array<TabView<E>> {\n const list: Array<TabView<E>> = []\n for (const child of f.read(children))\n if (typeof child !== 'string')\n list.push({\n label: child.props['data-label'] as string | undefined,\n children: child.children,\n })\n return list\n}\n\n/**\n * `[!TABS]`: the variants of one answer (an install per OS, an example per language), of which\n * the reader needs one. Every panel stays in the document, `hidden`, so a search on the page, a\n * copy of the whole reply and a screen reader's panel navigation still reach the others.\n *\n * The WAI-ARIA tabs pattern: one Tab stop for the row, each tab names the panel it controls.\n * `on` gives a tab its listeners and its handle (`onClick`, `onKeyDown`, `ref`).\n */\nexport function tabs<E, C>(\n f: Factory<E, C>,\n list: Array<TabView<E>>,\n current: number,\n base: string,\n labels: MarkdownLabels,\n on?: (index: number) => Props,\n): E {\n const tabId = (index: number) => `${base}-tab-${index}`\n const panelId = (index: number) => `${base}-panel-${index}`\n return f.element('div', { className: 'fd-tabs' }, [\n f.element(\n 'div',\n { className: 'fd-tab-list', role: 'tablist', 'aria-label': labels.variants },\n list.map((tab, index) =>\n f.element(\n 'button',\n {\n key: index,\n type: 'button',\n role: 'tab',\n id: tabId(index),\n className: 'fd-tab',\n 'aria-selected': index === current,\n 'aria-controls': panelId(index),\n tabIndex: index === current ? 0 : -1,\n ...on?.(index),\n },\n [f.text(tab.label || String(index + 1))],\n ),\n ),\n ),\n ...list.map((tab, index) =>\n f.element(\n 'div',\n {\n key: index,\n role: 'tabpanel',\n id: panelId(index),\n 'aria-labelledby': tabId(index),\n className: 'fd-tab-panel',\n hidden: index !== current,\n },\n tab.children,\n ),\n ),\n ])\n}\n\n/**\n * A table in a box of its own that scrolls sideways: a wide comparison then scrolls inside the\n * reply instead of widening the page. A box that scrolls is a region the Tab key stops on, so the\n * arrow keys can scroll it (WCAG 2.1.1); a table that fits stays out of the Tab order.\n */\nexport const table = <E, C>(\n f: Factory<E, C>,\n { node: _node, ...props }: Props,\n children: E[],\n scrolls: boolean,\n labels: MarkdownLabels,\n box?: Props,\n): E =>\n f.element(\n 'div',\n {\n ...box,\n className: 'fd-table',\n role: scrolls ? 'region' : undefined,\n 'aria-label': scrolls ? labels.scrollableTable : undefined,\n tabIndex: scrolls ? 0 : undefined,\n },\n [f.element('table', props, children)],\n )\n\n/** What a ```tree fence shows right now: which folders are shut, which row the Tab key stops on. */\nexport interface TreeState<E> {\n rows: TreeRow[]\n collapsed: ReadonlySet<string>\n /** The path of the one row in the Tab order; the first row shown when null or hidden. */\n focused: string | null\n /** The glyph before a row, for a page that colours files by type. */\n icon?: (row: TreeRow, open: boolean) => E\n /** A row's listeners and its handle. */\n on?: (row: TreeRow, visible: TreeRow[]) => Props\n}\n\n/** The rows of a tree that no shut folder hides. */\nexport function visibleRows(rows: TreeRow[], collapsed: ReadonlySet<string>): TreeRow[] {\n const hidden = (row: TreeRow): boolean => {\n for (let parent = row.parent; parent !== null; parent = rows[parent]!.parent)\n if (collapsed.has(rows[parent]!.path)) return true\n return false\n }\n return rows.filter((row) => !hidden(row))\n}\n\n/**\n * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. The\n * tree is one Tab stop walked with the arrow keys (the WAI-ARIA tree pattern).\n */\nexport function fileTree<E, C>(\n f: Factory<E, C>,\n { rows, collapsed, focused, icon, on }: TreeState<E>,\n labels: MarkdownLabels,\n): E {\n const visible = visibleRows(rows, collapsed)\n // One Tab stop: the row last focused, or the first while it is still shown.\n const rover = visible.some((row) => row.path === focused) ? focused : visible[0]?.path\n return f.element(\n 'div',\n { role: 'tree', 'aria-label': labels.fileTree, className: 'fd-tree' },\n visible.map((row) => {\n const open = !collapsed.has(row.path)\n return f.element(\n 'div',\n {\n key: row.path,\n role: 'treeitem',\n 'aria-level': row.depth + 1,\n 'aria-selected': false,\n ...(row.folder ? { 'aria-expanded': open } : {}),\n tabIndex: row.path === rover ? 0 : -1,\n className: 'fd-tree-row',\n 'data-folder': row.folder || undefined,\n style: { '--fd-tree-depth': row.depth },\n ...on?.(row, visible),\n },\n [\n ...(row.folder\n ? [\n glyph(f, OUTLINES.chevron, {\n className: 'fd-tree-chevron',\n 'data-open': open || undefined,\n }),\n ]\n : []),\n icon\n ? icon(row, open)\n : glyph(\n f,\n row.folder ? (open ? OUTLINES.folderOpen : OUTLINES.folder) : OUTLINES.file,\n { className: 'fd-tree-icon' },\n ),\n f.element('span', { className: 'fd-tree-name', title: row.name }, [\n f.text(row.name.replace(/\\/$/, '')),\n ]),\n ...(row.comment\n ? [\n f.element('span', { className: 'fd-tree-comment', title: row.comment }, [\n f.text(row.comment),\n ]),\n ]\n : []),\n ],\n )\n }),\n )\n}\n\n/**\n * A ```mermaid fence shown as the source it is: before it is drawn, where nothing draws it, or\n * when it could not be drawn, with a line saying why.\n */\nexport const diagramSource = <E, C>(\n f: Factory<E, C>,\n source: string,\n note: string | null,\n failed: boolean,\n figure?: Props,\n): E =>\n f.element(\n 'figure',\n { ...figure, className: 'fd-diagram', 'data-state': failed ? 'error' : 'source' },\n [\n f.element('pre', null, [\n f.element('code', { className: 'language-mermaid' }, [f.text(source)]),\n ]),\n ...(note\n ? [\n f.element('figcaption', { className: 'fd-diagram-note', role: 'status' }, [\n f.text(note),\n ]),\n ]\n : []),\n ],\n )\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Elements as plain values: what a target without elements of its own (a server writing HTML, a
3
+ * page with no framework) draws a document into. A tree of them is written out as text
4
+ * ({@link toHtml}) or put on a page and kept up to date (`dom/patch.ts`).
5
+ */
6
+ import { Props } from "./factory.js";
7
+ export interface VElement {
8
+ tag: string | VComponent;
9
+ props: Props | null;
10
+ children: VNode[];
11
+ }
12
+ /** A text, an element, or several of either where one is expected. */
13
+ export type VNode = string | VElement | VNode[];
14
+ /** An element of the page's own: it gets the tag's properties and children, and gives nodes. */
15
+ export type VComponent = (props: Props, children: VNode[]) => VNode;
16
+ export declare const escapeHtml: (text: string) => string;
17
+ /**
18
+ * `node` as HTML text. Text and attribute values are escaped; `innerHTML` (highlighted code, a
19
+ * typeset formula) goes in as it is, as its maker escaped it.
20
+ */
21
+ export declare function toHtml(node: VNode): string;
@@ -0,0 +1,194 @@
1
+ // src/render/vnode.ts
2
+ const read = (children, out) => {
3
+ for (const child of children) if (typeof child === "string") out.push(child);
4
+ else if (Array.isArray(child)) read(child, out);
5
+ else out.push({
6
+ tag: child.tag,
7
+ props: child.props ?? {},
8
+ children: child.children
9
+ });
10
+ return out;
11
+ };
12
+ const vnode = {
13
+ element: (tag, props, children) => ({
14
+ tag,
15
+ props,
16
+ children
17
+ }),
18
+ text: (value) => value,
19
+ fragment: (children) => children,
20
+ read: (children) => read(children, [])
21
+ };
22
+ /**
23
+ * `node` with every component in it called, outermost first: an element that lays its children
24
+ * out anew (tabs, a code block) reads them as they were written, before they are drawn.
25
+ */
26
+ function expand(node) {
27
+ const out = [];
28
+ expandInto(node, out);
29
+ return out;
30
+ }
31
+ function expandInto(node, out) {
32
+ if (typeof node === "string") {
33
+ if (node !== "") out.push(node);
34
+ } else if (Array.isArray(node)) for (const child of node) expandInto(child, out);
35
+ else if (typeof node.tag === "function") expandInto(node.tag(node.props ?? {}, node.children), out);
36
+ else {
37
+ const children = [];
38
+ for (const child of node.children) expandInto(child, children);
39
+ out.push({
40
+ tag: node.tag,
41
+ props: node.props,
42
+ children
43
+ });
44
+ }
45
+ }
46
+ const ESCAPES = {
47
+ "&": "&amp;",
48
+ "<": "&lt;",
49
+ ">": "&gt;",
50
+ "\"": "&quot;",
51
+ "'": "&#x27;"
52
+ };
53
+ const escapeHtml = (text) => /[&<>"']/.test(text) ? text.replace(/[&<>"']/g, (character) => ESCAPES[character]) : text;
54
+ const VOID = /* @__PURE__ */ new Set([
55
+ "area",
56
+ "base",
57
+ "br",
58
+ "col",
59
+ "embed",
60
+ "hr",
61
+ "img",
62
+ "input",
63
+ "link",
64
+ "meta",
65
+ "param",
66
+ "source",
67
+ "track",
68
+ "wbr"
69
+ ]);
70
+ /** Properties whose attribute is not their own name. */
71
+ const ATTRIBUTES = {
72
+ className: "class",
73
+ htmlFor: "for",
74
+ tabIndex: "tabindex",
75
+ httpEquiv: "http-equiv",
76
+ acceptCharset: "accept-charset",
77
+ strokeWidth: "stroke-width",
78
+ strokeLinecap: "stroke-linecap",
79
+ strokeLinejoin: "stroke-linejoin"
80
+ };
81
+ /** Attributes that are there or not: `hidden`, never `hidden="false"`. */
82
+ const BOOLEAN = /* @__PURE__ */ new Set([
83
+ "allowFullScreen",
84
+ "async",
85
+ "autoFocus",
86
+ "autoPlay",
87
+ "checked",
88
+ "controls",
89
+ "default",
90
+ "defer",
91
+ "disabled",
92
+ "hidden",
93
+ "inert",
94
+ "itemScope",
95
+ "loop",
96
+ "multiple",
97
+ "muted",
98
+ "noValidate",
99
+ "open",
100
+ "playsInline",
101
+ "readOnly",
102
+ "required",
103
+ "reversed",
104
+ "selected"
105
+ ]);
106
+ /** What never becomes an attribute: an element's handle, its place in a list, its inside. */
107
+ const NOT_ATTRIBUTES = /* @__PURE__ */ new Set([
108
+ "key",
109
+ "ref",
110
+ "children",
111
+ "innerHTML",
112
+ "node"
113
+ ]);
114
+ const VALID_NAME = /^[A-Za-z_:][\w:.-]*$/;
115
+ const UNITLESS = /^(?:animationIterationCount|aspectRatio|columnCount|flex|flexGrow|flexShrink|fontWeight|lineHeight|opacity|order|orphans|tabSize|widows|zIndex|zoom|fillOpacity|strokeOpacity)$/;
116
+ /** A `style` object as the attribute's text. */
117
+ function styleText(style) {
118
+ let out = "";
119
+ for (const name in style) {
120
+ const value = style[name];
121
+ if (value === null || value === void 0 || typeof value === "boolean" || value === "") continue;
122
+ const custom = name.startsWith("--");
123
+ const property = custom ? name : name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`).replace(/^ms-/, "-ms-");
124
+ const text = typeof value === "number" && value !== 0 && !custom && !UNITLESS.test(name) ? `${value}px` : String(value).trim();
125
+ out += `${out ? ";" : ""}${property}:${text}`;
126
+ }
127
+ return out;
128
+ }
129
+ /**
130
+ * One property as the attribute a page carries for it, or null when it is not one (a listener, a
131
+ * handle, a boolean that is off). The HTML writer and the DOM target both ask here, so a page
132
+ * written as text and a page built node by node carry the same attributes.
133
+ */
134
+ function attributeOf(name, value) {
135
+ if (value === null || value === void 0 || typeof value === "function") return null;
136
+ if (NOT_ATTRIBUTES.has(name) || typeof value === "symbol") return null;
137
+ if (name === "style") {
138
+ if (typeof value !== "object") return null;
139
+ const text = styleText(value);
140
+ return text ? {
141
+ name,
142
+ value: text
143
+ } : null;
144
+ }
145
+ if (name.length > 2 && (name.charCodeAt(0) | 32) === 111 && (name.charCodeAt(1) | 32) === 110) return null;
146
+ const written = attributeName(name);
147
+ if (!VALID_NAME.test(written)) return null;
148
+ if (BOOLEAN.has(name)) return value ? {
149
+ name: written,
150
+ value: ""
151
+ } : null;
152
+ if (typeof value === "boolean") {
153
+ const prefix = name.slice(0, 5);
154
+ return prefix === "aria-" || prefix === "data-" ? {
155
+ name: written,
156
+ value: String(value)
157
+ } : null;
158
+ }
159
+ return {
160
+ name: written,
161
+ value: String(value)
162
+ };
163
+ }
164
+ /** The attribute a property is written as (`className` is `class`). */
165
+ const attributeName = (name) => ATTRIBUTES[name] ?? name;
166
+ /**
167
+ * `node` as HTML text. Text and attribute values are escaped; `innerHTML` (highlighted code, a
168
+ * typeset formula) goes in as it is, as its maker escaped it.
169
+ */
170
+ function toHtml(node) {
171
+ if (typeof node === "string") return escapeHtml(node);
172
+ if (Array.isArray(node)) {
173
+ let out = "";
174
+ for (const child of node) out += toHtml(child);
175
+ return out;
176
+ }
177
+ if (typeof node.tag === "function") return toHtml(node.tag(node.props ?? {}, node.children));
178
+ const { tag, props } = node;
179
+ let out = `<${tag}`;
180
+ if (props !== null) for (const name in props) {
181
+ const written = attributeOf(name, props[name]);
182
+ if (written) out += ` ${written.name}="${escapeHtml(written.value)}"`;
183
+ }
184
+ if (VOID.has(tag)) return `${out}/>`;
185
+ out += ">";
186
+ let inside = "";
187
+ if (props !== null && props.innerHTML !== void 0 && props.innerHTML !== null) inside = String(props.innerHTML);
188
+ else for (const child of node.children) inside += toHtml(child);
189
+ if (inside.charCodeAt(0) === 10 && (tag === "pre" || tag === "textarea" || tag === "listing")) out += "\n";
190
+ return `${out}${inside}</${tag}>`;
191
+ }
192
+ export { attributeName, attributeOf, escapeHtml, expand, toHtml, vnode };
193
+
194
+ //# sourceMappingURL=vnode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vnode.js","names":[],"sources":["../../src/render/vnode.ts"],"sourcesContent":["/**\n * Elements as plain values: what a target without elements of its own (a server writing HTML, a\n * page with no framework) draws a document into. A tree of them is written out as text\n * ({@link toHtml}) or put on a page and kept up to date (`dom/patch.ts`).\n */\nimport type { Factory, Props, Read } from './factory'\n\nexport interface VElement {\n tag: string | VComponent\n props: Props | null\n children: VNode[]\n}\n\n/** A text, an element, or several of either where one is expected. */\nexport type VNode = string | VElement | VNode[]\n\n/** An element of the page's own: it gets the tag's properties and children, and gives nodes. */\nexport type VComponent = (props: Props, children: VNode[]) => VNode\n\nconst read = (children: VNode[], out: Array<Exclude<Read<VNode, VComponent>, null>>) => {\n for (const child of children) {\n if (typeof child === 'string') out.push(child)\n else if (Array.isArray(child)) read(child, out)\n else out.push({ tag: child.tag, props: child.props ?? {}, children: child.children })\n }\n return out\n}\n\nexport const vnode: Factory<VNode, VComponent> = {\n element: (tag, props, children) => ({ tag, props, children }),\n text: (value) => value,\n fragment: (children) => children,\n read: (children) => read(children, []),\n}\n\n/**\n * `node` with every component in it called, outermost first: an element that lays its children\n * out anew (tabs, a code block) reads them as they were written, before they are drawn.\n */\nexport function expand(node: VNode): Drawn[] {\n const out: Drawn[] = []\n expandInto(node, out)\n return out\n}\n\n/** What is left of a tree once its components are called: a text, or an element of a tag. */\nexport type Drawn = string | DrawnElement\n\nexport interface DrawnElement extends VElement {\n tag: string\n /** Flat: no list inside a list, and no empty text. */\n children: Drawn[]\n}\n\nfunction expandInto(node: VNode, out: Drawn[]): void {\n if (typeof node === 'string') {\n if (node !== '') out.push(node)\n } else if (Array.isArray(node)) {\n for (const child of node) expandInto(child, out)\n } else if (typeof node.tag === 'function') {\n expandInto(node.tag(node.props ?? {}, node.children), out)\n } else {\n const children: Drawn[] = []\n for (const child of node.children) expandInto(child, children)\n out.push({ tag: node.tag, props: node.props, children })\n }\n}\n\nconst ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n}\n\nexport const escapeHtml = (text: string): string =>\n /[&<>\"']/.test(text) ? text.replace(/[&<>\"']/g, (character) => ESCAPES[character]!) : text\n\nconst VOID = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n])\n\n/** Properties whose attribute is not their own name. */\nconst ATTRIBUTES: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n tabIndex: 'tabindex',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n strokeWidth: 'stroke-width',\n strokeLinecap: 'stroke-linecap',\n strokeLinejoin: 'stroke-linejoin',\n}\n\n/** Attributes that are there or not: `hidden`, never `hidden=\"false\"`. */\nconst BOOLEAN = new Set([\n 'allowFullScreen',\n 'async',\n 'autoFocus',\n 'autoPlay',\n 'checked',\n 'controls',\n 'default',\n 'defer',\n 'disabled',\n 'hidden',\n 'inert',\n 'itemScope',\n 'loop',\n 'multiple',\n 'muted',\n 'noValidate',\n 'open',\n 'playsInline',\n 'readOnly',\n 'required',\n 'reversed',\n 'selected',\n])\n\n/** What never becomes an attribute: an element's handle, its place in a list, its inside. */\nconst NOT_ATTRIBUTES = new Set(['key', 'ref', 'children', 'innerHTML', 'node'])\n\nconst VALID_NAME = /^[A-Za-z_:][\\w:.-]*$/\n\nconst UNITLESS =\n /^(?:animationIterationCount|aspectRatio|columnCount|flex|flexGrow|flexShrink|fontWeight|lineHeight|opacity|order|orphans|tabSize|widows|zIndex|zoom|fillOpacity|strokeOpacity)$/\n\n/** A `style` object as the attribute's text. */\nfunction styleText(style: Record<string, unknown>): string {\n let out = ''\n for (const name in style) {\n const value = style[name]\n if (value === null || value === undefined || typeof value === 'boolean' || value === '')\n continue\n const custom = name.startsWith('--')\n const property = custom\n ? name\n : name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`).replace(/^ms-/, '-ms-')\n const text =\n typeof value === 'number' && value !== 0 && !custom && !UNITLESS.test(name)\n ? `${value}px`\n : String(value).trim()\n out += `${out ? ';' : ''}${property}:${text}`\n }\n return out\n}\n\n/**\n * One property as the attribute a page carries for it, or null when it is not one (a listener, a\n * handle, a boolean that is off). The HTML writer and the DOM target both ask here, so a page\n * written as text and a page built node by node carry the same attributes.\n */\nexport function attributeOf(name: string, value: unknown): { name: string; value: string } | null {\n if (value === null || value === undefined || typeof value === 'function') return null\n if (NOT_ATTRIBUTES.has(name) || typeof value === 'symbol') return null\n if (name === 'style') {\n if (typeof value !== 'object') return null\n const text = styleText(value as Record<string, unknown>)\n return text ? { name, value: text } : null\n }\n // No attribute of a page starts with `on` but a handler: `onClick` given as anything, and an\n // `onclick` that came as text, are both left out, as React leaves them out.\n if (name.length > 2 && (name.charCodeAt(0) | 32) === 111 && (name.charCodeAt(1) | 32) === 110)\n return null\n const written = attributeName(name)\n if (!VALID_NAME.test(written)) return null\n if (BOOLEAN.has(name)) return value ? { name: written, value: '' } : null\n if (typeof value === 'boolean') {\n // Only `aria-*` and `data-*` say `true` and `false` as words.\n const prefix = name.slice(0, 5)\n return prefix === 'aria-' || prefix === 'data-' ? { name: written, value: String(value) } : null\n }\n return { name: written, value: String(value) }\n}\n\n/** The attribute a property is written as (`className` is `class`). */\nexport const attributeName = (name: string): string => ATTRIBUTES[name] ?? name\n\n/**\n * `node` as HTML text. Text and attribute values are escaped; `innerHTML` (highlighted code, a\n * typeset formula) goes in as it is, as its maker escaped it.\n */\nexport function toHtml(node: VNode): string {\n if (typeof node === 'string') return escapeHtml(node)\n if (Array.isArray(node)) {\n let out = ''\n for (const child of node) out += toHtml(child)\n return out\n }\n if (typeof node.tag === 'function') return toHtml(node.tag(node.props ?? {}, node.children))\n const { tag, props } = node\n let out = `<${tag}`\n if (props !== null)\n for (const name in props) {\n const written = attributeOf(name, props[name])\n if (written) out += ` ${written.name}=\"${escapeHtml(written.value)}\"`\n }\n if (VOID.has(tag)) return `${out}/>`\n out += '>'\n let inside = ''\n if (props !== null && props.innerHTML !== undefined && props.innerHTML !== null)\n inside = String(props.innerHTML)\n else for (const child of node.children) inside += toHtml(child)\n // An HTML parser drops a line break that comes first in these: a second one keeps the text's own.\n if (inside.charCodeAt(0) === 10 && (tag === 'pre' || tag === 'textarea' || tag === 'listing'))\n out += '\\n'\n return `${out}${inside}</${tag}>`\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `@localix-ai/flowdown/static`: Markdown as HTML text, without React or a browser.
3
+ *
4
+ * import { renderToHtml } from '@localix-ai/flowdown/static'
5
+ *
6
+ * const html = renderToHtml(reply)
7
+ *
8
+ * The elements are the ones `<Markdown>` draws, class for class, so `@localix-ai/flowdown/styles.css`
9
+ * draws them the same: for a server that sends a page, a static site, an e-mail, a test. Nothing
10
+ * on it is alive: tabs show their first variant and a code block's buttons do nothing until a
11
+ * renderer for the page takes it over (`@localix-ai/flowdown/dom`).
12
+ *
13
+ * `streaming: true` completes a reply that is still arriving, for a server that sends the page as
14
+ * the model writes it.
15
+ */
16
+ import { ViewComponents, ViewOptions } from "../render/view.js";
17
+ import { VComponent, VElement, VNode, escapeHtml, toHtml } from "../render/vnode.js";
18
+ import { DEFAULT_LABELS, LabelOverrides, MarkdownLabels } from "../render/labels.js";
19
+ import { Highlighter } from "../render/views.js";
20
+ import { UrlTransform, defaultUrlTransform } from "../engine/url.js";
21
+ import { Extension, Node } from "../engine/types.js";
22
+ export interface StaticOptions extends Omit<ViewOptions, 'host'> {
23
+ /** The text is a reply still arriving: its unfinished end is completed. */
24
+ streaming?: boolean;
25
+ }
26
+ /** `markdown` as the HTML `<Markdown>` draws, root element and all. */
27
+ export declare function renderToHtml(markdown: string, options?: StaticOptions): string;
28
+ export { DEFAULT_LABELS, type Extension, type Highlighter, type LabelOverrides, type MarkdownLabels, type Node, type UrlTransform, type VComponent, type VElement, type VNode, type ViewComponents, defaultUrlTransform, escapeHtml, toHtml };
@@ -0,0 +1,27 @@
1
+ import { createView } from "../render/view.js";
2
+ import { escapeHtml, toHtml } from "../render/vnode.js";
3
+ import { DEFAULT_LABELS } from "../render/labels.js";
4
+ import { defaultUrlTransform } from "../engine/url.js";
5
+ // src/static/index.ts
6
+ /**
7
+ * `@localix-ai/flowdown/static`: Markdown as HTML text, without React or a browser.
8
+ *
9
+ * import { renderToHtml } from '@localix-ai/flowdown/static'
10
+ *
11
+ * const html = renderToHtml(reply)
12
+ *
13
+ * The elements are the ones `<Markdown>` draws, class for class, so `@localix-ai/flowdown/styles.css`
14
+ * draws them the same: for a server that sends a page, a static site, an e-mail, a test. Nothing
15
+ * on it is alive: tabs show their first variant and a code block's buttons do nothing until a
16
+ * renderer for the page takes it over (`@localix-ai/flowdown/dom`).
17
+ *
18
+ * `streaming: true` completes a reply that is still arriving, for a server that sends the page as
19
+ * the model writes it.
20
+ */
21
+ /** `markdown` as the HTML `<Markdown>` draws, root element and all. */
22
+ function renderToHtml(markdown, options = {}) {
23
+ return toHtml(createView(options).render(markdown, options.streaming ?? false));
24
+ }
25
+ export { DEFAULT_LABELS, defaultUrlTransform, escapeHtml, renderToHtml, toHtml };
26
+
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/static/index.ts"],"sourcesContent":["/**\n * `@localix-ai/flowdown/static`: Markdown as HTML text, without React or a browser.\n *\n * import { renderToHtml } from '@localix-ai/flowdown/static'\n *\n * const html = renderToHtml(reply)\n *\n * The elements are the ones `<Markdown>` draws, class for class, so `@localix-ai/flowdown/styles.css`\n * draws them the same: for a server that sends a page, a static site, an e-mail, a test. Nothing\n * on it is alive: tabs show their first variant and a code block's buttons do nothing until a\n * renderer for the page takes it over (`@localix-ai/flowdown/dom`).\n *\n * `streaming: true` completes a reply that is still arriving, for a server that sends the page as\n * the model writes it.\n */\nimport { createView, type ViewOptions } from '../render/view'\nimport { toHtml } from '../render/vnode'\n\nexport type { ViewComponents } from '../render/view'\nexport { escapeHtml, toHtml, type VComponent, type VElement, type VNode } from '../render/vnode'\nexport { DEFAULT_LABELS, type LabelOverrides, type MarkdownLabels } from '../render/labels'\nexport type { Highlighter } from '../render/views'\nexport { defaultUrlTransform, type UrlTransform } from '../engine/url'\nexport type { Extension, Node } from '../engine/types'\n\nexport interface StaticOptions extends Omit<ViewOptions, 'host'> {\n /** The text is a reply still arriving: its unfinished end is completed. */\n streaming?: boolean\n}\n\n/** `markdown` as the HTML `<Markdown>` draws, root element and all. */\nexport function renderToHtml(markdown: string, options: StaticOptions = {}): string {\n return toHtml(createView(options).render(markdown, options.streaming ?? false))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,aAAa,UAAkB,UAAyB,CAAC,GAAW;CAClF,OAAO,OAAO,WAAW,OAAO,CAAC,CAAC,OAAO,UAAU,QAAQ,aAAa,KAAK,CAAC;AAChF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@localix-ai/flowdown/stream`: completing a reply that is still arriving, without React.
3
+ *
4
+ * `<Markdown streaming>` applies it already; it is here for a renderer of your own, or a server
5
+ * that sends partial HTML. `closePartialMarkdown` completes the constructs a streamed text ends
6
+ * inside (a fence, `**bold`, a link, a formula, a half-typed marker) so the partial reply renders
7
+ * the way the finished one will instead of flashing raw syntax. Apply it while the text grows,
8
+ * never to the final text.
9
+ */
10
+ import { closePartialMarkdown, isGrowingFence, openFenceBody } from "./core/heal.js";
11
+ import { MarkdownBlock, splitBlocks } from "./core/blocks.js";
12
+ export { type MarkdownBlock, closePartialMarkdown, isGrowingFence, openFenceBody, splitBlocks };
package/dist/stream.js ADDED
@@ -0,0 +1,3 @@
1
+ import { closePartialMarkdown, isGrowingFence, openFenceBody } from "./core/heal.js";
2
+ import { splitBlocks } from "./core/blocks.js";
3
+ export { closePartialMarkdown, isGrowingFence, openFenceBody, splitBlocks };