@stream-mdx/react 0.3.0 → 0.5.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 (57) hide show
  1. package/CHANGELOG.md +14 -4
  2. package/README.md +65 -14
  3. package/dist/components/bottom-stick-scroll-area.cjs +344 -0
  4. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  5. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  6. package/dist/components/bottom-stick-scroll-area.mjs +320 -0
  7. package/dist/components/index.cjs +511 -176
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +358 -26
  11. package/dist/{index-D7px9jug.d.cts → index-BVhp_pcF.d.ts} +4 -1
  12. package/dist/{index-D7px9jug.d.ts → index-EaeouYp2.d.cts} +4 -1
  13. package/dist/index.cjs +2146 -1145
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +3835 -2836
  17. package/dist/mdx-client.cjs +1 -1
  18. package/dist/mdx-client.mjs +1 -1
  19. package/dist/mdx-coordinator.cjs +28 -7
  20. package/dist/mdx-coordinator.d.cts +2 -2
  21. package/dist/mdx-coordinator.d.ts +2 -2
  22. package/dist/mdx-coordinator.mjs +28 -7
  23. package/dist/renderer/node-views.cjs +273 -24
  24. package/dist/renderer/node-views.d.cts +3 -1
  25. package/dist/renderer/node-views.d.ts +3 -1
  26. package/dist/renderer/node-views.mjs +274 -25
  27. package/dist/renderer/patch-batching.cjs +2 -0
  28. package/dist/renderer/patch-batching.d.cts +1 -1
  29. package/dist/renderer/patch-batching.d.ts +1 -1
  30. package/dist/renderer/patch-batching.mjs +2 -1
  31. package/dist/renderer/patch-commit-scheduler.cjs +82 -30
  32. package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
  33. package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
  34. package/dist/renderer/patch-commit-scheduler.mjs +84 -30
  35. package/dist/renderer/store.cjs +382 -36
  36. package/dist/renderer/store.d.cts +3 -0
  37. package/dist/renderer/store.d.ts +3 -0
  38. package/dist/renderer/store.mjs +382 -36
  39. package/dist/renderer.cjs +3386 -2727
  40. package/dist/renderer.d.cts +15 -12
  41. package/dist/renderer.d.ts +15 -12
  42. package/dist/renderer.mjs +3410 -2751
  43. package/dist/server.cjs +4842 -0
  44. package/dist/server.d.cts +254 -0
  45. package/dist/server.d.ts +254 -0
  46. package/dist/server.mjs +4824 -0
  47. package/dist/{streaming-markdown-Dp1IDgMT.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-DSC4L0xR.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +3476 -2794
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +3436 -2754
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +19 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/renderer/node-views.tsx
4
- import { stripCodeFence } from "@stream-mdx/core";
4
+ import { getDefaultCodeWrapperAttributes as getDefaultCodeWrapperAttributes2, stripCodeFence } from "@stream-mdx/core";
5
5
  import React5 from "react";
6
6
 
7
7
  // src/components/index.ts
@@ -488,7 +488,11 @@ var defaultInlineComponents = {
488
488
  const n = number ?? "?";
489
489
  const href = number ? `#fn:${n}` : void 0;
490
490
  const id = number ? `fnref:${n}` : void 0;
491
- return React2.createElement("sup", { className: "footnote-ref" }, React2.createElement("a", { href, id, "data-label": label }, String(n)));
491
+ return React2.createElement(
492
+ "sup",
493
+ { className: "footnote-ref" },
494
+ React2.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
495
+ );
492
496
  }
493
497
  };
494
498
  function renderParagraphMixedSegments(segments, inlineComponents, inlineHtmlRenderers) {
@@ -704,6 +708,7 @@ import {
704
708
  sanitizeHTML as sanitizeHTML2,
705
709
  PATCH_ROOT_ID
706
710
  } from "@stream-mdx/core";
711
+ import { getPatchKind } from "@stream-mdx/core/perf/patch-batching";
707
712
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
708
713
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
709
714
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
@@ -1182,8 +1187,10 @@ var ListBlockView = React5.memo(
1182
1187
  isFinalized: block?.isFinalized ?? false
1183
1188
  });
1184
1189
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
1185
- const listStyle = React5.useMemo(() => {
1186
- if (!ordered || listItemIds.length === 0) return void 0;
1190
+ const { listStyle, markerDigits } = React5.useMemo(() => {
1191
+ if (!ordered || listItemIds.length === 0) {
1192
+ return { listStyle: void 0, markerDigits: 1 };
1193
+ }
1187
1194
  let maxDigits = 1;
1188
1195
  listItemIds.forEach((childId, index) => {
1189
1196
  const childNode = store.getNode(childId);
@@ -1195,9 +1202,14 @@ var ListBlockView = React5.memo(
1195
1202
  const digits = digitMatch ? digitMatch.length : text.length;
1196
1203
  if (digits > maxDigits) maxDigits = digits;
1197
1204
  });
1198
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
1205
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
1206
+ const extraDigits = Math.max(0, maxDigits - 1);
1199
1207
  return {
1200
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
1208
+ markerDigits: maxDigits,
1209
+ listStyle: {
1210
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
1211
+ ["--list-marker-digits"]: String(maxDigits)
1212
+ }
1201
1213
  };
1202
1214
  }, [ordered, listItemIds, store, depth]);
1203
1215
  if (listItemIds.length === 0) return null;
@@ -1206,6 +1218,7 @@ var ListBlockView = React5.memo(
1206
1218
  {
1207
1219
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
1208
1220
  "data-list-depth": depth,
1221
+ "data-marker-digits": ordered ? markerDigits : void 0,
1209
1222
  "data-list-id": listDebugEnabled ? blockId : void 0,
1210
1223
  style: listStyle,
1211
1224
  children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx(
@@ -1387,6 +1400,7 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1387
1400
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
1388
1401
  );
1389
1402
  if (!node || !node.block) return null;
1403
+ const blockIsFinalized = node.block.isFinalized;
1390
1404
  const nodeVersion = node.version;
1391
1405
  const lines = React5.useMemo(() => {
1392
1406
  const seenIds = /* @__PURE__ */ new Set();
@@ -1417,13 +1431,25 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1417
1431
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
1418
1432
  const preAttrs = node.props?.preAttrs ?? void 0;
1419
1433
  const codeAttrs = node.props?.codeAttrs ?? void 0;
1434
+ const blockMeta = node.block.payload.meta ?? void 0;
1435
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
1436
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
1437
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
1438
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
1439
+ const lazySourceTextLineCount = React5.useMemo(() => {
1440
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
1441
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
1442
+ const sourceText = resolveCodeSourceText(raw, metaCode);
1443
+ if (sourceText.length === 0) return 0;
1444
+ return sourceText.split("\n").length;
1445
+ }, [blockMeta, node.block?.payload.raw]);
1446
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
1420
1447
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
1421
1448
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
1422
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
1449
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
1450
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
1423
1451
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
1424
1452
  const highlightRequester = useCodeHighlightRequester();
1425
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
1426
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
1427
1453
  const lastRangeRef = React5.useRef(null);
1428
1454
  const rafRef = React5.useRef(null);
1429
1455
  const idleRef = React5.useRef(null);
@@ -1504,25 +1530,111 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1504
1530
  virtualization.window.startIndex,
1505
1531
  virtualization.window.endIndex
1506
1532
  ]);
1507
- const highlightedHtml = React5.useMemo(() => {
1508
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
1509
- return node.block.payload.highlightedHtml;
1533
+ React5.useEffect(() => {
1534
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
1535
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
1536
+ const nextRange = {
1537
+ visibleStart: 0,
1538
+ visibleEnd: totalLazyLineCount,
1539
+ prefetchStart: 0,
1540
+ prefetchEnd: totalLazyLineCount,
1541
+ tokenizedUntil: lazyTokenizedUntil
1542
+ };
1543
+ const last = lastRangeRef.current;
1544
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
1545
+ return;
1510
1546
  }
1547
+ lastRangeRef.current = nextRange;
1548
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
1549
+ cancelAnimationFrame(rafRef.current);
1550
+ }
1551
+ const requestFullHighlight = () => {
1552
+ highlightRequester({
1553
+ blockId,
1554
+ startLine: 0,
1555
+ endLine: totalLazyLineCount,
1556
+ priority: "visible",
1557
+ reason: "finalize-full"
1558
+ });
1559
+ rafRef.current = null;
1560
+ };
1561
+ if (typeof requestAnimationFrame === "function") {
1562
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
1563
+ } else {
1564
+ requestFullHighlight();
1565
+ }
1566
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
1567
+ const highlightedHtml = React5.useMemo(() => {
1568
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
1569
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
1570
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
1571
+ const sourceText = resolveCodeSourceText(raw, metaCode);
1572
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
1573
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
1574
+ const rawFallbackLines = buildCodeFallbackLines(raw);
1575
+ const deterministicWrapperAttrs = getDefaultCodeWrapperAttributes2(lang);
1576
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
1577
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
1578
+ const shouldAppendTerminalNewline = (renderedLines) => {
1579
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
1580
+ return false;
1581
+ }
1582
+ const lastRendered = renderedLines[renderedLines.length - 1];
1583
+ const lastKnown = lines[lines.length - 1];
1584
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
1585
+ return false;
1586
+ }
1587
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
1588
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
1589
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
1590
+ };
1511
1591
  if (lines.length > 0) {
1592
+ const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
1593
+ if (!hasLineHtml && blockHtml) {
1594
+ return blockHtml;
1595
+ }
1512
1596
  if (shouldVirtualize) {
1513
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
1597
+ return composeHighlightedHtml(
1598
+ virtualization.window.visibleLines,
1599
+ composedPreAttrs,
1600
+ composedCodeAttrs,
1601
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
1602
+ );
1514
1603
  }
1515
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
1604
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
1605
+ }
1606
+ if (!shouldVirtualize && blockHtml) {
1607
+ const hasHighlightedLines = blockHtml.includes('class="line"');
1608
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
1609
+ return composeHighlightedHtml(
1610
+ rawFallbackLines,
1611
+ composedPreAttrs,
1612
+ composedCodeAttrs,
1613
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1614
+ );
1615
+ }
1616
+ return blockHtml;
1516
1617
  }
1517
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
1518
1618
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
1519
- const raw = node.block?.payload.raw ?? "";
1520
- if (raw.trim().length > 0) {
1521
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
1619
+ if (rawFallbackLines.length > 0) {
1620
+ return composeHighlightedHtml(
1621
+ rawFallbackLines,
1622
+ composedPreAttrs,
1623
+ composedCodeAttrs,
1624
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1625
+ );
1522
1626
  }
1523
1627
  }
1628
+ if (!blockHtml && rawFallbackLines.length > 0) {
1629
+ return composeHighlightedHtml(
1630
+ rawFallbackLines,
1631
+ composedPreAttrs,
1632
+ composedCodeAttrs,
1633
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1634
+ );
1635
+ }
1524
1636
  return blockHtml;
1525
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
1637
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
1526
1638
  const rendered = /* @__PURE__ */ jsx(
1527
1639
  CodeComponent,
1528
1640
  {
@@ -1534,13 +1646,19 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1534
1646
  codeAttrs
1535
1647
  }
1536
1648
  );
1649
+ const effectiveLineCount = React5.useMemo(() => {
1650
+ if (lines.length > 0) {
1651
+ return lines.length;
1652
+ }
1653
+ return 1;
1654
+ }, [lines.length]);
1537
1655
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
1538
1656
  const codeMetricsAttrs = {
1539
1657
  "data-code-block": "true",
1540
1658
  "data-block-id": blockId,
1541
1659
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
1542
- "data-code-total-lines": String(lines.length),
1543
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
1660
+ "data-code-total-lines": String(effectiveLineCount),
1661
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
1544
1662
  "data-code-window-size": String(virtualization.config.windowSize)
1545
1663
  };
1546
1664
  const codeView = shouldVirtualize ? (() => {
@@ -1586,21 +1704,152 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1586
1704
  return codeView;
1587
1705
  });
1588
1706
  CodeBlockView.displayName = "CodeBlockView";
1589
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
1707
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
1590
1708
  const lineMarkup = lines.map((line) => {
1591
- const content = line.html ?? escapeHtml(line.text);
1709
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml(line.text);
1592
1710
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
1593
1711
  return `<span class="line"${dataLine}>${content}</span>`;
1594
1712
  }).join("\n");
1595
1713
  const preAttr = attrsToString(preAttrs);
1596
1714
  const codeAttr = attrsToString(codeAttrs);
1597
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
1598
- </code></pre>`;
1715
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
1716
+ }
1717
+ var TOKEN_FONT_STYLE_ITALIC = 1;
1718
+ var TOKEN_FONT_STYLE_BOLD = 2;
1719
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
1720
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
1721
+ function renderTokenLineHtml(tokens) {
1722
+ if (!tokens || !Array.isArray(tokens.spans)) {
1723
+ return null;
1724
+ }
1725
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
1726
+ }
1727
+ function renderTokenSpanHtml(span) {
1728
+ const dark = span.v?.dark;
1729
+ const light = span.v?.light;
1730
+ const single = span.s;
1731
+ const styles = [];
1732
+ if (dark?.fg) {
1733
+ styles.push(`--shiki-dark:${dark.fg}`);
1734
+ }
1735
+ if (light?.fg) {
1736
+ styles.push(`--shiki-light:${light.fg}`);
1737
+ }
1738
+ if (!light?.fg && !dark?.fg && single?.fg) {
1739
+ styles.push(`color:${single.fg}`);
1740
+ }
1741
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
1742
+ if (typeof fontStyle === "number" && fontStyle > 0) {
1743
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
1744
+ styles.push("font-style: italic");
1745
+ }
1746
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
1747
+ styles.push("font-weight: bold");
1748
+ }
1749
+ const decorations = [];
1750
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
1751
+ decorations.push("underline");
1752
+ }
1753
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
1754
+ decorations.push("line-through");
1755
+ }
1756
+ if (decorations.length > 0) {
1757
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
1758
+ }
1759
+ }
1760
+ const content = escapeHtml(span.t ?? "");
1761
+ if (styles.length === 0) {
1762
+ return content;
1763
+ }
1764
+ return `<span style="${styles.join(";")}">${content}</span>`;
1765
+ }
1766
+ function mergeRenderableTokenSpans(spans) {
1767
+ if (spans.length <= 1) {
1768
+ return spans.slice();
1769
+ }
1770
+ const merged = [];
1771
+ let pendingWhitespace = "";
1772
+ for (const span of spans) {
1773
+ const text = typeof span.t === "string" ? span.t : "";
1774
+ if (/^[\t ]+$/.test(text)) {
1775
+ pendingWhitespace += text;
1776
+ continue;
1777
+ }
1778
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
1779
+ pendingWhitespace = "";
1780
+ const previous = merged[merged.length - 1];
1781
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
1782
+ previous.t += nextSpan.t;
1783
+ } else {
1784
+ merged.push({ ...nextSpan });
1785
+ }
1786
+ }
1787
+ if (pendingWhitespace) {
1788
+ const previous = merged[merged.length - 1];
1789
+ if (previous) {
1790
+ previous.t += pendingWhitespace;
1791
+ } else {
1792
+ merged.push({ t: pendingWhitespace });
1793
+ }
1794
+ }
1795
+ return merged;
1796
+ }
1797
+ function sameTokenSpanStyle(a, b) {
1798
+ const variantKeys = /* @__PURE__ */ new Set([
1799
+ ...Object.keys(a.v ?? {}),
1800
+ ...Object.keys(b.v ?? {})
1801
+ ]);
1802
+ for (const key of variantKeys) {
1803
+ const aStyle = a.v?.[key];
1804
+ const bStyle = b.v?.[key];
1805
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
1806
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
1807
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
1808
+ }
1809
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
1810
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
1811
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
1812
+ return true;
1813
+ }
1814
+ function resolveCodeSourceText(raw, metaCode) {
1815
+ if (metaCode !== null) {
1816
+ return metaCode.replace(/\r\n?/g, "\n");
1817
+ }
1818
+ if (!raw) return "";
1819
+ const normalized = raw.replace(/\r\n?/g, "\n");
1820
+ const fenced = stripCodeFence(normalized);
1821
+ return fenced.hadFence ? fenced.code : normalized;
1599
1822
  }
1600
1823
  function attrsToString(attrs) {
1601
1824
  if (!attrs) return "";
1602
1825
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
1603
1826
  }
1827
+ function buildCodeFallbackLines(raw) {
1828
+ const normalized = normalizeCodeFallbackRaw(raw);
1829
+ if (normalized.length === 0) {
1830
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
1831
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
1832
+ }
1833
+ return [];
1834
+ }
1835
+ return normalized.split("\n").map((line, index) => ({
1836
+ id: `temp-${index}`,
1837
+ index,
1838
+ text: line,
1839
+ html: null
1840
+ }));
1841
+ }
1842
+ function normalizeCodeFallbackRaw(raw) {
1843
+ if (!raw) return "";
1844
+ const normalized = raw.replace(/\r\n?/g, "\n");
1845
+ const fenced = stripCodeFence(normalized);
1846
+ if (!fenced.hadFence) {
1847
+ return normalized.replace(/\n+$/, "");
1848
+ }
1849
+ let code = fenced.code;
1850
+ code = code.replace(/\n+$/, "");
1851
+ return code;
1852
+ }
1604
1853
  function escapeHtml(value) {
1605
1854
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1606
1855
  }
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/renderer/patch-batching.ts
22
22
  var patch_batching_exports = {};
23
23
  __export(patch_batching_exports, {
24
+ getPatchKind: () => import_patch_batching.getPatchKind,
24
25
  isHeavyPatch: () => import_patch_batching.isHeavyPatch,
25
26
  splitPatchBatch: () => import_patch_batching.splitPatchBatch
26
27
  });
@@ -28,6 +29,7 @@ module.exports = __toCommonJS(patch_batching_exports);
28
29
  var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
29
30
  // Annotate the CommonJS export names for ESM import in node:
30
31
  0 && (module.exports = {
32
+ getPatchKind,
31
33
  isHeavyPatch,
32
34
  splitPatchBatch
33
35
  });
@@ -1 +1 @@
1
- export { isHeavyPatch, splitPatchBatch } from '@stream-mdx/core/perf/patch-batching';
1
+ export { getPatchKind, isHeavyPatch, splitPatchBatch } from '@stream-mdx/core/perf/patch-batching';
@@ -1 +1 @@
1
- export { isHeavyPatch, splitPatchBatch } from '@stream-mdx/core/perf/patch-batching';
1
+ export { getPatchKind, isHeavyPatch, splitPatchBatch } from '@stream-mdx/core/perf/patch-batching';
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  // src/renderer/patch-batching.ts
4
- import { isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
4
+ import { getPatchKind, isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
5
5
  export {
6
+ getPatchKind,
6
7
  isHeavyPatch,
7
8
  splitPatchBatch
8
9
  };