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
package/dist/react.js ADDED
@@ -0,0 +1,539 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ createElement,
4
+ memo,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ useSyncExternalStore,
10
+ useDeferredValue
11
+ } from "react";
12
+ import { BrookClient } from "./client.js";
13
+ import { CodeBlock } from "./renderers/CodeBlock.js";
14
+ import { MathBlock } from "./renderers/Math.js";
15
+ import { Mermaid } from "./renderers/Mermaid.js";
16
+ import { htmlToReact } from "./html-to-react.js";
17
+ const NO_DEFER_BLOCKS = [];
18
+ const warnedUnstable = /* @__PURE__ */ new Set();
19
+ function useUnstablePropWarning(name, value) {
20
+ const ref = useRef(value);
21
+ if (ref.current !== value) {
22
+ const prevDefined = ref.current !== void 0 && ref.current !== null;
23
+ const nextDefined = value !== void 0 && value !== null;
24
+ ref.current = value;
25
+ const env = globalThis.process?.env;
26
+ if (prevDefined && nextDefined && !warnedUnstable.has(name) && (!env || env.NODE_ENV !== "production")) {
27
+ warnedUnstable.add(name);
28
+ console.warn(
29
+ `<BrookMarkdown>: the \`${name}\` prop changed identity between renders. Hoist it to module scope or wrap it in useMemo \u2014 a fresh identity each render busts the per-block memo and re-parses every block on every patch.`
30
+ );
31
+ }
32
+ }
33
+ }
34
+ function __resetUnstableWarnings() {
35
+ warnedUnstable.clear();
36
+ }
37
+ function BrookMarkdownFromClient({
38
+ client,
39
+ components,
40
+ virtualize,
41
+ stickToBottom,
42
+ sanitize,
43
+ childMemo,
44
+ className,
45
+ id,
46
+ role,
47
+ "aria-live": ariaLive,
48
+ "aria-atomic": ariaAtomic,
49
+ onRenderMetrics,
50
+ deferTail,
51
+ decorators,
52
+ urlTransform
53
+ }) {
54
+ const blocks = useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot);
55
+ useUnstablePropWarning("decorators", decorators);
56
+ useUnstablePropWarning("urlTransform", urlTransform);
57
+ const deferred = useDeferredValue(deferTail ? blocks : NO_DEFER_BLOCKS);
58
+ const rendered = deferTail ? deferred : blocks;
59
+ const isDeferring = deferTail ? rendered !== blocks : false;
60
+ const comps = useMemo(
61
+ () => components && Object.keys(components).length > 0 ? components : void 0,
62
+ [components]
63
+ );
64
+ const onMetrics = useMemo(
65
+ () => onRenderMetrics ? (id2, m) => {
66
+ client.__noteRender();
67
+ onRenderMetrics(id2, m);
68
+ } : void 0,
69
+ [client, onRenderMetrics]
70
+ );
71
+ const rootClass = isDeferring ? className ? `brook-md brook-deferred ${className}` : "brook-md brook-deferred" : className ? `brook-md ${className}` : "brook-md";
72
+ return /* @__PURE__ */ jsxs(
73
+ "div",
74
+ {
75
+ className: rootClass,
76
+ id,
77
+ role,
78
+ "aria-live": ariaLive,
79
+ "aria-atomic": ariaAtomic,
80
+ children: [
81
+ rendered.map((b) => /* @__PURE__ */ jsx(
82
+ BlockView,
83
+ {
84
+ block: b,
85
+ components: comps,
86
+ virtualize,
87
+ sanitize,
88
+ childMemo,
89
+ onRenderMetrics: onMetrics,
90
+ decorators,
91
+ urlTransform
92
+ },
93
+ b.id
94
+ )),
95
+ stickToBottom && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { scrollSnapAlign: "end" }, className: "brook-bottom-anchor" })
96
+ ]
97
+ }
98
+ );
99
+ }
100
+ function useBrookStream(stream, options) {
101
+ const [client] = useState(() => new BrookClient({ config: options?.config, coalesce: true }));
102
+ const onErrorRef = useRef(options?.onError);
103
+ onErrorRef.current = options?.onError;
104
+ const prevStream = useRef(void 0);
105
+ useEffect(() => {
106
+ client.reattach();
107
+ return () => client.destroy();
108
+ }, [client]);
109
+ useEffect(() => {
110
+ if (stream == null) return;
111
+ const ac = new AbortController();
112
+ if (prevStream.current !== void 0 && prevStream.current !== stream) {
113
+ client.reset();
114
+ }
115
+ prevStream.current = stream;
116
+ client.pipeFrom(stream, { signal: ac.signal }).catch((e) => {
117
+ if (!ac.signal.aborted) {
118
+ onErrorRef.current?.(e instanceof Error ? e : new Error(String(e)));
119
+ }
120
+ });
121
+ return () => ac.abort();
122
+ }, [stream, client]);
123
+ return client;
124
+ }
125
+ function useBrookMarkdownString(content, options) {
126
+ const [client] = useState(() => new BrookClient({ config: options?.config, coalesce: true }));
127
+ useEffect(() => {
128
+ client.reattach();
129
+ return () => client.destroy();
130
+ }, [client]);
131
+ const streaming = options?.streaming;
132
+ useEffect(() => {
133
+ client.setContent(content, { done: streaming === false });
134
+ }, [client, content, streaming]);
135
+ return client;
136
+ }
137
+ function BrookMarkdownFromStream(props) {
138
+ const client = useBrookStream(props.stream, {
139
+ config: props.streamConfig,
140
+ onError: props.onStreamError
141
+ });
142
+ return /* @__PURE__ */ jsx(BrookMarkdownFromClient, { ...props, client });
143
+ }
144
+ function BrookMarkdownImpl(props) {
145
+ if (props.stream != null && props.client == null) {
146
+ return /* @__PURE__ */ jsx(BrookMarkdownFromStream, { ...props });
147
+ }
148
+ if (props.client == null) {
149
+ throw new Error("<BrookMarkdown>: pass either a `client` or a `stream` prop.");
150
+ }
151
+ return /* @__PURE__ */ jsx(BrookMarkdownFromClient, { ...props });
152
+ }
153
+ const BrookMarkdown = memo(BrookMarkdownImpl);
154
+ function decodeEntities(s) {
155
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
156
+ }
157
+ function decodeCodeText(html) {
158
+ const m = html.match(/<pre><code[^>]*>([\s\S]*?)<\/code><\/pre>/);
159
+ return m ? decodeEntities(m[1]) : "";
160
+ }
161
+ function decodeMathText(html) {
162
+ const d = html.match(/<div class="math math-display">([\s\S]*?)<\/div>/);
163
+ if (d) return decodeEntities(d[1]);
164
+ return decodeCodeText(html);
165
+ }
166
+ function blockKindProps(block, components) {
167
+ const props = {
168
+ block,
169
+ html: block.html,
170
+ open: block.open,
171
+ speculative: block.speculative
172
+ };
173
+ const data = block.kind.data;
174
+ if (block.kind.type === "CodeBlock") {
175
+ props.text = data?.code ?? decodeCodeText(block.html);
176
+ props.language = data?.lang ?? "";
177
+ if (typeof data?.code === "string") {
178
+ props.code = { lang: data.lang ?? null, code: data.code };
179
+ }
180
+ } else if (block.kind.type === "MathBlock") {
181
+ props.text = data?.latex ?? decodeMathText(block.html);
182
+ if (typeof data?.latex === "string") {
183
+ props.math = { latex: data.latex };
184
+ }
185
+ } else if (block.kind.type === "List") {
186
+ if (data && typeof data.start === "number") {
187
+ props.list = { ordered: !!data.ordered, start: data.start, items: data.items };
188
+ }
189
+ } else if (block.kind.type === "Component") {
190
+ props.tag = data?.tag ?? "";
191
+ props.attrs = reactAttrs(data?.attrs ?? []);
192
+ props.html = componentInnerHtml(block.html, props.tag);
193
+ props.children = htmlToReact(props.html, components ?? {});
194
+ } else if (block.kind.type === "Table") {
195
+ props.table = block.kind.data;
196
+ } else if (block.kind.type === "Heading") {
197
+ if (typeof block.kind.data === "object" && block.kind.data !== null) {
198
+ props.heading = block.kind.data;
199
+ }
200
+ } else if (block.kind.type === "Blockquote" || block.kind.type === "Alert") {
201
+ const cd = block.kind.data;
202
+ if (cd && Array.isArray(cd.nested)) {
203
+ props.container = { nested: cd.nested };
204
+ }
205
+ }
206
+ return props;
207
+ }
208
+ const REACT_ATTR_NAME = Object.assign(/* @__PURE__ */ Object.create(null), {
209
+ class: "className",
210
+ for: "htmlFor"
211
+ });
212
+ const ATTR_DENY = /* @__PURE__ */ new Set([
213
+ "dangerouslysetinnerhtml",
214
+ "ref",
215
+ "key",
216
+ "defaultvalue",
217
+ "defaultchecked",
218
+ "suppresshydrationwarning",
219
+ "suppresscontenteditablewarning"
220
+ ]);
221
+ const SAFE_ATTR_NAME = /^[a-z][a-z0-9-]*$/i;
222
+ function reactAttrs(pairs) {
223
+ const out = {};
224
+ for (const [k, v] of pairs) {
225
+ const lower = k.toLowerCase();
226
+ if (lower.startsWith("on")) continue;
227
+ if (ATTR_DENY.has(lower)) continue;
228
+ if (!(lower in REACT_ATTR_NAME) && !SAFE_ATTR_NAME.test(k)) continue;
229
+ out[REACT_ATTR_NAME[lower] ?? k] = v;
230
+ }
231
+ return out;
232
+ }
233
+ function componentInnerHtml(html, tag) {
234
+ const gt = html.indexOf(">");
235
+ if (gt < 0) return "";
236
+ let inner = html.slice(gt + 1);
237
+ const close = `</${tag}>`;
238
+ if (inner.endsWith(close)) inner = inner.slice(0, -close.length);
239
+ return inner.replace(/^\n/, "").replace(/\n$/, "");
240
+ }
241
+ const CHILD_MEMO_CAP = 4096;
242
+ function SafeHtml({
243
+ html,
244
+ components,
245
+ childMemo,
246
+ decorators,
247
+ urlTransform
248
+ }) {
249
+ const memoRef = useRef(null);
250
+ const compRef = useRef(null);
251
+ const decoRef = useRef(void 0);
252
+ const urlRef = useRef(void 0);
253
+ return useMemo(() => {
254
+ const opts = { decorators, urlTransform };
255
+ if (!childMemo) {
256
+ memoRef.current = null;
257
+ return htmlToReact(html, components, void 0, opts);
258
+ }
259
+ if (memoRef.current === null || compRef.current !== components || decoRef.current !== decorators || urlRef.current !== urlTransform) {
260
+ memoRef.current = /* @__PURE__ */ new Map();
261
+ compRef.current = components;
262
+ decoRef.current = decorators;
263
+ urlRef.current = urlTransform;
264
+ }
265
+ const map = memoRef.current;
266
+ if (map.size > CHILD_MEMO_CAP) map.clear();
267
+ return htmlToReact(html, components, map, opts);
268
+ }, [html, components, childMemo, decorators, urlTransform]);
269
+ }
270
+ function KeyedListItemImpl({
271
+ html,
272
+ components,
273
+ sanitize
274
+ }) {
275
+ const safe = sanitize ? sanitize(html) : html;
276
+ if (components) {
277
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(SafeHtml, { html: safe, components }) });
278
+ }
279
+ return /* @__PURE__ */ jsx("li", { dangerouslySetInnerHTML: { __html: safe } });
280
+ }
281
+ const KeyedListItem = memo(KeyedListItemImpl);
282
+ function KeyedList({
283
+ className,
284
+ ordered,
285
+ start,
286
+ items,
287
+ components,
288
+ sanitize
289
+ }) {
290
+ const children = items.map((it, i) => /* @__PURE__ */ jsx(KeyedListItem, { html: it.html, components, sanitize }, i));
291
+ const inner = ordered ? createElement("ol", start !== void 0 && start !== 1 ? { start } : null, children) : createElement("ul", null, children);
292
+ return /* @__PURE__ */ jsx("div", { className, children: inner });
293
+ }
294
+ function KeyedContainer({
295
+ block,
296
+ nested,
297
+ components
298
+ }) {
299
+ const tagName = block.kind.type === "Alert" ? "div" : "blockquote";
300
+ const attrs = useMemo(() => parseOpenTagAttrs(block.html), [block.html]);
301
+ const children = [];
302
+ if (block.kind.type === "Alert") {
303
+ const title = alertTitleHtml(block.html);
304
+ if (title) {
305
+ children.push(/* @__PURE__ */ jsx(SafeHtml, { html: title, components }, "title"));
306
+ }
307
+ }
308
+ for (let i = 0; i < nested.length; i++) {
309
+ children.push(/* @__PURE__ */ jsx(SafeHtml, { html: nested[i].html, components }, i));
310
+ }
311
+ return createElement(tagName, attrs, children);
312
+ }
313
+ const CONTAINER_ATTR_RE = /([a-zA-Z][a-zA-Z0-9-]*)="([^"]*)"/g;
314
+ function parseOpenTagAttrs(html) {
315
+ const gt = html.indexOf(">");
316
+ const open = gt < 0 ? html : html.slice(0, gt);
317
+ const out = {};
318
+ let m;
319
+ CONTAINER_ATTR_RE.lastIndex = 0;
320
+ while (m = CONTAINER_ATTR_RE.exec(open)) {
321
+ const name = m[1].toLowerCase();
322
+ if (name === "class") out.className = m[2];
323
+ else if (name === "dir" || name === "role" || name.startsWith("data-")) out[name] = m[2];
324
+ }
325
+ return out;
326
+ }
327
+ function alertTitleHtml(html) {
328
+ const m = html.match(/<p class="markdown-alert-title"[^>]*>.*?<\/p>/s);
329
+ return m ? m[0] : "";
330
+ }
331
+ const NO_COMPONENTS = Object.freeze(/* @__PURE__ */ Object.create(null));
332
+ const TableCellView = memo(function TableCellView2({
333
+ tag,
334
+ html,
335
+ align,
336
+ scope,
337
+ components,
338
+ sanitize
339
+ }) {
340
+ const tree = useMemo(
341
+ () => htmlToReact(sanitize ? sanitize(html) : html, components),
342
+ [html, components, sanitize]
343
+ );
344
+ return createElement(
345
+ tag,
346
+ {
347
+ scope: tag === "th" && scope ? "col" : void 0,
348
+ style: align ? { textAlign: align } : void 0
349
+ },
350
+ tree
351
+ );
352
+ });
353
+ function KeyedTable({
354
+ data,
355
+ html,
356
+ components,
357
+ sanitize
358
+ }) {
359
+ const comps = components ?? NO_COMPONENTS;
360
+ const headerPrefix = useMemo(() => {
361
+ const i = html.indexOf("</thead>");
362
+ return i === -1 ? html : html.slice(0, i);
363
+ }, [html]);
364
+ const dir = useMemo(
365
+ () => headerPrefix.startsWith('<table dir="auto"') ? "auto" : void 0,
366
+ [headerPrefix]
367
+ );
368
+ const scope = useMemo(() => headerPrefix.includes('<th scope="col"'), [headerPrefix]);
369
+ const aligns = data.aligns;
370
+ return /* @__PURE__ */ jsxs("table", { dir, children: [
371
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: data.headers.map((c, j) => /* @__PURE__ */ jsx(
372
+ TableCellView,
373
+ {
374
+ tag: "th",
375
+ html: c.html,
376
+ align: aligns[j] ?? null,
377
+ scope,
378
+ components: comps,
379
+ sanitize
380
+ },
381
+ j
382
+ )) }) }),
383
+ data.rows.length > 0 && /* @__PURE__ */ jsx("tbody", { children: data.rows.map((row, i) => /* @__PURE__ */ jsx("tr", { children: row.map((c, j) => /* @__PURE__ */ jsx(
384
+ TableCellView,
385
+ {
386
+ tag: "td",
387
+ html: c.html,
388
+ align: aligns[j] ?? null,
389
+ scope,
390
+ components: comps,
391
+ sanitize
392
+ },
393
+ j
394
+ )) }, i)) })
395
+ ] });
396
+ }
397
+ const INTRINSIC_PX = {
398
+ Paragraph: 80,
399
+ Heading: 44,
400
+ CodeBlock: 300,
401
+ MathBlock: 140,
402
+ Mermaid: 220,
403
+ List: 120,
404
+ Blockquote: 100,
405
+ Alert: 120,
406
+ Table: 200,
407
+ Rule: 24,
408
+ Html: 80,
409
+ Component: 120
410
+ };
411
+ function BlockViewImpl(props) {
412
+ const { block, virtualize, onRenderMetrics } = props;
413
+ const metricsRef = useRef(
414
+ onRenderMetrics ? { renderCount: 0, toggle: 0, speculative: block.speculative } : null
415
+ );
416
+ const hasPerf = typeof performance !== "undefined";
417
+ const t0 = onRenderMetrics && hasPerf ? performance.now() : 0;
418
+ const content = renderBlockContent(props);
419
+ if (onRenderMetrics) {
420
+ const m = metricsRef.current ??= { renderCount: 0, toggle: 0, speculative: block.speculative };
421
+ m.renderCount++;
422
+ if (m.speculative !== block.speculative) {
423
+ m.toggle++;
424
+ m.speculative = block.speculative;
425
+ }
426
+ onRenderMetrics(block.id, {
427
+ renderCount: m.renderCount,
428
+ speculativeToggleCount: m.toggle,
429
+ lastRenderMs: hasPerf ? performance.now() - t0 : 0,
430
+ kind: block.kind.type
431
+ });
432
+ }
433
+ if (virtualize && !block.open && !block.speculative) {
434
+ const px = INTRINSIC_PX[block.kind.type] ?? 120;
435
+ return /* @__PURE__ */ jsx("div", { style: { contentVisibility: "auto", containIntrinsicSize: `auto ${px}px` }, children: content });
436
+ }
437
+ return content;
438
+ }
439
+ function renderBlockContent({
440
+ block,
441
+ components,
442
+ sanitize,
443
+ childMemo,
444
+ decorators,
445
+ urlTransform
446
+ }) {
447
+ const kind = block.kind.type;
448
+ const hasInlineTransforms = !!decorators || !!urlTransform;
449
+ if (components) {
450
+ if (kind === "Component") {
451
+ const tag = block.kind.data?.tag;
452
+ const override = tag && components[tag] || components.Component;
453
+ if (override) {
454
+ return createElement(override, blockKindProps(block, components));
455
+ }
456
+ }
457
+ const blockOverride = components[kind];
458
+ if (blockOverride) {
459
+ return createElement(blockOverride, blockKindProps(block, components));
460
+ }
461
+ }
462
+ switch (kind) {
463
+ case "CodeBlock": {
464
+ const wantsCodeOverride = !!components && (!!components.pre || !!components.code);
465
+ if (!wantsCodeOverride) return /* @__PURE__ */ jsx(CodeBlock, { html: block.html, open: block.open });
466
+ break;
467
+ }
468
+ case "MathBlock":
469
+ return /* @__PURE__ */ jsx(MathBlock, { html: block.html, open: block.open });
470
+ case "Mermaid":
471
+ return /* @__PURE__ */ jsx(Mermaid, { html: block.html, open: block.open });
472
+ }
473
+ const className = "brook-block brook-block-" + kind.toLowerCase() + (block.open ? " brook-open" : "") + (block.speculative ? " brook-speculative" : "");
474
+ if (kind === "Table" && block.open && !hasInlineTransforms) {
475
+ const data = block.kind.data;
476
+ if (data && Array.isArray(data.rows)) {
477
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(KeyedTable, { data, html: block.html, components, sanitize }) });
478
+ }
479
+ }
480
+ if (block.open && kind === "List" && !hasInlineTransforms) {
481
+ const ld = block.kind.data;
482
+ const items = ld?.items;
483
+ const tagOverride = !!components && (!!components.ul || !!components.ol || !!components.li);
484
+ if (Array.isArray(items) && items.length > 0 && !tagOverride) {
485
+ return /* @__PURE__ */ jsx(
486
+ KeyedList,
487
+ {
488
+ className,
489
+ ordered: !!ld?.ordered,
490
+ start: ld?.start,
491
+ items,
492
+ components,
493
+ sanitize
494
+ }
495
+ );
496
+ }
497
+ }
498
+ if (components || hasInlineTransforms) {
499
+ if (components && !hasInlineTransforms && block.open && !sanitize && (kind === "Blockquote" || kind === "Alert")) {
500
+ const nested = block.kind.data?.nested;
501
+ if (Array.isArray(nested)) {
502
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(KeyedContainer, { block, nested, components }) });
503
+ }
504
+ }
505
+ const safe = sanitize ? sanitize(block.html) : block.html;
506
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
507
+ SafeHtml,
508
+ {
509
+ html: safe,
510
+ components: components ?? NO_COMPONENTS,
511
+ childMemo: childMemo && block.open,
512
+ decorators,
513
+ urlTransform
514
+ }
515
+ ) });
516
+ }
517
+ return /* @__PURE__ */ jsx(
518
+ "div",
519
+ {
520
+ className,
521
+ dangerouslySetInnerHTML: { __html: sanitize ? sanitize(block.html) : block.html }
522
+ }
523
+ );
524
+ }
525
+ function blocksEqual(prev, next) {
526
+ return prev.block.id === next.block.id && prev.block.html === next.block.html && prev.block.open === next.block.open && prev.block.speculative === next.block.speculative && prev.components === next.components && prev.virtualize === next.virtualize && prev.sanitize === next.sanitize && prev.childMemo === next.childMemo && prev.onRenderMetrics === next.onRenderMetrics && // Identity compare: an unstable decorators/urlTransform (fresh each render)
527
+ // busts the memo so every committed block re-decorates — the O(n²) footgun
528
+ // the dev warning calls out. A hoisted/memoized value keeps the memo holding.
529
+ prev.decorators === next.decorators && prev.urlTransform === next.urlTransform;
530
+ }
531
+ const BlockView = memo(BlockViewImpl, blocksEqual);
532
+ export {
533
+ BrookMarkdown,
534
+ __resetUnstableWarnings,
535
+ blockKindProps,
536
+ blocksEqual,
537
+ useBrookMarkdownString,
538
+ useBrookStream
539
+ };
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ html: string;
3
+ open: boolean;
4
+ }
5
+ declare function CodeBlockImpl({ html, open }: Props): import("react/jsx-runtime").JSX.Element;
6
+ export declare const CodeBlock: import("react").MemoExoticComponent<typeof CodeBlockImpl>;
7
+ export {};
@@ -0,0 +1,75 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { highlight } from "../hi.js";
4
+ import { extractLang } from "../block-props.js";
5
+ function decodeText(html) {
6
+ const m = html.match(/<pre><code[^>]*>([\s\S]*?)<\/code><\/pre>/);
7
+ if (!m) return "";
8
+ return m[1].replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
9
+ }
10
+ function CodeBlockImpl({ html, open }) {
11
+ const lang = extractLang(html) || "text";
12
+ const text = useMemo(() => open ? "" : decodeText(html), [html, open]);
13
+ const highlighted = useMemo(() => {
14
+ if (!text) return null;
15
+ return highlight(text, lang);
16
+ }, [text, lang]);
17
+ const [copied, setCopied] = useState(false);
18
+ const timerRef = useRef(null);
19
+ useEffect(() => {
20
+ if (open) setCopied(false);
21
+ }, [open, html]);
22
+ useEffect(() => {
23
+ return () => {
24
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
25
+ };
26
+ }, []);
27
+ const onCopy = useCallback(() => {
28
+ const write = typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText ? navigator.clipboard.writeText.bind(navigator.clipboard) : null;
29
+ if (!write || !text) return;
30
+ write(text).then(
31
+ () => {
32
+ setCopied(true);
33
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
34
+ timerRef.current = setTimeout(() => setCopied(false), 1500);
35
+ },
36
+ // Permission denied / blocked: stay silent, leave button usable.
37
+ () => {
38
+ }
39
+ );
40
+ }, [text]);
41
+ return /* @__PURE__ */ jsxs("div", { className: "brook-code-block" + (open ? " brook-streaming" : ""), children: [
42
+ /* @__PURE__ */ jsxs("div", { className: "brook-code-header", children: [
43
+ /* @__PURE__ */ jsx("span", { className: "brook-code-lang", children: lang }),
44
+ open ? /* @__PURE__ */ jsx("span", { className: "brook-code-streaming-pill", children: "streaming" }) : /* @__PURE__ */ jsx(
45
+ "button",
46
+ {
47
+ type: "button",
48
+ className: "brook-code-copy",
49
+ onClick: onCopy,
50
+ "aria-label": copied ? "Copied" : "Copy code",
51
+ "aria-live": "polite",
52
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
53
+ /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }),
54
+ /* @__PURE__ */ jsx("span", { children: "Copied" })
55
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
56
+ /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
57
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
58
+ /* @__PURE__ */ jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
59
+ ] }),
60
+ /* @__PURE__ */ jsx("span", { children: "Copy" })
61
+ ] })
62
+ }
63
+ )
64
+ ] }),
65
+ /* @__PURE__ */ jsx("div", { className: "brook-code-body", children: highlighted ? (
66
+ // tabIndex=0 + role/label so keyboard users can scroll long code and
67
+ // screen readers announce the region with its language.
68
+ /* @__PURE__ */ jsx("pre", { tabIndex: 0, role: "region", "aria-label": `${lang} code`, children: /* @__PURE__ */ jsx("code", { dangerouslySetInnerHTML: { __html: highlighted } }) })
69
+ ) : /* @__PURE__ */ jsx("div", { tabIndex: 0, role: "region", "aria-label": `${lang} code`, dangerouslySetInnerHTML: { __html: html } }) })
70
+ ] });
71
+ }
72
+ const CodeBlock = memo(CodeBlockImpl);
73
+ export {
74
+ CodeBlock
75
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Default math block — emits the LaTeX inside a `<div class="math
3
+ * math-display">` (or `<span class="math math-inline">` for inline). brookmd
4
+ * stays zero-dep, so it does not ship KaTeX/MathJax: bring your own typesetter
5
+ * (run it over the rendered `.math` nodes once a block closes), or override
6
+ * this slot via `components.MathBlock` to render the LaTeX yourself.
7
+ */
8
+ interface Props {
9
+ html: string;
10
+ open: boolean;
11
+ }
12
+ declare function MathImpl({ html, open }: Props): import("react/jsx-runtime").JSX.Element;
13
+ export declare const MathBlock: import("react").MemoExoticComponent<typeof MathImpl>;
14
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { memo } from "react";
3
+ function MathImpl({ html, open }) {
4
+ return /* @__PURE__ */ jsxs("div", { className: "brook-math-block" + (open ? " brook-streaming" : ""), children: [
5
+ /* @__PURE__ */ jsxs("div", { className: "brook-math-header", children: [
6
+ /* @__PURE__ */ jsx("span", { className: "brook-math-lang", children: "math" }),
7
+ open && /* @__PURE__ */ jsx("span", { className: "brook-code-streaming-pill", children: "streaming" })
8
+ ] }),
9
+ /* @__PURE__ */ jsx("div", { className: "brook-math-body", dangerouslySetInnerHTML: { __html: html } })
10
+ ] });
11
+ }
12
+ const MathBlock = memo(MathImpl);
13
+ export {
14
+ MathBlock
15
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Default mermaid block — renders the diagram source verbatim in a code-like
3
+ * container. brookmd stays zero-dep and does not ship the Mermaid runtime:
4
+ * override this slot via `components.Mermaid` to render to SVG with your own
5
+ * Mermaid build (typically `mermaid.run` over the closed-block source text).
6
+ */
7
+ interface Props {
8
+ html: string;
9
+ open: boolean;
10
+ }
11
+ declare function MermaidImpl({ html, open }: Props): import("react/jsx-runtime").JSX.Element;
12
+ export declare const Mermaid: import("react").MemoExoticComponent<typeof MermaidImpl>;
13
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { memo } from "react";
3
+ function MermaidImpl({ html, open }) {
4
+ return /* @__PURE__ */ jsxs("div", { className: "brook-mermaid-block" + (open ? " brook-streaming" : ""), children: [
5
+ /* @__PURE__ */ jsxs("div", { className: "brook-mermaid-header", children: [
6
+ /* @__PURE__ */ jsx("span", { className: "brook-mermaid-lang", children: "mermaid" }),
7
+ open && /* @__PURE__ */ jsx("span", { className: "brook-code-streaming-pill", children: "streaming" })
8
+ ] }),
9
+ /* @__PURE__ */ jsx("div", { className: "brook-mermaid-body", dangerouslySetInnerHTML: { __html: html } })
10
+ ] });
11
+ }
12
+ const Mermaid = memo(MermaidImpl);
13
+ export {
14
+ Mermaid
15
+ };