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.
- package/CHANGELOG.md +1229 -0
- package/LICENSE +21 -0
- package/README.md +1265 -0
- package/dist/block-props.d.ts +18 -0
- package/dist/block-props.js +75 -0
- package/dist/client.d.ts +370 -0
- package/dist/client.js +754 -0
- package/dist/decorate.d.ts +24 -0
- package/dist/decorate.js +71 -0
- package/dist/dom.d.ts +130 -0
- package/dist/dom.js +627 -0
- package/dist/element.d.ts +20 -0
- package/dist/element.js +288 -0
- package/dist/hi.d.ts +12 -0
- package/dist/hi.js +215 -0
- package/dist/html-to-react.d.ts +61 -0
- package/dist/html-to-react.js +338 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +18 -0
- package/dist/morph.d.ts +28 -0
- package/dist/morph.js +166 -0
- package/dist/react.d.ts +236 -0
- package/dist/react.js +539 -0
- package/dist/renderers/CodeBlock.d.ts +7 -0
- package/dist/renderers/CodeBlock.js +75 -0
- package/dist/renderers/Math.d.ts +14 -0
- package/dist/renderers/Math.js +15 -0
- package/dist/renderers/Mermaid.d.ts +13 -0
- package/dist/renderers/Mermaid.js +15 -0
- package/dist/server-react.d.ts +32 -0
- package/dist/server-react.js +48 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +82 -0
- package/dist/solid.d.ts +104 -0
- package/dist/solid.js +54 -0
- package/dist/styles.css +188 -0
- package/dist/svelte.d.ts +80 -0
- package/dist/svelte.js +59 -0
- package/dist/types-core.d.ts +436 -0
- package/dist/types-core.js +0 -0
- package/dist/types-react.d.ts +13 -0
- package/dist/types-react.js +0 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/url-safety.d.ts +12 -0
- package/dist/url-safety.js +45 -0
- package/dist/vue.d.ts +94 -0
- package/dist/vue.js +79 -0
- package/dist/wasm/LICENSE +21 -0
- package/dist/wasm/README.md +71 -0
- package/dist/wasm/brook_md_core.d.ts +166 -0
- package/dist/wasm/brook_md_core.js +512 -0
- package/dist/wasm/brook_md_core_bg.wasm +0 -0
- package/dist/wasm/brook_md_core_bg.wasm.d.ts +26 -0
- package/dist/worker-core.d.ts +65 -0
- package/dist/worker-core.js +155 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +49 -0
- package/package.json +87 -0
package/dist/react.d.ts
ADDED
|
@@ -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 {};
|