@stream-mdx/react 0.4.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 -15
  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 +503 -172
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +350 -22
  11. package/dist/{index-D2MYusAM.d.cts → index-BVhp_pcF.d.ts} +3 -0
  12. package/dist/{index-D2MYusAM.d.ts → index-EaeouYp2.d.cts} +3 -0
  13. package/dist/index.cjs +1472 -486
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +1256 -272
  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 +271 -26
  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 +272 -27
  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 +758 -114
  40. package/dist/renderer.d.cts +3 -1
  41. package/dist/renderer.d.ts +3 -1
  42. package/dist/renderer.mjs +760 -116
  43. package/dist/server.cjs +669 -71
  44. package/dist/server.d.cts +5 -0
  45. package/dist/server.d.ts +5 -0
  46. package/dist/server.mjs +670 -72
  47. package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +788 -121
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +790 -123
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +14 -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,29 +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;
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();
1510
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) {
1512
1592
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
1513
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
1514
- return node.block.payload.highlightedHtml;
1593
+ if (!hasLineHtml && blockHtml) {
1594
+ return blockHtml;
1515
1595
  }
1516
1596
  if (shouldVirtualize) {
1517
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
1597
+ return composeHighlightedHtml(
1598
+ virtualization.window.visibleLines,
1599
+ composedPreAttrs,
1600
+ composedCodeAttrs,
1601
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
1602
+ );
1518
1603
  }
1519
- 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;
1520
1617
  }
1521
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
1522
1618
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
1523
- const raw = node.block?.payload.raw ?? "";
1524
- if (raw.trim().length > 0) {
1525
- 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
+ );
1526
1626
  }
1527
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
+ }
1528
1636
  return blockHtml;
1529
- }, [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]);
1530
1638
  const rendered = /* @__PURE__ */ jsx(
1531
1639
  CodeComponent,
1532
1640
  {
@@ -1538,13 +1646,19 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1538
1646
  codeAttrs
1539
1647
  }
1540
1648
  );
1649
+ const effectiveLineCount = React5.useMemo(() => {
1650
+ if (lines.length > 0) {
1651
+ return lines.length;
1652
+ }
1653
+ return 1;
1654
+ }, [lines.length]);
1541
1655
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
1542
1656
  const codeMetricsAttrs = {
1543
1657
  "data-code-block": "true",
1544
1658
  "data-block-id": blockId,
1545
1659
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
1546
- "data-code-total-lines": String(lines.length),
1547
- "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),
1548
1662
  "data-code-window-size": String(virtualization.config.windowSize)
1549
1663
  };
1550
1664
  const codeView = shouldVirtualize ? (() => {
@@ -1590,21 +1704,152 @@ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1590
1704
  return codeView;
1591
1705
  });
1592
1706
  CodeBlockView.displayName = "CodeBlockView";
1593
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
1707
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
1594
1708
  const lineMarkup = lines.map((line) => {
1595
- const content = line.html ?? escapeHtml(line.text);
1709
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml(line.text);
1596
1710
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
1597
1711
  return `<span class="line"${dataLine}>${content}</span>`;
1598
1712
  }).join("\n");
1599
1713
  const preAttr = attrsToString(preAttrs);
1600
1714
  const codeAttr = attrsToString(codeAttrs);
1601
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
1602
- </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;
1603
1822
  }
1604
1823
  function attrsToString(attrs) {
1605
1824
  if (!attrs) return "";
1606
1825
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
1607
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
+ }
1608
1853
  function escapeHtml(value) {
1609
1854
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1610
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
  };