@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 @@
1
+ {"version":3,"file":"render.js","names":[],"sources":["../../src/react/render.tsx"],"sourcesContent":["import { createElement, Fragment, isValidElement, type ComponentType, type ReactNode } from 'react'\nimport type { Node } from '../engine/types'\nimport type { Factory, Props, Read } from '../render/factory'\nimport { createRenderer, type RenderOptions as TreeOptions } from '../render/tree'\n\nexport { ID_PREFIX } from '../render/tree'\n\n/** An element of the page's own for a tag: it gets the tag's props, and the node it draws. */\nexport type Components = {\n [Tag in keyof React.JSX.IntrinsicElements]?: ComponentType<\n React.JSX.IntrinsicElements[Tag] & { node?: Node }\n >\n} & {\n // `any`: a page's own tag takes whatever props its extension wrote. `never` here refused a map\n // that spreads one in beside HTML tags (a class component's `defaultProps` do not fit it).\n // oxlint-disable-next-line typescript/no-explicit-any\n [tag: string]: ComponentType<any> | undefined\n}\n\n// oxlint-disable-next-line typescript/no-explicit-any\ntype Component = ComponentType<any>\n\nexport type RenderOptions = Omit<TreeOptions<ReactNode, Component>, 'components'> & {\n components: Components\n}\n\nconst read = (children: ReactNode[], out: Array<Exclude<Read<ReactNode, Component>, null>>) => {\n for (const child of children) {\n if (typeof child === 'string') out.push(child)\n else if (typeof child === 'number') out.push(String(child))\n else if (Array.isArray(child)) read(child as ReactNode[], out)\n else if (isValidElement(child)) {\n const props = child.props as Props & { children?: ReactNode }\n const inner =\n props.children === undefined || props.children === null\n ? []\n : Array.isArray(props.children)\n ? (props.children as ReactNode[])\n : [props.children]\n if (child.type === Fragment) read(inner, out)\n else out.push({ tag: child.type as string | Component, props, children: inner })\n }\n }\n return out\n}\n\n/** React's elements, as the factory every HTML renderer of the package is written against. */\nexport const react: Factory<ReactNode, Component> = {\n element(tag, props, children) {\n if (props !== null && props.innerHTML !== undefined) {\n // Markup already made (highlighted code, a typeset formula) is the element's whole inside.\n const { innerHTML, ...rest } = props\n return createElement(tag, { ...rest, dangerouslySetInnerHTML: { __html: innerHTML } })\n }\n return createElement(tag, props, ...children)\n },\n text: (value) => value,\n fragment: (children) => createElement(Fragment, null, ...children),\n read: (children) => read(children, []),\n}\n\nconst draw = createRenderer(react)\n\n/** Draws a tree. Every call gives new elements; the caller remembers them by the tree's text. */\nexport const render = (root: Node, options: RenderOptions): ReactNode =>\n draw(root, options as TreeOptions<ReactNode, Component>)\n"],"mappings":";;;AA0BA,MAAM,QAAQ,UAAuB,QAA0D;CAC7F,KAAK,MAAM,SAAS,UAClB,IAAI,OAAO,UAAU,UAAU,IAAI,KAAK,KAAK;MACxC,IAAI,OAAO,UAAU,UAAU,IAAI,KAAK,OAAO,KAAK,CAAC;MACrD,IAAI,MAAM,QAAQ,KAAK,GAAG,KAAK,OAAsB,GAAG;MACxD,IAAI,eAAe,KAAK,GAAG;EAC9B,MAAM,QAAQ,MAAM;EACpB,MAAM,QACJ,MAAM,aAAa,KAAA,KAAa,MAAM,aAAa,OAC/C,CAAC,IACD,MAAM,QAAQ,MAAM,QAAQ,IACzB,MAAM,WACP,CAAC,MAAM,QAAQ;EACvB,IAAI,MAAM,SAAS,UAAU,KAAK,OAAO,GAAG;OACvC,IAAI,KAAK;GAAE,KAAK,MAAM;GAA4B;GAAO,UAAU;EAAM,CAAC;CACjF;CAEF,OAAO;AACT;;AAGA,MAAa,QAAuC;CAClD,QAAQ,KAAK,OAAO,UAAU;EAC5B,IAAI,UAAU,QAAQ,MAAM,cAAc,KAAA,GAAW;GAEnD,MAAM,EAAE,WAAW,GAAG,SAAS;GAC/B,OAAO,cAAc,KAAK;IAAE,GAAG;IAAM,yBAAyB,EAAE,QAAQ,UAAU;GAAE,CAAC;EACvF;EACA,OAAO,cAAc,KAAK,OAAO,GAAG,QAAQ;CAC9C;CACA,OAAO,UAAU;CACjB,WAAW,aAAa,cAAc,UAAU,MAAM,GAAG,QAAQ;CACjE,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC;AACvC;AAEA,MAAM,OAAO,eAAe,KAAK;;AAGjC,MAAa,UAAU,MAAY,YACjC,KAAK,MAAM,OAA4C"}
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ // src/react/scrolls.ts
4
+ /**
5
+ * Whether a box scrolls sideways: a table's box, or the `<pre>` of a code block.
6
+ *
7
+ * A box that scrolls is a named group the Tab key stops on, so the arrow keys can scroll it:
8
+ * without that what is past the edge cannot be reached from the keyboard at all (WCAG 2.1.1). A box
9
+ * whose content fits stays out of the Tab order. It is measured again whenever it, or the `inner`
10
+ * element that holds its content, changes size.
11
+ *
12
+ * A line of code getting longer resizes neither, so the caller says when its text has changed
13
+ * (`again`): the box is then measured once in the next frame, when the page is laid out anyway. A
14
+ * measuring per token would lay the page out per token.
15
+ *
16
+ * Two hooks and no effect: a code block is drawn again on every token of its text, and an update
17
+ * there is short enough that each hook a component calls shows in the stream's whole time.
18
+ */
19
+ function useScrolls(inner) {
20
+ const [scrolls, setScrolls] = useState(false);
21
+ const [box] = useState(() => watch(setScrolls, inner));
22
+ return [
23
+ box.attach,
24
+ scrolls,
25
+ box.again
26
+ ];
27
+ }
28
+ /** The watching of one box, from React attaching it (`attach(element)`) to its leaving (`attach(null)`). */
29
+ function watch(tell, inner) {
30
+ let box = null;
31
+ let observer = null;
32
+ let frame = 0;
33
+ const measure = () => {
34
+ if (box) tell(box.scrollWidth > box.clientWidth + 1);
35
+ };
36
+ return {
37
+ attach(element) {
38
+ if (frame) cancelAnimationFrame(frame);
39
+ frame = 0;
40
+ observer?.disconnect();
41
+ observer = null;
42
+ box = element;
43
+ if (!element) return;
44
+ measure();
45
+ if (typeof ResizeObserver === "undefined") return;
46
+ observer = new ResizeObserver(measure);
47
+ observer.observe(element);
48
+ const content = inner ? element.querySelector(inner) : null;
49
+ if (content) observer.observe(content);
50
+ },
51
+ again() {
52
+ if (!box || typeof requestAnimationFrame !== "function") return;
53
+ frame ||= requestAnimationFrame(() => {
54
+ frame = 0;
55
+ measure();
56
+ });
57
+ }
58
+ };
59
+ }
60
+ export { useScrolls };
61
+
62
+ //# sourceMappingURL=scrolls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrolls.js","names":[],"sources":["../../src/react/scrolls.ts"],"sourcesContent":["'use client'\n\nimport { useState } from 'react'\n\n/**\n * Whether a box scrolls sideways: a table's box, or the `<pre>` of a code block.\n *\n * A box that scrolls is a named group the Tab key stops on, so the arrow keys can scroll it:\n * without that what is past the edge cannot be reached from the keyboard at all (WCAG 2.1.1). A box\n * whose content fits stays out of the Tab order. It is measured again whenever it, or the `inner`\n * element that holds its content, changes size.\n *\n * A line of code getting longer resizes neither, so the caller says when its text has changed\n * (`again`): the box is then measured once in the next frame, when the page is laid out anyway. A\n * measuring per token would lay the page out per token.\n *\n * Two hooks and no effect: a code block is drawn again on every token of its text, and an update\n * there is short enough that each hook a component calls shows in the stream's whole time.\n */\nexport function useScrolls<T extends HTMLElement>(\n inner?: string,\n): [attach: (element: T | null) => void, scrolls: boolean, again: () => void] {\n const [scrolls, setScrolls] = useState(false)\n const [box] = useState(() => watch<T>(setScrolls, inner))\n return [box.attach, scrolls, box.again]\n}\n\n/** The watching of one box, from React attaching it (`attach(element)`) to its leaving (`attach(null)`). */\nfunction watch<T extends HTMLElement>(tell: (scrolls: boolean) => void, inner: string | undefined) {\n let box: T | null = null\n let observer: ResizeObserver | null = null\n let frame = 0\n // One pixel of slack: a subpixel content width rounds up past the box it fits in.\n const measure = () => {\n if (box) tell(box.scrollWidth > box.clientWidth + 1)\n }\n return {\n attach(element: T | null): void {\n if (frame) cancelAnimationFrame(frame)\n frame = 0\n observer?.disconnect()\n observer = null\n box = element\n if (!element) return\n measure()\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver(measure)\n observer.observe(element)\n const content = inner ? element.querySelector(inner) : null\n if (content) observer.observe(content)\n },\n again(): void {\n if (!box || typeof requestAnimationFrame !== 'function') return\n frame ||= requestAnimationFrame(() => {\n frame = 0\n measure()\n })\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,SAAgB,WACd,OAC4E;CAC5E,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,CAAC,OAAO,eAAe,MAAS,YAAY,KAAK,CAAC;CACxD,OAAO;EAAC,IAAI;EAAQ;EAAS,IAAI;CAAK;AACxC;;AAGA,SAAS,MAA6B,MAAkC,OAA2B;CACjG,IAAI,MAAgB;CACpB,IAAI,WAAkC;CACtC,IAAI,QAAQ;CAEZ,MAAM,gBAAgB;EACpB,IAAI,KAAK,KAAK,IAAI,cAAc,IAAI,cAAc,CAAC;CACrD;CACA,OAAO;EACL,OAAO,SAAyB;GAC9B,IAAI,OAAO,qBAAqB,KAAK;GACrC,QAAQ;GACR,UAAU,WAAW;GACrB,WAAW;GACX,MAAM;GACN,IAAI,CAAC,SAAS;GACd,QAAQ;GACR,IAAI,OAAO,mBAAmB,aAAa;GAC3C,WAAW,IAAI,eAAe,OAAO;GACrC,SAAS,QAAQ,OAAO;GACxB,MAAM,UAAU,QAAQ,QAAQ,cAAc,KAAK,IAAI;GACvD,IAAI,SAAS,SAAS,QAAQ,OAAO;EACvC;EACA,QAAc;GACZ,IAAI,CAAC,OAAO,OAAO,0BAA0B,YAAY;GACzD,UAAU,4BAA4B;IACpC,QAAQ;IACR,QAAQ;GACV,CAAC;EACH;CACF;AACF"}
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { tableRows } from "../document/table.js";
3
+ import { draw } from "./draw.js";
4
+ import { createElement, memo } from "react";
5
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
6
+ // src/react/table-rows.tsx
7
+ /**
8
+ * A table is one block, and a model that prints results row by row grows it to hundreds of rows:
9
+ * drawn through one parse, every token read and drew the whole table again. A row depends only on
10
+ * the header above it, so the table is drawn as its header and its rows in pieces of {@link ROWS};
11
+ * a piece that is complete is parsed and drawn once, and a token redraws only the last piece.
12
+ *
13
+ * Each piece is the header and its own rows through the same parser, so the cells are what one
14
+ * parse of the whole table makes: the same elements, in the same table.
15
+ */
16
+ const ROWS = 16;
17
+ const Children = ({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children });
18
+ const Nothing = () => null;
19
+ /** A document's options with the table's own shell taken off: a piece yields its rows alone. */
20
+ const parts = /* @__PURE__ */ new WeakMap();
21
+ function partsOf(options) {
22
+ let made = parts.get(options);
23
+ if (!made) {
24
+ const { components } = options;
25
+ made = {
26
+ head: {
27
+ ...options,
28
+ components: {
29
+ ...components,
30
+ table: Children,
31
+ tbody: Nothing
32
+ }
33
+ },
34
+ body: {
35
+ ...options,
36
+ components: {
37
+ ...components,
38
+ table: Children,
39
+ thead: Nothing,
40
+ tbody: Children
41
+ }
42
+ }
43
+ };
44
+ parts.set(options, made);
45
+ }
46
+ return made;
47
+ }
48
+ /** A header with some of its rows, read and drawn once for as long as its text is the same. */
49
+ const Piece = memo(function Piece({ source, options }) {
50
+ return draw(source, 0, null, options);
51
+ });
52
+ function TableRows({ head, rows, options }) {
53
+ const { head: headParts, body: bodyParts } = partsOf(options);
54
+ const pieces = [];
55
+ for (let at = 0; at < rows.length; at += ROWS) pieces.push(/* @__PURE__ */ jsx(Piece, {
56
+ source: head + rows.slice(at, at + ROWS).join("\n"),
57
+ options: bodyParts
58
+ }, at));
59
+ return createElement(options.components.table, null, /* @__PURE__ */ jsx(Piece, {
60
+ source: head,
61
+ options: headParts
62
+ }), /* @__PURE__ */ jsx("tbody", { children: pieces }));
63
+ }
64
+ export { TableRows, tableRows };
65
+
66
+ //# sourceMappingURL=table-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-rows.js","names":[],"sources":["../../src/react/table-rows.tsx"],"sourcesContent":["'use client'\nimport { createElement, memo, type ReactNode } from 'react'\nimport { draw, type BlockOptions } from './draw'\n\nexport { tableRows } from '../document/table'\n\n/**\n * A table is one block, and a model that prints results row by row grows it to hundreds of rows:\n * drawn through one parse, every token read and drew the whole table again. A row depends only on\n * the header above it, so the table is drawn as its header and its rows in pieces of {@link ROWS};\n * a piece that is complete is parsed and drawn once, and a token redraws only the last piece.\n *\n * Each piece is the header and its own rows through the same parser, so the cells are what one\n * parse of the whole table makes: the same elements, in the same table.\n */\nconst ROWS = 16\n\nconst Children = ({ children }: { children?: ReactNode }) => <>{children}</>\nconst Nothing = () => null\n\n/** A document's options with the table's own shell taken off: a piece yields its rows alone. */\nconst parts = new WeakMap<BlockOptions, { head: BlockOptions; body: BlockOptions }>()\nfunction partsOf(options: BlockOptions) {\n let made = parts.get(options)\n if (!made) {\n const { components } = options\n made = {\n head: { ...options, components: { ...components, table: Children, tbody: Nothing } as never },\n body: {\n ...options,\n components: { ...components, table: Children, thead: Nothing, tbody: Children } as never,\n },\n }\n parts.set(options, made)\n }\n return made\n}\n\n/** A header with some of its rows, read and drawn once for as long as its text is the same. */\nconst Piece = memo(function Piece({ source, options }: { source: string; options: BlockOptions }) {\n return draw(source, 0, null, options)\n})\n\nexport function TableRows({\n head,\n rows,\n options,\n}: {\n head: string\n rows: string[]\n /** What the block is read and drawn with. One object per document: a piece compares it. */\n options: BlockOptions\n}) {\n const { head: headParts, body: bodyParts } = partsOf(options)\n const pieces: ReactNode[] = []\n for (let at = 0; at < rows.length; at += ROWS)\n pieces.push(\n <Piece key={at} source={head + rows.slice(at, at + ROWS).join('\\n')} options={bodyParts} />,\n )\n return createElement(\n options.components.table as never,\n null,\n <Piece source={head} options={headParts} />,\n <tbody>{pieces}</tbody>,\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,OAAO;AAEb,MAAM,YAAY,EAAE,eAAyC,oBAAA,YAAA,EAAG,SAAW,CAAA;AAC3E,MAAM,gBAAgB;;AAGtB,MAAM,wBAAQ,IAAI,QAAkE;AACpF,SAAS,QAAQ,SAAuB;CACtC,IAAI,OAAO,MAAM,IAAI,OAAO;CAC5B,IAAI,CAAC,MAAM;EACT,MAAM,EAAE,eAAe;EACvB,OAAO;GACL,MAAM;IAAE,GAAG;IAAS,YAAY;KAAE,GAAG;KAAY,OAAO;KAAU,OAAO;IAAQ;GAAW;GAC5F,MAAM;IACJ,GAAG;IACH,YAAY;KAAE,GAAG;KAAY,OAAO;KAAU,OAAO;KAAS,OAAO;IAAS;GAChF;EACF;EACA,MAAM,IAAI,SAAS,IAAI;CACzB;CACA,OAAO;AACT;;AAGA,MAAM,QAAQ,KAAK,SAAS,MAAM,EAAE,QAAQ,WAAsD;CAChG,OAAO,KAAK,QAAQ,GAAG,MAAM,OAAO;AACtC,CAAC;AAED,SAAgB,UAAU,EACxB,MACA,MACA,WAMC;CACD,MAAM,EAAE,MAAM,WAAW,MAAM,cAAc,QAAQ,OAAO;CAC5D,MAAM,SAAsB,CAAC;CAC7B,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,QAAQ,MAAM,MACvC,OAAO,KACL,oBAAC,OAAD;EAAgB,QAAQ,OAAO,KAAK,MAAM,IAAI,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI;EAAG,SAAS;CAAY,GAA9E,EAA8E,CAC5F;CACF,OAAO,cACL,QAAQ,WAAW,OACnB,MACA,oBAAC,OAAD;EAAO,QAAQ;EAAM,SAAS;CAAY,CAAA,GAC1C,oBAAC,SAAD,EAAA,UAAQ,OAAc,CAAA,CACxB;AACF"}
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { table } from "../render/views.js";
3
+ import { useLabels } from "./labels.js";
4
+ import { react } from "./render.js";
5
+ import { useScrolls } from "./scrolls.js";
6
+ // src/react/table.tsx
7
+ /**
8
+ * A table in a box of its own that scrolls sideways: a wide comparison then scrolls inside the
9
+ * reply instead of widening the page, which on a phone pushed every line of text off screen.
10
+ *
11
+ * A box that scrolls is a Tab stop, and one that fits is not: see `useScrolls`. The box is measured
12
+ * again whenever it or the table changes size: a reply that streams a row in, or a window made
13
+ * narrower.
14
+ */
15
+ function Table({ children, ...props }) {
16
+ const labels = useLabels();
17
+ const [attach, scrolls] = useScrolls("table");
18
+ return table(react, props, children === void 0 ? [] : [children], scrolls, labels, { ref: attach });
19
+ }
20
+ export { Table };
21
+
22
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.js","names":[],"sources":["../../src/react/table.tsx"],"sourcesContent":["'use client'\n\nimport type { ComponentPropsWithoutRef } from 'react'\nimport type { Props } from '../render/factory'\nimport { table } from '../render/views'\nimport { useLabels } from './labels'\nimport { react } from './render'\nimport { useScrolls } from './scrolls'\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, which on a phone pushed every line of text off screen.\n *\n * A box that scrolls is a Tab stop, and one that fits is not: see `useScrolls`. The box is measured\n * again whenever it or the table changes size: a reply that streams a row in, or a window made\n * narrower.\n */\nexport function Table({\n children,\n ...props\n}: ComponentPropsWithoutRef<'table'> & { node?: unknown }) {\n const labels = useLabels()\n const [attach, scrolls] = useScrolls<HTMLDivElement>('table')\n return table(react, props as Props, children === undefined ? [] : [children], scrolls, labels, {\n ref: attach,\n })\n}\n"],"mappings":";;;;;;;;;;;;;;AAiBA,SAAgB,MAAM,EACpB,UACA,GAAG,SACsD;CACzD,MAAM,SAAS,UAAU;CACzB,MAAM,CAAC,QAAQ,WAAW,WAA2B,OAAO;CAC5D,OAAO,MAAM,OAAO,OAAgB,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,QAAQ,EAC7F,KAAK,OACP,CAAC;AACH"}
@@ -0,0 +1,25 @@
1
+ import { ReactNode } from "react";
2
+ type TabProps = {
3
+ children?: ReactNode;
4
+ node?: unknown;
5
+ 'data-label'?: string;
6
+ };
7
+ /**
8
+ * One variant of a `[!TABS]` form. Its label rides as a data attribute; the panel is drawn by
9
+ * `Tabs`, so on its own this is just its content.
10
+ */
11
+ export declare function Tab({ children }: TabProps): import("react").JSX.Element;
12
+ /**
13
+ * `[!TABS]`: the variants of one answer (an install per OS, an example per language), of which
14
+ * the reader needs one. The first is shown. Every panel stays in the document, `hidden`, so a
15
+ * search on the page, a copy of the whole reply and a screen reader's panel navigation still
16
+ * reach the others.
17
+ *
18
+ * The WAI-ARIA tabs pattern: one Tab stop for the row, the arrow keys, Home and End move between
19
+ * tabs and select them, each tab names the panel it controls.
20
+ */
21
+ export declare function Tabs({ children }: {
22
+ children?: ReactNode;
23
+ node?: unknown;
24
+ }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null | undefined;
25
+ export {};
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { readTabs, tabs } from "../render/views.js";
3
+ import { useLabels } from "./labels.js";
4
+ import { react } from "./render.js";
5
+ import { useId, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
7
+ // src/react/tabs.tsx
8
+ /**
9
+ * One variant of a `[!TABS]` form. Its label rides as a data attribute; the panel is drawn by
10
+ * `Tabs`, so on its own this is just its content.
11
+ */
12
+ function Tab({ children }) {
13
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
14
+ }
15
+ /**
16
+ * `[!TABS]`: the variants of one answer (an install per OS, an example per language), of which
17
+ * the reader needs one. The first is shown. Every panel stays in the document, `hidden`, so a
18
+ * search on the page, a copy of the whole reply and a screen reader's panel navigation still
19
+ * reach the others.
20
+ *
21
+ * The WAI-ARIA tabs pattern: one Tab stop for the row, the arrow keys, Home and End move between
22
+ * tabs and select them, each tab names the panel it controls.
23
+ */
24
+ function Tabs({ children }) {
25
+ const labels = useLabels();
26
+ const base = useId();
27
+ const [active, setActive] = useState(0);
28
+ const buttons = useRef([]);
29
+ const list = readTabs(react, children === void 0 ? [] : [children]);
30
+ if (list.length === 0) return null;
31
+ const current = active < list.length ? active : 0;
32
+ const select = (index) => {
33
+ const next = (index + list.length) % list.length;
34
+ setActive(next);
35
+ buttons.current[next]?.focus();
36
+ };
37
+ const onKeyDown = (event, index) => {
38
+ const target = {
39
+ ArrowRight: index + 1,
40
+ ArrowLeft: index - 1,
41
+ Home: 0,
42
+ End: list.length - 1
43
+ }[event.key];
44
+ if (target === void 0) return;
45
+ event.preventDefault();
46
+ select(target);
47
+ };
48
+ return tabs(react, list, current, base, labels, (index) => ({
49
+ ref: (element) => {
50
+ buttons.current[index] = element;
51
+ },
52
+ onClick: () => setActive(index),
53
+ onKeyDown: (event) => onKeyDown(event, index)
54
+ }));
55
+ }
56
+ export { Tab, Tabs };
57
+
58
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabs.js","names":[],"sources":["../../src/react/tabs.tsx"],"sourcesContent":["'use client'\nimport { useId, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { readTabs, tabs } from '../render/views'\nimport { useLabels } from './labels'\nimport { react } from './render'\n\ntype TabProps = { children?: ReactNode; node?: unknown; 'data-label'?: string }\n\n/**\n * One variant of a `[!TABS]` form. Its label rides as a data attribute; the panel is drawn by\n * `Tabs`, so on its own this is just its content.\n */\nexport function Tab({ children }: TabProps) {\n return <>{children}</>\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. The first is shown. Every panel stays in the document, `hidden`, so a\n * search on the page, a copy of the whole reply and a screen reader's panel navigation still\n * reach the others.\n *\n * The WAI-ARIA tabs pattern: one Tab stop for the row, the arrow keys, Home and End move between\n * tabs and select them, each tab names the panel it controls.\n */\nexport function Tabs({ children }: { children?: ReactNode; node?: unknown }) {\n const labels = useLabels()\n const base = useId()\n const [active, setActive] = useState(0)\n const buttons = useRef<Array<HTMLButtonElement | null>>([])\n const list = readTabs(react, children === undefined ? [] : [children])\n if (list.length === 0) return null\n // A streamed form can lose a tab between renders only by being re-parsed; never point past it.\n const current = active < list.length ? active : 0\n\n const select = (index: number) => {\n const next = (index + list.length) % list.length\n setActive(next)\n buttons.current[next]?.focus()\n }\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const moves: Record<string, number> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: list.length - 1,\n }\n const target = moves[event.key]\n if (target === undefined) return\n event.preventDefault()\n select(target)\n }\n\n return tabs(react, list, current, base, labels, (index) => ({\n ref: (element: HTMLButtonElement | null) => {\n buttons.current[index] = element\n },\n onClick: () => setActive(index),\n onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => onKeyDown(event, index),\n }))\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,IAAI,EAAE,YAAsB;CAC1C,OAAO,oBAAA,YAAA,EAAG,SAAW,CAAA;AACvB;;;;;;;;;;AAWA,SAAgB,KAAK,EAAE,YAAsD;CAC3E,MAAM,SAAS,UAAU;CACzB,MAAM,OAAO,MAAM;CACnB,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,MAAM,UAAU,OAAwC,CAAC,CAAC;CAC1D,MAAM,OAAO,SAAS,OAAO,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;CACrE,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,MAAM,UAAU,SAAS,KAAK,SAAS,SAAS;CAEhD,MAAM,UAAU,UAAkB;EAChC,MAAM,QAAQ,QAAQ,KAAK,UAAU,KAAK;EAC1C,UAAU,IAAI;EACd,QAAQ,QAAQ,KAAK,EAAE,MAAM;CAC/B;CACA,MAAM,aAAa,OAAyC,UAAkB;EAO5E,MAAM,SAAS;GALb,YAAY,QAAQ;GACpB,WAAW,QAAQ;GACnB,MAAM;GACN,KAAK,KAAK,SAAS;EAEF,EAAE,MAAM;EAC3B,IAAI,WAAW,KAAA,GAAW;EAC1B,MAAM,eAAe;EACrB,OAAO,MAAM;CACf;CAEA,OAAO,KAAK,OAAO,MAAM,SAAS,MAAM,SAAS,WAAW;EAC1D,MAAM,YAAsC;GAC1C,QAAQ,QAAQ,SAAS;EAC3B;EACA,eAAe,UAAU,KAAK;EAC9B,YAAY,UAA4C,UAAU,OAAO,KAAK;CAChF,EAAE;AACJ"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `text` as it should be shown now. While `streaming`, growth is paced; when it stops, what is
3
+ * left is shown out. The second value says whether the shown text is still behind: a caller keeps
4
+ * its "still arriving" look until it is false.
5
+ */
6
+ export declare function useSmoothText(text: string, streaming: boolean, catchUp?: number): [shown: string, behind: boolean];
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { continues } from "../core/continues.js";
3
+ import { useEffect, useState } from "react";
4
+ /** The least time between two steps: about 30 a second, each of which is drawn. */
5
+ const STEP_MS = 30;
6
+ /** How fast the speed itself follows the backlog. */
7
+ const EASE_MS = 140;
8
+ /** A reply that has not grown for this long is between words, not inside one. */
9
+ const PAUSE_MS = 160;
10
+ /** A word longer than this (a URL, a hash) is shown in pieces rather than waited for. */
11
+ const LONGEST_WORD = 24;
12
+ const isSpace = (code) => code === 32 || code === 10 || code === 9 || code === 13;
13
+ /** CJK and kana are written without spaces: every character there is a place to stop. */
14
+ const isWide = (code) => code >= 11904 && code <= 65519;
15
+ /** Where the last word of `text` begins, or its length when it ends on a break. */
16
+ function lastWordStart(text) {
17
+ let at = text.length;
18
+ while (at > 0) {
19
+ const code = text.charCodeAt(at - 1);
20
+ if (isSpace(code) || isWide(code)) break;
21
+ at--;
22
+ }
23
+ return at;
24
+ }
25
+ /**
26
+ * How much of `text` may be shown: all of it once it is `settled`, and otherwise all but a last
27
+ * word that is still being typed. A long token (a URL, a hash) is not waited for.
28
+ */
29
+ function showable(text, settled) {
30
+ if (settled) return text.length;
31
+ const start = lastWordStart(text);
32
+ return text.length - start > LONGEST_WORD ? text.length : start;
33
+ }
34
+ /** From `at`, the end of the word it is inside (and the spaces after it), no further than `limit`. */
35
+ function wordEnd(text, at, limit) {
36
+ if (at >= limit) return limit;
37
+ let end = at;
38
+ const reach = Math.min(limit, at + LONGEST_WORD);
39
+ while (end < reach) {
40
+ const code = text.charCodeAt(end);
41
+ if (isSpace(code) || isWide(code)) break;
42
+ end++;
43
+ }
44
+ if (end === reach && reach < limit) return at;
45
+ while (end < limit && isSpace(text.charCodeAt(end))) end++;
46
+ if (end < limit && isLowSurrogate(text.charCodeAt(end))) end++;
47
+ return end;
48
+ }
49
+ const isLowSurrogate = (code) => code >= 56320 && code <= 57343;
50
+ /**
51
+ * One step: how much of `text` is shown `elapsed` ms after `pace`. `settled` says no more of the
52
+ * last word is coming (the reply ended, or has stood still for a moment).
53
+ */
54
+ function advance(text, pace, elapsed, catchUp, settled) {
55
+ const limit = showable(text, settled);
56
+ if (pace.shown >= limit) return {
57
+ shown: Math.min(pace.shown, text.length),
58
+ speed: 0
59
+ };
60
+ const wanted = (limit - pace.shown) / catchUp;
61
+ const speed = pace.speed + (wanted - pace.speed) * Math.min(1, elapsed / EASE_MS);
62
+ let shown = wordEnd(text, Math.min(limit, pace.shown + Math.max(1, Math.round(speed * elapsed))), limit);
63
+ if (shown < text.length && isLowSurrogate(text.charCodeAt(shown))) shown++;
64
+ return {
65
+ shown,
66
+ speed
67
+ };
68
+ }
69
+ const reducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
70
+ const now = () => typeof performance === "undefined" ? 0 : performance.now();
71
+ function commonPrefix(a, b) {
72
+ const limit = Math.min(a.length, b.length);
73
+ let at = 0;
74
+ while (at < limit && a.charCodeAt(at) === b.charCodeAt(at)) at++;
75
+ return at;
76
+ }
77
+ /**
78
+ * The pacer of one text: how much of it is shown, and the frame loop that shows more. It lives
79
+ * outside React's state on purpose: the length belongs to the text it was counted in, and is
80
+ * corrected in the same render that brings a new text, never a frame later.
81
+ */
82
+ function createPacer(initial) {
83
+ let text = initial;
84
+ let pace = {
85
+ shown: initial.length,
86
+ speed: 0
87
+ };
88
+ let streaming = false;
89
+ let grew = 0;
90
+ let stepped = 0;
91
+ let frame = 0;
92
+ return {
93
+ /** The length of `next` to show now. */
94
+ take(next, live) {
95
+ if (next !== text) {
96
+ const shown = !live && !streaming ? next.length : continues(next, text, pace.shown) ? pace.shown : commonPrefix(text, next);
97
+ text = next;
98
+ pace = {
99
+ shown,
100
+ speed: pace.speed
101
+ };
102
+ grew = now();
103
+ }
104
+ streaming = live;
105
+ if (pace.shown < text.length && reducedMotion()) pace = {
106
+ shown: text.length,
107
+ speed: 0
108
+ };
109
+ return Math.min(pace.shown, text.length);
110
+ },
111
+ /** Show the rest, a step at a time; `redraw` is called after each step that showed more. */
112
+ run(catchUp, redraw) {
113
+ if (frame !== 0 || pace.shown >= text.length) return;
114
+ stepped = now();
115
+ const step = (time) => {
116
+ const elapsed = time - stepped;
117
+ if (elapsed >= STEP_MS) {
118
+ stepped = time;
119
+ const settled = !streaming || time - grew > PAUSE_MS;
120
+ const next = advance(text, pace, elapsed, catchUp, settled);
121
+ const moved = next.shown !== pace.shown;
122
+ pace = next;
123
+ if (moved) redraw();
124
+ }
125
+ frame = pace.shown < text.length ? requestAnimationFrame(step) : 0;
126
+ };
127
+ frame = requestAnimationFrame(step);
128
+ },
129
+ stop() {
130
+ cancelAnimationFrame(frame);
131
+ frame = 0;
132
+ }
133
+ };
134
+ }
135
+ /**
136
+ * `text` as it should be shown now. While `streaming`, growth is paced; when it stops, what is
137
+ * left is shown out. The second value says whether the shown text is still behind: a caller keeps
138
+ * its "still arriving" look until it is false.
139
+ */
140
+ function useSmoothText(text, streaming, catchUp = 240) {
141
+ const [pacer] = useState(() => createPacer(text));
142
+ const [, setDrawn] = useState(0);
143
+ const end = pacer.take(text, streaming);
144
+ useEffect(() => {
145
+ pacer.run(catchUp, () => setDrawn((count) => count + 1));
146
+ }, [
147
+ pacer,
148
+ text,
149
+ streaming,
150
+ catchUp
151
+ ]);
152
+ useEffect(() => () => pacer.stop(), [pacer]);
153
+ return [end >= text.length ? text : text.slice(0, end), end < text.length];
154
+ }
155
+ export { advance, showable, useSmoothText, wordEnd };
156
+
157
+ //# sourceMappingURL=use-smooth-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-smooth-text.js","names":[],"sources":["../../src/react/use-smooth-text.ts"],"sourcesContent":["'use client'\n/**\n * Pacing a reply that arrives in bursts.\n *\n * A model's text reaches the page in uneven pieces: a network frame holds one word or forty, a\n * cache hit holds the whole reply. Drawn as it lands, the page jumps by a line at a time and then\n * stands still. This shows the text at an even pace instead, a few frames behind what has arrived:\n *\n * - the speed follows the backlog (what has arrived and is not shown yet is shown over about\n * `catchUp` ms) and changes gradually, so a burst speeds the flow up instead of landing whole;\n * - text is shown a word at a time: a word typed letter by letter at the end of a line grows\n * there and then jumps to the next line when it no longer fits, and a whole word never does;\n * - when the reply ends, what is left is shown at the same pace instead of landing at once;\n * - text that was there before streaming began is shown at once: only growth is paced;\n * - with `prefers-reduced-motion`, nothing is paced.\n *\n * It never shows text that has not arrived, and costs nothing once it has caught up: the frame\n * loop runs only while there is a backlog.\n */\nimport { useEffect, useState } from 'react'\nimport { continues } from '../core/continues'\n\n/** How long a backlog takes to show, by default. */\nexport const DEFAULT_CATCH_UP_MS = 240\n/** The least time between two steps: about 30 a second, each of which is drawn. */\nconst STEP_MS = 30\n/** How fast the speed itself follows the backlog. */\nconst EASE_MS = 140\n/** A reply that has not grown for this long is between words, not inside one. */\nconst PAUSE_MS = 160\n/** A word longer than this (a URL, a hash) is shown in pieces rather than waited for. */\nconst LONGEST_WORD = 24\n\nexport interface Pace {\n /** Characters shown. */\n shown: number\n /** Characters a millisecond, as of the last step. */\n speed: number\n}\n\nconst isSpace = (code: number) => code === 32 || code === 10 || code === 9 || code === 13\n/** CJK and kana are written without spaces: every character there is a place to stop. */\nconst isWide = (code: number) => code >= 0x2e80 && code <= 0xffef\n\n/** Where the last word of `text` begins, or its length when it ends on a break. */\nfunction lastWordStart(text: string): number {\n let at = text.length\n while (at > 0) {\n const code = text.charCodeAt(at - 1)\n if (isSpace(code) || isWide(code)) break\n at--\n }\n return at\n}\n\n/**\n * How much of `text` may be shown: all of it once it is `settled`, and otherwise all but a last\n * word that is still being typed. A long token (a URL, a hash) is not waited for.\n */\nexport function showable(text: string, settled: boolean): number {\n if (settled) return text.length\n const start = lastWordStart(text)\n return text.length - start > LONGEST_WORD ? text.length : start\n}\n\n/** From `at`, the end of the word it is inside (and the spaces after it), no further than `limit`. */\nexport function wordEnd(text: string, at: number, limit: number): number {\n if (at >= limit) return limit\n let end = at\n const reach = Math.min(limit, at + LONGEST_WORD)\n while (end < reach) {\n const code = text.charCodeAt(end)\n if (isSpace(code) || isWide(code)) break\n end++\n }\n // No break in reach: a long token, shown as far as the pace got.\n if (end === reach && reach < limit) return at\n while (end < limit && isSpace(text.charCodeAt(end))) end++\n // A surrogate pair is one character.\n if (end < limit && isLowSurrogate(text.charCodeAt(end))) end++\n return end\n}\n\nconst isLowSurrogate = (code: number) => code >= 0xdc00 && code <= 0xdfff\n\n/**\n * One step: how much of `text` is shown `elapsed` ms after `pace`. `settled` says no more of the\n * last word is coming (the reply ended, or has stood still for a moment).\n */\nexport function advance(\n text: string,\n pace: Pace,\n elapsed: number,\n catchUp: number,\n settled: boolean,\n): Pace {\n const limit = showable(text, settled)\n if (pace.shown >= limit) return { shown: Math.min(pace.shown, text.length), speed: 0 }\n const backlog = limit - pace.shown\n const wanted = backlog / catchUp\n const speed = pace.speed + (wanted - pace.speed) * Math.min(1, elapsed / EASE_MS)\n const reached = Math.min(limit, pace.shown + Math.max(1, Math.round(speed * elapsed)))\n let shown = wordEnd(text, reached, limit)\n if (shown < text.length && isLowSurrogate(text.charCodeAt(shown))) shown++\n return { shown, speed }\n}\n\nconst reducedMotion = () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\nconst now = () => (typeof performance === 'undefined' ? 0 : performance.now())\n\nfunction commonPrefix(a: string, b: string): number {\n const limit = Math.min(a.length, b.length)\n let at = 0\n while (at < limit && a.charCodeAt(at) === b.charCodeAt(at)) at++\n return at\n}\n\n/**\n * The pacer of one text: how much of it is shown, and the frame loop that shows more. It lives\n * outside React's state on purpose: the length belongs to the text it was counted in, and is\n * corrected in the same render that brings a new text, never a frame later.\n */\nfunction createPacer(initial: string) {\n // Text that is there from the start (a settled reply, a remount mid-stream) is not replayed.\n let text = initial\n let pace: Pace = { shown: initial.length, speed: 0 }\n let streaming = false\n let grew = 0\n let stepped = 0\n let frame = 0\n\n return {\n /** The length of `next` to show now. */\n take(next: string, live: boolean): number {\n if (next !== text) {\n // A reply that is not streaming is shown whole, whatever it was before. Otherwise, a\n // different text, not a longer one (a regenerated reply, an edit): keep what they share.\n const shown =\n !live && !streaming\n ? next.length\n : continues(next, text, pace.shown)\n ? pace.shown\n : commonPrefix(text, next)\n text = next\n pace = { shown, speed: pace.speed }\n grew = now()\n }\n streaming = live\n if (pace.shown < text.length && reducedMotion()) pace = { shown: text.length, speed: 0 }\n return Math.min(pace.shown, text.length)\n },\n /** Show the rest, a step at a time; `redraw` is called after each step that showed more. */\n run(catchUp: number, redraw: () => void) {\n if (frame !== 0 || pace.shown >= text.length) return\n stepped = now()\n const step = (time: number) => {\n const elapsed = time - stepped\n if (elapsed >= STEP_MS) {\n stepped = time\n // A reply that has stood still for a moment is not in the middle of a word.\n const settled = !streaming || time - grew > PAUSE_MS\n const next = advance(text, pace, elapsed, catchUp, settled)\n const moved = next.shown !== pace.shown\n pace = next\n if (moved) redraw()\n }\n frame = pace.shown < text.length ? requestAnimationFrame(step) : 0\n }\n frame = requestAnimationFrame(step)\n },\n stop() {\n cancelAnimationFrame(frame)\n frame = 0\n },\n }\n}\n\n/**\n * `text` as it should be shown now. While `streaming`, growth is paced; when it stops, what is\n * left is shown out. The second value says whether the shown text is still behind: a caller keeps\n * its \"still arriving\" look until it is false.\n */\nexport function useSmoothText(\n text: string,\n streaming: boolean,\n catchUp: number = DEFAULT_CATCH_UP_MS,\n): [shown: string, behind: boolean] {\n const [pacer] = useState(() => createPacer(text))\n const [, setDrawn] = useState(0)\n const end = pacer.take(text, streaming)\n\n // `text` and `streaming` are here to start the loop again when there is more to show.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n pacer.run(catchUp, () => setDrawn((count) => count + 1))\n }, [pacer, text, streaming, catchUp])\n useEffect(() => () => pacer.stop(), [pacer])\n\n return [end >= text.length ? text : text.slice(0, end), end < text.length]\n}\n"],"mappings":";;;;AAyBA,MAAM,UAAU;;AAEhB,MAAM,UAAU;;AAEhB,MAAM,WAAW;;AAEjB,MAAM,eAAe;AASrB,MAAM,WAAW,SAAiB,SAAS,MAAM,SAAS,MAAM,SAAS,KAAK,SAAS;;AAEvF,MAAM,UAAU,SAAiB,QAAQ,SAAU,QAAQ;;AAG3D,SAAS,cAAc,MAAsB;CAC3C,IAAI,KAAK,KAAK;CACd,OAAO,KAAK,GAAG;EACb,MAAM,OAAO,KAAK,WAAW,KAAK,CAAC;EACnC,IAAI,QAAQ,IAAI,KAAK,OAAO,IAAI,GAAG;EACnC;CACF;CACA,OAAO;AACT;;;;;AAMA,SAAgB,SAAS,MAAc,SAA0B;CAC/D,IAAI,SAAS,OAAO,KAAK;CACzB,MAAM,QAAQ,cAAc,IAAI;CAChC,OAAO,KAAK,SAAS,QAAQ,eAAe,KAAK,SAAS;AAC5D;;AAGA,SAAgB,QAAQ,MAAc,IAAY,OAAuB;CACvE,IAAI,MAAM,OAAO,OAAO;CACxB,IAAI,MAAM;CACV,MAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,YAAY;CAC/C,OAAO,MAAM,OAAO;EAClB,MAAM,OAAO,KAAK,WAAW,GAAG;EAChC,IAAI,QAAQ,IAAI,KAAK,OAAO,IAAI,GAAG;EACnC;CACF;CAEA,IAAI,QAAQ,SAAS,QAAQ,OAAO,OAAO;CAC3C,OAAO,MAAM,SAAS,QAAQ,KAAK,WAAW,GAAG,CAAC,GAAG;CAErD,IAAI,MAAM,SAAS,eAAe,KAAK,WAAW,GAAG,CAAC,GAAG;CACzD,OAAO;AACT;AAEA,MAAM,kBAAkB,SAAiB,QAAQ,SAAU,QAAQ;;;;;AAMnE,SAAgB,QACd,MACA,MACA,SACA,SACA,SACM;CACN,MAAM,QAAQ,SAAS,MAAM,OAAO;CACpC,IAAI,KAAK,SAAS,OAAO,OAAO;EAAE,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM;EAAG,OAAO;CAAE;CAErF,MAAM,UADU,QAAQ,KAAK,SACJ;CACzB,MAAM,QAAQ,KAAK,SAAS,SAAS,KAAK,SAAS,KAAK,IAAI,GAAG,UAAU,OAAO;CAEhF,IAAI,QAAQ,QAAQ,MADJ,KAAK,IAAI,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CACpD,GAAG,KAAK;CACxC,IAAI,QAAQ,KAAK,UAAU,eAAe,KAAK,WAAW,KAAK,CAAC,GAAG;CACnE,OAAO;EAAE;EAAO;CAAM;AACxB;AAEA,MAAM,sBACJ,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,CAAC,CAAC;AAExD,MAAM,YAAa,OAAO,gBAAgB,cAAc,IAAI,YAAY,IAAI;AAE5E,SAAS,aAAa,GAAW,GAAmB;CAClD,MAAM,QAAQ,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;CACzC,IAAI,KAAK;CACT,OAAO,KAAK,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG;CAC5D,OAAO;AACT;;;;;;AAOA,SAAS,YAAY,SAAiB;CAEpC,IAAI,OAAO;CACX,IAAI,OAAa;EAAE,OAAO,QAAQ;EAAQ,OAAO;CAAE;CACnD,IAAI,YAAY;CAChB,IAAI,OAAO;CACX,IAAI,UAAU;CACd,IAAI,QAAQ;CAEZ,OAAO;;EAEL,KAAK,MAAc,MAAuB;GACxC,IAAI,SAAS,MAAM;IAGjB,MAAM,QACJ,CAAC,QAAQ,CAAC,YACN,KAAK,SACL,UAAU,MAAM,MAAM,KAAK,KAAK,IAC9B,KAAK,QACL,aAAa,MAAM,IAAI;IAC/B,OAAO;IACP,OAAO;KAAE;KAAO,OAAO,KAAK;IAAM;IAClC,OAAO,IAAI;GACb;GACA,YAAY;GACZ,IAAI,KAAK,QAAQ,KAAK,UAAU,cAAc,GAAG,OAAO;IAAE,OAAO,KAAK;IAAQ,OAAO;GAAE;GACvF,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM;EACzC;;EAEA,IAAI,SAAiB,QAAoB;GACvC,IAAI,UAAU,KAAK,KAAK,SAAS,KAAK,QAAQ;GAC9C,UAAU,IAAI;GACd,MAAM,QAAQ,SAAiB;IAC7B,MAAM,UAAU,OAAO;IACvB,IAAI,WAAW,SAAS;KACtB,UAAU;KAEV,MAAM,UAAU,CAAC,aAAa,OAAO,OAAO;KAC5C,MAAM,OAAO,QAAQ,MAAM,MAAM,SAAS,SAAS,OAAO;KAC1D,MAAM,QAAQ,KAAK,UAAU,KAAK;KAClC,OAAO;KACP,IAAI,OAAO,OAAO;IACpB;IACA,QAAQ,KAAK,QAAQ,KAAK,SAAS,sBAAsB,IAAI,IAAI;GACnE;GACA,QAAQ,sBAAsB,IAAI;EACpC;EACA,OAAO;GACL,qBAAqB,KAAK;GAC1B,QAAQ;EACV;CACF;AACF;;;;;;AAOA,SAAgB,cACd,MACA,WACA,UAAA,KACkC;CAClC,MAAM,CAAC,SAAS,eAAe,YAAY,IAAI,CAAC;CAChD,MAAM,GAAG,YAAY,SAAS,CAAC;CAC/B,MAAM,MAAM,MAAM,KAAK,MAAM,SAAS;CAItC,gBAAgB;EACd,MAAM,IAAI,eAAe,UAAU,UAAU,QAAQ,CAAC,CAAC;CACzD,GAAG;EAAC;EAAO;EAAM;EAAW;CAAO,CAAC;CACpC,sBAAsB,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC;CAE3C,OAAO,CAAC,OAAO,KAAK,SAAS,OAAO,KAAK,MAAM,GAAG,GAAG,GAAG,MAAM,KAAK,MAAM;AAC3E"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A stage of the pipeline that a page may load on demand: math (`@localix-ai/flowdown/math`), raw HTML
3
+ * (`@localix-ai/flowdown/html`). Given as the module itself (it is used at once, server rendering
4
+ * included), or as a loader that imports it (it is fetched the first time a document needs it).
5
+ */
6
+ export type StageLoader<T> = () => Promise<T | {
7
+ default: T;
8
+ }>;
9
+ export type StageOption<T> = T | StageLoader<T> | null;
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { useEffect, useRef, useState } from "react";
3
+ // src/react/use-stage.ts
4
+ /** A failed load is tried again this many times, this far apart. */
5
+ const STAGE_RETRIES = 3;
6
+ const STAGE_RETRY_MS = 1500;
7
+ /** Stages already loaded on this page, by the loader that brought them. */
8
+ const loaded = /* @__PURE__ */ new WeakMap();
9
+ const unwrap = (module) => "default" in module ? module.default : module;
10
+ /**
11
+ * The stage for a document, or null while it is not there. Each `<Markdown>` calls its loader
12
+ * once, the first time its document needs the stage, and keeps what it brought: a loader written
13
+ * inline, a new function on every render, is not called again, and the stage does not drop out
14
+ * while the page re-renders (on every token of a reply). The bundler fetches the module once for
15
+ * the whole page. Until it resolves the document renders without it; a failed load is tried again
16
+ * a few times, a second and a half apart, and after that the document stays without it (the next
17
+ * document to mount asks again).
18
+ */
19
+ function useStage(option, needed, { retries = STAGE_RETRIES, retryMs = STAGE_RETRY_MS } = {}) {
20
+ const lazy = typeof option === "function" ? option : null;
21
+ const latest = useRef(lazy);
22
+ useEffect(() => {
23
+ latest.current = lazy;
24
+ });
25
+ const [stage, setStage] = useState(() => lazy ? loaded.get(lazy) ?? null : null);
26
+ const [failures, setFailures] = useState(0);
27
+ const isLazy = lazy !== null;
28
+ useEffect(() => {
29
+ const load = latest.current;
30
+ if (!isLazy || !needed || stage || !load || failures > retries) return;
31
+ let current = true;
32
+ let retry;
33
+ load().then((module) => {
34
+ const value = unwrap(module);
35
+ loaded.set(load, value);
36
+ if (current) setStage(value);
37
+ }, () => {
38
+ if (current) retry = setTimeout(() => setFailures((count) => count + 1), retryMs);
39
+ });
40
+ return () => {
41
+ current = false;
42
+ clearTimeout(retry);
43
+ };
44
+ }, [
45
+ isLazy,
46
+ needed,
47
+ stage,
48
+ failures,
49
+ retries,
50
+ retryMs
51
+ ]);
52
+ if (!option || !needed) return null;
53
+ return lazy ? stage : option;
54
+ }
55
+ export { useStage };
56
+
57
+ //# sourceMappingURL=use-stage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-stage.js","names":[],"sources":["../../src/react/use-stage.ts"],"sourcesContent":["'use client'\nimport { useEffect, useRef, useState } from 'react'\n\n/**\n * A stage of the pipeline that a page may load on demand: math (`@localix-ai/flowdown/math`), raw HTML\n * (`@localix-ai/flowdown/html`). Given as the module itself (it is used at once, server rendering\n * included), or as a loader that imports it (it is fetched the first time a document needs it).\n */\nexport type StageLoader<T> = () => Promise<T | { default: T }>\nexport type StageOption<T> = T | StageLoader<T> | null\n\n/** A failed load is tried again this many times, this far apart. */\nconst STAGE_RETRIES = 3\nconst STAGE_RETRY_MS = 1_500\n\n/** Stages already loaded on this page, by the loader that brought them. */\nconst loaded = new WeakMap<StageLoader<object>, object>()\n\nconst unwrap = <T extends object>(module: T | { default: T }): T =>\n 'default' in module ? module.default : module\n\n/**\n * The stage for a document, or null while it is not there. Each `<Markdown>` calls its loader\n * once, the first time its document needs the stage, and keeps what it brought: a loader written\n * inline, a new function on every render, is not called again, and the stage does not drop out\n * while the page re-renders (on every token of a reply). The bundler fetches the module once for\n * the whole page. Until it resolves the document renders without it; a failed load is tried again\n * a few times, a second and a half apart, and after that the document stays without it (the next\n * document to mount asks again).\n */\nexport function useStage<T extends object>(\n option: StageOption<T>,\n needed: boolean,\n {\n retries = STAGE_RETRIES,\n retryMs = STAGE_RETRY_MS,\n }: { retries?: number; retryMs?: number } = {},\n): T | null {\n const lazy = typeof option === 'function' ? (option as StageLoader<T>) : null\n const latest = useRef(lazy)\n useEffect(() => {\n latest.current = lazy\n })\n // A stage another document on the page already loaded is there from the first render.\n const [stage, setStage] = useState<T | null>(() =>\n lazy ? ((loaded.get(lazy) as T | undefined) ?? null) : null,\n )\n // Each failed load counts here, and a new count asks again: a chunk that failed to come over a\n // bad connection is there a moment later, and the reader should not have to reload the page.\n const [failures, setFailures] = useState(0)\n const isLazy = lazy !== null\n useEffect(() => {\n const load = latest.current\n if (!isLazy || !needed || stage || !load || failures > retries) return\n let current = true\n let retry: ReturnType<typeof setTimeout> | undefined\n load().then(\n (module) => {\n const value = unwrap(module)\n loaded.set(load, value)\n if (current) setStage(value)\n },\n () => {\n // The document stays readable without the stage meanwhile, and after the last try.\n if (current) retry = setTimeout(() => setFailures((count) => count + 1), retryMs)\n },\n )\n return () => {\n current = false\n clearTimeout(retry)\n }\n }, [isLazy, needed, stage, failures, retries, retryMs])\n if (!option || !needed) return null\n return lazy ? stage : (option as T)\n}\n"],"mappings":";;;;AAYA,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;;AAGvB,MAAM,yBAAS,IAAI,QAAqC;AAExD,MAAM,UAA4B,WAChC,aAAa,SAAS,OAAO,UAAU;;;;;;;;;;AAWzC,SAAgB,SACd,QACA,QACA,EACE,UAAU,eACV,UAAU,mBACgC,CAAC,GACnC;CACV,MAAM,OAAO,OAAO,WAAW,aAAc,SAA4B;CACzE,MAAM,SAAS,OAAO,IAAI;CAC1B,gBAAgB;EACd,OAAO,UAAU;CACnB,CAAC;CAED,MAAM,CAAC,OAAO,YAAY,eACxB,OAAS,OAAO,IAAI,IAAI,KAAuB,OAAQ,IACzD;CAGA,MAAM,CAAC,UAAU,eAAe,SAAS,CAAC;CAC1C,MAAM,SAAS,SAAS;CACxB,gBAAgB;EACd,MAAM,OAAO,OAAO;EACpB,IAAI,CAAC,UAAU,CAAC,UAAU,SAAS,CAAC,QAAQ,WAAW,SAAS;EAChE,IAAI,UAAU;EACd,IAAI;EACJ,KAAK,CAAC,CAAC,MACJ,WAAW;GACV,MAAM,QAAQ,OAAO,MAAM;GAC3B,OAAO,IAAI,MAAM,KAAK;GACtB,IAAI,SAAS,SAAS,KAAK;EAC7B,SACM;GAEJ,IAAI,SAAS,QAAQ,iBAAiB,aAAa,UAAU,QAAQ,CAAC,GAAG,OAAO;EAClF,CACF;EACA,aAAa;GACX,UAAU;GACV,aAAa,KAAK;EACpB;CACF,GAAG;EAAC;EAAQ;EAAQ;EAAO;EAAU;EAAS;CAAO,CAAC;CACtD,IAAI,CAAC,UAAU,CAAC,QAAQ,OAAO;CAC/B,OAAO,OAAO,QAAS;AACzB"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@localix-ai/flowdown/remark`: the dialect as a remark plugin, without React or the engine's renderer.
3
+ *
4
+ * For a unified pipeline of your own: a static site build, a renderer that is not React. The
5
+ * dialect becomes ordinary HTML with `fd-` classes and data attributes, which `styles.css` draws;
6
+ * `answer-tabs` / `answer-tab` elements hold the variants of a `[!TABS]` form, for your own tab
7
+ * component. GitHub's syntax is `remark-gfm`'s to read, before this plugin:
8
+ *
9
+ * unified().use(remarkParse).use(remarkGfm).use(remarkFlowdown).use(remarkRehype).use(rehypeStringify)
10
+ */
11
+ import { remarkAnswerBlocks } from "./core/remark-answer-blocks.js";
12
+ import { sanitizeSchemaFor } from "./core/sanitize-schema.js";
13
+ import { MarkdownHeading, extractMarkdownHeadings, headingIdsByOffset, remarkHeadingIds } from "./core/headings.js";
14
+ import { BlockSplitter, MarkdownBlock, createBlockSplitter, splitBlocks } from "./core/blocks.js";
15
+ import { escapeLiteralDollars, isLiteralDollar } from "./core/dollars.js";
16
+ import { hasHtmlSyntax, hasMathSyntax } from "./core/syntax.js";
17
+ import { markdownProseWithoutCode } from "./core/prose.js";
18
+ import { TreeRow, parseFileTree } from "./core/file-tree-parse.js";
19
+ import { ANSWER_FORM_EXAMPLES, ATTRIBUTION_PREFIX, CALLOUT_KINDS, CONTAINER_KINDS, CalloutKind, CalloutMarker, ContainerKind, ContainerMarker, DIAGRAM_FENCE_LANGUAGE, DIAGRAM_KINDS, TREE_FENCE_LANGUAGE, containerMarker, fenceTitle, parseCalloutMarker, parseContainerMarker } from "./dialect.js";
20
+ export { ANSWER_FORM_EXAMPLES, ATTRIBUTION_PREFIX, type BlockSplitter, CALLOUT_KINDS, CONTAINER_KINDS, CalloutKind, CalloutMarker, ContainerKind, ContainerMarker, DIAGRAM_FENCE_LANGUAGE, DIAGRAM_KINDS, type MarkdownBlock, type MarkdownHeading, TREE_FENCE_LANGUAGE, type TreeRow, containerMarker, createBlockSplitter, escapeLiteralDollars, extractMarkdownHeadings as extractHeadings, fenceTitle, hasHtmlSyntax, hasMathSyntax, headingIdsByOffset, isLiteralDollar, markdownProseWithoutCode, parseCalloutMarker, parseContainerMarker, parseFileTree, remarkAnswerBlocks, remarkAnswerBlocks as remarkFlowdown, remarkHeadingIds, sanitizeSchemaFor, splitBlocks };
package/dist/remark.js ADDED
@@ -0,0 +1,10 @@
1
+ import { remarkAnswerBlocks } from "./core/remark-answer-blocks.js";
2
+ import { sanitizeSchemaFor } from "./core/sanitize-schema.js";
3
+ import { extractMarkdownHeadings, headingIdsByOffset, remarkHeadingIds } from "./core/headings.js";
4
+ import { createBlockSplitter, splitBlocks } from "./core/blocks.js";
5
+ import { escapeLiteralDollars, isLiteralDollar } from "./core/dollars.js";
6
+ import { hasHtmlSyntax, hasMathSyntax } from "./core/syntax.js";
7
+ import { markdownProseWithoutCode } from "./core/prose.js";
8
+ import { parseFileTree } from "./core/file-tree-parse.js";
9
+ import { ANSWER_FORM_EXAMPLES, ATTRIBUTION_PREFIX, CALLOUT_KINDS, CONTAINER_KINDS, DIAGRAM_FENCE_LANGUAGE, DIAGRAM_KINDS, TREE_FENCE_LANGUAGE, containerMarker, fenceTitle, parseCalloutMarker, parseContainerMarker } from "./dialect.js";
10
+ export { ANSWER_FORM_EXAMPLES, ATTRIBUTION_PREFIX, CALLOUT_KINDS, CONTAINER_KINDS, DIAGRAM_FENCE_LANGUAGE, DIAGRAM_KINDS, TREE_FENCE_LANGUAGE, containerMarker, createBlockSplitter, escapeLiteralDollars, extractMarkdownHeadings as extractHeadings, fenceTitle, hasHtmlSyntax, hasMathSyntax, headingIdsByOffset, isLiteralDollar, markdownProseWithoutCode, parseCalloutMarker, parseContainerMarker, parseFileTree, remarkAnswerBlocks, remarkAnswerBlocks as remarkFlowdown, remarkHeadingIds, sanitizeSchemaFor, splitBlocks };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What a renderer for HTML is written against, so that it is written once.
3
+ *
4
+ * Everything that decides what a document looks like as elements (which tag a node becomes, what a
5
+ * callout or a code block is made of) is a function of a {@link Factory}: it asks for elements and
6
+ * never says what an element is. React answers with its own, a server with text, the DOM with
7
+ * nodes. A new framework is a factory of a few lines, and draws every document as the others do.
8
+ */
9
+ /**
10
+ * An element's properties, by the names the DOM gives them (`className`, `tabIndex`, `colSpan`),
11
+ * as React takes them and hast writes them; `data-*` and `aria-*` as they are written. `style` is
12
+ * an object. `innerHTML` is markup already made and escaped (highlighted code, a typeset formula).
13
+ * A function is a listener (`onClick`): a target that cannot listen leaves it out.
14
+ */
15
+ export type Props = Record<string, unknown>;