@copilotkit/markdown 2.0.0-rc.20 → 2.0.0-rc.21

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.
@@ -1,67 +1,8 @@
1
- import { Fragment, useMemo, useRef } from "react";
2
- import { createStreamingMarkdownParserState, finalizeStreamingMarkdown, parseStreamingMarkdownChunk } from "@copilotkit/markdown";
1
+ import { STREAMING_MARKDOWN_CARET_ATTRS, STREAMING_MARKDOWN_CARET_STYLE, STREAMING_MARKDOWN_ROOT_CLASS, buildStreamingMarkdownRenderTree, findStreamingMarkdownOpenNode, getStreamingMarkdownStyles, updateStreamingMarkdownParserSession } from "@copilotkit/markdown";
2
+ import { Fragment, createElement, useMemo, useRef } from "react";
3
3
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/react/use-streaming-markdown-parser.tsx
6
- const DEFAULT_OPTIONS = {
7
- segmenter: true,
8
- enableTables: true,
9
- enableAutolinks: true
10
- };
11
- function normalizeOptions(options) {
12
- return {
13
- segmenter: options?.segmenter ?? DEFAULT_OPTIONS.segmenter,
14
- enableTables: options?.enableTables ?? DEFAULT_OPTIONS.enableTables,
15
- enableAutolinks: options?.enableAutolinks ?? DEFAULT_OPTIONS.enableAutolinks
16
- };
17
- }
18
- function getSegmenterKey(segmenter) {
19
- if (segmenter === true || segmenter === false) return String(segmenter);
20
- return `object:${segmenter.locale ?? ""}:${segmenter.granularity ?? "word"}`;
21
- }
22
- function getOptionsKey(options) {
23
- return `${getSegmenterKey(options.segmenter)}|tables:${String(options.enableTables)}|autolinks:${String(options.enableAutolinks)}`;
24
- }
25
- function parseFullText(text, options, isCompleteInput) {
26
- const initialState = createStreamingMarkdownParserState(options);
27
- const parsedState = text.length > 0 ? parseStreamingMarkdownChunk(initialState, text) : initialState;
28
- return isCompleteInput ? finalizeStreamingMarkdown(parsedState) : parsedState;
29
- }
30
- function createSession(text, options, optionsKey, isCompleteInput) {
31
- return {
32
- parserState: parseFullText(text, options, isCompleteInput),
33
- text,
34
- optionsKey,
35
- isCompleteInput
36
- };
37
- }
38
- function resolveNextSession(previous, text, options, optionsKey, isCompleteInput) {
39
- const optionsChanged = previous.optionsKey !== optionsKey;
40
- const completionChanged = previous.isCompleteInput !== isCompleteInput;
41
- if (optionsChanged) return createSession(text, options, optionsKey, isCompleteInput);
42
- const textChanged = text !== previous.text;
43
- if (!textChanged && !completionChanged) return previous;
44
- if (!textChanged && completionChanged) {
45
- const parserState = isCompleteInput ? finalizeStreamingMarkdown(previous.parserState) : parseStreamingMarkdownChunk(previous.parserState, "");
46
- return {
47
- ...previous,
48
- parserState,
49
- isCompleteInput
50
- };
51
- }
52
- let nextParserState;
53
- if (text.startsWith(previous.text)) {
54
- const suffix = text.slice(previous.text.length);
55
- nextParserState = suffix.length > 0 ? parseStreamingMarkdownChunk(previous.parserState, suffix) : previous.parserState;
56
- } else nextParserState = parseFullText(text, options, false);
57
- if (isCompleteInput) nextParserState = finalizeStreamingMarkdown(nextParserState);
58
- return {
59
- parserState: nextParserState,
60
- text,
61
- optionsKey,
62
- isCompleteInput
63
- };
64
- }
65
6
  /**
66
7
  * Internal prop-driven hook for streaming markdown parsing in React.
67
8
  *
@@ -72,42 +13,19 @@ function resolveNextSession(previous, text, options, optionsKey, isCompleteInput
72
13
  */
73
14
  function useStreamingMarkdownParser(text, options, isCompleteInput = false) {
74
15
  const sessionRef = useRef(null);
75
- const segmenter = options?.segmenter;
76
- const segmenterKind = typeof segmenter === "object" && segmenter !== null ? "object" : String(segmenter ?? true);
77
- const segmenterLocale = typeof segmenter === "object" && segmenter !== null ? segmenter.locale ?? "" : "";
78
- const segmenterGranularity = typeof segmenter === "object" && segmenter !== null ? segmenter.granularity ?? "word" : "";
79
- const normalizedOptions = useMemo(() => normalizeOptions(options), [
80
- options?.enableAutolinks,
81
- options?.enableTables,
82
- segmenterKind,
83
- segmenterLocale,
84
- segmenterGranularity
85
- ]);
86
- const optionsKey = getOptionsKey(normalizedOptions);
87
16
  return useMemo(() => {
88
- const next = resolveNextSession(sessionRef.current ?? createSession(text, normalizedOptions, optionsKey, isCompleteInput), text, normalizedOptions, optionsKey, isCompleteInput);
17
+ const next = updateStreamingMarkdownParserSession(sessionRef.current, text, options, isCompleteInput);
89
18
  sessionRef.current = next;
90
19
  return next;
91
20
  }, [
92
21
  text,
93
- normalizedOptions,
94
- optionsKey,
22
+ options,
95
23
  isCompleteInput
96
24
  ]).parserState;
97
25
  }
98
26
 
99
27
  //#endregion
100
28
  //#region src/react/streaming-markdown-renderer.tsx
101
- const SAFE_HREF = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
102
- const SAFE_IMG_SRC = /^(https?:|data:image\/(?!svg)|\/|\.\/|\.\.\/)/i;
103
- function sanitizeHref(href) {
104
- if (!href) return void 0;
105
- return SAFE_HREF.test(href.trim()) ? href : void 0;
106
- }
107
- function sanitizeImgSrc(src) {
108
- if (!src) return void 0;
109
- return SAFE_IMG_SRC.test(src.trim()) ? src : void 0;
110
- }
111
29
  /**
112
30
  * Helper for creating typed streaming markdown node renderer maps.
113
31
  *
@@ -116,128 +34,10 @@ function sanitizeImgSrc(src) {
116
34
  function createStreamingMarkdownNodeRenderers(renderers) {
117
35
  return renderers;
118
36
  }
119
- const WORD_JOINER = "⁠";
120
- const DEFAULT_ROOT_CLASS = "cpk-streaming-markdown-root";
121
- const DEFAULT_CITATION_CLASS = "cpk-streaming-markdown-citation";
122
- const DEFAULT_CITATION_LABEL_CLASS = "cpk-streaming-markdown-citation-label";
123
- const DEFAULT_STYLES = `
124
- .${DEFAULT_ROOT_CLASS} .cpk-streaming-markdown-segment {
125
- opacity: 1;
126
- transition: opacity 400ms ease-out;
127
- @starting-style {
128
- opacity: 0;
129
- }
130
- }
131
-
132
- .${DEFAULT_ROOT_CLASS} .${DEFAULT_CITATION_CLASS} {
133
- vertical-align: baseline;
134
- }
135
-
136
- .${DEFAULT_ROOT_CLASS} .${DEFAULT_CITATION_LABEL_CLASS} {
137
- display: inline-flex;
138
- align-items: center;
139
- justify-content: center;
140
- inline-size: 1.4em;
141
- block-size: 1.4em;
142
- border-radius: 999px;
143
- border: 1px solid hsl(0 0% 50% / 0.35);
144
- background-color: hsl(0 0% 50% / 0.16);
145
- color: inherit;
146
- font-size: 0.7em;
147
- line-height: 1;
148
- font-variant-numeric: tabular-nums;
149
- text-decoration: none;
150
- transform: translateY(-0.15em);
151
- }
152
- `;
153
- const DEFAULT_STYLES_FALLBACK = `
154
- .${DEFAULT_ROOT_CLASS} .cpk-streaming-markdown-segment {
155
- opacity: 1;
156
- transition: opacity 400ms ease-out;
157
- }
158
-
159
- .${DEFAULT_ROOT_CLASS} .${DEFAULT_CITATION_CLASS} {
160
- vertical-align: baseline;
161
- }
162
-
163
- .${DEFAULT_ROOT_CLASS} .${DEFAULT_CITATION_LABEL_CLASS} {
164
- display: inline-flex;
165
- align-items: center;
166
- justify-content: center;
167
- inline-size: 1.4em;
168
- block-size: 1.4em;
169
- border-radius: 999px;
170
- border: 1px solid hsl(0 0% 50% / 0.35);
171
- background-color: hsl(0 0% 50% / 0.16);
172
- color: inherit;
173
- font-size: 0.7em;
174
- line-height: 1;
175
- font-variant-numeric: tabular-nums;
176
- text-decoration: none;
177
- transform: translateY(-0.15em);
178
- }
179
- `;
180
- const NODE_TYPE_TO_RENDERER_KEY = {
181
- document: "document",
182
- paragraph: "paragraph",
183
- heading: "heading",
184
- blockquote: "blockquote",
185
- list: "list",
186
- "list-item": "listItem",
187
- "code-block": "codeBlock",
188
- table: "table",
189
- "table-row": "tableRow",
190
- "table-cell": "tableCell",
191
- "thematic-break": "thematicBreak",
192
- text: "text",
193
- em: "em",
194
- strong: "strong",
195
- strikethrough: "strikethrough",
196
- "inline-code": "inlineCode",
197
- "soft-break": "softBreak",
198
- "hard-break": "hardBreak",
199
- image: "image",
200
- link: "link",
201
- autolink: "autolink",
202
- citation: "citation"
203
- };
204
- function renderWithOverride(node, context, defaultNode, children = null, citation) {
205
- const rendererKey = NODE_TYPE_TO_RENDERER_KEY[node.type];
206
- const renderer = context.nodeRenderers?.[rendererKey] ?? context.nodeRenderers?.node;
207
- if (!renderer) return defaultNode;
208
- return renderer({
209
- node,
210
- children,
211
- defaultNode,
212
- citation
213
- });
214
- }
215
- function renderChildren(node, context) {
216
- return node.children.map((childId) => {
217
- const childNode = context.nodeById.get(childId);
218
- if (!childNode) return null;
219
- return /* @__PURE__ */ jsx(Fragment, { children: renderNode(childNode, context) }, childId);
220
- });
221
- }
222
- function renderCaret(nodeId, context) {
223
- if (context.caretTargetNodeId !== nodeId) return null;
224
- return /* @__PURE__ */ jsx(Fragment, { children: context.caretNode }, `caret-${nodeId}`);
225
- }
226
37
  function renderDefaultCaret() {
227
38
  return /* @__PURE__ */ jsx("span", {
228
- "aria-hidden": true,
229
- className: "cpk-streaming-markdown-caret",
230
- "data-streaming-markdown-caret": true,
231
- style: {
232
- display: "inline-block",
233
- width: "0.48em",
234
- height: "0.48em",
235
- marginInlineStart: "0.08em",
236
- verticalAlign: "-0.08em",
237
- borderRadius: "999px",
238
- backgroundColor: "currentColor",
239
- opacity: .55
240
- }
39
+ ...STREAMING_MARKDOWN_CARET_ATTRS,
40
+ style: STREAMING_MARKDOWN_CARET_STYLE
241
41
  });
242
42
  }
243
43
  function resolveCaretNode(caret, props) {
@@ -246,289 +46,97 @@ function resolveCaretNode(caret, props) {
246
46
  if (typeof caret === "function") return caret(props);
247
47
  return caret;
248
48
  }
249
- function renderTextSegments(node) {
250
- if (node.text.length === 0) return null;
251
- if (node.segments.length === 0) return /* @__PURE__ */ jsx("span", {
252
- className: "cpk-streaming-markdown-segment",
253
- "data-streaming-markdown-segment-kind": "full",
254
- "data-streaming-markdown-whitespace": "false",
255
- children: node.text
256
- }, `segment-${node.id}-full`);
257
- return node.segments.map((segment) => /* @__PURE__ */ jsx("span", {
258
- className: "cpk-streaming-markdown-segment",
259
- "data-streaming-markdown-segment-kind": segment.kind,
260
- "data-streaming-markdown-whitespace": String(segment.isWhitespace),
261
- children: segment.noBreakBefore ? `${WORD_JOINER}${segment.text}` : segment.text
262
- }, `segment-${node.id}-${segment.start}-${segment.kind}`));
263
- }
264
- function handleLinkClick(context, node, url) {
265
- return (event) => {
266
- context.onLinkClickRef.current?.(event, url, node);
49
+ function toReactProps(element, context) {
50
+ const props = { key: element.key };
51
+ const entries = Object.entries(element.attrs);
52
+ for (const [name, value] of entries) props[name === "class" ? "className" : name] = value;
53
+ if (element.tag === "input") props.readOnly = true;
54
+ const action = element.action;
55
+ if (action?.type === "link") props.onClick = (event) => {
56
+ context.onLinkClickRef.current?.(event, action.url, action.node);
267
57
  };
268
- }
269
- function getCitationRenderData(node, context) {
270
- const number = node.number ?? context.citations.numbers[node.idRef] ?? node.idRef;
271
- const definition = context.citations.definitions[node.idRef];
272
- return {
273
- id: node.idRef,
274
- number,
275
- definition
58
+ if (action?.type === "citation") props.onClick = (event) => {
59
+ context.onCitationClickRef.current?.(event, action.citation, action.node);
276
60
  };
277
- }
278
- function renderDefaultCitation(node, context, citation) {
279
- const label = String(citation.number);
280
- const href = sanitizeHref(citation.definition?.url);
281
- if (!href) return /* @__PURE__ */ jsx("sup", {
282
- className: DEFAULT_CITATION_CLASS,
283
- "data-streaming-markdown-node": node.type,
284
- "data-node-open": String(!node.closed),
285
- children: /* @__PURE__ */ jsx("span", {
286
- role: "doc-noteref",
287
- className: DEFAULT_CITATION_LABEL_CLASS,
288
- children: label
289
- })
290
- }, node.id);
291
- return /* @__PURE__ */ jsx("sup", {
292
- className: DEFAULT_CITATION_CLASS,
293
- "data-streaming-markdown-node": node.type,
294
- "data-node-open": String(!node.closed),
295
- children: /* @__PURE__ */ jsx("a", {
296
- href,
297
- target: "_blank",
298
- rel: "noopener noreferrer",
299
- role: "doc-noteref",
300
- className: DEFAULT_CITATION_LABEL_CLASS,
301
- onClick: (event) => {
302
- context.onCitationClickRef.current?.(event, citation, node);
303
- },
304
- children: label
305
- })
306
- }, node.id);
307
- }
308
- function renderContainerNode(node, context) {
309
- const children = /* @__PURE__ */ jsxs(Fragment$1, { children: [renderChildren(node, context), renderCaret(node.id, context)] });
310
- if (node.type === "document") return renderWithOverride(node, context, /* @__PURE__ */ jsx(Fragment, { children }, node.id), children);
311
- if (node.type === "paragraph") return renderWithOverride(node, context, /* @__PURE__ */ jsx("p", {
312
- "data-streaming-markdown-node": node.type,
313
- "data-node-open": String(!node.closed),
314
- children
315
- }, node.id), children);
316
- if (node.type === "heading") {
317
- const headingTag = `h${node.level}`;
318
- return renderWithOverride(node, context, headingTag === "h1" ? /* @__PURE__ */ jsx("h1", {
319
- "data-streaming-markdown-node": node.type,
320
- "data-node-open": String(!node.closed),
321
- children
322
- }, node.id) : headingTag === "h2" ? /* @__PURE__ */ jsx("h2", {
323
- "data-streaming-markdown-node": node.type,
324
- "data-node-open": String(!node.closed),
325
- children
326
- }, node.id) : headingTag === "h3" ? /* @__PURE__ */ jsx("h3", {
327
- "data-streaming-markdown-node": node.type,
328
- "data-node-open": String(!node.closed),
329
- children
330
- }, node.id) : headingTag === "h4" ? /* @__PURE__ */ jsx("h4", {
331
- "data-streaming-markdown-node": node.type,
332
- "data-node-open": String(!node.closed),
333
- children
334
- }, node.id) : headingTag === "h5" ? /* @__PURE__ */ jsx("h5", {
335
- "data-streaming-markdown-node": node.type,
336
- "data-node-open": String(!node.closed),
337
- children
338
- }, node.id) : /* @__PURE__ */ jsx("h6", {
339
- "data-streaming-markdown-node": node.type,
340
- "data-node-open": String(!node.closed),
341
- children
342
- }, node.id), children);
343
- }
344
- if (node.type === "blockquote") return renderWithOverride(node, context, /* @__PURE__ */ jsx("blockquote", {
345
- "data-streaming-markdown-node": node.type,
346
- "data-node-open": String(!node.closed),
347
- children
348
- }, node.id), children);
349
- if (node.type === "list") {
350
- if (node.ordered) return renderWithOverride(node, context, /* @__PURE__ */ jsx("ol", {
351
- start: node.start ?? void 0,
352
- "data-streaming-markdown-node": node.type,
353
- "data-node-open": String(!node.closed),
354
- "data-list-tight": String(node.tight),
355
- children
356
- }, node.id), children);
357
- return renderWithOverride(node, context, /* @__PURE__ */ jsx("ul", {
358
- "data-streaming-markdown-node": node.type,
359
- "data-node-open": String(!node.closed),
360
- "data-list-tight": String(node.tight),
361
- children
362
- }, node.id), children);
363
- }
364
- if (node.type === "list-item") return renderWithOverride(node, context, /* @__PURE__ */ jsx("li", {
365
- "data-streaming-markdown-node": node.type,
366
- "data-node-open": String(!node.closed),
367
- children
368
- }, node.id), children);
369
- if (node.type === "table") return renderWithOverride(node, context, /* @__PURE__ */ jsx("table", {
370
- "data-streaming-markdown-node": node.type,
371
- "data-node-open": String(!node.closed),
372
- children: /* @__PURE__ */ jsx("tbody", { children })
373
- }, node.id), children);
374
- if (node.type === "table-row") return renderWithOverride(node, context, /* @__PURE__ */ jsx("tr", {
375
- "data-streaming-markdown-node": node.type,
376
- "data-node-open": String(!node.closed),
377
- children
378
- }, node.id), children);
379
- if (node.type === "table-cell") {
380
- const parent = node.parentId == null ? void 0 : context.nodeById.get(node.parentId);
381
- if (parent?.type === "table-row" && parent.isHeader) return renderWithOverride(node, context, /* @__PURE__ */ jsx("th", {
382
- "data-streaming-markdown-node": node.type,
383
- "data-node-open": String(!node.closed),
384
- children
385
- }, node.id), children);
386
- return renderWithOverride(node, context, /* @__PURE__ */ jsx("td", {
387
- "data-streaming-markdown-node": node.type,
388
- "data-node-open": String(!node.closed),
389
- children
390
- }, node.id), children);
61
+ return props;
62
+ }
63
+ const VOID_TAGS = new Set([
64
+ "br",
65
+ "hr",
66
+ "img",
67
+ "input"
68
+ ]);
69
+ function renderList(children, context, slot) {
70
+ return children.map((child) => renderChild(child, context, slot));
71
+ }
72
+ /** Unwraps single-entry lists so overrides see `"text"` rather than `["text"]`. */
73
+ function collapse(nodes) {
74
+ return nodes.length === 1 ? nodes[0] : nodes;
75
+ }
76
+ function renderChild(child, context, slot) {
77
+ switch (child.kind) {
78
+ case "node": return renderRenderNode(child, context);
79
+ case "element": {
80
+ const props = toReactProps(child, context);
81
+ return VOID_TAGS.has(child.tag) ? createElement(child.tag, props) : createElement(child.tag, props, ...renderList(child.children, context, slot));
82
+ }
83
+ case "text": return child.text;
84
+ case "caret": return /* @__PURE__ */ jsx(Fragment, { children: context.caretNode }, child.key);
85
+ case "slot": return /* @__PURE__ */ jsx(Fragment, { children: slot }, child.key);
86
+ default: return child;
391
87
  }
392
- if (node.type === "em") return renderWithOverride(node, context, /* @__PURE__ */ jsx("em", {
393
- "data-streaming-markdown-node": node.type,
394
- "data-node-open": String(!node.closed),
395
- children
396
- }, node.id), children);
397
- if (node.type === "strong") return renderWithOverride(node, context, /* @__PURE__ */ jsx("strong", {
398
- "data-streaming-markdown-node": node.type,
399
- "data-node-open": String(!node.closed),
400
- children
401
- }, node.id), children);
402
- if (node.type === "strikethrough") return renderWithOverride(node, context, /* @__PURE__ */ jsx("s", {
403
- "data-streaming-markdown-node": node.type,
404
- "data-node-open": String(!node.closed),
405
- children
406
- }, node.id), children);
407
- const href = sanitizeHref(node.url);
408
- return renderWithOverride(node, context, /* @__PURE__ */ jsx("a", {
409
- href,
410
- target: "_blank",
411
- rel: "noopener noreferrer",
412
- title: node.title,
413
- onClick: href ? handleLinkClick(context, node, href) : void 0,
414
- "data-streaming-markdown-node": node.type,
415
- "data-node-open": String(!node.closed),
416
- children
417
- }, node.id), children);
418
88
  }
419
- function renderNode(node, context) {
420
- if (node.type === "document" || node.type === "paragraph" || node.type === "heading" || node.type === "blockquote" || node.type === "list" || node.type === "list-item" || node.type === "table" || node.type === "table-row" || node.type === "table-cell" || node.type === "em" || node.type === "strong" || node.type === "strikethrough" || node.type === "link") return renderContainerNode(node, context);
421
- if (node.type === "code-block") return renderWithOverride(node, context, /* @__PURE__ */ jsx("pre", {
422
- "data-streaming-markdown-node": node.type,
423
- "data-node-open": String(!node.closed),
424
- children: /* @__PURE__ */ jsxs("code", {
425
- "data-code-info": node.info ?? void 0,
426
- children: [node.text, renderCaret(node.id, context)]
427
- })
428
- }, node.id));
429
- if (node.type === "thematic-break") return renderWithOverride(node, context, /* @__PURE__ */ jsx("hr", {
430
- "data-streaming-markdown-node": node.type,
431
- "data-node-open": String(!node.closed)
432
- }, node.id));
433
- if (node.type === "text") {
434
- const children = renderTextSegments(node);
435
- return renderWithOverride(node, context, /* @__PURE__ */ jsx(Fragment, { children }, node.id), children);
436
- }
437
- if (node.type === "inline-code") return renderWithOverride(node, context, /* @__PURE__ */ jsx("code", {
438
- "data-streaming-markdown-node": node.type,
439
- "data-node-open": String(!node.closed),
440
- children: node.text
441
- }, node.id), node.text);
442
- if (node.type === "soft-break") {
443
- const children = "\n";
444
- return renderWithOverride(node, context, /* @__PURE__ */ jsx(Fragment, { children }, node.id), children);
445
- }
446
- if (node.type === "hard-break") return renderWithOverride(node, context, /* @__PURE__ */ jsx("br", {
447
- "data-streaming-markdown-node": node.type,
448
- "data-node-open": String(!node.closed)
449
- }, node.id));
450
- if (node.type === "image") return renderWithOverride(node, context, /* @__PURE__ */ jsx("img", {
451
- src: sanitizeImgSrc(node.url),
452
- alt: node.alt,
453
- title: node.title,
454
- "data-streaming-markdown-node": node.type,
455
- "data-node-open": String(!node.closed)
456
- }, node.id));
457
- if (node.type === "autolink") {
458
- const href = sanitizeHref(node.url);
459
- return renderWithOverride(node, context, /* @__PURE__ */ jsx("a", {
460
- href,
461
- target: "_blank",
462
- rel: "noopener noreferrer",
463
- onClick: href ? handleLinkClick(context, node, href) : void 0,
464
- "data-streaming-markdown-node": node.type,
465
- "data-node-open": String(!node.closed),
466
- children: node.text
467
- }, node.id), node.text);
468
- }
469
- const citation = getCitationRenderData(node, context);
470
- return renderWithOverride(node, context, renderDefaultCitation(node, context, citation), null, citation);
89
+ function renderRenderNode(renderNode, context) {
90
+ const children = renderNode.children === null ? null : collapse(renderList(renderNode.children, context, null));
91
+ const defaultNode = collapse(renderList(renderNode.output, context, children));
92
+ const renderer = context.nodeRenderers?.[renderNode.rendererKey] ?? context.nodeRenderers?.node;
93
+ return /* @__PURE__ */ jsx(Fragment, { children: renderer ? renderer({
94
+ node: renderNode.node,
95
+ children,
96
+ defaultNode,
97
+ citation: renderNode.citation
98
+ }) : defaultNode }, renderNode.key);
471
99
  }
472
100
  /**
473
101
  * React renderer for streaming markdown parser output.
474
102
  *
475
103
  * @public
476
104
  */
477
- function StreamingMarkdownRenderer({ children, options, isComplete = false, caret, className, onLinkClick, onCitationClick, nodeRenderers }) {
105
+ function StreamingMarkdownRenderer({ children, options, isComplete = false, caret, className, onLinkClick, onCitationClick, nodeRenderers, nodeClassNames }) {
478
106
  const parserState = useStreamingMarkdownParser(children ?? "", options, isComplete);
479
107
  const onLinkClickRef = useRef(onLinkClick);
480
108
  const onCitationClickRef = useRef(onCitationClick);
481
109
  onLinkClickRef.current = onLinkClick;
482
110
  onCitationClickRef.current = onCitationClick;
483
- const nodeById = useMemo(() => {
484
- const map = /* @__PURE__ */ new Map();
485
- for (const node of parserState.nodes) map.set(node.id, node);
486
- return map;
487
- }, [parserState.nodes]);
488
- const context = useMemo(() => {
489
- const openNode = findDeepestOpenRenderableNode(parserState.stack, nodeById);
111
+ const content = useMemo(() => {
490
112
  const caretNode = resolveCaretNode(caret, {
491
113
  isComplete: parserState.isComplete,
492
- openNode
114
+ openNode: findStreamingMarkdownOpenNode(parserState)
115
+ });
116
+ const tree = buildStreamingMarkdownRenderTree(parserState, {
117
+ caret: caretNode != null,
118
+ nodeClassNames
493
119
  });
494
- const caretTargetNodeId = !parserState.isComplete && openNode != null && caretNode != null ? openNode.id : null;
495
- return {
496
- nodeById,
497
- citations: parserState.citations,
120
+ if (!tree.root) return null;
121
+ return renderRenderNode(tree.root, {
498
122
  onLinkClickRef,
499
123
  onCitationClickRef,
500
124
  nodeRenderers,
501
- caretTargetNodeId,
502
125
  caretNode
503
- };
126
+ });
504
127
  }, [
505
- nodeById,
506
- parserState.citations,
507
- parserState.isComplete,
508
- parserState.stack,
128
+ parserState,
509
129
  nodeRenderers,
130
+ nodeClassNames,
510
131
  caret
511
132
  ]);
512
- const rootNode = parserState.rootId == null ? void 0 : nodeById.get(parserState.rootId);
513
- const rootClassName = className ? `${DEFAULT_ROOT_CLASS} ${className}` : DEFAULT_ROOT_CLASS;
514
- const styleText = typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent) ? DEFAULT_STYLES_FALLBACK : DEFAULT_STYLES;
515
- if (!rootNode) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("style", { children: styleText }), /* @__PURE__ */ jsx("div", {
516
- className: rootClassName,
517
- "data-streaming-markdown-root": true
518
- })] });
519
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("style", { children: styleText }), /* @__PURE__ */ jsx("div", {
133
+ const rootClassName = className ? `${STREAMING_MARKDOWN_ROOT_CLASS} ${className}` : STREAMING_MARKDOWN_ROOT_CLASS;
134
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("style", { children: getStreamingMarkdownStyles() }), /* @__PURE__ */ jsx("div", {
520
135
  className: rootClassName,
521
136
  "data-streaming-markdown-root": true,
522
- children: renderNode(rootNode, context)
137
+ children: content
523
138
  })] });
524
139
  }
525
- function findDeepestOpenRenderableNode(stack, nodeById) {
526
- for (let index = stack.length - 1; index >= 0; index -= 1) {
527
- const candidate = nodeById.get(stack[index]) ?? null;
528
- if (candidate && candidate.type !== "document") return candidate;
529
- }
530
- return null;
531
- }
532
140
 
533
141
  //#endregion
534
142
  export { StreamingMarkdownRenderer, createStreamingMarkdownNodeRenderers, useStreamingMarkdownParser };