@mui/internal-docs-infra 0.11.1-canary.12 → 0.11.1-canary.14

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 (69) hide show
  1. package/CodeHighlighter/CodeHighlighter.mjs +47 -4
  2. package/CodeHighlighter/CodeHighlighterClient.mjs +128 -11
  3. package/CodeHighlighter/CodeHighlighterContext.d.mts +8 -1
  4. package/CodeHighlighter/CodeHighlighterFallbackContext.d.mts +14 -2
  5. package/CodeHighlighter/CodeHighlighterFallbackContext.mjs +1 -3
  6. package/CodeHighlighter/codeToFallbackProps.d.mts +28 -2
  7. package/CodeHighlighter/codeToFallbackProps.mjs +253 -52
  8. package/CodeHighlighter/errors.d.mts +3 -0
  9. package/CodeHighlighter/errors.mjs +5 -0
  10. package/CodeHighlighter/fallbackCompression.d.mts +79 -0
  11. package/CodeHighlighter/fallbackCompression.mjs +230 -0
  12. package/CodeHighlighter/fallbackFormat.d.mts +93 -0
  13. package/CodeHighlighter/fallbackFormat.mjs +323 -0
  14. package/CodeHighlighter/index.d.mts +2 -0
  15. package/CodeHighlighter/index.mjs +1 -0
  16. package/CodeHighlighter/types.d.mts +48 -18
  17. package/CodeHighlighter/useCodeFallback.d.mts +51 -0
  18. package/CodeHighlighter/useCodeFallback.mjs +132 -0
  19. package/abstractCreateTypes/typesToJsx.mjs +18 -2
  20. package/cli/runBrowser.mjs +1 -1
  21. package/package.json +12 -2
  22. package/pipeline/hastUtils/frameFallbackFromSpans.d.mts +18 -0
  23. package/pipeline/hastUtils/frameFallbackFromSpans.mjs +24 -0
  24. package/pipeline/hastUtils/hastUtils.d.mts +4 -3
  25. package/pipeline/hastUtils/hastUtils.mjs +18 -6
  26. package/pipeline/hastUtils/index.d.mts +2 -1
  27. package/pipeline/hastUtils/index.mjs +2 -1
  28. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.d.mts +5 -4
  29. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.mjs +56 -9
  30. package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.d.mts +9 -1
  31. package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.mjs +45 -4
  32. package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +1 -1
  33. package/pipeline/loadIsomorphicCodeVariant/diffHast.mjs +89 -0
  34. package/pipeline/loadIsomorphicCodeVariant/flattenCodeVariant.mjs +4 -2
  35. package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.mjs +2 -7
  36. package/pipeline/loadIsomorphicCodeVariant/loadIsomorphicCodeVariant.mjs +72 -3
  37. package/pipeline/loadServerTypes/hastTypeUtils.d.mts +2 -2
  38. package/pipeline/loadServerTypes/hastTypeUtils.mjs +4 -4
  39. package/pipeline/parseSource/addLineGutters.mjs +18 -16
  40. package/pipeline/parseSource/frameVisibility.d.mts +6 -0
  41. package/pipeline/parseSource/frameVisibility.mjs +20 -0
  42. package/pipeline/parseSource/redistributeFrameFallbacks.d.mts +40 -0
  43. package/pipeline/parseSource/redistributeFrameFallbacks.mjs +138 -0
  44. package/pipeline/parseSource/restructureFrames.d.mts +5 -0
  45. package/pipeline/parseSource/restructureFrames.mjs +75 -2
  46. package/useCode/Pre.browser.mjs +1 -1
  47. package/useCode/Pre.d.mts +3 -0
  48. package/useCode/Pre.mjs +18 -9
  49. package/useCode/sourceLineCounts.d.mts +2 -1
  50. package/useCode/sourceLineCounts.mjs +8 -8
  51. package/useCode/useCode.mjs +5 -0
  52. package/useCode/useCodeUtils.d.mts +4 -3
  53. package/useCode/useCodeUtils.mjs +10 -5
  54. package/useCode/useCopyFunctionality.d.mts +13 -1
  55. package/useCode/useCopyFunctionality.mjs +36 -8
  56. package/useCode/useEditable.mjs +85 -21
  57. package/useCode/useFileNavigation.d.mts +9 -1
  58. package/useCode/useFileNavigation.mjs +53 -6
  59. package/useCode/useSourceEditing.mjs +13 -5
  60. package/useCode/useSourceEnhancing.d.mts +5 -1
  61. package/useCode/useSourceEnhancing.mjs +16 -7
  62. package/useCode/useTransformManagement.mjs +7 -5
  63. package/useCoordinated/index.d.mts +3 -1
  64. package/useCoordinated/index.mjs +3 -1
  65. package/useCoordinated/layoutShiftGate.d.mts +24 -0
  66. package/useCoordinated/layoutShiftGate.mjs +159 -0
  67. package/useCoordinated/useCoordinated.mjs +40 -1
  68. package/useCoordinated/useCoordinatedLazy.d.mts +17 -0
  69. package/useCoordinated/useCoordinatedLazy.mjs +38 -0
@@ -6,7 +6,8 @@ import { maybeCodeInitialData } from "../pipeline/loadIsomorphicCodeVariant/mayb
6
6
  import { hasAllVariants } from "../pipeline/loadIsomorphicCodeVariant/hasAllCodeVariants.mjs";
7
7
  import { getFileNameFromUrl, getLanguageFromExtension } from "../pipeline/loaderUtils/index.mjs";
8
8
  import { replaceUrlPrefix } from "../pipeline/loaderUtils/applyUrlPrefix.mjs";
9
- import { codeToFallbackProps } from "./codeToFallbackProps.mjs";
9
+ import { codeToFallbackProps, stripFallbackHastsFromCode } from "./codeToFallbackProps.mjs";
10
+ import { collapseRenderedFallbacks, compressResidualFallbacks, extractResidualFallbacks, mergeResidualFallbacks, residualDictionaryText } from "./fallbackCompression.mjs";
10
11
  import * as Errors from "./errors.mjs";
11
12
  import { jsx as _jsx } from "react/jsx-runtime";
12
13
  const DEBUG = false; // Set to true for debugging purposes
@@ -45,6 +46,7 @@ function createClientProps(props) {
45
46
  enhanceAfter: enhanceAfter || 'idle',
46
47
  skipFallback: props.skipFallback,
47
48
  controlled: props.controlled,
49
+ residualFallbacks: props.residualFallbacks,
48
50
  name: props.name,
49
51
  slug: props.slug,
50
52
  // Use processedGlobalsCode if available, otherwise fall back to raw globalsCode
@@ -198,14 +200,44 @@ function renderWithInitialSource(props) {
198
200
  code,
199
201
  initialFilename,
200
202
  fallbackUsesExtraFiles,
201
- fallbackUsesAllVariants
203
+ fallbackUsesAllVariants,
204
+ fallbackCollapsed
202
205
  } = props;
203
206
 
207
+ // Strip fallbackHast entries from Code — they move to ContentLoading props
208
+ // as source/extraSource instead of being serialized on Code.
209
+ const {
210
+ strippedCode,
211
+ allFallbackHasts
212
+ } = stripFallbackHastsFromCode(code, initialVariant, fallbackUsesExtraFiles, fallbackUsesAllVariants);
213
+
204
214
  // Rewrite the top-level URL before it reaches the loading fallback so the
205
215
  // browser never sees `file://` URLs. See `createClientProps` for the same
206
216
  // rewrite on the regular client path.
207
217
  const url = props.urlPrefix && props.url ? replaceUrlPrefix(props.url, props.urlPrefix) : props.url;
208
- const fallbackProps = codeToFallbackProps(initialVariant, code, initialFilename, fallbackUsesExtraFiles, fallbackUsesAllVariants);
218
+
219
+ // `fallbackCollapsed` paints only each file's collapsed window in the loading
220
+ // UI; the full fallbacks defer into the blob. Otherwise the loading UI gets
221
+ // the full rendered subset, as usual.
222
+ const contentLoadingHasts = fallbackCollapsed ? collapseRenderedFallbacks(allFallbackHasts) : allFallbackHasts;
223
+ const fallbackProps = codeToFallbackProps(initialVariant, strippedCode, initialFilename, fallbackUsesExtraFiles, fallbackUsesAllVariants, contentLoadingHasts);
224
+
225
+ // Consolidate every fallback the loading UI won't render into a single DEFLATE
226
+ // blob, primed with the rendered (collapsed, when `fallbackCollapsed`) text so
227
+ // it dedupes against what's already on the client. That's everything still on
228
+ // `strippedCode` after hoisting — plus, when `fallbackCollapsed`, each
229
+ // rendered file's *full* fallback (the loading UI only painted its collapsed
230
+ // window, so the rest must travel here). The blob crosses once; `wireCode`
231
+ // carries no inline fallbacks, and the client scatters them back after the
232
+ // rendered text hoists. When there's nothing worth compressing, keep the
233
+ // plain inline fallbacks unchanged.
234
+ const {
235
+ wireCode,
236
+ residual
237
+ } = extractResidualFallbacks(strippedCode);
238
+ const fullResidual = fallbackCollapsed ? mergeResidualFallbacks(residual, allFallbackHasts) : residual;
239
+ const residualFallbacks = compressResidualFallbacks(fullResidual, residualDictionaryText(contentLoadingHasts));
240
+ const codeForClient = residualFallbacks ? wireCode : strippedCode;
209
241
 
210
242
  // Get the component for the selected variant
211
243
  const component = props.components?.[initialVariant];
@@ -221,7 +253,12 @@ function renderWithInitialSource(props) {
221
253
  initialFilename,
222
254
  initialVariant,
223
255
  component,
224
- components
256
+ components,
257
+ // Signals the ContentLoading that `source` is only the collapsed window,
258
+ // so it can disable any expand control until the full content swaps in.
259
+ ...(fallbackCollapsed ? {
260
+ fallbackCollapsed: true
261
+ } : undefined)
225
262
  };
226
263
  const fallback = /*#__PURE__*/_jsx(ContentLoading, {
227
264
  ...contentProps
@@ -232,6 +269,9 @@ function renderWithInitialSource(props) {
232
269
  children: /*#__PURE__*/_jsx(CodeHighlighterSuspense, {
233
270
  children: renderCodeHighlighter({
234
271
  ...props,
272
+ code: codeForClient,
273
+ precompute: residualFallbacks ? codeForClient : props.precompute,
274
+ residualFallbacks,
235
275
  fallback,
236
276
  skipFallback: props.enhanceAfter === 'stream'
237
277
  })
@@ -240,6 +280,9 @@ function renderWithInitialSource(props) {
240
280
  }
241
281
  return renderCodeHighlighter({
242
282
  ...props,
283
+ code: codeForClient,
284
+ precompute: residualFallbacks ? codeForClient : props.precompute,
285
+ residualFallbacks,
243
286
  fallback
244
287
  });
245
288
  }
@@ -7,9 +7,11 @@ import { maybeCodeInitialData } from "../pipeline/loadIsomorphicCodeVariant/mayb
7
7
  import { hasAllVariants } from "../pipeline/loadIsomorphicCodeVariant/hasAllCodeVariants.mjs";
8
8
  import { CodeHighlighterFallbackContext } from "./CodeHighlighterFallbackContext.mjs";
9
9
  import { useControlledCode } from "../CodeControllerContext/index.mjs";
10
- import { codeToFallbackProps } from "./codeToFallbackProps.mjs";
10
+ import { codeToFallbackProps, deriveFallbacksFromCode, stripFallbackHastsFromCode } from "./codeToFallbackProps.mjs";
11
+ import { decompressResidualFallbacks, residualDictionaryText, scatterResidualFallbacks } from "./fallbackCompression.mjs";
11
12
  import { mergeCodeMetadata } from "../pipeline/loadIsomorphicCodeVariant/mergeCodeMetadata.mjs";
12
13
  import { getAvailableTransforms } from "../pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.mjs";
14
+ import { useCoordinatedLazy } from "../useCoordinated/useCoordinatedLazy.mjs";
13
15
  import * as Errors from "./errors.mjs";
14
16
  import { jsx as _jsx } from "react/jsx-runtime";
15
17
  const DEBUG = false; // Set to true for debugging purposes
@@ -26,7 +28,8 @@ function useInitialData({
26
28
  fallbackUsesAllVariants,
27
29
  isControlled,
28
30
  globalsCode,
29
- setProcessedGlobalsCode
31
+ setProcessedGlobalsCode,
32
+ handleSetFallbackHasts
30
33
  }) {
31
34
  const {
32
35
  sourceParser,
@@ -88,14 +91,22 @@ function useInitialData({
88
91
  if ('error' in loaded) {
89
92
  console.error(new Errors.ErrorCodeHighlighterClientLoadFallbackFailure(loaded.error));
90
93
  } else {
91
- setCode(loaded.code);
94
+ // Strip fallbacks from code and hoist them directly
95
+ const {
96
+ strippedCode,
97
+ allFallbackHasts
98
+ } = stripFallbackHastsFromCode(loaded.code, variantName, fallbackUsesExtraFiles, fallbackUsesAllVariants);
99
+ setCode(strippedCode);
100
+ for (const [variant, hasts] of Object.entries(allFallbackHasts)) {
101
+ handleSetFallbackHasts(variant, hasts);
102
+ }
92
103
  // Store processed globalsCode from loadCodeFallback result
93
104
  if (loaded.processedGlobalsCode) {
94
105
  setProcessedGlobalsCode(loaded.processedGlobalsCode);
95
106
  }
96
107
  }
97
108
  })();
98
- }, [initialData, reason, needsFallback, variantName, code, setCode, highlightAfter, url, sourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallback]);
109
+ }, [initialData, reason, needsFallback, variantName, code, setCode, highlightAfter, url, sourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallback, handleSetFallbackHasts]);
99
110
  return {
100
111
  fallbackPending
101
112
  };
@@ -777,6 +788,40 @@ export function CodeHighlighterClient(props) {
777
788
  fallbackUsesExtraFiles,
778
789
  fallbackUsesAllVariants
779
790
  } = props;
791
+
792
+ // ── Fallback hoisting ──
793
+ // State for fallbacks hoisted from ContentLoading via useCodeFallback.
794
+ // Content is stripped from Code on the server and passed to ContentLoading
795
+ // as source/extraSource props. ContentLoading hoists them back here so
796
+ // CodeHighlighterClient can derive text dictionaries for decompression.
797
+ const [hoistedFallbackHasts, setHoistedFallbackHasts] = React.useState({});
798
+
799
+ // Track whether ContentLoading called useCodeFallback via callback.
800
+ const hookCalledRef = React.useRef(false);
801
+ // Whether the fallback (ContentLoading) has mounted at least once. Until it
802
+ // has, we force it to mount even when the code is already ready, so its
803
+ // `useCodeFallback` effect can hoist the root fallback (the DEFLATE
804
+ // dictionary needed to decompress `hastCompressed`). Without this, a
805
+ // server-rendered, fully-loaded highlighter would skip the fallback branch
806
+ // entirely and Content could never decode the compressed HAST.
807
+ const [fallbackMounted, setFallbackMounted] = React.useState(false);
808
+ const handleHookCalled = React.useCallback(() => {
809
+ hookCalledRef.current = true;
810
+ setFallbackMounted(true);
811
+ }, []);
812
+
813
+ // Stable callback for ContentLoading to hoist its fallbacks.
814
+ const handleSetFallbackHasts = React.useCallback((variant, hasts) => {
815
+ setHoistedFallbackHasts(prev => {
816
+ if (prev[variant] === hasts) {
817
+ return prev;
818
+ }
819
+ return {
820
+ ...prev,
821
+ [variant]: hasts
822
+ };
823
+ });
824
+ }, []);
780
825
  const {
781
826
  fallbackPending
782
827
  } = useInitialData({
@@ -791,9 +836,31 @@ export function CodeHighlighterClient(props) {
791
836
  fallbackUsesAllVariants,
792
837
  isControlled,
793
838
  globalsCode: props.globalsCode,
794
- setProcessedGlobalsCode
839
+ setProcessedGlobalsCode,
840
+ handleSetFallbackHasts
795
841
  });
796
842
 
843
+ // Reverse the server-side residual consolidation. The blob is primed with the
844
+ // rendered subset's text, which only reaches the client via the hoist — so we
845
+ // wait for `hoistedFallbackHasts[variantName]` (the rendered initial variant,
846
+ // hoisted atomically) before decompressing. Residual files are hidden until a
847
+ // post-hoist swap, so deferring costs nothing; if the hoist never arrives the
848
+ // existing fallback-hoist check throws anyway.
849
+ const residualFallbacks = props.residualFallbacks;
850
+ const renderedHoist = hoistedFallbackHasts[variantName];
851
+ const residualMap = React.useMemo(() => {
852
+ if (!residualFallbacks || !renderedHoist) {
853
+ return undefined;
854
+ }
855
+ return decompressResidualFallbacks(residualFallbacks, residualDictionaryText(hoistedFallbackHasts));
856
+ }, [residualFallbacks, renderedHoist, hoistedFallbackHasts]);
857
+
858
+ // Scatter the residual back onto whichever code carries it. Memoized so the
859
+ // freshly-cloned code keeps a stable identity until the residual or its base
860
+ // changes (the downstream merges/parses are keyed on it).
861
+ const resolvedPropsCode = React.useMemo(() => props.code && residualMap ? scatterResidualFallbacks(props.code, residualMap) : props.code, [props.code, residualMap]);
862
+ const resolvedStateCode = React.useMemo(() => code && residualMap ? scatterResidualFallbacks(code, residualMap) : code, [code, residualMap]);
863
+
797
864
  // Use useSyncExternalStore to detect hydration
798
865
  const subscribe = React.useCallback(() => () => {}, []);
799
866
  const getSnapshot = React.useCallback(() => true, []);
@@ -843,6 +910,20 @@ export function CodeHighlighterClient(props) {
843
910
  const regularCode = props.code || code;
844
911
  return regularCode ? hasAllVariants(variants, regularCode) : false;
845
912
  }, [activeCode, isEnhanceAllowed, controlled?.code, variants, props.code, code]);
913
+
914
+ // Whether the fallback branch will actually mount this render. A fallback
915
+ // that exists but hasn't hoisted yet is forced to mount once (regardless of
916
+ // `activeCodeReady`) so `useCodeFallback` can hoist the root fallback.
917
+ const isFallbackRendered = !!props.fallback && !props.skipFallback && (!activeCodeReady || !fallbackMounted);
918
+
919
+ // Validate that ContentLoading calls useCodeFallback(props).
920
+ // Child effects fire before parent effects, so hookCalledRef is
921
+ // guaranteed to be set by the time this effect runs.
922
+ React.useEffect(() => {
923
+ if (isFallbackRendered && !hookCalledRef.current) {
924
+ throw new Errors.ErrorCodeHighlighterClientMissingFallbackHoist();
925
+ }
926
+ }, [isFallbackRendered]);
846
927
  useAllVariants({
847
928
  readyForContent,
848
929
  variants,
@@ -859,7 +940,7 @@ export function CodeHighlighterClient(props) {
859
940
  // Merge globalsCode with internal state code (fetched data) - this should be stable once ready
860
941
  const stateCodeWithGlobals = useGlobalsCodeMerging({
861
942
  url,
862
- code,
943
+ code: resolvedStateCode,
863
944
  // Only use internal state, not props.code
864
945
  globalsCode: props.globalsCode,
865
946
  processedGlobalsCode,
@@ -870,7 +951,7 @@ export function CodeHighlighterClient(props) {
870
951
 
871
952
  // For props.code (controlled), always re-merge when it changes (don't cache in state)
872
953
  const propsCodeWithGlobals = usePropsCodeGlobalsMerging({
873
- code: props.code,
954
+ code: resolvedPropsCode,
874
955
  globalsCode: props.globalsCode,
875
956
  processedGlobalsCode,
876
957
  variants
@@ -912,6 +993,14 @@ export function CodeHighlighterClient(props) {
912
993
  // their stored-preference resolution doesn't pay the deltas latency.
913
994
  const deferHighlight = deferHighlightForParsing || availableTransforms.length > 0 && waitingForTransformedCode;
914
995
 
996
+ // Declare this block to the page-wide layout-shift gate. While any block is
997
+ // still mid-swap, `useCoordinated` holds its layout-shifting commits, so the
998
+ // first transform/variant change lands as one unified update instead of a
999
+ // cascade as blocks swap in at staggered idle times. Settled once the block
1000
+ // has finished its initial fallback→content swap (no longer rendering the
1001
+ // fallback, and not mid-highlight); released on unmount.
1002
+ useCoordinatedLazy(!isFallbackRendered && !deferHighlight);
1003
+
915
1004
  // Per-highlighter pre-parsed HAST cache. Lives in a ref so the same Map
916
1005
  // instance is shared across renders without becoming a React dep. The
917
1006
  // editable populates it via `useSourceEditing` (which reads it from
@@ -931,8 +1020,29 @@ export function CodeHighlighterClient(props) {
931
1020
  const overlaidCode = parsedControlledCode || transformedCode || codeWithGlobals;
932
1021
 
933
1022
  // For fallback context, use the processed code or fall back to non-controlled code
934
- const codeForFallback = overlaidCode || (controlled?.code ? undefined : props.code || code);
935
- const fallbackContext = React.useMemo(() => activeCodeReady ? undefined : codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants), [activeCodeReady, variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants]);
1023
+ const codeForFallback = overlaidCode || (controlled?.code ? undefined : resolvedPropsCode || resolvedStateCode);
1024
+
1025
+ // Resolve the active variant's fallbacks from the two places one can cross
1026
+ // the server→client boundary: the hoisted copy (from a `ContentLoading`
1027
+ // component, which had it stripped off `Code`) and the variant's own
1028
+ // `fallback` field on `Code` (present without a `ContentLoading`, or scattered
1029
+ // back from the residual blob). For most files only one is populated. When
1030
+ // both are — a `fallbackCollapsed` block hoists the *visible* window but
1031
+ // scatters the *full* fallback onto `Code` — the `Code` copy must win, since
1032
+ // the full text is the DEFLATE dictionary `hastCompressed` needs. So merge
1033
+ // with the derived (`Code`) copy taking precedence.
1034
+ const activeFallbacks = React.useMemo(() => {
1035
+ const merged = {
1036
+ ...hoistedFallbackHasts[variantName],
1037
+ ...deriveFallbacksFromCode(codeForFallback, variantName)
1038
+ };
1039
+ return Object.keys(merged).length > 0 ? merged : undefined;
1040
+ }, [hoistedFallbackHasts, variantName, codeForFallback]);
1041
+ const fallbackContext = React.useMemo(() => ({
1042
+ extraVariants: codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants).extraVariants,
1043
+ setFallbackHasts: handleSetFallbackHasts,
1044
+ onHookCalled: handleHookCalled
1045
+ }), [variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants, handleSetFallbackHasts, handleHookCalled]);
936
1046
  const context = React.useMemo(() => ({
937
1047
  code: overlaidCode,
938
1048
  // Use processed/transformed code
@@ -945,14 +1055,21 @@ export function CodeHighlighterClient(props) {
945
1055
  availableTransforms: controlled?.code ? [] : availableTransforms,
946
1056
  url: props.url,
947
1057
  deferHighlight,
1058
+ fallbacks: activeFallbacks,
948
1059
  highlightReady,
949
1060
  highlightAfter,
950
1061
  preParsedCache
951
- }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, controlled?.code, availableTransforms, props.url, deferHighlight, highlightReady, highlightAfter, preParsedCache]);
1062
+ }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, controlled?.code, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, preParsedCache]);
952
1063
  if (!props.variants && !props.components && !activeCode) {
953
1064
  throw new Errors.ErrorCodeHighlighterClientMissingData();
954
1065
  }
955
1066
 
1067
+ // Reset only when fallback is actually rendered so the flag isn't sticky across cycles.
1068
+ // The child's effect will set it again if useCodeFallback(props) is called.
1069
+ if (isFallbackRendered) {
1070
+ hookCalledRef.current = false;
1071
+ }
1072
+
956
1073
  // If this CodeHighlighter is nested inside another CodeHighlighter that is
957
1074
  // currently rendering its fallback, hold our own fallback->full transition
958
1075
  // until the outer one swaps. Otherwise, when the outer swaps from its
@@ -964,7 +1081,7 @@ export function CodeHighlighterClient(props) {
964
1081
  const outerFallbackContext = React.useContext(CodeHighlighterFallbackContext);
965
1082
  const isNestedInsideOuterFallback = outerFallbackContext !== undefined;
966
1083
  const fallback = props.fallback;
967
- if (fallback && !props.skipFallback && (!activeCodeReady || isNestedInsideOuterFallback)) {
1084
+ if (fallback && !props.skipFallback && (!activeCodeReady || isNestedInsideOuterFallback || !fallbackMounted)) {
968
1085
  return /*#__PURE__*/_jsx(CodeHighlighterFallbackContext.Provider, {
969
1086
  value: fallbackContext,
970
1087
  children: fallback
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type Code, type ControlledCode, type HastRoot } from "./types.mjs";
2
+ import { type Code, type ControlledCode, type Fallbacks, type HastRoot } from "./types.mjs";
3
3
  import { type Selection } from "../CodeControllerContext/index.mjs";
4
4
  /**
5
5
  * One cached pre-parsed file. Stored per-fileName: each new write replaces
@@ -21,6 +21,13 @@ export interface CodeHighlighterContextType {
21
21
  availableTransforms?: string[];
22
22
  url?: string;
23
23
  deferHighlight?: boolean;
24
+ /**
25
+ /**
26
+ * Compact fallback data for the active variant, keyed by fileName.
27
+ * Used by `Pre` to both render the fallback and derive text dictionaries
28
+ * for decompressing `hastCompressed` payloads.
29
+ */
30
+ fallbacks?: Fallbacks;
24
31
  /**
25
32
  * Render-side readiness gate. `true` once the highlight trigger
26
33
  * (`init` / `hydration` / `idle` / `visible`) has fired *and* the
@@ -1,7 +1,19 @@
1
1
  import * as React from 'react';
2
- import { type ContentLoadingVariant } from "./types.mjs";
3
- export interface CodeHighlighterFallbackContext extends ContentLoadingVariant {
2
+ import type { Fallbacks, ContentLoadingVariant } from "./types.mjs";
3
+ export interface CodeHighlighterFallbackContext {
4
4
  extraVariants?: Record<string, ContentLoadingVariant>;
5
+ /**
6
+ * Callback used by `useCodeFallback` to hoist fallback data
7
+ * back to `CodeHighlighterClient` so it can derive text dictionaries
8
+ * for decompressing `hastCompressed` payloads.
9
+ */
10
+ setFallbackHasts?: (variantName: string, hasts: Fallbacks) => void;
11
+ /**
12
+ * Callback invoked by `useCodeFallback` in an effect to signal that
13
+ * the hook was used. Allows the parent to detect when a ContentLoading
14
+ * component forgets to call the hook.
15
+ */
16
+ onHookCalled?: () => void;
5
17
  }
6
18
  export declare const CodeHighlighterFallbackContext: React.Context<CodeHighlighterFallbackContext | undefined>;
7
19
  export declare function useCodeHighlighterFallbackContext(): CodeHighlighterFallbackContext;
@@ -9,6 +9,4 @@ export function useCodeHighlighterFallbackContext() {
9
9
  throw new Error('CodeHighlighterFallbackContext is missing. `useCodeHighlighterFallbackContext` must be used within a `CodeHighlighter` component.');
10
10
  }
11
11
  return context;
12
- }
13
-
14
- // TODO: rename to ContentMinimal
12
+ }
@@ -1,2 +1,28 @@
1
- import { type BaseContentLoadingProps, type Code } from "./types.mjs";
2
- export declare function codeToFallbackProps(variant: string, code?: Code, fileName?: string, needsAllFiles?: boolean, needsAllVariants?: boolean): BaseContentLoadingProps;
1
+ import type { BaseContentLoadingProps, Code, Fallbacks } from "./types.mjs";
2
+ export declare function codeToFallbackProps(variant: string, code?: Code, _fileName?: string, _needsAllFiles?: boolean, needsAllVariants?: boolean, allFallbackHasts?: Record<string, Fallbacks>): BaseContentLoadingProps;
3
+ /**
4
+ * Read a variant's per-file fallbacks straight off its `VariantCode` `fallback`
5
+ * fields (main + extra files), returning a `Fallbacks` map keyed by file name.
6
+ *
7
+ * The fallback crosses the server→client boundary exactly once: either on the
8
+ * `VariantCode` (no `ContentLoading`) or — after `stripFallbackHastsFromCode`
9
+ * moves it — on the `ContentLoading` props. This reads the former location, so
10
+ * the client can resolve the DEFLATE dictionary for `hastCompressed` without a
11
+ * hoist when there's no `ContentLoading`. Returns `undefined` when the variant
12
+ * carries no fallback (a string variant, a live-HAST source, or one whose
13
+ * fallbacks were stripped for a `ContentLoading` component) — in which case the
14
+ * hoisted copy is used instead.
15
+ */
16
+ export declare function deriveFallbacksFromCode(code: Code | undefined, variantName: string): Fallbacks | undefined;
17
+ /**
18
+ * Strip `fallback` entries from a `Code` object and return the
19
+ * stripped Code alongside the extracted fallbacks grouped by variant → fileName.
20
+ *
21
+ * Used on the server to separate the fallback data from the Code
22
+ * so Code is sent to CodeHighlighterClient without fallbacks, and
23
+ * the data is passed to ContentLoading as source/extraSource props.
24
+ */
25
+ export declare function stripFallbackHastsFromCode(code: Code | undefined, variantName: string, fallbackUsesExtraFiles?: boolean, fallbackUsesAllVariants?: boolean): {
26
+ strippedCode: Code;
27
+ allFallbackHasts: Record<string, Fallbacks>;
28
+ };