@stream-mdx/react 0.1.0 → 0.2.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 (47) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +4 -0
  3. package/dist/components/index.cjs +497 -163
  4. package/dist/components/index.d.cts +1 -1
  5. package/dist/components/index.d.ts +1 -1
  6. package/dist/components/index.mjs +496 -163
  7. package/dist/{index-Bt1opGCs.d.cts → index-D0akq48G.d.cts} +24 -2
  8. package/dist/{index-Bt1opGCs.d.ts → index-D0akq48G.d.ts} +24 -2
  9. package/dist/index.cjs +3797 -2048
  10. package/dist/index.d.cts +43 -5
  11. package/dist/index.d.ts +43 -5
  12. package/dist/index.mjs +3741 -1990
  13. package/dist/mdx-client.cjs +60 -18
  14. package/dist/mdx-client.d.cts +11 -0
  15. package/dist/mdx-client.d.ts +11 -0
  16. package/dist/mdx-client.mjs +60 -18
  17. package/dist/mdx-coordinator.cjs +60 -18
  18. package/dist/mdx-coordinator.mjs +60 -18
  19. package/dist/renderer/node-views.cjs +481 -130
  20. package/dist/renderer/node-views.d.cts +1 -1
  21. package/dist/renderer/node-views.d.ts +1 -1
  22. package/dist/renderer/node-views.mjs +424 -65
  23. package/dist/renderer/patch-commit-scheduler.cjs +68 -7
  24. package/dist/renderer/patch-commit-scheduler.d.cts +6 -5
  25. package/dist/renderer/patch-commit-scheduler.d.ts +6 -5
  26. package/dist/renderer/patch-commit-scheduler.mjs +68 -7
  27. package/dist/renderer/store.cjs +481 -56
  28. package/dist/renderer/store.d.cts +2 -1
  29. package/dist/renderer/store.d.ts +2 -1
  30. package/dist/renderer/store.mjs +479 -47
  31. package/dist/renderer/virtualized-code.cjs +8 -2
  32. package/dist/renderer/virtualized-code.d.cts +4 -0
  33. package/dist/renderer/virtualized-code.d.ts +4 -0
  34. package/dist/renderer/virtualized-code.mjs +8 -2
  35. package/dist/renderer.cjs +3188 -2172
  36. package/dist/renderer.d.cts +4 -2
  37. package/dist/renderer.d.ts +4 -2
  38. package/dist/renderer.mjs +3009 -1985
  39. package/dist/streaming-markdown-Ch6PwjAa.d.cts +154 -0
  40. package/dist/streaming-markdown-tca-Mf8D.d.ts +154 -0
  41. package/dist/streaming-markdown.cjs +3929 -2202
  42. package/dist/streaming-markdown.d.cts +6 -95
  43. package/dist/streaming-markdown.d.ts +6 -95
  44. package/dist/streaming-markdown.mjs +3943 -2208
  45. package/dist/utils/inline-html.d.cts +1 -1
  46. package/dist/utils/inline-html.d.ts +1 -1
  47. package/package.json +3 -3
@@ -2,10 +2,68 @@
2
2
 
3
3
  // src/renderer/node-views.tsx
4
4
  import { stripCodeFence } from "@stream-mdx/core";
5
- import React3 from "react";
5
+ import React5 from "react";
6
6
 
7
7
  // src/components/index.ts
8
8
  import { createTrustedHTML, sanitizeHTML } from "@stream-mdx/core";
9
+
10
+ // src/renderer/use-deferred-render.ts
11
+ import { useEffect, useState } from "react";
12
+ function useDeferredRender(ref, options) {
13
+ const enabled = options?.enabled ?? true;
14
+ const [shouldRender, setShouldRender] = useState(!enabled);
15
+ useEffect(() => {
16
+ if (!enabled) {
17
+ setShouldRender(true);
18
+ return;
19
+ }
20
+ const target = ref.current;
21
+ if (!target) {
22
+ return;
23
+ }
24
+ let cancelled = false;
25
+ let idleId = null;
26
+ let timerId = null;
27
+ const scheduleRender = () => {
28
+ if (cancelled) return;
29
+ setShouldRender(true);
30
+ };
31
+ const runWithIdle = () => {
32
+ if (typeof globalThis.requestIdleCallback === "function") {
33
+ idleId = globalThis.requestIdleCallback(scheduleRender, { timeout: options?.idleTimeoutMs ?? 200 });
34
+ } else {
35
+ timerId = window.setTimeout(scheduleRender, options?.idleTimeoutMs ?? 200);
36
+ }
37
+ };
38
+ const observer = new IntersectionObserver(
39
+ (entries) => {
40
+ for (const entry of entries) {
41
+ if (entry.isIntersecting || entry.intersectionRatio > 0) {
42
+ observer.disconnect();
43
+ const debounceMs = options?.debounceMs ?? 80;
44
+ timerId = window.setTimeout(runWithIdle, debounceMs);
45
+ return;
46
+ }
47
+ }
48
+ },
49
+ { rootMargin: options?.rootMargin ?? "200px 0px" }
50
+ );
51
+ observer.observe(target);
52
+ return () => {
53
+ cancelled = true;
54
+ observer.disconnect();
55
+ if (idleId !== null && typeof globalThis.cancelIdleCallback === "function") {
56
+ globalThis.cancelIdleCallback(idleId);
57
+ }
58
+ if (timerId !== null) {
59
+ window.clearTimeout(timerId);
60
+ }
61
+ };
62
+ }, [enabled, options?.debounceMs, options?.idleTimeoutMs, options?.rootMargin, ref]);
63
+ return shouldRender;
64
+ }
65
+
66
+ // src/components/index.ts
9
67
  import { removeHeadingMarkers } from "@stream-mdx/core";
10
68
  import React2 from "react";
11
69
 
@@ -279,6 +337,29 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
279
337
  ]);
280
338
 
281
339
  // src/components/index.ts
340
+ var MdxRuntimeBoundary = class extends React2.Component {
341
+ constructor() {
342
+ super(...arguments);
343
+ this.state = { hasError: false };
344
+ }
345
+ static getDerivedStateFromError() {
346
+ return { hasError: true };
347
+ }
348
+ componentDidCatch(error) {
349
+ this.props.onError(error);
350
+ }
351
+ componentDidUpdate(prevProps) {
352
+ if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
353
+ this.setState({ hasError: false });
354
+ }
355
+ }
356
+ render() {
357
+ if (this.state.hasError) {
358
+ return null;
359
+ }
360
+ return this.props.children;
361
+ }
362
+ };
282
363
  function renderInlineNodes(nodes, components) {
283
364
  return nodes.map((node, index) => {
284
365
  const key = `${node.kind}-${index}`;
@@ -602,8 +683,72 @@ function useRendererChildren(store, id) {
602
683
  return snapshot.children;
603
684
  }
604
685
 
686
+ // src/renderer/code-highlight-context.tsx
687
+ import React3 from "react";
688
+ var CodeHighlightRequestContext = React3.createContext(null);
689
+ function useCodeHighlightRequester() {
690
+ return React3.useContext(CodeHighlightRequestContext);
691
+ }
692
+
693
+ // src/renderer/deferred-render-context.tsx
694
+ import React4 from "react";
695
+ var DeferredRenderContext = React4.createContext(null);
696
+
697
+ // src/renderer/store.ts
698
+ import {
699
+ cloneBlock,
700
+ createBlockSnapshot,
701
+ extractCodeWrapperAttributes,
702
+ getDefaultCodeWrapperAttributes,
703
+ sanitizeCodeHTML,
704
+ sanitizeHTML as sanitizeHTML2,
705
+ PATCH_ROOT_ID
706
+ } from "@stream-mdx/core";
707
+ var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
708
+ var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
709
+ var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
710
+ var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
711
+ var EMPTY_CHILDREN = Object.freeze([]);
712
+ var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
713
+ var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
714
+ version: -1,
715
+ children: EMPTY_CHILDREN
716
+ });
717
+ function isListPatchDebugEnabled() {
718
+ if (typeof globalThis !== "undefined") {
719
+ const flag = globalThis.__STREAMING_LIST_DEBUG__;
720
+ if (flag === true) return true;
721
+ }
722
+ if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_STREAMING_LIST_DEBUG === "true") {
723
+ return true;
724
+ }
725
+ return false;
726
+ }
727
+ var CODE_BLOCK_DEBUG_ENABLED = (() => {
728
+ try {
729
+ if (typeof process !== "undefined" && process.env && process.env.NEXT_PUBLIC_STREAMING_DEBUG_CODELINES === "1") {
730
+ return true;
731
+ }
732
+ if (typeof globalThis !== "undefined") {
733
+ const debug = globalThis?.__STREAMING_DEBUG__;
734
+ if (debug?.codeLines) {
735
+ return true;
736
+ }
737
+ }
738
+ } catch {
739
+ }
740
+ return false;
741
+ })() || false;
742
+ var CODE_BLOCK_VALIDATION_ENABLED = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
743
+ var getPerfNow = (() => {
744
+ if (typeof performance !== "undefined" && typeof performance.now === "function") {
745
+ return () => performance.now();
746
+ }
747
+ return () => Date.now();
748
+ })();
749
+
605
750
  // src/renderer/virtualized-code.tsx
606
- import { useCallback, useEffect, useMemo as useMemo2, useRef, useState } from "react";
751
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
607
752
  var DEFAULT_VIRTUALIZED_CODE_CONFIG = {
608
753
  enabled: true,
609
754
  windowSize: 100,
@@ -619,6 +764,8 @@ function calculateCodeWindow(lines, scrollTop, containerHeight, lineHeight, conf
619
764
  return {
620
765
  startIndex: 0,
621
766
  endIndex: totalLines,
767
+ visibleStart: 0,
768
+ visibleEnd: totalLines,
622
769
  visibleLines: lines,
623
770
  totalLines,
624
771
  mountedLines: totalLines
@@ -626,11 +773,15 @@ function calculateCodeWindow(lines, scrollTop, containerHeight, lineHeight, conf
626
773
  }
627
774
  const firstVisibleLine = Math.floor(scrollTop / lineHeight);
628
775
  const lastVisibleLine = Math.ceil((scrollTop + containerHeight) / lineHeight);
629
- const startIndex = Math.max(0, firstVisibleLine - config.bufferSize);
630
- const endIndex = Math.min(totalLines, lastVisibleLine + config.bufferSize);
776
+ const visibleStart = Math.max(0, firstVisibleLine);
777
+ const visibleEnd = Math.min(totalLines, Math.max(visibleStart, lastVisibleLine));
778
+ const startIndex = Math.max(0, visibleStart - config.bufferSize);
779
+ const endIndex = Math.min(totalLines, visibleEnd + config.bufferSize);
631
780
  return {
632
781
  startIndex,
633
782
  endIndex,
783
+ visibleStart,
784
+ visibleEnd,
634
785
  visibleLines: lines.slice(startIndex, endIndex),
635
786
  totalLines,
636
787
  mountedLines: endIndex - startIndex
@@ -638,10 +789,10 @@ function calculateCodeWindow(lines, scrollTop, containerHeight, lineHeight, conf
638
789
  }
639
790
  function useVirtualizedCode(lines, config = DEFAULT_VIRTUALIZED_CODE_CONFIG) {
640
791
  const containerRef = useRef(null);
641
- const [scrollTop, setScrollTop] = useState(0);
642
- const [containerHeight, setContainerHeight] = useState(0);
792
+ const [scrollTop, setScrollTop] = useState2(0);
793
+ const [containerHeight, setContainerHeight] = useState2(0);
643
794
  const lineHeightRef = useRef(20);
644
- useEffect(() => {
795
+ useEffect2(() => {
645
796
  if (!containerRef.current || lines.length === 0) return;
646
797
  const timeoutId = setTimeout(() => {
647
798
  if (!containerRef.current) return;
@@ -660,7 +811,7 @@ function useVirtualizedCode(lines, config = DEFAULT_VIRTUALIZED_CODE_CONFIG) {
660
811
  setScrollTop(target.scrollTop);
661
812
  setContainerHeight(target.clientHeight);
662
813
  }, []);
663
- useEffect(() => {
814
+ useEffect2(() => {
664
815
  if (!containerRef.current) return;
665
816
  const resizeObserver = new ResizeObserver((entries) => {
666
817
  for (const entry of entries) {
@@ -714,7 +865,7 @@ var BlockNodeRenderer = ({ store, blockId, registry }) => {
714
865
  return registry.renderBlock(block);
715
866
  }
716
867
  };
717
- var ParagraphBlockView = React3.memo(({ store, blockId, registry }) => {
868
+ var ParagraphBlockView = React5.memo(({ store, blockId, registry }) => {
718
869
  const node = useRendererNode(store, blockId);
719
870
  const block = node?.block;
720
871
  const childIds = useRendererChildren(store, blockId);
@@ -734,7 +885,7 @@ var ParagraphBlockView = React3.memo(({ store, blockId, registry }) => {
734
885
  const structured = renderParagraphMixedSegments(segments2, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
735
886
  if (structured.length === 1) {
736
887
  const [single] = structured;
737
- return React3.isValidElement(single) ? single : single;
888
+ return React5.isValidElement(single) ? single : single;
738
889
  }
739
890
  return structured;
740
891
  }
@@ -752,7 +903,7 @@ var ParagraphBlockView = React3.memo(({ store, blockId, registry }) => {
752
903
  const structured = renderParagraphMixedSegments(derivedSegments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
753
904
  if (structured.length === 1) {
754
905
  const [single] = structured;
755
- return React3.isValidElement(single) ? single : single;
906
+ return React5.isValidElement(single) ? single : single;
756
907
  }
757
908
  return structured;
758
909
  }
@@ -771,7 +922,7 @@ var ParagraphBlockView = React3.memo(({ store, blockId, registry }) => {
771
922
  const structured = renderParagraphMixedSegments(segments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
772
923
  if (structured.length === 1) {
773
924
  const [single] = structured;
774
- return React3.isValidElement(single) ? single : single;
925
+ return React5.isValidElement(single) ? single : single;
775
926
  }
776
927
  return structured;
777
928
  }
@@ -788,31 +939,31 @@ var ParagraphBlockView = React3.memo(({ store, blockId, registry }) => {
788
939
  const structured = renderParagraphMixedSegments(derivedSegments, inlineComponents, DEFAULT_INLINE_HTML_RENDERERS);
789
940
  if (structured.length === 1) {
790
941
  const [single] = structured;
791
- return React3.isValidElement(single) ? single : single;
942
+ return React5.isValidElement(single) ? single : single;
792
943
  }
793
944
  return structured;
794
945
  }
795
946
  const ParagraphComponent = registry.getBlockComponent("paragraph");
796
- const baseElement = React3.createElement(ParagraphComponent, {
947
+ const baseElement = React5.createElement(ParagraphComponent, {
797
948
  inlines: block.payload.inline ?? [],
798
949
  raw: block.payload.raw,
799
950
  meta: block.payload.meta
800
951
  });
801
952
  if (childIds.length > 0) {
802
953
  const renderedChildren = childIds.map((childId) => /* @__PURE__ */ jsx(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
803
- if (React3.isValidElement(baseElement)) {
954
+ if (React5.isValidElement(baseElement)) {
804
955
  const { children: _ignored, className = "markdown-paragraph", inlines: _inlines, raw: _raw, meta: _meta, ...rest } = baseElement.props ?? {};
805
- return React3.createElement("p", { ...rest, className }, renderedChildren);
956
+ return React5.createElement("p", { ...rest, className }, renderedChildren);
806
957
  }
807
958
  return /* @__PURE__ */ jsx("p", { className: "markdown-paragraph", children: renderedChildren });
808
959
  }
809
- if (React3.isValidElement(baseElement)) {
960
+ if (React5.isValidElement(baseElement)) {
810
961
  return baseElement;
811
962
  }
812
963
  return /* @__PURE__ */ jsx("p", { className: "markdown-paragraph", children: renderInlineNodes(block.payload.inline ?? [], inlineComponents) });
813
964
  });
814
965
  ParagraphBlockView.displayName = "ParagraphBlockView";
815
- var BlockquoteBlockView = React3.memo(({ store, blockId, registry }) => {
966
+ var BlockquoteBlockView = React5.memo(({ store, blockId, registry }) => {
816
967
  const node = useRendererNode(store, blockId);
817
968
  const block = node?.block;
818
969
  const childIds = useRendererChildren(store, blockId);
@@ -858,7 +1009,7 @@ var BlockquoteBlockView = React3.memo(({ store, blockId, registry }) => {
858
1009
  elements.push(htmlNode);
859
1010
  } else {
860
1011
  elements.push(
861
- React3.createElement("span", {
1012
+ React5.createElement("span", {
862
1013
  key: `blockquote-html-${block.id}-${segmentIndex}`,
863
1014
  className: "markdown-inline-html",
864
1015
  /* biome-ignore lint/security/noDangerouslySetInnerHtml: segment content is sanitized by the worker before streaming */
@@ -876,10 +1027,10 @@ var BlockquoteBlockView = React3.memo(({ store, blockId, registry }) => {
876
1027
  default: {
877
1028
  const inline = renderInlineContent(segment.inline, `blockquote-text-inline-${block.id}-${segmentIndex}-`);
878
1029
  if (inline) {
879
- elements.push(/* @__PURE__ */ jsx(React3.Fragment, { children: inline }, `blockquote-inline-${block.id}-${segmentIndex}`));
1030
+ elements.push(/* @__PURE__ */ jsx(React5.Fragment, { children: inline }, `blockquote-inline-${block.id}-${segmentIndex}`));
880
1031
  } else {
881
1032
  elements.push(
882
- /* @__PURE__ */ jsx(React3.Fragment, { children: renderTextWithBreaks(segment.value, `blockquote-text-${block.id}-${segmentIndex}-`) }, `blockquote-text-${block.id}-${segmentIndex}`)
1033
+ /* @__PURE__ */ jsx(React5.Fragment, { children: renderTextWithBreaks(segment.value, `blockquote-text-${block.id}-${segmentIndex}-`) }, `blockquote-text-${block.id}-${segmentIndex}`)
883
1034
  );
884
1035
  }
885
1036
  break;
@@ -918,7 +1069,7 @@ var BlockquoteBlockView = React3.memo(({ store, blockId, registry }) => {
918
1069
  return null;
919
1070
  })();
920
1071
  const BlockquoteComponent = registry.getBlockComponent("blockquote");
921
- const baseElement = React3.createElement(
1072
+ const baseElement = React5.createElement(
922
1073
  BlockquoteComponent,
923
1074
  {
924
1075
  inlines: block.payload.inline ?? [],
@@ -926,13 +1077,13 @@ var BlockquoteBlockView = React3.memo(({ store, blockId, registry }) => {
926
1077
  },
927
1078
  renderedContent
928
1079
  );
929
- if (React3.isValidElement(baseElement)) {
1080
+ if (React5.isValidElement(baseElement)) {
930
1081
  return baseElement;
931
1082
  }
932
1083
  return /* @__PURE__ */ jsx("blockquote", { className: "markdown-blockquote", children: renderedContent });
933
1084
  });
934
1085
  BlockquoteBlockView.displayName = "BlockquoteBlockView";
935
- var MixedSegmentView = React3.memo(({ store, nodeId, inlineComponents }) => {
1086
+ var MixedSegmentView = React5.memo(({ store, nodeId, inlineComponents }) => {
936
1087
  const node = useRendererNode(store, nodeId);
937
1088
  if (!node) return null;
938
1089
  switch (node.type) {
@@ -960,7 +1111,7 @@ var MixedSegmentView = React3.memo(({ store, nodeId, inlineComponents }) => {
960
1111
  return rendered;
961
1112
  }
962
1113
  if (!html) return null;
963
- return React3.createElement("span", {
1114
+ return React5.createElement("span", {
964
1115
  className: "markdown-inline-html",
965
1116
  /* biome-ignore lint/security/noDangerouslySetInnerHtml: html is sanitized upstream before reaching the renderer */
966
1117
  dangerouslySetInnerHTML: { __html: html }
@@ -985,19 +1136,19 @@ var MixedSegmentView = React3.memo(({ store, nodeId, inlineComponents }) => {
985
1136
  }
986
1137
  });
987
1138
  MixedSegmentView.displayName = "MixedSegmentView";
988
- var HtmlBlockView = React3.memo(({ store, blockId, registry }) => {
1139
+ var HtmlBlockView = React5.memo(({ store, blockId, registry }) => {
989
1140
  const node = useRendererNode(store, blockId);
990
1141
  if (!node || !node.block) return null;
991
1142
  return registry.renderBlock(node.block);
992
1143
  });
993
1144
  HtmlBlockView.displayName = "HtmlBlockView";
994
- var MdxBlockView = React3.memo(({ store, blockId, registry }) => {
1145
+ var MdxBlockView = React5.memo(({ store, blockId, registry }) => {
995
1146
  const node = useRendererNode(store, blockId);
996
1147
  if (!node || !node.block) return null;
997
1148
  return registry.renderBlock(node.block);
998
1149
  });
999
1150
  MdxBlockView.displayName = "MdxBlockView";
1000
- var ListBlockView = React3.memo(
1151
+ var ListBlockView = React5.memo(
1001
1152
  ({ store, blockId, registry, depth = 0 }) => {
1002
1153
  const node = useRendererNode(store, blockId);
1003
1154
  const block = node?.block;
@@ -1005,23 +1156,77 @@ var ListBlockView = React3.memo(
1005
1156
  const inlineComponents = registry.getInlineComponents();
1006
1157
  const ordered = Boolean(node?.props?.ordered ?? block?.payload.meta?.ordered);
1007
1158
  const Tag = ordered ? "ol" : "ul";
1008
- return /* @__PURE__ */ jsx(Tag, { className: `markdown-list ${ordered ? "ordered" : "unordered"}`, "data-list-depth": depth, children: childIds.map((childId, index) => /* @__PURE__ */ jsx(
1009
- ListItemView,
1010
- {
1011
- store,
1012
- nodeId: childId,
1013
- inlineComponents,
1014
- registry,
1159
+ const listDebugEnabled = isListPatchDebugEnabled();
1160
+ const listItemIds = React5.useMemo(() => {
1161
+ const ids = [];
1162
+ for (const childId of childIds) {
1163
+ const childNode = store.getNode(childId);
1164
+ if (childNode?.type === "list-item") {
1165
+ ids.push(childId);
1166
+ }
1167
+ }
1168
+ return ids;
1169
+ }, [childIds, store]);
1170
+ React5.useEffect(() => {
1171
+ if (!listDebugEnabled) return;
1172
+ const childTypes = childIds.map((childId) => store.getNode(childId)?.type ?? "missing");
1173
+ console.info("[stream-mdx:list-render]", {
1174
+ listId: blockId,
1175
+ depth,
1015
1176
  ordered,
1016
- index,
1017
- depth
1018
- },
1019
- childId
1020
- )) });
1177
+ childIds,
1178
+ childTypes,
1179
+ listItemIds,
1180
+ listItems: listItemIds.length,
1181
+ totalChildren: childIds.length,
1182
+ isFinalized: block?.isFinalized ?? false
1183
+ });
1184
+ }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
1185
+ const listStyle = React5.useMemo(() => {
1186
+ if (!ordered || listItemIds.length === 0) return void 0;
1187
+ let maxDigits = 1;
1188
+ listItemIds.forEach((childId, index) => {
1189
+ const childNode = store.getNode(childId);
1190
+ const raw = typeof childNode?.block?.payload?.raw === "string" ? childNode.block.payload.raw : void 0;
1191
+ const markerMatch = raw ? raw.match(/^([^\s]+)\s+/) : null;
1192
+ const marker = markerMatch?.[1]?.trim();
1193
+ const text = marker ?? `${index + 1}.`;
1194
+ const digitMatch = text.match(/\d/g);
1195
+ const digits = digitMatch ? digitMatch.length : text.length;
1196
+ if (digits > maxDigits) maxDigits = digits;
1197
+ });
1198
+ const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
1199
+ return {
1200
+ ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
1201
+ };
1202
+ }, [ordered, listItemIds, store, depth]);
1203
+ if (listItemIds.length === 0) return null;
1204
+ return /* @__PURE__ */ jsx(
1205
+ Tag,
1206
+ {
1207
+ className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
1208
+ "data-list-depth": depth,
1209
+ "data-list-id": listDebugEnabled ? blockId : void 0,
1210
+ style: listStyle,
1211
+ children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx(
1212
+ ListItemView,
1213
+ {
1214
+ store,
1215
+ nodeId: childId,
1216
+ inlineComponents,
1217
+ registry,
1218
+ ordered,
1219
+ index,
1220
+ depth
1221
+ },
1222
+ childId
1223
+ ))
1224
+ }
1225
+ );
1021
1226
  }
1022
1227
  );
1023
1228
  ListBlockView.displayName = "ListBlockView";
1024
- var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ordered, index, depth }) => {
1229
+ var ListItemView = React5.memo(({ store, nodeId, inlineComponents, registry, ordered, index, depth }) => {
1025
1230
  const node = useRendererNode(store, nodeId);
1026
1231
  const childIds = useRendererChildren(store, nodeId);
1027
1232
  const inline = node?.props?.inline ?? [];
@@ -1034,7 +1239,9 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1034
1239
  const inferredIndex = typeof node?.props?.index === "number" ? Number(node?.props?.index) : index;
1035
1240
  const isOrdered = Boolean(node?.props?.ordered ?? ordered);
1036
1241
  const counterText = !isTask ? isOrdered ? marker ? marker : `${inferredIndex + 1}.` : "\u2022" : void 0;
1037
- const [segmentChildIds, contentChildIds] = React3.useMemo(() => {
1242
+ const listDebugEnabled = isListPatchDebugEnabled();
1243
+ const inlineLength = inline.length;
1244
+ const [segmentChildIds, contentChildIds] = React5.useMemo(() => {
1038
1245
  const segments = [];
1039
1246
  const structural = [];
1040
1247
  for (const childId of childIds) {
@@ -1048,16 +1255,16 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1048
1255
  }
1049
1256
  return [segments, structural];
1050
1257
  }, [childIds, store]);
1051
- const segmentElements = React3.useMemo(() => {
1258
+ const segmentElements = React5.useMemo(() => {
1052
1259
  return segmentChildIds.map((childId) => /* @__PURE__ */ jsx(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
1053
1260
  }, [segmentChildIds, store, inlineComponents]);
1054
- const primaryContent = React3.useMemo(() => {
1261
+ const primaryContent = React5.useMemo(() => {
1055
1262
  if (segmentChildIds.length > 0) {
1056
1263
  return segmentElements;
1057
1264
  }
1058
1265
  return renderInlineNodes(inline, inlineComponents);
1059
1266
  }, [segmentChildIds, segmentElements, inline, inlineComponents]);
1060
- const trailingChildren = React3.useMemo(() => {
1267
+ const trailingChildren = React5.useMemo(() => {
1061
1268
  return contentChildIds.map((childId) => {
1062
1269
  const childNode = store.getNode(childId);
1063
1270
  if (!childNode) return null;
@@ -1075,22 +1282,67 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1075
1282
  }, [contentChildIds, store, inlineComponents, registry, depth]);
1076
1283
  const filteredChildren = trailingChildren.filter((child) => Boolean(child));
1077
1284
  const hasChildren = filteredChildren.length > 0;
1078
- return /* @__PURE__ */ jsxs("li", { className: `markdown-list-item${isTask ? " markdown-list-item-task" : ""}`, "data-counter-text": counterText, "data-list-depth": depth, children: [
1079
- isTask ? /* @__PURE__ */ jsxs("div", { className: "markdown-task", children: [
1080
- /* @__PURE__ */ jsx("input", { type: "checkbox", className: "markdown-task-checkbox", checked: isChecked, readOnly: true, disabled: true, tabIndex: -1, "aria-checked": isChecked }),
1081
- /* @__PURE__ */ jsx("span", { className: "markdown-task-content", children: primaryContent })
1082
- ] }) : primaryContent,
1083
- hasChildren ? /* @__PURE__ */ jsx("div", { className: "markdown-list-item-children", children: filteredChildren }) : null
1084
- ] });
1285
+ React5.useEffect(() => {
1286
+ if (!listDebugEnabled) return;
1287
+ const childTypes = childIds.map((childId) => store.getNode(childId)?.type ?? "missing");
1288
+ console.info("[stream-mdx:list-item-render]", {
1289
+ itemId: nodeId,
1290
+ depth,
1291
+ ordered: isOrdered,
1292
+ inferredIndex,
1293
+ marker,
1294
+ isTask,
1295
+ isChecked,
1296
+ childIds,
1297
+ childTypes,
1298
+ segmentChildIds,
1299
+ contentChildIds,
1300
+ inlineLength,
1301
+ hasChildren,
1302
+ isFinalized: block?.isFinalized ?? false
1303
+ });
1304
+ }, [
1305
+ listDebugEnabled,
1306
+ nodeId,
1307
+ depth,
1308
+ isOrdered,
1309
+ inferredIndex,
1310
+ marker,
1311
+ isTask,
1312
+ isChecked,
1313
+ childIds,
1314
+ segmentChildIds,
1315
+ contentChildIds,
1316
+ inlineLength,
1317
+ hasChildren,
1318
+ store,
1319
+ block?.isFinalized
1320
+ ]);
1321
+ return /* @__PURE__ */ jsxs(
1322
+ "li",
1323
+ {
1324
+ className: `markdown-list-item${isTask ? " markdown-list-item-task" : ""}`,
1325
+ "data-counter-text": counterText,
1326
+ "data-list-depth": depth,
1327
+ "data-list-item-id": listDebugEnabled ? nodeId : void 0,
1328
+ children: [
1329
+ isTask ? /* @__PURE__ */ jsxs("div", { className: "markdown-task", children: [
1330
+ /* @__PURE__ */ jsx("input", { type: "checkbox", className: "markdown-task-checkbox", checked: isChecked, readOnly: true, disabled: true, tabIndex: -1, "aria-checked": isChecked }),
1331
+ /* @__PURE__ */ jsx("span", { className: "markdown-task-content", children: primaryContent })
1332
+ ] }) : primaryContent,
1333
+ hasChildren ? /* @__PURE__ */ jsx("div", { className: "markdown-list-item-children", children: filteredChildren }) : null
1334
+ ]
1335
+ }
1336
+ );
1085
1337
  });
1086
1338
  ListItemView.displayName = "ListItemView";
1087
- var TableBlockView = React3.memo(({ store, blockId, registry }) => {
1339
+ var TableBlockView = React5.memo(({ store, blockId, registry }) => {
1088
1340
  const tableElements = registry.getTableElements();
1089
1341
  const childIds = useRendererChildren(store, blockId);
1090
1342
  return /* @__PURE__ */ jsx(tableElements.Table, { className: "markdown-table w-full caption-bottom text-base", children: childIds.map((sectionId) => /* @__PURE__ */ jsx(TableSectionView, { store, nodeId: sectionId, registry }, sectionId)) });
1091
1343
  });
1092
1344
  TableBlockView.displayName = "TableBlockView";
1093
- var TableSectionView = React3.memo(({ store, nodeId, registry }) => {
1345
+ var TableSectionView = React5.memo(({ store, nodeId, registry }) => {
1094
1346
  const node = useRendererNode(store, nodeId);
1095
1347
  if (!node) return null;
1096
1348
  const tableElements = registry.getTableElements();
@@ -1105,13 +1357,13 @@ var TableSectionView = React3.memo(({ store, nodeId, registry }) => {
1105
1357
  return null;
1106
1358
  });
1107
1359
  TableSectionView.displayName = "TableSectionView";
1108
- var TableRowView = React3.memo(({ store, nodeId, registry }) => {
1360
+ var TableRowView = React5.memo(({ store, nodeId, registry }) => {
1109
1361
  const tableElements = registry.getTableElements();
1110
1362
  const cellIds = useRendererChildren(store, nodeId);
1111
1363
  return /* @__PURE__ */ jsx(tableElements.Tr, { children: cellIds.map((cellId) => /* @__PURE__ */ jsx(TableCellView, { store, nodeId: cellId, registry }, cellId)) });
1112
1364
  });
1113
1365
  TableRowView.displayName = "TableRowView";
1114
- var TableCellView = React3.memo(({ store, nodeId, registry }) => {
1366
+ var TableCellView = React5.memo(({ store, nodeId, registry }) => {
1115
1367
  const node = useRendererNode(store, nodeId);
1116
1368
  const inlineComponents = registry.getInlineComponents();
1117
1369
  const tableElements = registry.getTableElements();
@@ -1124,13 +1376,19 @@ var TableCellView = React3.memo(({ store, nodeId, registry }) => {
1124
1376
  return /* @__PURE__ */ jsx(Tag, { ...alignProps, children: content });
1125
1377
  });
1126
1378
  TableCellView.displayName = "TableCellView";
1127
- var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1379
+ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1128
1380
  const node = useRendererNode(store, blockId);
1129
1381
  const childIds = useRendererChildren(store, blockId);
1130
1382
  const CodeComponent = registry.getBlockComponent("code");
1383
+ const deferredConfig = React5.useContext(DeferredRenderContext);
1384
+ const deferredRef = React5.useRef(null);
1385
+ const shouldRenderDeferred = useDeferredRender(
1386
+ deferredRef,
1387
+ deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
1388
+ );
1131
1389
  if (!node || !node.block) return null;
1132
1390
  const nodeVersion = node.version;
1133
- const lines = React3.useMemo(() => {
1391
+ const lines = React5.useMemo(() => {
1134
1392
  const seenIds = /* @__PURE__ */ new Set();
1135
1393
  const seenIndices = /* @__PURE__ */ new Set();
1136
1394
  const deduped = [];
@@ -1140,6 +1398,7 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1140
1398
  const index = typeof child.props?.index === "number" ? child.props?.index : deduped.length;
1141
1399
  const text = typeof child.props?.text === "string" ? child.props?.text : "";
1142
1400
  const html = typeof child.props?.html === "string" ? child.props?.html : null;
1401
+ const tokens = Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens") ? child.props?.tokens : void 0;
1143
1402
  if (seenIds.has(child.id) || seenIndices.has(index)) {
1144
1403
  console.warn("[renderer-view] duplicate code line detected", {
1145
1404
  blockId,
@@ -1150,7 +1409,7 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1150
1409
  }
1151
1410
  seenIds.add(child.id);
1152
1411
  seenIndices.add(index);
1153
- deduped.push({ id: child.id, index, text, html });
1412
+ deduped.push({ id: child.id, index, text, html, tokens });
1154
1413
  }
1155
1414
  deduped.sort((a, b) => a.index - b.index);
1156
1415
  return deduped;
@@ -1159,9 +1418,93 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1159
1418
  const preAttrs = node.props?.preAttrs ?? void 0;
1160
1419
  const codeAttrs = node.props?.codeAttrs ?? void 0;
1161
1420
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
1162
- const shouldVirtualize = virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
1421
+ 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;
1163
1423
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
1164
- const highlightedHtml = React3.useMemo(() => {
1424
+ 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
+ const lastRangeRef = React5.useRef(null);
1428
+ const rafRef = React5.useRef(null);
1429
+ const idleRef = React5.useRef(null);
1430
+ React5.useEffect(() => {
1431
+ if (!shouldVirtualize || !lazyEnabled || !highlightRequester) return;
1432
+ const { visibleStart, visibleEnd, startIndex, endIndex } = virtualization.window;
1433
+ if (endIndex <= lazyTokenizedUntil) return;
1434
+ const nextRange = {
1435
+ visibleStart,
1436
+ visibleEnd,
1437
+ prefetchStart: startIndex,
1438
+ prefetchEnd: endIndex,
1439
+ tokenizedUntil: lazyTokenizedUntil
1440
+ };
1441
+ const last = lastRangeRef.current;
1442
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
1443
+ return;
1444
+ }
1445
+ lastRangeRef.current = nextRange;
1446
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
1447
+ cancelAnimationFrame(rafRef.current);
1448
+ }
1449
+ if (typeof requestAnimationFrame === "function") {
1450
+ rafRef.current = requestAnimationFrame(() => {
1451
+ highlightRequester({
1452
+ blockId,
1453
+ startLine: visibleStart,
1454
+ endLine: visibleEnd,
1455
+ priority: "visible",
1456
+ reason: "scroll"
1457
+ });
1458
+ rafRef.current = null;
1459
+ });
1460
+ } else {
1461
+ highlightRequester({
1462
+ blockId,
1463
+ startLine: visibleStart,
1464
+ endLine: visibleEnd,
1465
+ priority: "visible",
1466
+ reason: "scroll"
1467
+ });
1468
+ }
1469
+ const prefetchNeeded = startIndex < visibleStart || endIndex > visibleEnd;
1470
+ if (prefetchNeeded) {
1471
+ const idleCallback = globalThis.requestIdleCallback;
1472
+ const cancelIdle = globalThis.cancelIdleCallback;
1473
+ if (idleRef.current !== null) {
1474
+ if (typeof cancelIdle === "function") {
1475
+ cancelIdle(idleRef.current);
1476
+ } else if (typeof clearTimeout === "function") {
1477
+ clearTimeout(idleRef.current);
1478
+ }
1479
+ }
1480
+ const schedule = () => {
1481
+ highlightRequester({
1482
+ blockId,
1483
+ startLine: startIndex,
1484
+ endLine: endIndex,
1485
+ priority: "prefetch",
1486
+ reason: "buffer"
1487
+ });
1488
+ idleRef.current = null;
1489
+ };
1490
+ if (typeof idleCallback === "function") {
1491
+ idleRef.current = idleCallback(() => schedule());
1492
+ } else if (typeof setTimeout === "function") {
1493
+ idleRef.current = setTimeout(schedule, 80);
1494
+ }
1495
+ }
1496
+ }, [
1497
+ blockId,
1498
+ highlightRequester,
1499
+ lazyEnabled,
1500
+ lazyTokenizedUntil,
1501
+ shouldVirtualize,
1502
+ virtualization.window.visibleStart,
1503
+ virtualization.window.visibleEnd,
1504
+ virtualization.window.startIndex,
1505
+ virtualization.window.endIndex
1506
+ ]);
1507
+ const highlightedHtml = React5.useMemo(() => {
1165
1508
  if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
1166
1509
  return node.block.payload.highlightedHtml;
1167
1510
  }
@@ -1191,12 +1534,20 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1191
1534
  codeAttrs
1192
1535
  }
1193
1536
  );
1194
- const codeFrameClass = "not-prose my-3 flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
1537
+ const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
1538
+ const codeMetricsAttrs = {
1539
+ "data-code-block": "true",
1540
+ "data-block-id": blockId,
1541
+ "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),
1544
+ "data-code-window-size": String(virtualization.config.windowSize)
1545
+ };
1195
1546
  const codeView = shouldVirtualize ? (() => {
1196
1547
  const { containerRef, window: window2, handleScroll, lineHeight } = virtualization;
1197
1548
  const spacerTop = window2.startIndex * lineHeight;
1198
1549
  const spacerBottom = (window2.totalLines - window2.endIndex) * lineHeight;
1199
- return /* @__PURE__ */ jsx("pre", { className: codeFrameClass, children: /* @__PURE__ */ jsxs(
1550
+ return /* @__PURE__ */ jsx("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: /* @__PURE__ */ jsxs(
1200
1551
  "div",
1201
1552
  {
1202
1553
  ref: containerRef,
@@ -1210,14 +1561,22 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1210
1561
  ]
1211
1562
  }
1212
1563
  ) });
1213
- })() : /* @__PURE__ */ jsx("pre", { className: codeFrameClass, children: rendered });
1564
+ })() : /* @__PURE__ */ jsx("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: rendered });
1214
1565
  const blockComponentMap = registry.getBlockComponentMap();
1215
1566
  const MermaidComponent = Object.prototype.hasOwnProperty.call(blockComponentMap, "mermaid") ? blockComponentMap.mermaid : null;
1216
1567
  if ((lang ?? "").toLowerCase() === "mermaid" && MermaidComponent) {
1217
1568
  const raw = typeof node.block.payload.raw === "string" ? node.block.payload.raw : "";
1218
1569
  const fenced = stripCodeFence(raw);
1219
1570
  const code = fenced.hadFence ? fenced.code : raw;
1220
- return React3.createElement(MermaidComponent, {
1571
+ if (deferredConfig) {
1572
+ return /* @__PURE__ */ jsx("div", { ref: deferredRef, children: shouldRenderDeferred ? React5.createElement(MermaidComponent, {
1573
+ code,
1574
+ renderCode: codeView,
1575
+ meta: node.block.payload.meta,
1576
+ isFinalized: node.block.isFinalized
1577
+ }) : codeView });
1578
+ }
1579
+ return React5.createElement(MermaidComponent, {
1221
1580
  code,
1222
1581
  renderCode: codeView,
1223
1582
  meta: node.block.payload.meta,