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,24 @@
1
+ import type { Decorator } from "./types-core.js";
2
+ /** Ancestor tags inside which decoration is skipped by default. */
3
+ export declare const DEFAULT_SKIP: readonly string[];
4
+ /** A run of the original text, or one matched span the caller turns into a node. */
5
+ export type DecorateSegment = {
6
+ type: "text";
7
+ text: string;
8
+ } | {
9
+ type: "match";
10
+ decorator: Decorator;
11
+ matchText: string;
12
+ groups: string[];
13
+ };
14
+ /**
15
+ * Split one inline text node's string into segments per the active decorators.
16
+ * Decorators apply in order; each one only re-scans the still-TEXT segments left
17
+ * by the previous ones (so a match is never decorated twice). `ancestors` is the
18
+ * chain of enclosing tag names — a decorator whose `skipInside` (default
19
+ * {@link DEFAULT_SKIP}) intersects it is not applied here.
20
+ *
21
+ * Returns `null` when nothing matched at all, so the caller can take the
22
+ * zero-allocation fast path of emitting the original text node unchanged.
23
+ */
24
+ export declare function decorateSegments(text: string, decorators: Decorator[], ancestors: string[]): DecorateSegment[] | null;
@@ -0,0 +1,71 @@
1
+ const DEFAULT_SKIP = ["a", "code", "pre", "kbd"];
2
+ function escapeRegExp(s) {
3
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
+ }
5
+ function toGlobalRegExp(match) {
6
+ if (typeof match === "string") return new RegExp(escapeRegExp(match), "g");
7
+ let flags = match.flags.replace("y", "");
8
+ if (!flags.includes("g")) flags += "g";
9
+ return new RegExp(match.source, flags);
10
+ }
11
+ function skippedByAncestor(ancestors, skip) {
12
+ for (let i = 0; i < ancestors.length; i++) {
13
+ const a = ancestors[i].toLowerCase();
14
+ for (let j = 0; j < skip.length; j++) {
15
+ if (a === skip[j].toLowerCase()) return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ function splitOne(text, dec, out) {
21
+ const re = toGlobalRegExp(dec.match);
22
+ let last = 0;
23
+ let matched = false;
24
+ let m;
25
+ while ((m = re.exec(text)) !== null) {
26
+ const matchText = m[0];
27
+ if (matchText.length === 0) {
28
+ re.lastIndex++;
29
+ continue;
30
+ }
31
+ matched = true;
32
+ if (m.index > last) out.push({ type: "text", text: text.slice(last, m.index) });
33
+ const groups = [];
34
+ for (let i = 1; i < m.length; i++) groups.push(m[i] ?? "");
35
+ out.push({ type: "match", decorator: dec, matchText, groups });
36
+ last = m.index + matchText.length;
37
+ }
38
+ if (!matched) return false;
39
+ if (last < text.length) out.push({ type: "text", text: text.slice(last) });
40
+ return true;
41
+ }
42
+ function decorateSegments(text, decorators, ancestors) {
43
+ let segments = null;
44
+ for (let d = 0; d < decorators.length; d++) {
45
+ const dec = decorators[d];
46
+ if (skippedByAncestor(ancestors, dec.skipInside ?? DEFAULT_SKIP)) continue;
47
+ const source = segments ?? [{ type: "text", text }];
48
+ let changedThisDecorator = false;
49
+ const next = [];
50
+ for (let i = 0; i < source.length; i++) {
51
+ const seg = source[i];
52
+ if (seg.type !== "text") {
53
+ next.push(seg);
54
+ continue;
55
+ }
56
+ const before = next.length;
57
+ if (splitOne(seg.text, dec, next)) {
58
+ changedThisDecorator = true;
59
+ } else {
60
+ next.length = before;
61
+ next.push(seg);
62
+ }
63
+ }
64
+ if (changedThisDecorator) segments = next;
65
+ }
66
+ return segments;
67
+ }
68
+ export {
69
+ DEFAULT_SKIP,
70
+ decorateSegments
71
+ };
package/dist/dom.d.ts ADDED
@@ -0,0 +1,130 @@
1
+ import type { BrookClient } from "./client.js";
2
+ import type { Block, BlockComponentProps, Decorator, RenderMetricsHook, UrlTransform } from "./types-core.js";
3
+ /**
4
+ * Framework-neutral DOM renderer for a {@link BrookClient}. Mounts the streaming
5
+ * document into a container and keeps it in sync via direct DOM mutation,
6
+ * mirroring the JSX renderer's block model: each block is keyed by its stable
7
+ * parser-assigned id, and a committed block's node is reused untouched on every
8
+ * later patch (the parity analogue of the JSX renderer's block memo). Only the
9
+ * streaming tail is rebuilt.
10
+ *
11
+ * This is the foundation the Web Component / Vue / Svelte / Solid bindings
12
+ * build on; it imports only neutral modules and carries no framework dependency.
13
+ *
14
+ * ## Custom components
15
+ *
16
+ * Pass `components` to override a whole block kind (or a component tag). Keys
17
+ * are capitalized block-kind names (`CodeBlock`, `Table`, `Mermaid`…) or, for
18
+ * `Component` blocks, the tag name (e.g. `Thinking`) with `Component` as the
19
+ * generic fallback. A component receives {@link BlockComponentProps} and returns
20
+ * an `HTMLElement` or an HTML string. There is no tag-level override path (no
21
+ * `table`/`a`/`code` keys) — that requires an HTML→tree pass the DOM renderer
22
+ * doesn't carry.
23
+ */
24
+ export interface MountHandle {
25
+ destroy(): void;
26
+ refresh(): void;
27
+ /**
28
+ * The id of the streaming **tail** block — the one block that may re-render on
29
+ * the next patch (a committed block's node is frozen, so its id never appears
30
+ * here). Returns `null` when no block is open (idle / fully committed).
31
+ *
32
+ * Purely derived from the live snapshot; reading it renders nothing and mutates
33
+ * nothing. It exists so a fine-grained framework binding (Solid `createMemo`,
34
+ * Vue `computed`, Svelte `derived`) can narrow a reactive cell to *just the tail*
35
+ * for its own scheduling/diagnostics — the DOM is already updated by the
36
+ * renderer's own subscribe loop, so this never changes what is drawn.
37
+ */
38
+ openBlockId(): number | null;
39
+ }
40
+ export type DomBlockComponent = (props: BlockComponentProps) => HTMLElement | string;
41
+ /** Override map: capitalized block-kind / component-tag keys only. */
42
+ export type DomComponents = Record<string, DomBlockComponent>;
43
+ export interface MountOptions {
44
+ components?: DomComponents;
45
+ /**
46
+ * Optional HTML sanitizer applied to every generic block's HTML before it is
47
+ * injected via `innerHTML` — **including the streaming (open/speculative)
48
+ * tail**. The built-in code/math/mermaid renderers operate on already-escaped
49
+ * content and are not run through it (same as the JSX renderer). When omitted,
50
+ * rendering is byte-identical and zero-cost.
51
+ */
52
+ sanitize?: (html: string) => string;
53
+ /**
54
+ * Skip layout/paint for off-screen *closed* blocks via CSS
55
+ * `content-visibility: auto` (for very long documents). Off by default.
56
+ */
57
+ virtualize?: boolean;
58
+ /**
59
+ * Keep a bottom snap target so the view follows the streaming tail. CSS-only:
60
+ * emits a sentinel with `scroll-snap-align: end`; you add
61
+ * `scroll-snap-type: y proximity` to your scroll container. Off by default.
62
+ */
63
+ stickToBottom?: boolean;
64
+ /** Use the built-in code highlighter. Default true; suppressed when a
65
+ * `components.CodeBlock` override is supplied. */
66
+ highlightCode?: boolean;
67
+ /** Coalesce patches into one DOM write per animation frame. Default true. */
68
+ batch?: boolean;
69
+ /**
70
+ * Opt-in (default false). When a generic open/streaming block grows, morph its
71
+ * existing DOM subtree **in place** toward the new HTML instead of rebuilding
72
+ * the whole node with `innerHTML`. The browser then only repaints/relayouts
73
+ * the parts that changed, and focus/text-selection inside the streaming tail
74
+ * survive a token append. The default path (full rebuild) is byte-identical
75
+ * and unchanged; this only affects generic blocks rendered via the `innerHTML`
76
+ * fast path (not code/math/mermaid/component overrides). The morphed subtree is
77
+ * equivalent to the rebuilt one. */
78
+ morphOpenBlocks?: boolean;
79
+ /**
80
+ * Wrap or replace matched inline **text** while streaming (parity with the
81
+ * React `decorators` prop). Each {@link Decorator} runs POST-render over the
82
+ * block's real TEXT nodes via a `TreeWalker` (after `innerHTML`), once per
83
+ * committed block, honoring `skipInside` (default `a`/`code`/`pre`/`kbd`).
84
+ *
85
+ * **Trusted surface.** A decorator's `replace` may return a `Node` or a string;
86
+ * a returned Node is inserted as-is and is NOT sanitized (a `javascript:` href
87
+ * on a user-built `<a>` reaches the DOM). Route hrefs through the exported
88
+ * {@link safeUrl}. Enabling decorators moves a block onto the walk path (off the
89
+ * `innerHTML`/prefix-append/morph fast paths) — still O(n) per block.
90
+ */
91
+ decorators?: Decorator[];
92
+ /**
93
+ * Rewrite `href`/`src`/`poster` URLs as blocks render (parity with the React
94
+ * `urlTransform` prop). The output is re-sanitized
95
+ * (`safeUrl(urlTransform(safeUrl(v)))`) so it can never introduce a dangerous
96
+ * scheme. O(1) per attribute.
97
+ */
98
+ urlTransform?: UrlTransform;
99
+ /** Appended to the root's `className` (the `brook-md` class is always present). */
100
+ className?: string;
101
+ /** Set on the root element. */
102
+ id?: string;
103
+ /** Set on the root element (e.g. `"article"`, `"log"`). */
104
+ role?: string;
105
+ /**
106
+ * Make the root a live region so screen readers announce streamed content.
107
+ * `"polite"` coalesces rapid updates (does not read every token). Off by default.
108
+ */
109
+ ariaLive?: "off" | "polite" | "assertive";
110
+ /** Live-region atomicity; pair with `ariaLive`. Off by default. */
111
+ ariaAtomic?: boolean;
112
+ /**
113
+ * Optional render-churn probe. Fires once per ACTUAL node build/rebuild of a
114
+ * block — never for a committed block whose node is reused untouched on a
115
+ * tail-only patch. The callback gets the block id and a {@link RenderMetrics}
116
+ * sample (per-block `renderCount`/rebuild count, `speculativeToggleCount`,
117
+ * `lastRenderMs`, `kind`). Zero overhead when omitted, and advances
118
+ * `client.getMetrics().rebuildCount`.
119
+ */
120
+ onRenderMetrics?: RenderMetricsHook;
121
+ }
122
+ export declare function mountBrookMarkdown(client: BrookClient, container: HTMLElement, options?: MountOptions): MountHandle;
123
+ /**
124
+ * Derive the streaming tail's block id from an ordered snapshot: the id of the
125
+ * last block when it is open, else `null`. The open block is always the tail by
126
+ * construction (the parser only keeps the final block speculative/open), so this
127
+ * is an O(1) read of the last element — no scan. Shared so the framework
128
+ * adapters expose the same "what may re-render next" signal as the DOM handle.
129
+ */
130
+ export declare function tailOpenBlockId(snapshot: readonly Block[]): number | null;