@stream-mdx/react 0.1.1 → 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 (45) hide show
  1. package/dist/components/index.cjs +497 -163
  2. package/dist/components/index.d.cts +1 -1
  3. package/dist/components/index.d.ts +1 -1
  4. package/dist/components/index.mjs +496 -163
  5. package/dist/{index-Bt1opGCs.d.cts → index-D0akq48G.d.cts} +24 -2
  6. package/dist/{index-Bt1opGCs.d.ts → index-D0akq48G.d.ts} +24 -2
  7. package/dist/index.cjs +3761 -2043
  8. package/dist/index.d.cts +43 -5
  9. package/dist/index.d.ts +43 -5
  10. package/dist/index.mjs +3985 -2265
  11. package/dist/mdx-client.cjs +60 -18
  12. package/dist/mdx-client.d.cts +11 -0
  13. package/dist/mdx-client.d.ts +11 -0
  14. package/dist/mdx-client.mjs +60 -18
  15. package/dist/mdx-coordinator.cjs +60 -18
  16. package/dist/mdx-coordinator.mjs +60 -18
  17. package/dist/renderer/node-views.cjs +466 -133
  18. package/dist/renderer/node-views.d.cts +1 -1
  19. package/dist/renderer/node-views.d.ts +1 -1
  20. package/dist/renderer/node-views.mjs +409 -68
  21. package/dist/renderer/patch-commit-scheduler.cjs +68 -7
  22. package/dist/renderer/patch-commit-scheduler.d.cts +6 -5
  23. package/dist/renderer/patch-commit-scheduler.d.ts +6 -5
  24. package/dist/renderer/patch-commit-scheduler.mjs +68 -7
  25. package/dist/renderer/store.cjs +481 -56
  26. package/dist/renderer/store.d.cts +2 -1
  27. package/dist/renderer/store.d.ts +2 -1
  28. package/dist/renderer/store.mjs +479 -47
  29. package/dist/renderer/virtualized-code.cjs +8 -2
  30. package/dist/renderer/virtualized-code.d.cts +4 -0
  31. package/dist/renderer/virtualized-code.d.ts +4 -0
  32. package/dist/renderer/virtualized-code.mjs +8 -2
  33. package/dist/renderer.cjs +3193 -2208
  34. package/dist/renderer.d.cts +4 -2
  35. package/dist/renderer.d.ts +4 -2
  36. package/dist/renderer.mjs +2950 -1957
  37. package/dist/streaming-markdown-Ch6PwjAa.d.cts +154 -0
  38. package/dist/streaming-markdown-tca-Mf8D.d.ts +154 -0
  39. package/dist/streaming-markdown.cjs +3944 -2248
  40. package/dist/streaming-markdown.d.cts +6 -95
  41. package/dist/streaming-markdown.d.ts +6 -95
  42. package/dist/streaming-markdown.mjs +3956 -2252
  43. package/dist/utils/inline-html.d.cts +1 -1
  44. package/dist/utils/inline-html.d.ts +1 -1
  45. 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,10 +1156,36 @@ 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
- const listStyle = React3.useMemo(() => {
1009
- if (!ordered || childIds.length === 0) return void 0;
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,
1176
+ ordered,
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;
1010
1187
  let maxDigits = 1;
1011
- childIds.forEach((childId, index) => {
1188
+ listItemIds.forEach((childId, index) => {
1012
1189
  const childNode = store.getNode(childId);
1013
1190
  const raw = typeof childNode?.block?.payload?.raw === "string" ? childNode.block.payload.raw : void 0;
1014
1191
  const markerMatch = raw ? raw.match(/^([^\s]+)\s+/) : null;
@@ -1022,24 +1199,34 @@ var ListBlockView = React3.memo(
1022
1199
  return {
1023
1200
  ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
1024
1201
  };
1025
- }, [ordered, childIds, store, depth]);
1026
- return /* @__PURE__ */ jsx(Tag, { className: `markdown-list ${ordered ? "ordered" : "unordered"}`, "data-list-depth": depth, style: listStyle, children: childIds.map((childId, index) => /* @__PURE__ */ jsx(
1027
- ListItemView,
1202
+ }, [ordered, listItemIds, store, depth]);
1203
+ if (listItemIds.length === 0) return null;
1204
+ return /* @__PURE__ */ jsx(
1205
+ Tag,
1028
1206
  {
1029
- store,
1030
- nodeId: childId,
1031
- inlineComponents,
1032
- registry,
1033
- ordered,
1034
- index,
1035
- depth
1036
- },
1037
- childId
1038
- )) });
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
+ );
1039
1226
  }
1040
1227
  );
1041
1228
  ListBlockView.displayName = "ListBlockView";
1042
- var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ordered, index, depth }) => {
1229
+ var ListItemView = React5.memo(({ store, nodeId, inlineComponents, registry, ordered, index, depth }) => {
1043
1230
  const node = useRendererNode(store, nodeId);
1044
1231
  const childIds = useRendererChildren(store, nodeId);
1045
1232
  const inline = node?.props?.inline ?? [];
@@ -1052,7 +1239,9 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1052
1239
  const inferredIndex = typeof node?.props?.index === "number" ? Number(node?.props?.index) : index;
1053
1240
  const isOrdered = Boolean(node?.props?.ordered ?? ordered);
1054
1241
  const counterText = !isTask ? isOrdered ? marker ? marker : `${inferredIndex + 1}.` : "\u2022" : void 0;
1055
- const [segmentChildIds, contentChildIds] = React3.useMemo(() => {
1242
+ const listDebugEnabled = isListPatchDebugEnabled();
1243
+ const inlineLength = inline.length;
1244
+ const [segmentChildIds, contentChildIds] = React5.useMemo(() => {
1056
1245
  const segments = [];
1057
1246
  const structural = [];
1058
1247
  for (const childId of childIds) {
@@ -1066,16 +1255,16 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1066
1255
  }
1067
1256
  return [segments, structural];
1068
1257
  }, [childIds, store]);
1069
- const segmentElements = React3.useMemo(() => {
1258
+ const segmentElements = React5.useMemo(() => {
1070
1259
  return segmentChildIds.map((childId) => /* @__PURE__ */ jsx(MixedSegmentView, { store, nodeId: childId, inlineComponents }, childId));
1071
1260
  }, [segmentChildIds, store, inlineComponents]);
1072
- const primaryContent = React3.useMemo(() => {
1261
+ const primaryContent = React5.useMemo(() => {
1073
1262
  if (segmentChildIds.length > 0) {
1074
1263
  return segmentElements;
1075
1264
  }
1076
1265
  return renderInlineNodes(inline, inlineComponents);
1077
1266
  }, [segmentChildIds, segmentElements, inline, inlineComponents]);
1078
- const trailingChildren = React3.useMemo(() => {
1267
+ const trailingChildren = React5.useMemo(() => {
1079
1268
  return contentChildIds.map((childId) => {
1080
1269
  const childNode = store.getNode(childId);
1081
1270
  if (!childNode) return null;
@@ -1093,22 +1282,67 @@ var ListItemView = React3.memo(({ store, nodeId, inlineComponents, registry, ord
1093
1282
  }, [contentChildIds, store, inlineComponents, registry, depth]);
1094
1283
  const filteredChildren = trailingChildren.filter((child) => Boolean(child));
1095
1284
  const hasChildren = filteredChildren.length > 0;
1096
- return /* @__PURE__ */ jsxs("li", { className: `markdown-list-item${isTask ? " markdown-list-item-task" : ""}`, "data-counter-text": counterText, "data-list-depth": depth, children: [
1097
- isTask ? /* @__PURE__ */ jsxs("div", { className: "markdown-task", children: [
1098
- /* @__PURE__ */ jsx("input", { type: "checkbox", className: "markdown-task-checkbox", checked: isChecked, readOnly: true, disabled: true, tabIndex: -1, "aria-checked": isChecked }),
1099
- /* @__PURE__ */ jsx("span", { className: "markdown-task-content", children: primaryContent })
1100
- ] }) : primaryContent,
1101
- hasChildren ? /* @__PURE__ */ jsx("div", { className: "markdown-list-item-children", children: filteredChildren }) : null
1102
- ] });
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
+ );
1103
1337
  });
1104
1338
  ListItemView.displayName = "ListItemView";
1105
- var TableBlockView = React3.memo(({ store, blockId, registry }) => {
1339
+ var TableBlockView = React5.memo(({ store, blockId, registry }) => {
1106
1340
  const tableElements = registry.getTableElements();
1107
1341
  const childIds = useRendererChildren(store, blockId);
1108
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)) });
1109
1343
  });
1110
1344
  TableBlockView.displayName = "TableBlockView";
1111
- var TableSectionView = React3.memo(({ store, nodeId, registry }) => {
1345
+ var TableSectionView = React5.memo(({ store, nodeId, registry }) => {
1112
1346
  const node = useRendererNode(store, nodeId);
1113
1347
  if (!node) return null;
1114
1348
  const tableElements = registry.getTableElements();
@@ -1123,13 +1357,13 @@ var TableSectionView = React3.memo(({ store, nodeId, registry }) => {
1123
1357
  return null;
1124
1358
  });
1125
1359
  TableSectionView.displayName = "TableSectionView";
1126
- var TableRowView = React3.memo(({ store, nodeId, registry }) => {
1360
+ var TableRowView = React5.memo(({ store, nodeId, registry }) => {
1127
1361
  const tableElements = registry.getTableElements();
1128
1362
  const cellIds = useRendererChildren(store, nodeId);
1129
1363
  return /* @__PURE__ */ jsx(tableElements.Tr, { children: cellIds.map((cellId) => /* @__PURE__ */ jsx(TableCellView, { store, nodeId: cellId, registry }, cellId)) });
1130
1364
  });
1131
1365
  TableRowView.displayName = "TableRowView";
1132
- var TableCellView = React3.memo(({ store, nodeId, registry }) => {
1366
+ var TableCellView = React5.memo(({ store, nodeId, registry }) => {
1133
1367
  const node = useRendererNode(store, nodeId);
1134
1368
  const inlineComponents = registry.getInlineComponents();
1135
1369
  const tableElements = registry.getTableElements();
@@ -1142,13 +1376,19 @@ var TableCellView = React3.memo(({ store, nodeId, registry }) => {
1142
1376
  return /* @__PURE__ */ jsx(Tag, { ...alignProps, children: content });
1143
1377
  });
1144
1378
  TableCellView.displayName = "TableCellView";
1145
- var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1379
+ var CodeBlockView = React5.memo(({ store, blockId, registry }) => {
1146
1380
  const node = useRendererNode(store, blockId);
1147
1381
  const childIds = useRendererChildren(store, blockId);
1148
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
+ );
1149
1389
  if (!node || !node.block) return null;
1150
1390
  const nodeVersion = node.version;
1151
- const lines = React3.useMemo(() => {
1391
+ const lines = React5.useMemo(() => {
1152
1392
  const seenIds = /* @__PURE__ */ new Set();
1153
1393
  const seenIndices = /* @__PURE__ */ new Set();
1154
1394
  const deduped = [];
@@ -1158,6 +1398,7 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1158
1398
  const index = typeof child.props?.index === "number" ? child.props?.index : deduped.length;
1159
1399
  const text = typeof child.props?.text === "string" ? child.props?.text : "";
1160
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;
1161
1402
  if (seenIds.has(child.id) || seenIndices.has(index)) {
1162
1403
  console.warn("[renderer-view] duplicate code line detected", {
1163
1404
  blockId,
@@ -1168,7 +1409,7 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1168
1409
  }
1169
1410
  seenIds.add(child.id);
1170
1411
  seenIndices.add(index);
1171
- deduped.push({ id: child.id, index, text, html });
1412
+ deduped.push({ id: child.id, index, text, html, tokens });
1172
1413
  }
1173
1414
  deduped.sort((a, b) => a.index - b.index);
1174
1415
  return deduped;
@@ -1177,9 +1418,93 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1177
1418
  const preAttrs = node.props?.preAttrs ?? void 0;
1178
1419
  const codeAttrs = node.props?.codeAttrs ?? void 0;
1179
1420
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
1180
- 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;
1181
1423
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
1182
- 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(() => {
1183
1508
  if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
1184
1509
  return node.block.payload.highlightedHtml;
1185
1510
  }
@@ -1210,11 +1535,19 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1210
1535
  }
1211
1536
  );
1212
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
+ };
1213
1546
  const codeView = shouldVirtualize ? (() => {
1214
1547
  const { containerRef, window: window2, handleScroll, lineHeight } = virtualization;
1215
1548
  const spacerTop = window2.startIndex * lineHeight;
1216
1549
  const spacerBottom = (window2.totalLines - window2.endIndex) * lineHeight;
1217
- return /* @__PURE__ */ jsx("pre", { className: codeFrameClass, children: /* @__PURE__ */ jsxs(
1550
+ return /* @__PURE__ */ jsx("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: /* @__PURE__ */ jsxs(
1218
1551
  "div",
1219
1552
  {
1220
1553
  ref: containerRef,
@@ -1228,14 +1561,22 @@ var CodeBlockView = React3.memo(({ store, blockId, registry }) => {
1228
1561
  ]
1229
1562
  }
1230
1563
  ) });
1231
- })() : /* @__PURE__ */ jsx("pre", { className: codeFrameClass, children: rendered });
1564
+ })() : /* @__PURE__ */ jsx("pre", { className: codeFrameClass, ...codeMetricsAttrs, children: rendered });
1232
1565
  const blockComponentMap = registry.getBlockComponentMap();
1233
1566
  const MermaidComponent = Object.prototype.hasOwnProperty.call(blockComponentMap, "mermaid") ? blockComponentMap.mermaid : null;
1234
1567
  if ((lang ?? "").toLowerCase() === "mermaid" && MermaidComponent) {
1235
1568
  const raw = typeof node.block.payload.raw === "string" ? node.block.payload.raw : "";
1236
1569
  const fenced = stripCodeFence(raw);
1237
1570
  const code = fenced.hadFence ? fenced.code : raw;
1238
- 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, {
1239
1580
  code,
1240
1581
  renderCode: codeView,
1241
1582
  meta: node.block.payload.meta,