@mui/internal-docs-infra 0.12.1-canary.44 → 0.12.1-canary.45

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 (135) hide show
  1. package/CodeControllerContext/CodeControllerContext.d.mts +2 -2
  2. package/CodeHighlighter/CodeHighlighter.mjs +2 -1
  3. package/CodeHighlighter/CodeHighlighterChunk.mjs +2 -1
  4. package/CodeHighlighter/CodeHighlighterClient.mjs +127 -81
  5. package/CodeHighlighter/CodeHighlighterContext.d.mts +5 -4
  6. package/CodeHighlighter/CodeHighlighterFallbackContext.d.mts +2 -0
  7. package/CodeHighlighter/buildCodeHighlighterChunkProps.d.mts +1 -1
  8. package/CodeHighlighter/buildCodeHighlighterChunkProps.mjs +1 -1
  9. package/CodeHighlighter/createClientProps.mjs +1 -0
  10. package/CodeHighlighter/fallbackFormat.d.mts +1 -2
  11. package/CodeHighlighter/fallbackFormat.mjs +1 -2
  12. package/CodeHighlighter/parseControlledCode.mjs +20 -14
  13. package/CodeHighlighter/scheduleDeferredPrecompute.d.mts +14 -0
  14. package/CodeHighlighter/scheduleDeferredPrecompute.mjs +57 -0
  15. package/CodeHighlighter/types.d.mts +31 -28
  16. package/CodeHighlighter/useCodeFallback.d.mts +5 -0
  17. package/CodeHighlighter/useCodeFallback.mjs +13 -10
  18. package/CodeHighlighter/useSpeculativeEditingPreload.d.mts +3 -3
  19. package/CodeHighlighter/useSpeculativeEditingPreload.mjs +8 -7
  20. package/CodeProvider/CodeContext.d.mts +12 -18
  21. package/CodeProvider/CodeContext.mjs +13 -1
  22. package/CodeProvider/CodeProvider.mjs +2 -3
  23. package/CodeProvider/CodeProviderLazy.mjs +4 -4
  24. package/CodeProvider/constants.d.mts +3 -3
  25. package/CodeProvider/constants.mjs +2 -2
  26. package/CodeProvider/useCodeProviderValue.d.mts +3 -3
  27. package/CodeProvider/useCodeProviderValue.mjs +32 -51
  28. package/abstractCreateDemo/DemoRootContext.d.mts +6 -0
  29. package/abstractCreateDemo/DemoRootContext.mjs +19 -0
  30. package/abstractCreateDemo/abstractCreateDemo.d.mts +1 -0
  31. package/abstractCreateDemo/abstractCreateDemo.mjs +8 -3
  32. package/abstractCreateDemoClient/abstractCreateDemoClient.mjs +6 -15
  33. package/abstractCreateTypes/TypeCode.d.mts +1 -1
  34. package/abstractCreateTypes/TypeCode.mjs +9 -4
  35. package/abstractCreateTypes/typesToJsx.mjs +10 -7
  36. package/createDemoData/createDemoData.mjs +1 -0
  37. package/createDemoData/types.d.mts +2 -0
  38. package/package.json +7 -16
  39. package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +2 -2
  40. package/pipeline/hastUtils/index.d.mts +1 -1
  41. package/pipeline/hastUtils/index.mjs +1 -1
  42. package/pipeline/loadIsomorphicCodeVariant/createEditableSourceProjection.d.mts +5 -0
  43. package/pipeline/loadIsomorphicCodeVariant/createEditableSourceProjection.mjs +85 -0
  44. package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +1 -1
  45. package/pipeline/loadIsomorphicCodeVariant/diffHast.mjs +7 -81
  46. package/pipeline/loadIsomorphicCodeVariant/embedTransforms.d.mts +0 -16
  47. package/pipeline/loadIsomorphicCodeVariant/embedTransforms.mjs +7 -70
  48. package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.d.mts +4 -13
  49. package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.mjs +3 -40
  50. package/pipeline/loadIsomorphicCodeVariant/loadIsomorphicCodeVariant.mjs +30 -1
  51. package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +4 -0
  52. package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.mjs +65 -2
  53. package/pipeline/parseCreateFactoryCall/replacePrecomputeValue.d.mts +1 -0
  54. package/pipeline/parseCreateFactoryCall/replacePrecomputeValue.mjs +1 -0
  55. package/pipeline/parseSource/detectFileTypes.d.mts +2 -2
  56. package/pipeline/parseSource/detectGrammarScopes.d.mts +2 -2
  57. package/pipeline/parseSource/detectGrammarScopes.mjs +1 -1
  58. package/pipeline/parseSource/frameVisibility.mjs +3 -4
  59. package/pipeline/parseSource/grammarCache.d.mts +2 -0
  60. package/pipeline/parseSource/grammarCache.mjs +8 -4
  61. package/pipeline/parseSource/parseSource.mjs +57 -35
  62. package/useCode/CodeEditor.d.mts +34 -0
  63. package/useCode/CodeEditor.mjs +241 -0
  64. package/useCode/CodeEditorLazy.d.mts +12 -0
  65. package/useCode/CodeEditorLazy.mjs +31 -0
  66. package/useCode/Pre.browser.mjs +52 -280
  67. package/useCode/Pre.d.mts +16 -106
  68. package/useCode/Pre.mjs +71 -462
  69. package/useCode/SourceEditingEngine.d.mts +12 -2
  70. package/useCode/SourceEditingEngine.mjs +65 -13
  71. package/useCode/TransformEngine.d.mts +3 -2
  72. package/useCode/TransformEngine.mjs +26 -6
  73. package/useCode/codeEditorCache.d.mts +8 -0
  74. package/useCode/codeEditorCache.mjs +30 -0
  75. package/useCode/index.d.mts +2 -1
  76. package/useCode/index.mjs +2 -1
  77. package/useCode/resolveActionVariant.d.mts +9 -0
  78. package/useCode/resolveActionVariant.mjs +121 -0
  79. package/useCode/sourceEditingEngineCache.d.mts +6 -0
  80. package/useCode/sourceEditingEngineCache.mjs +25 -0
  81. package/useCode/sourceLineCounts.d.mts +2 -63
  82. package/useCode/sourceLineCounts.mjs +0 -175
  83. package/useCode/useCode.d.mts +27 -113
  84. package/useCode/useCode.mjs +114 -180
  85. package/useCode/useCodeUtils.d.mts +3 -91
  86. package/useCode/useCodeUtils.mjs +2 -205
  87. package/useCode/useCopyFunctionality.d.mts +13 -1
  88. package/useCode/useCopyFunctionality.mjs +47 -7
  89. package/useCode/useFileNavigation.d.mts +19 -51
  90. package/useCode/useFileNavigation.mjs +61 -65
  91. package/useCode/useSourceEditing.d.mts +16 -17
  92. package/useCode/useSourceEditing.mjs +86 -43
  93. package/useCode/useTransformManagement.d.mts +6 -89
  94. package/useCode/useTransformManagement.mjs +72 -501
  95. package/useCode/useUIState.d.mts +3 -1
  96. package/useCode/useUIState.mjs +11 -6
  97. package/useCode/useVariantSelection.d.mts +7 -127
  98. package/useCode/useVariantSelection.mjs +57 -578
  99. package/useDemo/useDemo.d.mts +7 -4
  100. package/useDemo/useDemo.mjs +82 -66
  101. package/useDemoController/instantiateElement.mjs +3 -1
  102. package/useDemoController/useDemoController.d.mts +3 -7
  103. package/useDemoController/useDemoController.mjs +26 -21
  104. package/useDemoController/useRunner.mjs +3 -2
  105. package/useDemoController/useVariantBuilds.d.mts +1 -1
  106. package/useDemoController/useVariantBuilds.mjs +14 -2
  107. package/withDocsInfra/withDeploymentConfig.mjs +6 -7
  108. package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.d.mts +0 -16
  109. package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.mjs +0 -74
  110. package/useCode/EditableEngine.d.mts +0 -233
  111. package/useCode/EditableEngine.mjs +0 -1712
  112. package/useCode/EditingEngine.d.mts +0 -13
  113. package/useCode/EditingEngine.mjs +0 -14
  114. package/useCode/cloneRangeWithInlineStyles.d.mts +0 -47
  115. package/useCode/cloneRangeWithInlineStyles.mjs +0 -123
  116. package/useCode/editingEngineCache.d.mts +0 -29
  117. package/useCode/editingEngineCache.mjs +0 -68
  118. package/useCode/useEditable.browser.d.mts +0 -1
  119. package/useCode/useEditable.browser.mjs +0 -1471
  120. package/useCode/useEditable.d.mts +0 -31
  121. package/useCode/useEditable.integration.browser.d.mts +0 -1
  122. package/useCode/useEditable.integration.browser.mjs +0 -870
  123. package/useCode/useEditable.mjs +0 -239
  124. package/useCode/useEditableUtils.d.mts +0 -132
  125. package/useCode/useEditableUtils.mjs +0 -402
  126. package/useCode/useHighlightGate.d.mts +0 -17
  127. package/useCode/useHighlightGate.mjs +0 -147
  128. package/useCode/useTransitionPhase.d.mts +0 -24
  129. package/useCode/useTransitionPhase.mjs +0 -49
  130. package/useCrossTabState/index.d.mts +0 -2
  131. package/useCrossTabState/index.mjs +0 -2
  132. package/useCrossTabState/useCrossTabMirror.d.mts +0 -26
  133. package/useCrossTabState/useCrossTabMirror.mjs +0 -108
  134. package/useCrossTabState/useCrossTabState.d.mts +0 -21
  135. package/useCrossTabState/useCrossTabState.mjs +0 -31
@@ -78,8 +78,8 @@ export interface CodeControllerContext {
78
78
  export type CodeControllerProps<T extends {} = {}> = {
79
79
  children: React.ReactNode;
80
80
  /**
81
- * The demo's url — identifies this controller, e.g. as the per-demo key for
82
- * cross-tab sync. Supplied by the demo factory, or passed explicitly.
81
+ * The demo's URL, supplied by the demo factory or passed explicitly so custom
82
+ * controllers can identify the demo they wrap.
83
83
  */
84
84
  url?: string;
85
85
  } & T;
@@ -79,7 +79,8 @@ export function CodeHighlighter(props) {
79
79
  const renderChunk = options => {
80
80
  const userProps = {
81
81
  ...props,
82
- code: options.preloaded,
82
+ code: props.loadPrecompute ? undefined : options.preloaded,
83
+ precompute: props.loadPrecompute ? options.preloaded : props.precompute,
83
84
  ContentLoading,
84
85
  initialVariant: initialKey,
85
86
  fallback: options.fallback,
@@ -28,7 +28,8 @@ function CodeHighlighterChunkContent(props) {
28
28
  } = props;
29
29
  const clientProps = createClientProps({
30
30
  ...userProps,
31
- code: data ?? userProps.code
31
+ code: userProps.loadPrecompute ? undefined : data ?? userProps.code,
32
+ precompute: userProps.loadPrecompute ? data ?? userProps.precompute : userProps.precompute
32
33
  });
33
34
  return /*#__PURE__*/_jsx(CodeHighlighterClient, {
34
35
  ...clientProps
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import * as React from 'react';
4
- import { useCodeContext } from "../CodeProvider/CodeContext.mjs";
4
+ import { DemoRootContext } from "../abstractCreateDemo/DemoRootContext.mjs";
5
+ import { useCodeContext, useDemandSourceParser } from "../CodeProvider/CodeContext.mjs";
5
6
  import { CodeHighlighterContext } from "./CodeHighlighterContext.mjs";
6
7
  import { maybeCodeInitialData } from "../pipeline/loadIsomorphicCodeVariant/maybeCodeInitialData.mjs";
7
8
  import { hasAllVariants } from "../pipeline/loadIsomorphicCodeVariant/hasAllCodeVariants.mjs";
@@ -24,10 +25,15 @@ import { useCoordinatedSwap } from "../CoordinatedLazy/useCoordinatedSwap.mjs";
24
25
  import { CoordinatedFallbackContext } from "../CoordinatedLazy/CoordinatedFallbackContext.mjs";
25
26
  import { CoordinatedContentContext } from "../CoordinatedLazy/CoordinatedContentContext.mjs";
26
27
  import { requestIdle } from "../useCoordinated/scheduleTasks.mjs";
28
+ import { scheduleDeferredPrecompute } from "./scheduleDeferredPrecompute.mjs";
27
29
  import * as Errors from "./errors.mjs";
28
30
  import { jsx as _jsx } from "react/jsx-runtime";
29
31
  const DEBUG = false; // Set to true for debugging purposes
30
32
 
33
+ // Deferred source loading should stay behind the initial paint without leaving
34
+ // an untouched demo on its server fallback for too long.
35
+ const DEFERRED_PRECOMPUTE_IDLE_TIMEOUT_MS = 2_000;
36
+
31
37
  // Safety-net deadline (ms) for the default `'idle'` highlight/enhance swaps. They
32
38
  // defer the swap to `requestIdleCallback`, which a busy main thread can starve
33
39
  // INDEFINITELY — leaving code stuck at its un-highlighted first paint until a full
@@ -78,7 +84,7 @@ function useInitialData({
78
84
  handleSetFallbackHasts
79
85
  }) {
80
86
  const {
81
- sourceParser,
87
+ loadSourceParser,
82
88
  loadCodeMeta,
83
89
  loadVariantMeta,
84
90
  loadSource,
@@ -128,11 +134,15 @@ function useInitialData({
128
134
  // Lazily resolve the heavy fallback loader (instant under an eager
129
135
  // CodeProvider, a deduped fetch under CodeProviderLazy) before loading.
130
136
  const loadCodeFallback = await loadCodeFallbackLoader();
137
+
138
+ // Only start the parser load when the fallback is highlighted here;
139
+ // `loadCodeFallback` never awaits it otherwise.
140
+ const shouldHighlight = highlightAfter === 'init';
131
141
  const loaded = await loadCodeFallback(url, variantName, code, {
132
- shouldHighlight: highlightAfter === 'init',
142
+ shouldHighlight,
133
143
  fallbackUsesExtraFiles,
134
144
  fallbackUsesAllVariants,
135
- sourceParser,
145
+ sourceParser: shouldHighlight ? loadSourceParser?.() : undefined,
136
146
  loadSource,
137
147
  loadVariantMeta,
138
148
  loadCodeMeta,
@@ -174,7 +184,7 @@ function useInitialData({
174
184
  }
175
185
  return strippedCode;
176
186
  }
177
- }), [reason, variantName, code, setCode, highlightAfter, url, sourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallbackLoader, handleSetFallbackHasts]);
187
+ }), [reason, variantName, code, setCode, highlightAfter, url, loadSourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallbackLoader, handleSetFallbackHasts]);
178
188
  const fallbackConfig = React.useMemo(() => ({
179
189
  ChunkContent: NoopChunkContent,
180
190
  source: fallbackSource
@@ -359,7 +369,7 @@ function useCodeParsing({
359
369
  url
360
370
  }) {
361
371
  const {
362
- sourceParser,
372
+ loadSourceParser,
363
373
  parseSource,
364
374
  parseCode
365
375
  } = useCodeContext();
@@ -407,6 +417,7 @@ function useCodeParsing({
407
417
  // adds no delay on the common path.
408
418
  const grammarScopes = React.useMemo(() => code ? detectGrammarScopes(code) : [], [code]);
409
419
  const grammarsReady = useGrammarsReady(grammarScopes, !!code && shouldHighlight && !allVariantsAlreadyHighlighted);
420
+ useDemandSourceParser(loadSourceParser, !!code && shouldHighlight && !allVariantsAlreadyHighlighted && grammarsReady && !parseSource);
410
421
 
411
422
  // Parse the internal code state when ready and timing conditions are met
412
423
  const parsedCode = React.useMemo(() => {
@@ -419,10 +430,10 @@ function useCodeParsing({
419
430
  return undefined;
420
431
  }
421
432
  if (!parseSource) {
422
- // A CodeProvider is present and its async `sourceParser` promise hasn't
423
- // resolved yet — wait for it instead of erroring. The memo will re-run
424
- // once `parseSource` is populated.
425
- if (sourceParser) {
433
+ // A CodeProvider is present and its parser hasn't loaded yet — wait for
434
+ // it instead of erroring. The memo will re-run once `parseSource` is
435
+ // populated.
436
+ if (loadSourceParser) {
426
437
  return undefined;
427
438
  }
428
439
  if (forceClient) {
@@ -441,12 +452,11 @@ function useCodeParsing({
441
452
  return undefined;
442
453
  }
443
454
  return parseCode(code, parseSource);
444
- }, [code, shouldHighlight, allVariantsAlreadyHighlighted, grammarsReady, sourceParser, parseSource, parseCode, forceClient, url]);
455
+ }, [code, shouldHighlight, allVariantsAlreadyHighlighted, grammarsReady, loadSourceParser, parseSource, parseCode, forceClient, url]);
445
456
 
446
457
  // Keep highlighting deferred until parsed HAST is actually available for the
447
458
  // variants that need it. `shouldHighlight` can flip true ~30ms after
448
- // hydration, but `parseCode` only runs once the async `sourceParser` promise
449
- // resolves. Without this wait, downstream consumers (e.g. the transform
459
+ // hydration, but `parseCode` only runs once the source parser has loaded. Without this wait, downstream consumers (e.g. the transform
450
460
  // swap) would commit while the visible variant is still rendered from its
451
461
  // raw string source, producing a structure swap on the DOM moments later.
452
462
  const waitingForParsedCode = shouldHighlight && !!code && !allVariantsAlreadyHighlighted && !parsedCode;
@@ -495,7 +505,7 @@ function useCodeTransforms({
495
505
  variantName
496
506
  }) {
497
507
  const {
498
- sourceParser,
508
+ loadSourceParser,
499
509
  computeHastDeltasLoader
500
510
  } = useCodeContext();
501
511
  // Track which `parsedCode` the cached `transformedCode` was computed from
@@ -513,7 +523,7 @@ function useCodeTransforms({
513
523
  // the no-async case is derived during render below instead of being stored,
514
524
  // so this effect never publishes a synchronous pass-through state.
515
525
  React.useEffect(() => {
516
- if (!parsedCode || !sourceParser || !computeHastDeltasLoader) {
526
+ if (!parsedCode || !loadSourceParser || !computeHastDeltasLoader) {
517
527
  return undefined;
518
528
  }
519
529
 
@@ -544,7 +554,7 @@ function useCodeTransforms({
544
554
  // Resolve the parser and the (lazy) transform-delta computer in parallel
545
555
  // before computing deltas. computeHastDeltas pulls jsondiffpatch, so it's
546
556
  // kept out of the initial bundle under CodeProviderLazy.
547
- const [parseSource, computeHastDeltas] = await Promise.all([sourceParser, computeHastDeltasLoader()]);
557
+ const [parseSource, computeHastDeltas] = await Promise.all([loadSourceParser(), computeHastDeltasLoader()]);
548
558
  const enhanced = await computeHastDeltas(parsedCode, parseSource);
549
559
  commit(enhanced);
550
560
  } catch (error) {
@@ -558,7 +568,7 @@ function useCodeTransforms({
558
568
  settled = true; // a newer run (or unmount) supersedes this one; ignore late writes
559
569
  clearTimeout(timer);
560
570
  };
561
- }, [parsedCode, sourceParser, computeHastDeltasLoader]);
571
+ }, [parsedCode, loadSourceParser, computeHastDeltasLoader]);
562
572
 
563
573
  // When the full async pipeline is wired, expose the cached output regardless
564
574
  // of whether `parsedCode` changed since the last computation — falling back
@@ -568,7 +578,7 @@ function useCodeTransforms({
568
578
  // `useTransformManagement` / `useVariantSelection`) hold off committing a
569
579
  // swap until fresh deltas land. Without the pipeline, `transformedCode` is a
570
580
  // synchronous pass-through of `parsedCode` derived during render.
571
- const hasAsyncPipeline = !!parsedCode && !!sourceParser && !!computeHastDeltasLoader;
581
+ const hasAsyncPipeline = !!parsedCode && !!loadSourceParser && !!computeHastDeltasLoader;
572
582
  const transformedCode = hasAsyncPipeline ? transformedState.output : parsedCode;
573
583
 
574
584
  // Async hast-deltas pipeline status. While true, consumers (notably
@@ -580,7 +590,7 @@ function useCodeTransforms({
580
590
  // two later when `transformedCode` arrives, producing a visible jump
581
591
  // on top of the just-played collapse animation.
582
592
  //
583
- // Only relevant when both a worker (`sourceParser`) and a deltas
593
+ // Only relevant when both a parser (`loadSourceParser`) and a deltas
584
594
  // computer (`computeHastDeltas`) are wired up — environments without
585
595
  // them resolve `transformedCode` synchronously to `parsedCode` in the
586
596
  // effect above, so the deltas phase is a no-op. We compare the cached
@@ -596,26 +606,29 @@ function useCodeTransforms({
596
606
  }
597
607
  function useControlledCodeParsing({
598
608
  code,
609
+ grammarScopes,
599
610
  forceClient,
600
611
  url,
601
612
  preParsedCache
602
613
  }) {
603
614
  const {
604
- sourceParser,
615
+ loadSourceParser,
605
616
  parseSource,
606
617
  parseControlledCode
607
618
  } = useCodeContext();
619
+ const grammarsReady = useGrammarsReady(grammarScopes, Boolean(code));
620
+ useDemandSourceParser(loadSourceParser, Boolean(code) && grammarsReady && !parseSource);
608
621
 
609
622
  // Parse the controlled code separately (no need to check readyForContent)
610
623
  const parsedControlledCode = React.useMemo(() => {
611
- if (!code) {
624
+ if (!code || !grammarsReady) {
612
625
  return undefined;
613
626
  }
614
627
  if (!parseSource) {
615
- // A CodeProvider is present and its async `sourceParser` promise hasn't
616
- // resolved yet (e.g. CodeProviderLazy dynamic-importing the engine) — wait
617
- // for it instead of erroring. The memo re-runs once `parseSource` lands.
618
- if (sourceParser) {
628
+ // A CodeProvider is present and its parser hasn't loaded yet (e.g.
629
+ // CodeProviderLazy dynamic-importing the engine) — wait for it instead of
630
+ // erroring. The memo re-runs once `parseSource` lands.
631
+ if (loadSourceParser) {
619
632
  return undefined;
620
633
  }
621
634
  if (forceClient) {
@@ -634,7 +647,7 @@ function useControlledCodeParsing({
634
647
  return undefined;
635
648
  }
636
649
  return parseControlledCode(code, parseSource, preParsedCache);
637
- }, [code, sourceParser, parseSource, parseControlledCode, forceClient, url, preParsedCache]);
650
+ }, [code, grammarsReady, loadSourceParser, parseSource, parseControlledCode, forceClient, url, preParsedCache]);
638
651
  return {
639
652
  parsedControlledCode
640
653
  };
@@ -888,8 +901,41 @@ function usePropsCodeGlobalsMerging({
888
901
  }
889
902
  export function CodeHighlighterClient(props) {
890
903
  const controlled = useControlledCode();
904
+ const demoRootRef = React.useContext(DemoRootContext);
891
905
  const isControlled = Boolean(props.code || controlled?.code);
892
906
  const [code, setCode] = React.useState(typeof props.precompute === 'object' ? props.precompute : undefined);
907
+ const [deferredPrecomputeLoaded, setDeferredPrecomputeLoaded] = React.useState(!props.loadPrecompute);
908
+ const [deferredPrecomputeStarted, setDeferredPrecomputeStarted] = React.useState(false);
909
+ React.useEffect(() => {
910
+ if (!props.loadPrecompute || deferredPrecomputeLoaded) {
911
+ return undefined;
912
+ }
913
+ let active = true;
914
+ async function load() {
915
+ setDeferredPrecomputeStarted(true);
916
+ try {
917
+ const loaded = await props.loadPrecompute();
918
+ if (active) {
919
+ React.startTransition(() => {
920
+ setCode(loaded);
921
+ setDeferredPrecomputeLoaded(true);
922
+ });
923
+ }
924
+ } catch {
925
+ // Keep the server-rendered fallback when the deferred chunk fails.
926
+ }
927
+ }
928
+ const cancel = scheduleDeferredPrecompute({
929
+ root: demoRootRef?.current,
930
+ enhanceAfter: props.enhanceAfter,
931
+ load,
932
+ timeout: DEFERRED_PRECOMPUTE_IDLE_TIMEOUT_MS
933
+ });
934
+ return () => {
935
+ active = false;
936
+ cancel();
937
+ };
938
+ }, [props.loadPrecompute, props.enhanceAfter, deferredPrecomputeLoaded, demoRootRef]);
893
939
 
894
940
  // Sync code state with precompute prop changes (for hot-reload). Done with
895
941
  // the store-previous-prop render-phase derivation rather than an effect:
@@ -902,7 +948,7 @@ export function CodeHighlighterClient(props) {
902
948
  const [prevPrecompute, setPrevPrecompute] = React.useState(props.precompute);
903
949
  if (props.precompute !== prevPrecompute) {
904
950
  setPrevPrecompute(props.precompute);
905
- if (typeof props.precompute === 'object') {
951
+ if (typeof props.precompute === 'object' && !(props.loadPrecompute && deferredPrecomputeLoaded)) {
906
952
  setCode(props.precompute);
907
953
  } else if (props.precompute === undefined) {
908
954
  setCode(undefined);
@@ -950,50 +996,10 @@ export function CodeHighlighterClient(props) {
950
996
  });
951
997
 
952
998
  // Per-block editing activation: flipped once when the block first engages for
953
- // editing — threaded down to `useEditable.onActivate` via `CodeHighlighterContext`
999
+ // editing — threaded down to the textarea's focus handler via `CodeHighlighterContext`
954
1000
  // (immediately in `'eager'`, on hover/focus/click in `'interaction'`). Drives
955
1001
  // the editable speculative preload below and notifies the CodeControllerContext.
956
1002
  const [editingActivated, setEditingActivated] = React.useState(false);
957
- const controllerOnActivate = controlled?.onActivate;
958
- // Which live-editing engine chunks the controller should preload on activation: `js`
959
- // if the demo has any JS/TS file, `css` if any CSS file. Stable across keystrokes
960
- // (editing changes source content, never which files exist), like the grammar scopes
961
- // below.
962
- const editableFileTypes = React.useMemo(() => {
963
- const editableCode = props.code ?? code;
964
- return editableCode ? detectFileTypes(editableCode) : {
965
- js: false,
966
- css: false
967
- };
968
- }, [props.code, code]);
969
- const handleEditingActivated = React.useCallback(() => {
970
- setEditingActivated(true);
971
- controllerOnActivate?.(editableFileTypes);
972
- }, [controllerOnActivate, editableFileTypes]);
973
-
974
- // Grammar scopes the editable files need for live re-highlighting. Unlike the
975
- // speculative highlight/transform preloads — which intentionally skip
976
- // controlled blocks (`speculativeCode` is cleared above) — an editable block
977
- // DOES re-highlight its edits on the client, so its grammars must load or the
978
- // edited source falls back to plain text. The editable file set (and thus the
979
- // scopes) comes from `props.code`: editing changes source *content*, never
980
- // which files exist, so this stays stable across keystrokes.
981
- const editableGrammarScopes = React.useMemo(() => {
982
- const editableCode = props.code ?? code;
983
- return editableCode ? detectGrammarScopes(editableCode) : [];
984
- }, [props.code, code]);
985
-
986
- // When the block is editable (a CodeControllerContext with `setCode` is in
987
- // scope), warm the live-editing engine, the per-language grammars, and the
988
- // worker so they're in flight before the user edits. Deduped page-wide. In
989
- // `editActivation: 'interaction'` mode the warming waits until the block is
990
- // `activated` (engaged) — that mode defers loading until the reader engages.
991
- useSpeculativeEditingPreload({
992
- enabled: Boolean(controlled?.setCode),
993
- editActivation,
994
- activated: editingActivated,
995
- scopes: editableGrammarScopes
996
- });
997
1003
 
998
1004
  // Preload the client-side transform applier (the `jsondiffpatch` chunk) when
999
1005
  // the code declares transforms — so it is warm before the reader switches a
@@ -1060,7 +1066,7 @@ export function CodeHighlighterClient(props) {
1060
1066
  highlightAfter,
1061
1067
  fallbackUsesExtraFiles,
1062
1068
  fallbackUsesAllVariants,
1063
- isControlled,
1069
+ isControlled: isControlled || !deferredPrecomputeLoaded,
1064
1070
  globalsCode: props.globalsCode,
1065
1071
  setProcessedGlobalsCode,
1066
1072
  handleSetFallbackHasts
@@ -1141,15 +1147,15 @@ export function CodeHighlighterClient(props) {
1141
1147
  }
1142
1148
  }, [enhanceAfter, isHydrated]);
1143
1149
  const readyForContent = React.useMemo(() => {
1144
- if (!code) {
1150
+ if (!code || !deferredPrecomputeLoaded) {
1145
1151
  return false;
1146
1152
  }
1147
1153
  return hasAllVariants(variants, code);
1148
- }, [code, variants]);
1154
+ }, [code, variants, deferredPrecomputeLoaded]);
1149
1155
 
1150
1156
  // Separate check for activeCode to determine when to show fallback
1151
1157
  const activeCodeReady = React.useMemo(() => {
1152
- if (!activeCode || !isEnhanceAllowed) {
1158
+ if (!activeCode || !isEnhanceAllowed || !deferredPrecomputeLoaded) {
1153
1159
  return false;
1154
1160
  }
1155
1161
 
@@ -1161,13 +1167,13 @@ export function CodeHighlighterClient(props) {
1161
1167
  // For regular code, use the existing hasAllVariants function
1162
1168
  const regularCode = props.code || code;
1163
1169
  return regularCode ? hasAllVariants(variants, regularCode) : false;
1164
- }, [activeCode, isEnhanceAllowed, controlled?.code, variants, props.code, code]);
1170
+ }, [activeCode, isEnhanceAllowed, deferredPrecomputeLoaded, controlled?.code, variants, props.code, code]);
1165
1171
  const {
1166
1172
  refresh: refreshAllVariants
1167
1173
  } = useAllVariants({
1168
1174
  readyForContent,
1169
1175
  variants,
1170
- isControlled,
1176
+ isControlled: isControlled || !deferredPrecomputeLoaded,
1171
1177
  url,
1172
1178
  code,
1173
1179
  setCode,
@@ -1199,6 +1205,40 @@ export function CodeHighlighterClient(props) {
1199
1205
 
1200
1206
  // Use props.code result if available, otherwise use state code result
1201
1207
  const codeWithGlobals = propsCodeWithGlobals || stateCodeWithGlobals;
1208
+
1209
+ // Union immutable and live metadata: either graph can be partial while editing activates.
1210
+ const editableFileTypes = React.useMemo(() => {
1211
+ const initial = codeWithGlobals ? detectFileTypes(codeWithGlobals) : {
1212
+ js: false,
1213
+ css: false
1214
+ };
1215
+ const live = controlled?.code ? detectFileTypes(controlled.code) : {
1216
+ js: false,
1217
+ css: false
1218
+ };
1219
+ return {
1220
+ js: initial.js || live.js,
1221
+ css: initial.css || live.css
1222
+ };
1223
+ }, [codeWithGlobals, controlled]);
1224
+ const editableGrammarScopes = React.useMemo(() => {
1225
+ const scopes = new Set(codeWithGlobals ? detectGrammarScopes(codeWithGlobals) : []);
1226
+ if (controlled?.code) {
1227
+ detectGrammarScopes(controlled.code).forEach(scope => scopes.add(scope));
1228
+ }
1229
+ return [...scopes];
1230
+ }, [codeWithGlobals, controlled]);
1231
+ const controllerOnActivate = controlled?.onActivate;
1232
+ const handleEditingActivated = React.useCallback(() => {
1233
+ setEditingActivated(true);
1234
+ controllerOnActivate?.(editableFileTypes);
1235
+ }, [controllerOnActivate, editableFileTypes]);
1236
+ useSpeculativeEditingPreload({
1237
+ enabled: Boolean(controlled?.setCode),
1238
+ editActivation,
1239
+ activated: editingActivated,
1240
+ scopes: editableGrammarScopes
1241
+ });
1202
1242
  const {
1203
1243
  parsedCode,
1204
1244
  deferHighlight: deferHighlightForParsing,
@@ -1301,6 +1341,7 @@ export function CodeHighlighterClient(props) {
1301
1341
  parsedControlledCode
1302
1342
  } = useControlledCodeParsing({
1303
1343
  code: controlled?.code,
1344
+ grammarScopes: editableGrammarScopes,
1304
1345
  forceClient: props.forceClient,
1305
1346
  url: props.url,
1306
1347
  preParsedCache
@@ -1330,9 +1371,10 @@ export function CodeHighlighterClient(props) {
1330
1371
  }, [hoistedFallbackHasts, variantName, codeForFallback]);
1331
1372
  const fallbackContext = React.useMemo(() => ({
1332
1373
  extraVariants: codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants).extraVariants,
1374
+ canLoadContent: !props.loadPrecompute || deferredPrecomputeStarted,
1333
1375
  setFallbackHasts: handleSetFallbackHasts,
1334
1376
  onHookCalled: handleHookCalled
1335
- }), [variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants, handleSetFallbackHasts, handleHookCalled]);
1377
+ }), [variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants, props.loadPrecompute, deferredPrecomputeStarted, handleSetFallbackHasts, handleHookCalled]);
1336
1378
 
1337
1379
  // Keep the build-time render for any variant the controller hasn't produced a live
1338
1380
  // preview for yet — an in-flight build, a build error, or a variant with no live build
@@ -1357,16 +1399,17 @@ export function CodeHighlighterClient(props) {
1357
1399
  for (const variant of Object.keys(live)) {
1358
1400
  const buildTime = props.components?.[variant];
1359
1401
  const liveNode = injectFallback(live[variant], buildTime);
1360
- merged[variant] = /*#__PURE__*/React.createElement(React.Suspense, {
1361
- key: variant,
1362
- fallback: buildTime ?? null
1363
- }, liveNode);
1402
+ merged[variant] = /*#__PURE__*/_jsx(React.Suspense, {
1403
+ fallback: buildTime ?? null,
1404
+ children: liveNode
1405
+ }, variant);
1364
1406
  }
1365
1407
  return merged;
1366
1408
  }, [controlled?.components, props.components]);
1367
1409
  const context = React.useMemo(() => ({
1368
1410
  code: overlaidCode,
1369
1411
  // Use processed/transformed code
1412
+ initialCode: codeWithGlobals,
1370
1413
  setCode: controlled?.setCode,
1371
1414
  selection: controlled?.selection || selection,
1372
1415
  setSelection: controlled?.setSelection || setSelection,
@@ -1382,7 +1425,7 @@ export function CodeHighlighterClient(props) {
1382
1425
  onEditingActivated: handleEditingActivated,
1383
1426
  refresh,
1384
1427
  preParsedCache
1385
- }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, bridgedComponents, controlled?.errors, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, editActivation, handleEditingActivated, refresh, preParsedCache]);
1428
+ }), [overlaidCode, codeWithGlobals, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, bridgedComponents, controlled?.errors, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, editActivation, handleEditingActivated, refresh, preParsedCache]);
1386
1429
  if (!props.variants && !props.components && !activeCode) {
1387
1430
  throw new Errors.ErrorCodeHighlighterClientMissingData();
1388
1431
  }
@@ -1406,6 +1449,9 @@ export function CodeHighlighterClient(props) {
1406
1449
  children: props.fallback
1407
1450
  })
1408
1451
  });
1452
+ const contentChildren = props.loadPrecompute && /*#__PURE__*/React.isValidElement(props.children) ? /*#__PURE__*/React.cloneElement(props.children, {
1453
+ code: overlaidCode ?? codeWithGlobals ?? code
1454
+ }) : props.children;
1409
1455
 
1410
1456
  // The content subtree. A dynamically-imported content (`LazyContent`) reads the
1411
1457
  // loading `fallback` from `CoordinatedContentContext` and shows it as its own
@@ -1415,7 +1461,7 @@ export function CodeHighlighterClient(props) {
1415
1461
  value: context,
1416
1462
  children: /*#__PURE__*/_jsx(CoordinatedContentContext.Provider, {
1417
1463
  value: contentContext,
1418
- children: props.children
1464
+ children: contentChildren
1419
1465
  })
1420
1466
  });
1421
1467
 
@@ -14,6 +14,8 @@ export interface PreParsedCacheEntry {
14
14
  }
15
15
  export interface CodeHighlighterContextType {
16
16
  code?: Code;
17
+ /** Loaded source before controlled edits are overlaid. */
18
+ initialCode?: Code;
17
19
  setCode?: React.Dispatch<React.SetStateAction<ControlledCode | null>>;
18
20
  selection?: Selection;
19
21
  setSelection?: React.Dispatch<React.SetStateAction<Selection>>;
@@ -64,15 +66,14 @@ export interface CodeHighlighterContextType {
64
66
  /**
65
67
  * Echo of the `editActivation` prop on the surrounding `CodeHighlighter` /
66
68
  * `CodeHighlighterClient`. `useCode` reads it from here and threads it down to
67
- * `useEditable` (which defers the `contentEditable` attach when
68
- * `'interaction'`), so the editing-activation strategy can be configured at
69
+ * `Pre` (which defers the textarea load when `'interaction'`), so the strategy can be configured at
69
70
  * the `CodeHighlighter` / demo level rather than inside the content subtree.
70
71
  */
71
72
  editActivation?: 'eager' | 'interaction';
72
73
  /**
73
- * Callback `useCode` threads down to `useEditable`'s `onActivate`, fired once
74
+ * Callback `useCode` threads down to the editor's `onActivate`, fired once
74
75
  * when the block first engages for editing. `CodeHighlighterClient` supplies it
75
- * to flip its per-block `activated` state (warming the live-editing engine,
76
+ * to flip its per-block `activated` state (warming the live-editing runtime,
76
77
  * grammars, and worker) and to notify the `CodeControllerContext`.
77
78
  */
78
79
  onEditingActivated?: () => void;
@@ -2,6 +2,8 @@ import * as React from 'react';
2
2
  import type { Fallbacks, ContentLoadingVariant } from "./types.mjs";
3
3
  export interface CodeHighlighterFallbackContext {
4
4
  extraVariants?: Record<string, ContentLoadingVariant>;
5
+ /** Whether the loading fallback may begin loading independently lazy content. */
6
+ canLoadContent?: boolean;
5
7
  /**
6
8
  * Callback used by `useCodeFallback` to hoist fallback data
7
9
  * back to `CodeHighlighterClient` so it can derive text dictionaries
@@ -30,7 +30,7 @@ export interface CodeHighlighterChunkProps {
30
30
  forceClient: boolean;
31
31
  }
32
32
  /** The `CodeHighlighter` props this decision reads. */
33
- type BuildCodeHighlighterChunkPropsInput<T extends {}> = Pick<CodeHighlighterProps<T>, 'code' | 'precompute' | 'components' | 'variants' | 'variant' | 'initialVariant' | 'defaultVariant' | 'controlled' | 'forceClient' | 'highlightAfter' | 'fallbackUsesExtraFiles' | 'fallbackUsesAllVariants' | 'loadCodeMeta' | 'loadVariantMeta' | 'loadSource' | 'sourceParser' | 'sourceTransformers' | 'url' | 'fileName'>;
33
+ type BuildCodeHighlighterChunkPropsInput<T extends {}> = Pick<CodeHighlighterProps<T>, 'code' | 'precompute' | 'loadPrecompute' | 'components' | 'variants' | 'variant' | 'initialVariant' | 'defaultVariant' | 'controlled' | 'forceClient' | 'highlightAfter' | 'fallbackUsesExtraFiles' | 'fallbackUsesAllVariants' | 'loadCodeMeta' | 'loadVariantMeta' | 'loadSource' | 'sourceParser' | 'sourceTransformers' | 'url' | 'fileName'>;
34
34
  /**
35
35
  * Map `CodeHighlighter`'s props onto the generic chunk render-decision inputs.
36
36
  * This replaces the bespoke `renderCodeHighlighter`/`renderWithInitialSource`
@@ -34,7 +34,7 @@ export function buildCodeHighlighterChunkProps(props) {
34
34
  // Every variant already highlighted (or controlled editing) -> render the
35
35
  // client directly with no loading.
36
36
  const allVariantsLoaded = Boolean(code && hasAllVariants(variants, code, true));
37
- const controlled = Boolean(props.controlled) || allVariantsLoaded;
37
+ const controlled = !props.loadPrecompute && (Boolean(props.controlled) || allVariantsLoaded);
38
38
 
39
39
  // No loading functions (or the caller forced the client) -> the server cannot
40
40
  // load, so route to a client/content branch.
@@ -54,6 +54,7 @@ export function createClientProps(props) {
54
54
  url,
55
55
  code,
56
56
  precompute,
57
+ loadPrecompute: props.loadPrecompute,
57
58
  components: props.components,
58
59
  variants: props.variants,
59
60
  variant: props.variant,
@@ -127,8 +127,7 @@ export declare function redistributeRootFallback(root: HastRoot, fallback: Fallb
127
127
  * When `collapsesToEmpty` is `true` the source records `focusedLines === 0`
128
128
  * (the `oversizedFocus: 'hide'` collapse-to-nothing case): the collapsed window
129
129
  * is intentionally empty, so the first-frame fallback is skipped and an empty
130
- * array is returned. Mirrors the runtime rule in `Pre.tsx` /
131
- * `getInitialVisibleSourceLines`.
130
+ * array is returned. Mirrors the runtime rule in `Pre.tsx`.
132
131
  *
133
132
  * Used by `fallbackCollapsed` to paint only the on-screen lines while the
134
133
  * file's full fallback rides along compressed (see the prop-compression
@@ -384,8 +384,7 @@ function fallbackFrameType(frame) {
384
384
  * When `collapsesToEmpty` is `true` the source records `focusedLines === 0`
385
385
  * (the `oversizedFocus: 'hide'` collapse-to-nothing case): the collapsed window
386
386
  * is intentionally empty, so the first-frame fallback is skipped and an empty
387
- * array is returned. Mirrors the runtime rule in `Pre.tsx` /
388
- * `getInitialVisibleSourceLines`.
387
+ * array is returned. Mirrors the runtime rule in `Pre.tsx`.
389
388
  *
390
389
  * Used by `fallbackCollapsed` to paint only the on-screen lines while the
391
390
  * file's full fallback rides along compressed (see the prop-compression
@@ -1,3 +1,6 @@
1
+ import { isGrammarRegistered } from "../pipeline/parseSource/grammarCache.mjs";
2
+ import { resolveGrammarScope } from "../pipeline/parseSource/grammarMaps.mjs";
3
+
1
4
  /**
2
5
  * Cache key for a parsed file. Qualified by variant so two variants that share a
3
6
  * file name (e.g. both have `Demo.tsx`) get independent entries instead of
@@ -28,8 +31,12 @@ export function parseControlledCode(controlledCode, parseSource, preParsedCache)
28
31
  */
29
32
  const resolveSource = (variant, fileName, source) => {
30
33
  const key = preParsedCacheKey(variant, fileName);
34
+ const grammarScope = resolveGrammarScope(fileName);
35
+ // An existing parser missing this file's grammar returns valid-looking plain HAST.
36
+ // Do not retain that temporary fallback across the grammar-readiness rerender.
37
+ const cacheable = !grammarScope || isGrammarRegistered(grammarScope) !== false;
31
38
  const entry = preParsedCache?.get(key);
32
- if (entry) {
39
+ if (entry && cacheable) {
33
40
  if (entry.source === source) {
34
41
  return entry.hast;
35
42
  }
@@ -37,10 +44,12 @@ export function parseControlledCode(controlledCode, parseSource, preParsedCache)
37
44
  }
38
45
  try {
39
46
  const hast = parseSource(source, fileName);
40
- preParsedCache?.set(key, {
41
- source,
42
- hast
43
- });
47
+ if (cacheable) {
48
+ preParsedCache?.set(key, {
49
+ source,
50
+ hast
51
+ });
52
+ }
44
53
  return hast;
45
54
  } catch {
46
55
  return source;
@@ -84,26 +93,23 @@ export function parseControlledCode(controlledCode, parseSource, preParsedCache)
84
93
  const fileSourceToProcess = fileData.source === null ? '' : fileData.source;
85
94
  if (typeof fileSourceToProcess === 'string') {
86
95
  parsedExtraFiles[fileName] = {
87
- source: resolveSource(variant, fileName, fileSourceToProcess),
88
- comments: fileData.comments
96
+ ...fileData,
97
+ source: resolveSource(variant, fileName, fileSourceToProcess)
89
98
  };
90
99
  } else {
91
100
  // Keep other values as-is
92
101
  parsedExtraFiles[fileName] = {
93
- source: fileSourceToProcess,
94
- comments: fileData.comments
102
+ ...fileData,
103
+ source: fileSourceToProcess
95
104
  };
96
105
  }
97
106
  }
98
107
  extraFiles = parsedExtraFiles;
99
108
  }
100
109
  parsed[variant] = {
101
- fileName: variantCode.fileName,
102
- url: variantCode.url,
110
+ ...variantCode,
103
111
  source: mainSource,
104
- extraFiles,
105
- filesOrder: variantCode.filesOrder,
106
- comments: variantCode.comments
112
+ extraFiles
107
113
  };
108
114
  }
109
115
  }