@stream-mdx/react 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -4
  2. package/README.md +65 -14
  3. package/dist/components/bottom-stick-scroll-area.cjs +344 -0
  4. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  5. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  6. package/dist/components/bottom-stick-scroll-area.mjs +320 -0
  7. package/dist/components/index.cjs +511 -176
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +358 -26
  11. package/dist/{index-D7px9jug.d.cts → index-BVhp_pcF.d.ts} +4 -1
  12. package/dist/{index-D7px9jug.d.ts → index-EaeouYp2.d.cts} +4 -1
  13. package/dist/index.cjs +2146 -1145
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +3835 -2836
  17. package/dist/mdx-client.cjs +1 -1
  18. package/dist/mdx-client.mjs +1 -1
  19. package/dist/mdx-coordinator.cjs +28 -7
  20. package/dist/mdx-coordinator.d.cts +2 -2
  21. package/dist/mdx-coordinator.d.ts +2 -2
  22. package/dist/mdx-coordinator.mjs +28 -7
  23. package/dist/renderer/node-views.cjs +273 -24
  24. package/dist/renderer/node-views.d.cts +3 -1
  25. package/dist/renderer/node-views.d.ts +3 -1
  26. package/dist/renderer/node-views.mjs +274 -25
  27. package/dist/renderer/patch-batching.cjs +2 -0
  28. package/dist/renderer/patch-batching.d.cts +1 -1
  29. package/dist/renderer/patch-batching.d.ts +1 -1
  30. package/dist/renderer/patch-batching.mjs +2 -1
  31. package/dist/renderer/patch-commit-scheduler.cjs +82 -30
  32. package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
  33. package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
  34. package/dist/renderer/patch-commit-scheduler.mjs +84 -30
  35. package/dist/renderer/store.cjs +382 -36
  36. package/dist/renderer/store.d.cts +3 -0
  37. package/dist/renderer/store.d.ts +3 -0
  38. package/dist/renderer/store.mjs +382 -36
  39. package/dist/renderer.cjs +3386 -2727
  40. package/dist/renderer.d.cts +15 -12
  41. package/dist/renderer.d.ts +15 -12
  42. package/dist/renderer.mjs +3410 -2751
  43. package/dist/server.cjs +4842 -0
  44. package/dist/server.d.cts +254 -0
  45. package/dist/server.d.ts +254 -0
  46. package/dist/server.mjs +4824 -0
  47. package/dist/{streaming-markdown-Dp1IDgMT.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-DSC4L0xR.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +3476 -2794
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +3436 -2754
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +19 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
@@ -72,7 +72,7 @@ var MDXClient = class {
72
72
  if (block.type !== "mdx") {
73
73
  throw new Error("Block is not MDX type");
74
74
  }
75
- const cacheKey = block.id;
75
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
76
76
  const cachedPromise = this.cache.get(cacheKey);
77
77
  if (cachedPromise) {
78
78
  return cachedPromise;
@@ -34,7 +34,7 @@ var MDXClient = class {
34
34
  if (block.type !== "mdx") {
35
35
  throw new Error("Block is not MDX type");
36
36
  }
37
- const cacheKey = block.id;
37
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
38
38
  const cachedPromise = this.cache.get(cacheKey);
39
39
  if (cachedPromise) {
40
40
  return cachedPromise;
@@ -71,7 +71,7 @@ var MDXClient = class {
71
71
  if (block.type !== "mdx") {
72
72
  throw new Error("Block is not MDX type");
73
73
  }
74
- const cacheKey = block.id;
74
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
75
75
  const cachedPromise = this.cache.get(cacheKey);
76
76
  if (cachedPromise) {
77
77
  return cachedPromise;
@@ -399,13 +399,14 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
399
399
  const mode = options?.mode ?? "server";
400
400
  const [compiledMap, setCompiledMap] = (0, import_react2.useState)(/* @__PURE__ */ new Map());
401
401
  const blockSignatureRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
402
+ const failedSignatureRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
402
403
  const applyCompiledRef = (0, import_react2.useCallback)(
403
404
  (block, compiledId) => {
404
405
  if (mode === "worker") {
405
406
  return;
406
407
  }
407
408
  if (options?.workerClient) {
408
- options.workerClient.setMdxCompiled(block.id, compiledId);
409
+ options.workerClient.setMdxCompiled(block.id, compiledId, block.payload.raw ?? "");
409
410
  return;
410
411
  }
411
412
  const store = options?.store;
@@ -416,6 +417,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
416
417
  const index = currentBlocks.findIndex((candidate) => candidate.id === block.id);
417
418
  if (index === -1) return;
418
419
  const current = currentBlocks[index];
420
+ if ((current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
421
+ return;
422
+ }
419
423
  if (current.payload.compiledMdxRef?.id === compiledId) {
420
424
  return;
421
425
  }
@@ -445,7 +449,13 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
445
449
  if (mode === "worker") {
446
450
  return;
447
451
  }
448
- const targets = blocks.filter((b) => b.type === "mdx" && b.isFinalized && !b.payload.compiledMdxRef && !compiledMap.has(b.id));
452
+ const targets = blocks.filter((b) => {
453
+ if (b.type !== "mdx" || !b.isFinalized || b.payload.compiledMdxRef || compiledMap.has(b.id)) {
454
+ return false;
455
+ }
456
+ const raw = b.payload.raw ?? "";
457
+ return failedSignatureRef.current.get(b.id) !== raw;
458
+ });
449
459
  if (targets.length === 0) return;
450
460
  let cancelled = false;
451
461
  (async () => {
@@ -464,22 +474,29 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
464
474
  next.set(block.id, compiled.id);
465
475
  return next;
466
476
  });
477
+ failedSignatureRef.current.delete(block.id);
467
478
  applyCompiledRef(block, compiled.id);
468
479
  } catch (e) {
469
480
  console.warn("MDX compile failed for block", block.id, e);
481
+ failedSignatureRef.current.set(block.id, block.payload.raw ?? "");
470
482
  if (options?.workerClient) {
471
- options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e));
483
+ options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e), block.payload.raw ?? "");
472
484
  } else if (options?.store) {
485
+ const currentBlocks = options.store.getBlocks();
486
+ const current = currentBlocks.find((candidate) => candidate.id === block.id);
487
+ if (!current || (current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
488
+ continue;
489
+ }
473
490
  options.store.applyPatches([
474
491
  {
475
492
  op: "setProps",
476
493
  at: { blockId: block.id, nodeId: block.id },
477
494
  props: {
478
495
  block: {
479
- ...block,
496
+ ...current,
480
497
  payload: {
481
- ...block.payload,
482
- meta: { ...block.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
498
+ ...current.payload,
499
+ meta: { ...current.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
483
500
  }
484
501
  }
485
502
  }
@@ -512,6 +529,7 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
512
529
  const currentSignature = candidate.payload.raw ?? "";
513
530
  if (previousSignature !== void 0 && previousSignature !== currentSignature) {
514
531
  next.delete(blockId);
532
+ failedSignatureRef.current.delete(blockId);
515
533
  mutated = true;
516
534
  }
517
535
  }
@@ -521,6 +539,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
521
539
  for (const block of blocks) {
522
540
  if (block.type === "mdx") {
523
541
  signatureMap.set(block.id, block.payload.raw ?? "");
542
+ if (!block.isFinalized || block.payload.compiledMdxRef) {
543
+ failedSignatureRef.current.delete(block.id);
544
+ }
524
545
  }
525
546
  }
526
547
  blockSignatureRef.current = signatureMap;
@@ -8,8 +8,8 @@ import { RendererStore } from './renderer/store.cjs';
8
8
  * - Returns a derived blocks array with `compiledMdxRef` populated
9
9
  */
10
10
  interface MdxWorkerClientLike {
11
- setMdxCompiled(blockId: string, compiledId: string): void;
12
- setMdxError(blockId: string, message: string): void;
11
+ setMdxCompiled(blockId: string, compiledId: string, rawSignature: string): void;
12
+ setMdxError(blockId: string, message: string, rawSignature: string): void;
13
13
  }
14
14
  interface MdxCoordinatorOptions {
15
15
  workerClient?: MdxWorkerClientLike;
@@ -8,8 +8,8 @@ import { RendererStore } from './renderer/store.js';
8
8
  * - Returns a derived blocks array with `compiledMdxRef` populated
9
9
  */
10
10
  interface MdxWorkerClientLike {
11
- setMdxCompiled(blockId: string, compiledId: string): void;
12
- setMdxError(blockId: string, message: string): void;
11
+ setMdxCompiled(blockId: string, compiledId: string, rawSignature: string): void;
12
+ setMdxError(blockId: string, message: string, rawSignature: string): void;
13
13
  }
14
14
  interface MdxCoordinatorOptions {
15
15
  workerClient?: MdxWorkerClientLike;
@@ -38,7 +38,7 @@ var MDXClient = class {
38
38
  if (block.type !== "mdx") {
39
39
  throw new Error("Block is not MDX type");
40
40
  }
41
- const cacheKey = block.id;
41
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
42
42
  const cachedPromise = this.cache.get(cacheKey);
43
43
  if (cachedPromise) {
44
44
  return cachedPromise;
@@ -366,13 +366,14 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
366
366
  const mode = options?.mode ?? "server";
367
367
  const [compiledMap, setCompiledMap] = useState(/* @__PURE__ */ new Map());
368
368
  const blockSignatureRef = useRef(/* @__PURE__ */ new Map());
369
+ const failedSignatureRef = useRef(/* @__PURE__ */ new Map());
369
370
  const applyCompiledRef = useCallback(
370
371
  (block, compiledId) => {
371
372
  if (mode === "worker") {
372
373
  return;
373
374
  }
374
375
  if (options?.workerClient) {
375
- options.workerClient.setMdxCompiled(block.id, compiledId);
376
+ options.workerClient.setMdxCompiled(block.id, compiledId, block.payload.raw ?? "");
376
377
  return;
377
378
  }
378
379
  const store = options?.store;
@@ -383,6 +384,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
383
384
  const index = currentBlocks.findIndex((candidate) => candidate.id === block.id);
384
385
  if (index === -1) return;
385
386
  const current = currentBlocks[index];
387
+ if ((current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
388
+ return;
389
+ }
386
390
  if (current.payload.compiledMdxRef?.id === compiledId) {
387
391
  return;
388
392
  }
@@ -412,7 +416,13 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
412
416
  if (mode === "worker") {
413
417
  return;
414
418
  }
415
- const targets = blocks.filter((b) => b.type === "mdx" && b.isFinalized && !b.payload.compiledMdxRef && !compiledMap.has(b.id));
419
+ const targets = blocks.filter((b) => {
420
+ if (b.type !== "mdx" || !b.isFinalized || b.payload.compiledMdxRef || compiledMap.has(b.id)) {
421
+ return false;
422
+ }
423
+ const raw = b.payload.raw ?? "";
424
+ return failedSignatureRef.current.get(b.id) !== raw;
425
+ });
416
426
  if (targets.length === 0) return;
417
427
  let cancelled = false;
418
428
  (async () => {
@@ -431,22 +441,29 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
431
441
  next.set(block.id, compiled.id);
432
442
  return next;
433
443
  });
444
+ failedSignatureRef.current.delete(block.id);
434
445
  applyCompiledRef(block, compiled.id);
435
446
  } catch (e) {
436
447
  console.warn("MDX compile failed for block", block.id, e);
448
+ failedSignatureRef.current.set(block.id, block.payload.raw ?? "");
437
449
  if (options?.workerClient) {
438
- options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e));
450
+ options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e), block.payload.raw ?? "");
439
451
  } else if (options?.store) {
452
+ const currentBlocks = options.store.getBlocks();
453
+ const current = currentBlocks.find((candidate) => candidate.id === block.id);
454
+ if (!current || (current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
455
+ continue;
456
+ }
440
457
  options.store.applyPatches([
441
458
  {
442
459
  op: "setProps",
443
460
  at: { blockId: block.id, nodeId: block.id },
444
461
  props: {
445
462
  block: {
446
- ...block,
463
+ ...current,
447
464
  payload: {
448
- ...block.payload,
449
- meta: { ...block.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
465
+ ...current.payload,
466
+ meta: { ...current.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
450
467
  }
451
468
  }
452
469
  }
@@ -479,6 +496,7 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
479
496
  const currentSignature = candidate.payload.raw ?? "";
480
497
  if (previousSignature !== void 0 && previousSignature !== currentSignature) {
481
498
  next.delete(blockId);
499
+ failedSignatureRef.current.delete(blockId);
482
500
  mutated = true;
483
501
  }
484
502
  }
@@ -488,6 +506,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
488
506
  for (const block of blocks) {
489
507
  if (block.type === "mdx") {
490
508
  signatureMap.set(block.id, block.payload.raw ?? "");
509
+ if (!block.isFinalized || block.payload.compiledMdxRef) {
510
+ failedSignatureRef.current.delete(block.id);
511
+ }
491
512
  }
492
513
  }
493
514
  blockSignatureRef.current = signatureMap;
@@ -521,7 +521,11 @@ var defaultInlineComponents = {
521
521
  const n = number ?? "?";
522
522
  const href = number ? `#fn:${n}` : void 0;
523
523
  const id = number ? `fnref:${n}` : void 0;
524
- return import_react3.default.createElement("sup", { className: "footnote-ref" }, import_react3.default.createElement("a", { href, id, "data-label": label }, String(n)));
524
+ return import_react3.default.createElement(
525
+ "sup",
526
+ { className: "footnote-ref" },
527
+ import_react3.default.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
528
+ );
525
529
  }
526
530
  };
527
531
  function renderParagraphMixedSegments(segments, inlineComponents, inlineHtmlRenderers) {
@@ -729,6 +733,7 @@ var DeferredRenderContext = import_react6.default.createContext(null);
729
733
 
730
734
  // src/renderer/store.ts
731
735
  var import_core3 = require("@stream-mdx/core");
736
+ var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
732
737
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
733
738
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
734
739
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
@@ -1207,8 +1212,10 @@ var ListBlockView = import_react8.default.memo(
1207
1212
  isFinalized: block?.isFinalized ?? false
1208
1213
  });
1209
1214
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
1210
- const listStyle = import_react8.default.useMemo(() => {
1211
- if (!ordered || listItemIds.length === 0) return void 0;
1215
+ const { listStyle, markerDigits } = import_react8.default.useMemo(() => {
1216
+ if (!ordered || listItemIds.length === 0) {
1217
+ return { listStyle: void 0, markerDigits: 1 };
1218
+ }
1212
1219
  let maxDigits = 1;
1213
1220
  listItemIds.forEach((childId, index) => {
1214
1221
  const childNode = store.getNode(childId);
@@ -1220,9 +1227,14 @@ var ListBlockView = import_react8.default.memo(
1220
1227
  const digits = digitMatch ? digitMatch.length : text.length;
1221
1228
  if (digits > maxDigits) maxDigits = digits;
1222
1229
  });
1223
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
1230
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
1231
+ const extraDigits = Math.max(0, maxDigits - 1);
1224
1232
  return {
1225
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
1233
+ markerDigits: maxDigits,
1234
+ listStyle: {
1235
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
1236
+ ["--list-marker-digits"]: String(maxDigits)
1237
+ }
1226
1238
  };
1227
1239
  }, [ordered, listItemIds, store, depth]);
1228
1240
  if (listItemIds.length === 0) return null;
@@ -1231,6 +1243,7 @@ var ListBlockView = import_react8.default.memo(
1231
1243
  {
1232
1244
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
1233
1245
  "data-list-depth": depth,
1246
+ "data-marker-digits": ordered ? markerDigits : void 0,
1234
1247
  "data-list-id": listDebugEnabled ? blockId : void 0,
1235
1248
  style: listStyle,
1236
1249
  children: listItemIds.map((childId, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1412,6 +1425,7 @@ var CodeBlockView = import_react8.default.memo(({ store, blockId, registry }) =>
1412
1425
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
1413
1426
  );
1414
1427
  if (!node || !node.block) return null;
1428
+ const blockIsFinalized = node.block.isFinalized;
1415
1429
  const nodeVersion = node.version;
1416
1430
  const lines = import_react8.default.useMemo(() => {
1417
1431
  const seenIds = /* @__PURE__ */ new Set();
@@ -1442,13 +1456,25 @@ var CodeBlockView = import_react8.default.memo(({ store, blockId, registry }) =>
1442
1456
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
1443
1457
  const preAttrs = node.props?.preAttrs ?? void 0;
1444
1458
  const codeAttrs = node.props?.codeAttrs ?? void 0;
1459
+ const blockMeta = node.block.payload.meta ?? void 0;
1460
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
1461
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
1462
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
1463
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
1464
+ const lazySourceTextLineCount = import_react8.default.useMemo(() => {
1465
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
1466
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
1467
+ const sourceText = resolveCodeSourceText(raw, metaCode);
1468
+ if (sourceText.length === 0) return 0;
1469
+ return sourceText.split("\n").length;
1470
+ }, [blockMeta, node.block?.payload.raw]);
1471
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
1445
1472
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
1446
1473
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
1447
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
1474
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
1475
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
1448
1476
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
1449
1477
  const highlightRequester = useCodeHighlightRequester();
1450
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
1451
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
1452
1478
  const lastRangeRef = import_react8.default.useRef(null);
1453
1479
  const rafRef = import_react8.default.useRef(null);
1454
1480
  const idleRef = import_react8.default.useRef(null);
@@ -1529,25 +1555,111 @@ var CodeBlockView = import_react8.default.memo(({ store, blockId, registry }) =>
1529
1555
  virtualization.window.startIndex,
1530
1556
  virtualization.window.endIndex
1531
1557
  ]);
1532
- const highlightedHtml = import_react8.default.useMemo(() => {
1533
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
1534
- return node.block.payload.highlightedHtml;
1558
+ import_react8.default.useEffect(() => {
1559
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
1560
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
1561
+ const nextRange = {
1562
+ visibleStart: 0,
1563
+ visibleEnd: totalLazyLineCount,
1564
+ prefetchStart: 0,
1565
+ prefetchEnd: totalLazyLineCount,
1566
+ tokenizedUntil: lazyTokenizedUntil
1567
+ };
1568
+ const last = lastRangeRef.current;
1569
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
1570
+ return;
1535
1571
  }
1572
+ lastRangeRef.current = nextRange;
1573
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
1574
+ cancelAnimationFrame(rafRef.current);
1575
+ }
1576
+ const requestFullHighlight = () => {
1577
+ highlightRequester({
1578
+ blockId,
1579
+ startLine: 0,
1580
+ endLine: totalLazyLineCount,
1581
+ priority: "visible",
1582
+ reason: "finalize-full"
1583
+ });
1584
+ rafRef.current = null;
1585
+ };
1586
+ if (typeof requestAnimationFrame === "function") {
1587
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
1588
+ } else {
1589
+ requestFullHighlight();
1590
+ }
1591
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
1592
+ const highlightedHtml = import_react8.default.useMemo(() => {
1593
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
1594
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
1595
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
1596
+ const sourceText = resolveCodeSourceText(raw, metaCode);
1597
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
1598
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
1599
+ const rawFallbackLines = buildCodeFallbackLines(raw);
1600
+ const deterministicWrapperAttrs = (0, import_core4.getDefaultCodeWrapperAttributes)(lang);
1601
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
1602
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
1603
+ const shouldAppendTerminalNewline = (renderedLines) => {
1604
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
1605
+ return false;
1606
+ }
1607
+ const lastRendered = renderedLines[renderedLines.length - 1];
1608
+ const lastKnown = lines[lines.length - 1];
1609
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
1610
+ return false;
1611
+ }
1612
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
1613
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
1614
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
1615
+ };
1536
1616
  if (lines.length > 0) {
1617
+ const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
1618
+ if (!hasLineHtml && blockHtml) {
1619
+ return blockHtml;
1620
+ }
1537
1621
  if (shouldVirtualize) {
1538
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
1622
+ return composeHighlightedHtml(
1623
+ virtualization.window.visibleLines,
1624
+ composedPreAttrs,
1625
+ composedCodeAttrs,
1626
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
1627
+ );
1539
1628
  }
1540
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
1629
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
1630
+ }
1631
+ if (!shouldVirtualize && blockHtml) {
1632
+ const hasHighlightedLines = blockHtml.includes('class="line"');
1633
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
1634
+ return composeHighlightedHtml(
1635
+ rawFallbackLines,
1636
+ composedPreAttrs,
1637
+ composedCodeAttrs,
1638
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1639
+ );
1640
+ }
1641
+ return blockHtml;
1541
1642
  }
1542
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
1543
1643
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
1544
- const raw = node.block?.payload.raw ?? "";
1545
- if (raw.trim().length > 0) {
1546
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
1644
+ if (rawFallbackLines.length > 0) {
1645
+ return composeHighlightedHtml(
1646
+ rawFallbackLines,
1647
+ composedPreAttrs,
1648
+ composedCodeAttrs,
1649
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1650
+ );
1547
1651
  }
1548
1652
  }
1653
+ if (!blockHtml && rawFallbackLines.length > 0) {
1654
+ return composeHighlightedHtml(
1655
+ rawFallbackLines,
1656
+ composedPreAttrs,
1657
+ composedCodeAttrs,
1658
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
1659
+ );
1660
+ }
1549
1661
  return blockHtml;
1550
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
1662
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
1551
1663
  const rendered = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1552
1664
  CodeComponent,
1553
1665
  {
@@ -1559,13 +1671,19 @@ var CodeBlockView = import_react8.default.memo(({ store, blockId, registry }) =>
1559
1671
  codeAttrs
1560
1672
  }
1561
1673
  );
1674
+ const effectiveLineCount = import_react8.default.useMemo(() => {
1675
+ if (lines.length > 0) {
1676
+ return lines.length;
1677
+ }
1678
+ return 1;
1679
+ }, [lines.length]);
1562
1680
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
1563
1681
  const codeMetricsAttrs = {
1564
1682
  "data-code-block": "true",
1565
1683
  "data-block-id": blockId,
1566
1684
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
1567
- "data-code-total-lines": String(lines.length),
1568
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
1685
+ "data-code-total-lines": String(effectiveLineCount),
1686
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
1569
1687
  "data-code-window-size": String(virtualization.config.windowSize)
1570
1688
  };
1571
1689
  const codeView = shouldVirtualize ? (() => {
@@ -1611,21 +1729,152 @@ var CodeBlockView = import_react8.default.memo(({ store, blockId, registry }) =>
1611
1729
  return codeView;
1612
1730
  });
1613
1731
  CodeBlockView.displayName = "CodeBlockView";
1614
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
1732
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
1615
1733
  const lineMarkup = lines.map((line) => {
1616
- const content = line.html ?? escapeHtml(line.text);
1734
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml(line.text);
1617
1735
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
1618
1736
  return `<span class="line"${dataLine}>${content}</span>`;
1619
1737
  }).join("\n");
1620
1738
  const preAttr = attrsToString(preAttrs);
1621
1739
  const codeAttr = attrsToString(codeAttrs);
1622
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
1623
- </code></pre>`;
1740
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
1741
+ }
1742
+ var TOKEN_FONT_STYLE_ITALIC = 1;
1743
+ var TOKEN_FONT_STYLE_BOLD = 2;
1744
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
1745
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
1746
+ function renderTokenLineHtml(tokens) {
1747
+ if (!tokens || !Array.isArray(tokens.spans)) {
1748
+ return null;
1749
+ }
1750
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
1751
+ }
1752
+ function renderTokenSpanHtml(span) {
1753
+ const dark = span.v?.dark;
1754
+ const light = span.v?.light;
1755
+ const single = span.s;
1756
+ const styles = [];
1757
+ if (dark?.fg) {
1758
+ styles.push(`--shiki-dark:${dark.fg}`);
1759
+ }
1760
+ if (light?.fg) {
1761
+ styles.push(`--shiki-light:${light.fg}`);
1762
+ }
1763
+ if (!light?.fg && !dark?.fg && single?.fg) {
1764
+ styles.push(`color:${single.fg}`);
1765
+ }
1766
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
1767
+ if (typeof fontStyle === "number" && fontStyle > 0) {
1768
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
1769
+ styles.push("font-style: italic");
1770
+ }
1771
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
1772
+ styles.push("font-weight: bold");
1773
+ }
1774
+ const decorations = [];
1775
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
1776
+ decorations.push("underline");
1777
+ }
1778
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
1779
+ decorations.push("line-through");
1780
+ }
1781
+ if (decorations.length > 0) {
1782
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
1783
+ }
1784
+ }
1785
+ const content = escapeHtml(span.t ?? "");
1786
+ if (styles.length === 0) {
1787
+ return content;
1788
+ }
1789
+ return `<span style="${styles.join(";")}">${content}</span>`;
1790
+ }
1791
+ function mergeRenderableTokenSpans(spans) {
1792
+ if (spans.length <= 1) {
1793
+ return spans.slice();
1794
+ }
1795
+ const merged = [];
1796
+ let pendingWhitespace = "";
1797
+ for (const span of spans) {
1798
+ const text = typeof span.t === "string" ? span.t : "";
1799
+ if (/^[\t ]+$/.test(text)) {
1800
+ pendingWhitespace += text;
1801
+ continue;
1802
+ }
1803
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
1804
+ pendingWhitespace = "";
1805
+ const previous = merged[merged.length - 1];
1806
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
1807
+ previous.t += nextSpan.t;
1808
+ } else {
1809
+ merged.push({ ...nextSpan });
1810
+ }
1811
+ }
1812
+ if (pendingWhitespace) {
1813
+ const previous = merged[merged.length - 1];
1814
+ if (previous) {
1815
+ previous.t += pendingWhitespace;
1816
+ } else {
1817
+ merged.push({ t: pendingWhitespace });
1818
+ }
1819
+ }
1820
+ return merged;
1821
+ }
1822
+ function sameTokenSpanStyle(a, b) {
1823
+ const variantKeys = /* @__PURE__ */ new Set([
1824
+ ...Object.keys(a.v ?? {}),
1825
+ ...Object.keys(b.v ?? {})
1826
+ ]);
1827
+ for (const key of variantKeys) {
1828
+ const aStyle = a.v?.[key];
1829
+ const bStyle = b.v?.[key];
1830
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
1831
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
1832
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
1833
+ }
1834
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
1835
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
1836
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
1837
+ return true;
1838
+ }
1839
+ function resolveCodeSourceText(raw, metaCode) {
1840
+ if (metaCode !== null) {
1841
+ return metaCode.replace(/\r\n?/g, "\n");
1842
+ }
1843
+ if (!raw) return "";
1844
+ const normalized = raw.replace(/\r\n?/g, "\n");
1845
+ const fenced = (0, import_core4.stripCodeFence)(normalized);
1846
+ return fenced.hadFence ? fenced.code : normalized;
1624
1847
  }
1625
1848
  function attrsToString(attrs) {
1626
1849
  if (!attrs) return "";
1627
1850
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
1628
1851
  }
1852
+ function buildCodeFallbackLines(raw) {
1853
+ const normalized = normalizeCodeFallbackRaw(raw);
1854
+ if (normalized.length === 0) {
1855
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
1856
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
1857
+ }
1858
+ return [];
1859
+ }
1860
+ return normalized.split("\n").map((line, index) => ({
1861
+ id: `temp-${index}`,
1862
+ index,
1863
+ text: line,
1864
+ html: null
1865
+ }));
1866
+ }
1867
+ function normalizeCodeFallbackRaw(raw) {
1868
+ if (!raw) return "";
1869
+ const normalized = raw.replace(/\r\n?/g, "\n");
1870
+ const fenced = (0, import_core4.stripCodeFence)(normalized);
1871
+ if (!fenced.hadFence) {
1872
+ return normalized.replace(/\n+$/, "");
1873
+ }
1874
+ let code = fenced.code;
1875
+ code = code.replace(/\n+$/, "");
1876
+ return code;
1877
+ }
1629
1878
  function escapeHtml(value) {
1630
1879
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1631
1880
  }
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
- import { C as ComponentRegistry } from '../index-D7px9jug.cjs';
2
+ import { C as ComponentRegistry } from '../index-EaeouYp2.cjs';
3
3
  import { RendererStore } from './store.cjs';
4
+ import '../components/bottom-stick-scroll-area.cjs';
5
+ import 'react/jsx-runtime';
4
6
  import '@stream-mdx/core';
5
7
 
6
8
  declare const BlockNodeRenderer: React.FC<{
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
- import { C as ComponentRegistry } from '../index-D7px9jug.js';
2
+ import { C as ComponentRegistry } from '../index-BVhp_pcF.js';
3
3
  import { RendererStore } from './store.js';
4
+ import '../components/bottom-stick-scroll-area.js';
5
+ import 'react/jsx-runtime';
4
6
  import '@stream-mdx/core';
5
7
 
6
8
  declare const BlockNodeRenderer: React.FC<{