brookmd 0.22.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 (59) hide show
  1. package/CHANGELOG.md +1229 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1265 -0
  4. package/dist/block-props.d.ts +18 -0
  5. package/dist/block-props.js +75 -0
  6. package/dist/client.d.ts +370 -0
  7. package/dist/client.js +754 -0
  8. package/dist/decorate.d.ts +24 -0
  9. package/dist/decorate.js +71 -0
  10. package/dist/dom.d.ts +130 -0
  11. package/dist/dom.js +627 -0
  12. package/dist/element.d.ts +20 -0
  13. package/dist/element.js +288 -0
  14. package/dist/hi.d.ts +12 -0
  15. package/dist/hi.js +215 -0
  16. package/dist/html-to-react.d.ts +61 -0
  17. package/dist/html-to-react.js +338 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.js +18 -0
  20. package/dist/morph.d.ts +28 -0
  21. package/dist/morph.js +166 -0
  22. package/dist/react.d.ts +236 -0
  23. package/dist/react.js +539 -0
  24. package/dist/renderers/CodeBlock.d.ts +7 -0
  25. package/dist/renderers/CodeBlock.js +75 -0
  26. package/dist/renderers/Math.d.ts +14 -0
  27. package/dist/renderers/Math.js +15 -0
  28. package/dist/renderers/Mermaid.d.ts +13 -0
  29. package/dist/renderers/Mermaid.js +15 -0
  30. package/dist/server-react.d.ts +32 -0
  31. package/dist/server-react.js +48 -0
  32. package/dist/server.d.ts +31 -0
  33. package/dist/server.js +82 -0
  34. package/dist/solid.d.ts +104 -0
  35. package/dist/solid.js +54 -0
  36. package/dist/styles.css +188 -0
  37. package/dist/svelte.d.ts +80 -0
  38. package/dist/svelte.js +59 -0
  39. package/dist/types-core.d.ts +436 -0
  40. package/dist/types-core.js +0 -0
  41. package/dist/types-react.d.ts +13 -0
  42. package/dist/types-react.js +0 -0
  43. package/dist/types.d.ts +2 -0
  44. package/dist/types.js +2 -0
  45. package/dist/url-safety.d.ts +12 -0
  46. package/dist/url-safety.js +45 -0
  47. package/dist/vue.d.ts +94 -0
  48. package/dist/vue.js +79 -0
  49. package/dist/wasm/LICENSE +21 -0
  50. package/dist/wasm/README.md +71 -0
  51. package/dist/wasm/brook_md_core.d.ts +166 -0
  52. package/dist/wasm/brook_md_core.js +512 -0
  53. package/dist/wasm/brook_md_core_bg.wasm +0 -0
  54. package/dist/wasm/brook_md_core_bg.wasm.d.ts +26 -0
  55. package/dist/worker-core.d.ts +65 -0
  56. package/dist/worker-core.js +155 -0
  57. package/dist/worker.d.ts +1 -0
  58. package/dist/worker.js +49 -0
  59. package/package.json +87 -0
@@ -0,0 +1,236 @@
1
+ import type { Block, BlockComponentProps, Components } from "./types.js";
2
+ import { BrookClient } from "./client.js";
3
+ import type { Decorator, ParserConfig, RenderMetricsHook, UrlTransform } from "./types-core.js";
4
+ /**
5
+ * Render a streaming markdown document from a BrookClient. Each block is its
6
+ * own memoized React node keyed by its stable parser-assigned ID, so React
7
+ * only reconciles the blocks whose HTML actually changed since the last
8
+ * patch. Heavy renderers (Shiki, KaTeX, Mermaid) defer work until a block
9
+ * is closed.
10
+ *
11
+ * ## Custom components
12
+ *
13
+ * Pass `components` to override rendering (see {@link Components}):
14
+ *
15
+ * ```tsx
16
+ * <BrookMarkdown
17
+ * client={client}
18
+ * components={{
19
+ * table: (p) => <table className="my-table" {...p} />, // tag-level
20
+ * a: (p) => <a target="_blank" rel="noreferrer" {...p} />,
21
+ * CodeBlock: (p) => <MyCodeBlock {...p} />, // block-kind
22
+ * }}
23
+ * />
24
+ * ```
25
+ *
26
+ * Rules:
27
+ * - **Tag-level** keys (`table`, `a`, `code`, `h1`…) replace that element
28
+ * wherever it appears inside a block. Applied by converting the block's
29
+ * trusted HTML to a React tree.
30
+ * - **Block-kind** keys ({@link BlockKindTag}: `CodeBlock`, `Mermaid`,
31
+ * `Table`…) replace the whole block; the component gets
32
+ * {@link BlockComponentProps}.
33
+ * - **Open / speculative** blocks always render via `innerHTML` (their HTML
34
+ * is partial); a tag-level override takes effect once the block commits.
35
+ * - With no `components` prop the renderer takes the original fast
36
+ * `innerHTML` path — output is byte-identical to before.
37
+ * - **Memoize `components`** (or hoist it) if you define it inside a
38
+ * component — a fresh object identity each render busts the block memo and
39
+ * forces every block to re-parse on every patch.
40
+ * - For code blocks the built-in highlighter is the default; it is bypassed
41
+ * (so your override wins) when you provide `components.CodeBlock`,
42
+ * `components.pre`, or `components.code`.
43
+ */
44
+ interface BrookMarkdownProps {
45
+ /**
46
+ * A caller-owned client (you drive `append`/`finalize` and own its lifecycle —
47
+ * the component never destroys it). Mutually exclusive with `stream`; if both
48
+ * are given, `client` wins (a dev warning fires).
49
+ */
50
+ client?: BrookClient;
51
+ /**
52
+ * A stream to render directly — the 1-line common case. Pass a `Response`, a
53
+ * `ReadableStream<Uint8Array>`, or an `AsyncIterable<string>` (e.g. SSE
54
+ * deltas) and the component owns an internal client, pipes the stream, and
55
+ * destroys it on unmount. A new `stream` identity supersedes the old.
56
+ */
57
+ stream?: AsyncIterable<string> | ReadableStream<Uint8Array> | Response;
58
+ /** Parser config for the internally-created client (stream mode only). */
59
+ streamConfig?: ParserConfig;
60
+ /** Called if piping the `stream` rejects (the source errored). Not the worker error channel. */
61
+ onStreamError?: (err: Error) => void;
62
+ components?: Components;
63
+ /**
64
+ * Skip layout/paint for off-screen blocks via CSS `content-visibility: auto`
65
+ * — for very long documents (hundreds+ of blocks). Off by default. Applies
66
+ * only to *closed* blocks (the streaming tail always renders fully). Keeps
67
+ * nodes in the DOM; it cuts rendering cost, not node count.
68
+ */
69
+ virtualize?: boolean;
70
+ /**
71
+ * Render a bottom snap target so the view follows the streaming tail. This is
72
+ * CSS-only: it emits a sentinel with `scroll-snap-align: end`; **you** add
73
+ * `scroll-snap-type: y proximity` to your scroll container. The view then
74
+ * follows the bottom as content streams in and releases when the user scrolls
75
+ * up (and re-locks when they scroll back near the bottom). Off by default.
76
+ */
77
+ stickToBottom?: boolean;
78
+ /**
79
+ * Optional HTML sanitizer applied to every block's HTML before it is injected
80
+ * via `innerHTML` — **including the streaming (open/speculative) tail**, the
81
+ * path that raw `innerHTML` would otherwise expose. Pass a real sanitizer
82
+ * (e.g. DOMPurify's `sanitize`) when rendering untrusted / LLM HTML with
83
+ * `unsafeHtml` on. brookmd stays zero-dep — you bring the sanitizer. The
84
+ * built-in code/math renderers operate on already-escaped content and are not
85
+ * run through it. When omitted, rendering is byte-identical and zero-cost.
86
+ *
87
+ * **Memoize / hoist this** (same trap as `components`): a fresh closure each
88
+ * render busts the per-block memo, so every block re-sanitizes and re-parses
89
+ * on every patch instead of only the streaming tail.
90
+ */
91
+ sanitize?: (html: string) => string;
92
+ /**
93
+ * Wrap or replace matched inline **text** while streaming, in O(n) — e.g. to
94
+ * bold financial figures (`$2.5B`, `10-15%`, `FY2024`) or linkify tickers. Each
95
+ * {@link Decorator} runs POST-PARSE on real inline TEXT nodes only (never URLs,
96
+ * code, or markup), once per committed block, so a long document stays linear.
97
+ *
98
+ * **Trusted surface.** A decorator's `replace` output is spliced straight into
99
+ * the tree and is **NOT** sanitized (React renders a `javascript:` href without
100
+ * complaint). Treat this exactly like `components`: build only trusted nodes,
101
+ * and route any link href through the exported `safeUrl` / the `wrapLink`
102
+ * helper. Matching is per-text-node — a value split by inline markup like
103
+ * `$2.<em>5</em>B` is two text nodes and won't match across them.
104
+ *
105
+ * **HOIST / memoize this array.** A fresh identity each render busts the block
106
+ * memo, forcing every committed block to re-parse + re-decorate every patch
107
+ * (O(n²)); a one-time dev warning fires if the identity changes. Enabling
108
+ * decorators routes a block through the walk path (off the `innerHTML` fast
109
+ * path) — expected, and still O(n) per block.
110
+ */
111
+ decorators?: Decorator[];
112
+ /**
113
+ * Rewrite `href`/`src`/`poster` URLs as blocks render — proxy images, add UTM
114
+ * params, etc. The output is re-sanitized (`safeUrl(urlTransform(safeUrl(v)))`)
115
+ * so it can never introduce a `javascript:` / `data:text/html` URL. **HOIST /
116
+ * memoize** for the same reason as `decorators`.
117
+ */
118
+ urlTransform?: UrlTransform;
119
+ /**
120
+ * Opt-in: when an OPEN (streaming) block re-renders each patch, reuse the
121
+ * React nodes of its top-level children whose HTML is unchanged and re-parse
122
+ * only the new trailing content, instead of re-parsing the block's whole HTML
123
+ * every tick. Only takes effect when `components` (or `sanitize`) routes a
124
+ * block through the parser; the no-`components` fast path (`innerHTML`) is
125
+ * untouched. Off by default and byte-identical to default rendering — enable
126
+ * it for documents with a long, slowly-growing streamed block under a custom
127
+ * `components` map. Closed blocks are already wholesale memo-skipped, so this
128
+ * applies only to the streaming tail.
129
+ */
130
+ childMemo?: boolean;
131
+ /** Appended to the root's `className` (the `brook-md` class is always present). */
132
+ className?: string;
133
+ /** Set on the root element. */
134
+ id?: string;
135
+ /** Set on the root element (e.g. `"article"`, `"log"`). */
136
+ role?: string;
137
+ /**
138
+ * Make the root a live region so screen readers announce streamed content.
139
+ * `"polite"` (recommended) coalesces rapid updates and announces when the
140
+ * reader is idle — it does **not** read every token. Off by default.
141
+ */
142
+ "aria-live"?: "off" | "polite" | "assertive";
143
+ /** Live-region atomicity; pair with `aria-live`. Off by default. */
144
+ "aria-atomic"?: boolean;
145
+ /**
146
+ * Optional render-churn probe. Fires once per ACTUAL render of a block —
147
+ * never for a committed block that memo-skips on a tail-only patch. The
148
+ * callback gets the block id and a {@link RenderMetrics} sample (per-block
149
+ * `renderCount`, `speculativeToggleCount`, `lastRenderMs`, `kind`). Zero
150
+ * overhead when omitted. **Memoize / hoist this** (same trap as `components`):
151
+ * a fresh closure each render busts the per-block memo, forcing every block to
152
+ * re-render on every patch.
153
+ */
154
+ onRenderMetrics?: RenderMetricsHook;
155
+ /**
156
+ * **Opt-in, off by default.** Render the block list through React's
157
+ * `useDeferredValue`, so a burst of patches can yield to higher-priority
158
+ * updates and the streaming tail commits at a lower priority. When a deferred
159
+ * render is in flight the root carries an extra `brook-deferred` class (style it
160
+ * however you like). This is a *no-op on a single patch*, has no effect during
161
+ * SSR (`useDeferredValue` is a client-only concern), and **does not change
162
+ * output** — only commit timing. Leaving it unset is recommended; rAF
163
+ * coalescing (the DOM adapter's batched path) is the preferred way to absorb
164
+ * high-frequency patches. Provided for callers who specifically want React's
165
+ * concurrent deferral of the visible tail.
166
+ */
167
+ deferTail?: boolean;
168
+ }
169
+ export declare function __resetUnstableWarnings(): void;
170
+ /**
171
+ * Own a {@link BrookClient} for the lifetime of a component and drive it from a
172
+ * `stream` (a `Response`, `ReadableStream<Uint8Array>`, or
173
+ * `AsyncIterable<string>`). Returns the client (read `outline()` / `getMetrics()`
174
+ * off it, or pass it to `<BrookMarkdown client={…} />`). The client is created
175
+ * once and destroyed on unmount; a new `stream` identity supersedes the old
176
+ * (the prior pipe is aborted, the parser is reset, the new stream is piped).
177
+ *
178
+ * Caveat (matches the manual `useEffect` form): a single-use stream — a
179
+ * `Response`/`ReadableStream`, or an async generator — can only be consumed
180
+ * once, so React **StrictMode**'s dev-only double-mount may truncate it in
181
+ * development. Production mounts once and is unaffected. If you need dev-exact
182
+ * streaming, drive a caller-owned client manually.
183
+ */
184
+ export declare function useBrookStream(stream: AsyncIterable<string> | ReadableStream<Uint8Array> | Response | null | undefined, options?: {
185
+ config?: ParserConfig;
186
+ onError?: (err: Error) => void;
187
+ }): BrookClient;
188
+ /**
189
+ * Own a {@link BrookClient} driven by a CONTROLLED full string — the bridge for
190
+ * UIs that hold a streaming message as a single growing string prop (the common
191
+ * React shape) rather than as a stream. Pass the whole document-so-far on each
192
+ * render and {@link BrookClient.setContent} diffs it: a prefix-extension appends
193
+ * only the delta; any divergence (e.g. the finished text swapped for a
194
+ * re-processed final string) resets and reparses. Returns the owned client —
195
+ * pass it to `<BrookMarkdown client={…} />` (and read `outline()` etc.).
196
+ *
197
+ * Pass `streaming: false` once the content is final to finalize the stream and
198
+ * commit its last block (only then does a finished code fence highlight + show
199
+ * its copy button). If `streaming` is omitted or `true` the stream is left OPEN
200
+ * — right for a still-growing string, but a *complete static* string rendered as
201
+ * `useBrookMarkdownString(md)` keeps its last block in the streaming state until
202
+ * you pass `{ streaming: false }`. (Inferring "done" from an absent flag is
203
+ * deliberately avoided: it would re-finalize on every token for callers that
204
+ * grow the string without the flag — an O(n²) reparse trap.) The client is
205
+ * created once and destroyed on unmount; StrictMode's dev double-mount is handled
206
+ * (reattach re-feeds the document). For a true stream source
207
+ * (`Response` / `ReadableStream` / SSE generator) use {@link useBrookStream}
208
+ * instead — it avoids buffering the whole document as a string.
209
+ */
210
+ export declare function useBrookMarkdownString(content: string, options?: {
211
+ config?: ParserConfig;
212
+ streaming?: boolean;
213
+ }): BrookClient;
214
+ declare function BrookMarkdownImpl(props: BrookMarkdownProps): import("react/jsx-runtime").JSX.Element;
215
+ export declare const BrookMarkdown: import("react").MemoExoticComponent<typeof BrookMarkdownImpl>;
216
+ export declare function blockKindProps(block: Block, components?: Components): BlockComponentProps;
217
+ export declare function blocksEqual(prev: {
218
+ block: Block;
219
+ components?: Components;
220
+ virtualize?: boolean;
221
+ sanitize?: (html: string) => string;
222
+ childMemo?: boolean;
223
+ onRenderMetrics?: RenderMetricsHook;
224
+ decorators?: Decorator[];
225
+ urlTransform?: UrlTransform;
226
+ }, next: {
227
+ block: Block;
228
+ components?: Components;
229
+ virtualize?: boolean;
230
+ sanitize?: (html: string) => string;
231
+ childMemo?: boolean;
232
+ onRenderMetrics?: RenderMetricsHook;
233
+ decorators?: Decorator[];
234
+ urlTransform?: UrlTransform;
235
+ }): boolean;
236
+ export {};