@mui/internal-docs-infra 0.11.1-canary.11 → 0.11.1-canary.12
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.
- package/CodeHighlighter/CodeHighlighter.mjs +6 -5
- package/CodeHighlighter/CodeHighlighterClient.mjs +141 -25
- package/CodeHighlighter/CodeHighlighterContext.d.mts +27 -0
- package/CodeHighlighter/codeToFallbackProps.mjs +23 -2
- package/CodeHighlighter/index.d.mts +2 -1
- package/CodeHighlighter/index.mjs +2 -1
- package/CodeHighlighter/mergeComments.d.mts +38 -0
- package/CodeHighlighter/mergeComments.mjs +80 -0
- package/CodeHighlighter/types.d.mts +127 -3
- package/CodeProvider/CodeContext.d.mts +0 -3
- package/CodeProvider/CodeProvider.mjs +2 -3
- package/LICENSE +1 -1
- package/package.json +13 -3
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +19 -0
- package/pipeline/hastUtils/hast.d.mts +27 -0
- package/pipeline/hastUtils/stripHighlightingSpans.d.mts +6 -2
- package/pipeline/hastUtils/stripHighlightingSpans.mjs +26 -9
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.d.mts +52 -6
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.mjs +305 -23
- package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.d.mts +5 -5
- package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.mjs +36 -66
- package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.d.mts +15 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.mjs +51 -0
- package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +26 -2
- package/pipeline/loadIsomorphicCodeVariant/diffHast.mjs +474 -19
- package/pipeline/loadIsomorphicCodeVariant/embedTransforms.d.mts +49 -0
- package/pipeline/loadIsomorphicCodeVariant/embedTransforms.mjs +152 -0
- package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.d.mts +51 -0
- package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.mjs +161 -0
- package/pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.d.mts +12 -0
- package/pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.mjs +44 -0
- package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.d.mts +16 -0
- package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.mjs +71 -0
- package/pipeline/loadIsomorphicCodeVariant/loadIsomorphicCodeVariant.mjs +45 -4
- package/pipeline/loadIsomorphicCodeVariant/transformSource.d.mts +2 -2
- package/pipeline/loadIsomorphicCodeVariant/transformSource.mjs +59 -22
- package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +8 -0
- package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.mjs +11 -7
- package/pipeline/parseSource/addLineGutters.mjs +27 -6
- package/pipeline/parseSource/restructureFrames.mjs +103 -14
- package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.mjs +146 -113
- package/pipeline/transformTypescriptToJavascript/removeTypes.d.mts +5 -8
- package/pipeline/transformTypescriptToJavascript/removeTypes.mjs +27 -93
- package/useCode/Pre.d.mts +90 -1
- package/useCode/Pre.mjs +286 -125
- package/useCode/sourceLineCounts.d.mts +79 -0
- package/useCode/sourceLineCounts.mjs +266 -0
- package/useCode/subscribeToggleNudge.d.mts +3 -0
- package/useCode/subscribeToggleNudge.mjs +95 -0
- package/useCode/useCode.d.mts +121 -0
- package/useCode/useCode.mjs +203 -14
- package/useCode/useCodeUtils.d.mts +141 -5
- package/useCode/useCodeUtils.mjs +339 -91
- package/useCode/useEditable.mjs +10 -0
- package/useCode/useFileNavigation.d.mts +64 -2
- package/useCode/useFileNavigation.mjs +106 -29
- package/useCode/useHighlightGate.d.mts +17 -0
- package/useCode/useHighlightGate.mjs +147 -0
- package/useCode/useSourceEnhancing.mjs +9 -32
- package/useCode/useTransformManagement.d.mts +88 -1
- package/useCode/useTransformManagement.mjs +405 -28
- package/useCode/useTransitionPhase.d.mts +24 -0
- package/useCode/useTransitionPhase.mjs +49 -0
- package/useCode/useVariantSelection.d.mts +130 -6
- package/useCode/useVariantSelection.mjs +515 -93
- package/useCoordinated/coordinatePreference.d.mts +439 -0
- package/useCoordinated/coordinatePreference.mjs +972 -0
- package/useCoordinated/coordinatePreference.testUtils.d.mts +21 -0
- package/useCoordinated/coordinatePreference.testUtils.mjs +69 -0
- package/useCoordinated/index.d.mts +3 -0
- package/useCoordinated/index.mjs +3 -0
- package/useCoordinated/useCoordinated.d.mts +193 -0
- package/useCoordinated/useCoordinated.mjs +403 -0
- package/useCoordinated/useCoordinatedLocalStorage.d.mts +16 -0
- package/useCoordinated/useCoordinatedLocalStorage.mjs +22 -0
- package/useCoordinated/useCoordinatedPreference.d.mts +20 -0
- package/useCoordinated/useCoordinatedPreference.mjs +26 -0
- package/useDemo/useDemo.d.mts +4 -2
- package/withDocsInfra/withDocsInfra.d.mts +8 -0
- package/withDocsInfra/withDocsInfra.mjs +6 -2
|
@@ -23,13 +23,13 @@ function createClientProps(props) {
|
|
|
23
23
|
// input).
|
|
24
24
|
const url = props.urlPrefix && props.url ? replaceUrlPrefix(props.url, props.urlPrefix) : props.url;
|
|
25
25
|
const contentProps = {
|
|
26
|
+
...props.contentProps,
|
|
26
27
|
code: props.code || props.precompute,
|
|
27
28
|
components: props.components,
|
|
28
29
|
name: props.name,
|
|
29
30
|
slug: props.slug,
|
|
30
31
|
url,
|
|
31
|
-
variantType: props.variantType
|
|
32
|
-
...props.contentProps
|
|
32
|
+
variantType: props.variantType
|
|
33
33
|
};
|
|
34
34
|
return {
|
|
35
35
|
url,
|
|
@@ -213,14 +213,15 @@ function renderWithInitialSource(props) {
|
|
|
213
213
|
// Only include components (plural) if we're also including extraVariants
|
|
214
214
|
const components = fallbackProps.extraVariants ? props.components : undefined;
|
|
215
215
|
const contentProps = {
|
|
216
|
+
...props.contentProps,
|
|
217
|
+
...fallbackProps,
|
|
216
218
|
name,
|
|
217
219
|
slug,
|
|
218
220
|
url,
|
|
219
221
|
initialFilename,
|
|
222
|
+
initialVariant,
|
|
220
223
|
component,
|
|
221
|
-
components
|
|
222
|
-
...fallbackProps,
|
|
223
|
-
...props.contentProps
|
|
224
|
+
components
|
|
224
225
|
};
|
|
225
226
|
const fallback = /*#__PURE__*/_jsx(ContentLoading, {
|
|
226
227
|
...contentProps
|
|
@@ -9,6 +9,7 @@ import { CodeHighlighterFallbackContext } from "./CodeHighlighterFallbackContext
|
|
|
9
9
|
import { useControlledCode } from "../CodeControllerContext/index.mjs";
|
|
10
10
|
import { codeToFallbackProps } from "./codeToFallbackProps.mjs";
|
|
11
11
|
import { mergeCodeMetadata } from "../pipeline/loadIsomorphicCodeVariant/mergeCodeMetadata.mjs";
|
|
12
|
+
import { getAvailableTransforms } from "../pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.mjs";
|
|
12
13
|
import * as Errors from "./errors.mjs";
|
|
13
14
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
15
|
const DEBUG = false; // Set to true for debugging purposes
|
|
@@ -82,7 +83,7 @@ function useInitialData({
|
|
|
82
83
|
variants,
|
|
83
84
|
globalsCode // Let loadCodeFallback handle processing
|
|
84
85
|
}).catch(error => ({
|
|
85
|
-
error
|
|
86
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
86
87
|
}));
|
|
87
88
|
if ('error' in loaded) {
|
|
88
89
|
console.error(new Errors.ErrorCodeHighlighterClientLoadFallbackFailure(loaded.error));
|
|
@@ -210,7 +211,7 @@ function useAllVariants({
|
|
|
210
211
|
name,
|
|
211
212
|
variant
|
|
212
213
|
})).catch(error => ({
|
|
213
|
-
error
|
|
214
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
214
215
|
}));
|
|
215
216
|
}));
|
|
216
217
|
const resultCode = {};
|
|
@@ -237,8 +238,9 @@ function useAllVariants({
|
|
|
237
238
|
};
|
|
238
239
|
}
|
|
239
240
|
function yieldToMain() {
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
const scheduler = globalThis.scheduler;
|
|
242
|
+
if (scheduler?.yield) {
|
|
243
|
+
return scheduler.yield();
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
// Fall back to yielding with setTimeout.
|
|
@@ -289,9 +291,16 @@ function useCodeParsing({
|
|
|
289
291
|
return isHighlightAllowed;
|
|
290
292
|
}, [readyForContent, isHighlightAllowed]);
|
|
291
293
|
|
|
294
|
+
// Memoize the "every variant is already in HAST form" check so it
|
|
295
|
+
// doesn't re-walk the variant + extraFiles trees on every render.
|
|
296
|
+
// Used both as the short-circuit inside the `parseCode` memo (fully-
|
|
297
|
+
// precomputed sites skip parsing entirely) and as the unmemoized
|
|
298
|
+
// `waitingForParsedCode` gate just below.
|
|
299
|
+
const allVariantsAlreadyHighlighted = React.useMemo(() => code ? hasAllVariants(Object.keys(code), code, true) : false, [code]);
|
|
300
|
+
|
|
292
301
|
// Parse the internal code state when ready and timing conditions are met
|
|
293
302
|
const parsedCode = React.useMemo(() => {
|
|
294
|
-
if (!code || !shouldHighlight ||
|
|
303
|
+
if (!code || !shouldHighlight || allVariantsAlreadyHighlighted) {
|
|
295
304
|
return undefined;
|
|
296
305
|
}
|
|
297
306
|
if (!parseSource) {
|
|
@@ -317,36 +326,80 @@ function useCodeParsing({
|
|
|
317
326
|
return undefined;
|
|
318
327
|
}
|
|
319
328
|
return parseCode(code, parseSource);
|
|
320
|
-
}, [code, shouldHighlight, sourceParser, parseSource, parseCode, forceClient, url]);
|
|
321
|
-
|
|
329
|
+
}, [code, shouldHighlight, allVariantsAlreadyHighlighted, sourceParser, parseSource, parseCode, forceClient, url]);
|
|
330
|
+
|
|
331
|
+
// Keep highlighting deferred until parsed HAST is actually available for the
|
|
332
|
+
// variants that need it. `shouldHighlight` can flip true ~30ms after
|
|
333
|
+
// hydration, but `parseCode` only runs once the async `sourceParser` promise
|
|
334
|
+
// resolves. Without this wait, downstream consumers (e.g. the transform
|
|
335
|
+
// swap) would commit while the visible variant is still rendered from its
|
|
336
|
+
// raw string source, producing a structure swap on the DOM moments later.
|
|
337
|
+
const waitingForParsedCode = shouldHighlight && !!code && !allVariantsAlreadyHighlighted && !parsedCode;
|
|
338
|
+
|
|
339
|
+
// Only signal `deferHighlight` while a highlight pass is actively in
|
|
340
|
+
// flight. When `shouldHighlight` is `false` (e.g. `highlightAt: 'idle'`
|
|
341
|
+
// before the idle window fires, or `'view'` before the block scrolls
|
|
342
|
+
// into view) we render the un-highlighted source as-is — downstream
|
|
343
|
+
// consumers like `useTransformManagement`'s `awaitHighlight` gate must
|
|
344
|
+
// commit eagerly against that source instead of blocking the barrier
|
|
345
|
+
// indefinitely. Once the trigger fires, `shouldHighlight` flips true,
|
|
346
|
+
// `waitingForParsedCode` becomes true while `parseCode` runs, and
|
|
347
|
+
// `deferHighlight` engages for the brief window before the next
|
|
348
|
+
// commit paints the highlighted tree.
|
|
349
|
+
const deferHighlight = waitingForParsedCode;
|
|
350
|
+
|
|
351
|
+
// Render-side readiness gate. `<Pre>` (via `useCode.shouldHighlight`)
|
|
352
|
+
// needs to know whether the published `code` should be rendered as
|
|
353
|
+
// highlighted HAST *now*. That answer is false in two distinct
|
|
354
|
+
// windows that `deferHighlight` deliberately collapses out:
|
|
355
|
+
// 1. The trigger for `highlightAt: 'hydration' | 'idle' | 'visible'`
|
|
356
|
+
// hasn't fired yet — `shouldHighlight` is still false. The
|
|
357
|
+
// precomputed `codeWithGlobals` already contains HAST, so
|
|
358
|
+
// without a render-side gate `<Pre>` would render highlighted
|
|
359
|
+
// spans on the SSR pass and on first client paint, defeating
|
|
360
|
+
// the whole point of deferred highlighting.
|
|
361
|
+
// 2. The trigger has fired (`shouldHighlight = true`) but
|
|
362
|
+
// `parseCode` hasn't resolved yet (`waitingForParsedCode`).
|
|
363
|
+
// Rendering would briefly flash un-highlighted text against
|
|
364
|
+
// the same tree position before the highlighted HAST lands.
|
|
365
|
+
//
|
|
366
|
+
// `highlightReady` is the inverse of the pre-`e7cc08b7` wide
|
|
367
|
+
// `deferHighlight` semantic, exposed separately so the narrow
|
|
368
|
+
// `deferHighlight` (barrier consumers only block on real in-flight
|
|
369
|
+
// work) and the render gate can diverge without coupling.
|
|
370
|
+
const highlightReady = shouldHighlight && !waitingForParsedCode;
|
|
322
371
|
return {
|
|
323
372
|
parsedCode,
|
|
324
|
-
deferHighlight
|
|
373
|
+
deferHighlight,
|
|
374
|
+
highlightReady
|
|
325
375
|
};
|
|
326
376
|
}
|
|
327
377
|
function useCodeTransforms({
|
|
328
378
|
parsedCode,
|
|
379
|
+
loadedCode,
|
|
329
380
|
variantName
|
|
330
381
|
}) {
|
|
331
382
|
const {
|
|
332
383
|
sourceParser,
|
|
333
|
-
getAvailableTransforms,
|
|
334
384
|
computeHastDeltas
|
|
335
385
|
} = useCodeContext();
|
|
336
|
-
|
|
386
|
+
// Track which `parsedCode` the cached `transformedCode` was computed from
|
|
387
|
+
// so a fresh `parsedCode` (e.g. a newly-loaded variant being added to the
|
|
388
|
+
// map) re-engages `waitingForTransformedCode` instead of returning the
|
|
389
|
+
// stale output for one render cycle. Storing input + output together lets
|
|
390
|
+
// callers detect staleness with reference equality.
|
|
391
|
+
const [transformedState, setTransformedState] = React.useState({});
|
|
337
392
|
|
|
338
393
|
// Get available transforms from the current variant (separate memo for efficiency)
|
|
339
|
-
const availableTransforms = React.useMemo(() =>
|
|
340
|
-
if (!getAvailableTransforms) {
|
|
341
|
-
return [];
|
|
342
|
-
}
|
|
343
|
-
return getAvailableTransforms(parsedCode, variantName);
|
|
344
|
-
}, [parsedCode, variantName, getAvailableTransforms]);
|
|
394
|
+
const availableTransforms = React.useMemo(() => getAvailableTransforms(parsedCode ?? loadedCode, variantName), [parsedCode, loadedCode, variantName]);
|
|
345
395
|
|
|
346
396
|
// Effect to compute transformations for all variants
|
|
347
397
|
React.useEffect(() => {
|
|
348
398
|
if (!parsedCode || !sourceParser || !computeHastDeltas) {
|
|
349
|
-
|
|
399
|
+
setTransformedState({
|
|
400
|
+
input: parsedCode,
|
|
401
|
+
output: parsedCode
|
|
402
|
+
});
|
|
350
403
|
return;
|
|
351
404
|
}
|
|
352
405
|
|
|
@@ -355,16 +408,49 @@ function useCodeTransforms({
|
|
|
355
408
|
try {
|
|
356
409
|
const parseSource = await sourceParser;
|
|
357
410
|
const enhanced = await computeHastDeltas(parsedCode, parseSource);
|
|
358
|
-
|
|
411
|
+
setTransformedState({
|
|
412
|
+
input: parsedCode,
|
|
413
|
+
output: enhanced
|
|
414
|
+
});
|
|
359
415
|
} catch (error) {
|
|
360
416
|
console.error(new Errors.ErrorCodeHighlighterClientTransformProcessingFailure(error));
|
|
361
|
-
|
|
417
|
+
setTransformedState({
|
|
418
|
+
input: parsedCode,
|
|
419
|
+
output: parsedCode
|
|
420
|
+
});
|
|
362
421
|
}
|
|
363
422
|
})();
|
|
364
423
|
}, [parsedCode, sourceParser, computeHastDeltas]);
|
|
424
|
+
|
|
425
|
+
// Expose the cached output regardless of whether `parsedCode` changed since
|
|
426
|
+
// the last computation — falling back to `undefined` here would yank the
|
|
427
|
+
// currently-displayed HAST for a frame while the async pipeline catches up.
|
|
428
|
+
// Staleness is signalled via `waitingForTransformedCode` so downstream
|
|
429
|
+
// gates (e.g. `useTransformManagement` / `useVariantSelection`) hold off
|
|
430
|
+
// committing a swap until fresh deltas land.
|
|
431
|
+
const transformedCode = transformedState.output;
|
|
432
|
+
|
|
433
|
+
// Async hast-deltas pipeline status. While true, consumers (notably
|
|
434
|
+
// `useTransformManagement`'s `deferHighlight` gate) should treat
|
|
435
|
+
// highlighting as not-yet-settled and hold off committing a transform
|
|
436
|
+
// swap. Without this, the swap can commit after `parsedCode` is ready
|
|
437
|
+
// but *before* `computeHastDeltas` resolves: the incoming tree first
|
|
438
|
+
// renders without the transform deltas, then re-renders a frame or
|
|
439
|
+
// two later when `transformedCode` arrives, producing a visible jump
|
|
440
|
+
// on top of the just-played collapse animation.
|
|
441
|
+
//
|
|
442
|
+
// Only relevant when both a worker (`sourceParser`) and a deltas
|
|
443
|
+
// computer (`computeHastDeltas`) are wired up — environments without
|
|
444
|
+
// them resolve `transformedCode` synchronously to `parsedCode` in the
|
|
445
|
+
// effect above, so the deltas phase is a no-op. We compare the cached
|
|
446
|
+
// `input` against the live `parsedCode` instead of just checking
|
|
447
|
+
// `!transformedCode` so a freshly-arriving variant re-engages the wait
|
|
448
|
+
// until its deltas land.
|
|
449
|
+
const waitingForTransformedCode = !!parsedCode && !!sourceParser && !!computeHastDeltas && transformedState.input !== parsedCode;
|
|
365
450
|
return {
|
|
366
451
|
transformedCode,
|
|
367
|
-
availableTransforms
|
|
452
|
+
availableTransforms,
|
|
453
|
+
waitingForTransformedCode
|
|
368
454
|
};
|
|
369
455
|
}
|
|
370
456
|
function useControlledCodeParsing({
|
|
@@ -794,7 +880,8 @@ export function CodeHighlighterClient(props) {
|
|
|
794
880
|
const codeWithGlobals = propsCodeWithGlobals || stateCodeWithGlobals;
|
|
795
881
|
const {
|
|
796
882
|
parsedCode,
|
|
797
|
-
deferHighlight
|
|
883
|
+
deferHighlight: deferHighlightForParsing,
|
|
884
|
+
highlightReady
|
|
798
885
|
} = useCodeParsing({
|
|
799
886
|
code: codeWithGlobals,
|
|
800
887
|
readyForContent: readyForContent || Boolean(props.code),
|
|
@@ -805,12 +892,26 @@ export function CodeHighlighterClient(props) {
|
|
|
805
892
|
});
|
|
806
893
|
const {
|
|
807
894
|
transformedCode,
|
|
808
|
-
availableTransforms
|
|
895
|
+
availableTransforms,
|
|
896
|
+
waitingForTransformedCode
|
|
809
897
|
} = useCodeTransforms({
|
|
810
898
|
parsedCode,
|
|
899
|
+
loadedCode: codeWithGlobals,
|
|
811
900
|
variantName
|
|
812
901
|
});
|
|
813
902
|
|
|
903
|
+
// Combined highlight-readiness gate consumed via context (notably by
|
|
904
|
+
// `useTransformManagement`). Stay deferred while either the sync
|
|
905
|
+
// `parseCode` pass or the async `computeHastDeltas` pass is still in
|
|
906
|
+
// flight — committing a transform swap with `transformedCode` still
|
|
907
|
+
// pending causes the incoming pre to first render without the
|
|
908
|
+
// transform deltas and then re-flow a frame or two later when the
|
|
909
|
+
// deltas land, producing a visible jump on top of the collapse
|
|
910
|
+
// animation. The wait only matters for highlighters with at least one
|
|
911
|
+
// applicable transform; plain (variant-only) highlighters skip it so
|
|
912
|
+
// their stored-preference resolution doesn't pay the deltas latency.
|
|
913
|
+
const deferHighlight = deferHighlightForParsing || availableTransforms.length > 0 && waitingForTransformedCode;
|
|
914
|
+
|
|
814
915
|
// Per-highlighter pre-parsed HAST cache. Lives in a ref so the same Map
|
|
815
916
|
// instance is shared across renders without becoming a React dep. The
|
|
816
917
|
// editable populates it via `useSourceEditing` (which reads it from
|
|
@@ -839,16 +940,31 @@ export function CodeHighlighterClient(props) {
|
|
|
839
940
|
selection: controlled?.selection || selection,
|
|
840
941
|
setSelection: controlled?.setSelection || setSelection,
|
|
841
942
|
components: controlled?.components || props.components,
|
|
842
|
-
|
|
943
|
+
// Only suppress when an external CodeController owns the code; static
|
|
944
|
+
// `props.code` still needs the locally-computed list.
|
|
945
|
+
availableTransforms: controlled?.code ? [] : availableTransforms,
|
|
843
946
|
url: props.url,
|
|
844
947
|
deferHighlight,
|
|
948
|
+
highlightReady,
|
|
949
|
+
highlightAfter,
|
|
845
950
|
preParsedCache
|
|
846
|
-
}), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components,
|
|
951
|
+
}), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, controlled?.code, availableTransforms, props.url, deferHighlight, highlightReady, highlightAfter, preParsedCache]);
|
|
847
952
|
if (!props.variants && !props.components && !activeCode) {
|
|
848
953
|
throw new Errors.ErrorCodeHighlighterClientMissingData();
|
|
849
954
|
}
|
|
955
|
+
|
|
956
|
+
// If this CodeHighlighter is nested inside another CodeHighlighter that is
|
|
957
|
+
// currently rendering its fallback, hold our own fallback->full transition
|
|
958
|
+
// until the outer one swaps. Otherwise, when the outer swaps from its
|
|
959
|
+
// fallback element to its children element, our subtree unmounts and a fresh
|
|
960
|
+
// inner instance mounts and re-runs its own fallback->full transition,
|
|
961
|
+
// producing a visible "fallback -> full -> fallback -> full" flicker. By
|
|
962
|
+
// staying in fallback while nested, we collapse this to a single transition
|
|
963
|
+
// that happens after the outer is fully rendered.
|
|
964
|
+
const outerFallbackContext = React.useContext(CodeHighlighterFallbackContext);
|
|
965
|
+
const isNestedInsideOuterFallback = outerFallbackContext !== undefined;
|
|
850
966
|
const fallback = props.fallback;
|
|
851
|
-
if (fallback && !props.skipFallback && !activeCodeReady) {
|
|
967
|
+
if (fallback && !props.skipFallback && (!activeCodeReady || isNestedInsideOuterFallback)) {
|
|
852
968
|
return /*#__PURE__*/_jsx(CodeHighlighterFallbackContext.Provider, {
|
|
853
969
|
value: fallbackContext,
|
|
854
970
|
children: fallback
|
|
@@ -21,6 +21,33 @@ export interface CodeHighlighterContextType {
|
|
|
21
21
|
availableTransforms?: string[];
|
|
22
22
|
url?: string;
|
|
23
23
|
deferHighlight?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Render-side readiness gate. `true` once the highlight trigger
|
|
26
|
+
* (`init` / `hydration` / `idle` / `visible`) has fired *and* the
|
|
27
|
+
* sync `parseCode` pass has resolved, so consumers like `<Pre>`
|
|
28
|
+
* can render the published `code` as highlighted HAST. While
|
|
29
|
+
* `false` they should render the un-highlighted fallback (plain
|
|
30
|
+
* text) — the published `code` may still contain precomputed HAST
|
|
31
|
+
* left over from SSR, so without this gate non-`init` demos would
|
|
32
|
+
* render highlighted spans on the first paint and defeat the
|
|
33
|
+
* deferred-highlighting trigger.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from `deferHighlight`, which is the narrower
|
|
36
|
+
* "highlight pass is actively in flight" signal consumed by
|
|
37
|
+
* barrier gates (e.g. `useTransformManagement.awaitHighlight`)
|
|
38
|
+
* that must not block when no work is queued.
|
|
39
|
+
*/
|
|
40
|
+
highlightReady?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Echo of the `highlightAfter` prop on the surrounding
|
|
43
|
+
* `CodeHighlighter` / `CodeHighlighterClient`. Consumers such as
|
|
44
|
+
* `useCode` use this to skip transient highlighting-suppression
|
|
45
|
+
* gates that only matter when highlighting is asynchronous — in
|
|
46
|
+
* `'init'` mode the precomputed HAST already carries the highlight
|
|
47
|
+
* spans, so those gates would just cause a visible flash of
|
|
48
|
+
* unhighlighted content during variant swaps.
|
|
49
|
+
*/
|
|
50
|
+
highlightAfter?: 'init' | 'hydration' | 'idle';
|
|
24
51
|
/**
|
|
25
52
|
* Per-file pre-parsed HAST cache. Populated by `useSourceEditing` when the
|
|
26
53
|
* editable supplies a worker-parsed result alongside a source change, and
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { stringOrHastToJsx } from "../pipeline/hastUtils/index.mjs";
|
|
2
|
+
import { getLanguageFromExtension } from "../pipeline/loaderUtils/getLanguageFromExtension.mjs";
|
|
3
|
+
function getLanguageFromFileName(fileName) {
|
|
4
|
+
if (!fileName) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
8
|
+
if (dotIndex === -1) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return getLanguageFromExtension(fileName.slice(dotIndex));
|
|
12
|
+
}
|
|
2
13
|
function toExtraSource(variantCode, fileName) {
|
|
3
14
|
return Object.entries(variantCode.extraFiles || {}).reduce((acc, [name, file]) => {
|
|
4
15
|
if (name !== fileName && typeof file === 'object' && file?.source) {
|
|
5
|
-
acc[name] =
|
|
16
|
+
acc[name] = {
|
|
17
|
+
source: stringOrHastToJsx(file.source),
|
|
18
|
+
language: file.language ?? getLanguageFromFileName(name)
|
|
19
|
+
};
|
|
6
20
|
}
|
|
7
21
|
return acc;
|
|
8
22
|
}, {});
|
|
@@ -14,13 +28,16 @@ export function codeToFallbackProps(variant, code, fileName, needsAllFiles = fal
|
|
|
14
28
|
}
|
|
15
29
|
const fileNames = [variantCode.fileName, ...Object.keys(variantCode.extraFiles || {})].filter(name => Boolean(name));
|
|
16
30
|
let source;
|
|
31
|
+
let language;
|
|
17
32
|
if (fileName && fileName !== variantCode.fileName) {
|
|
18
33
|
const fileData = variantCode.extraFiles?.[fileName];
|
|
19
34
|
if (fileData && typeof fileData === 'object' && 'source' in fileData && fileData.source) {
|
|
20
35
|
source = stringOrHastToJsx(fileData.source);
|
|
36
|
+
language = fileData.language ?? getLanguageFromFileName(fileName);
|
|
21
37
|
}
|
|
22
38
|
} else if (variantCode.source) {
|
|
23
39
|
source = stringOrHastToJsx(variantCode.source);
|
|
40
|
+
language = variantCode.language ?? getLanguageFromFileName(variantCode.fileName);
|
|
24
41
|
}
|
|
25
42
|
if (needsAllVariants || needsAllFiles) {
|
|
26
43
|
const extraSource = toExtraSource(variantCode, fileName);
|
|
@@ -32,6 +49,7 @@ export function codeToFallbackProps(variant, code, fileName, needsAllFiles = fal
|
|
|
32
49
|
fileNames: [vCode.fileName, ...Object.keys(vCode.extraFiles || {})].filter(fn => Boolean(fn)),
|
|
33
50
|
// TODO: use filesOrder if provided
|
|
34
51
|
source: vCode.source && stringOrHastToJsx(vCode.source),
|
|
52
|
+
language: vCode.source ? vCode.language ?? getLanguageFromFileName(vCode.fileName) : undefined,
|
|
35
53
|
extraSource: extraVariantExtraSource
|
|
36
54
|
};
|
|
37
55
|
}
|
|
@@ -40,6 +58,7 @@ export function codeToFallbackProps(variant, code, fileName, needsAllFiles = fal
|
|
|
40
58
|
return {
|
|
41
59
|
fileNames,
|
|
42
60
|
source,
|
|
61
|
+
language,
|
|
43
62
|
extraSource,
|
|
44
63
|
extraVariants
|
|
45
64
|
};
|
|
@@ -47,11 +66,13 @@ export function codeToFallbackProps(variant, code, fileName, needsAllFiles = fal
|
|
|
47
66
|
return {
|
|
48
67
|
fileNames,
|
|
49
68
|
source,
|
|
69
|
+
language,
|
|
50
70
|
extraSource
|
|
51
71
|
};
|
|
52
72
|
}
|
|
53
73
|
return {
|
|
54
74
|
fileNames,
|
|
55
|
-
source
|
|
75
|
+
source,
|
|
76
|
+
language
|
|
56
77
|
};
|
|
57
78
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./CodeHighlighter.mjs";
|
|
1
|
+
export * from "./CodeHighlighter.mjs";
|
|
2
|
+
export { mergeComments } from "./mergeComments.mjs";
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./CodeHighlighter.mjs";
|
|
1
|
+
export * from "./CodeHighlighter.mjs";
|
|
2
|
+
export { mergeComments } from "./mergeComments.mjs";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SourceComments } from "./types.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Merges two `SourceComments` maps by concatenating entries per line.
|
|
4
|
+
*
|
|
5
|
+
* Both maps are keyed by line number. The function does not interpret
|
|
6
|
+
* keys — it only matches them by value — so 0-indexed and 1-indexed
|
|
7
|
+
* conventions are both supported, but **both inputs must use the same
|
|
8
|
+
* convention**. The repository's `SourceTransformer` contract supplies
|
|
9
|
+
* 1-indexed line numbers; if you build `mine` by hand, match the
|
|
10
|
+
* upstream indexing of `input` or your markers will land on the wrong
|
|
11
|
+
* lines.
|
|
12
|
+
*
|
|
13
|
+
* In non-production builds a heuristic dev warning is emitted when the
|
|
14
|
+
* two inputs look like they disagree about indexing (one contains a
|
|
15
|
+
* `0` key and the other does not). The check has no runtime cost in
|
|
16
|
+
* production builds.
|
|
17
|
+
*
|
|
18
|
+
* For any line present in either map, the resulting entry is
|
|
19
|
+
* `[...input[line] ?? [], ...mine[line] ?? []]` — `input` markers come
|
|
20
|
+
* first, the transformer's own markers (`mine`) are appended.
|
|
21
|
+
*
|
|
22
|
+
* Returns `undefined` when the merge would produce no entries (both
|
|
23
|
+
* inputs absent, both empty, or every per-line array empty). Otherwise
|
|
24
|
+
* returns a fresh object whose per-line arrays are also fresh copies,
|
|
25
|
+
* so callers may safely mutate the result without affecting either
|
|
26
|
+
* input.
|
|
27
|
+
*
|
|
28
|
+
* Intended to be called by `SourceTransformer` implementations that
|
|
29
|
+
* receive an upstream `comments` map as their 3rd argument and want to
|
|
30
|
+
* preserve those entries alongside the markers they themselves emit.
|
|
31
|
+
*
|
|
32
|
+
* @param input - Comments map received by the transformer (may be
|
|
33
|
+
* `undefined` when no upstream comments exist).
|
|
34
|
+
* @param mine - Comments map the transformer wants to emit (may be
|
|
35
|
+
* `undefined` when the transformer has none of its own). Must use
|
|
36
|
+
* the same line-indexing convention as `input`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mergeComments(input: SourceComments | undefined, mine: SourceComments | undefined): SourceComments | undefined;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges two `SourceComments` maps by concatenating entries per line.
|
|
3
|
+
*
|
|
4
|
+
* Both maps are keyed by line number. The function does not interpret
|
|
5
|
+
* keys — it only matches them by value — so 0-indexed and 1-indexed
|
|
6
|
+
* conventions are both supported, but **both inputs must use the same
|
|
7
|
+
* convention**. The repository's `SourceTransformer` contract supplies
|
|
8
|
+
* 1-indexed line numbers; if you build `mine` by hand, match the
|
|
9
|
+
* upstream indexing of `input` or your markers will land on the wrong
|
|
10
|
+
* lines.
|
|
11
|
+
*
|
|
12
|
+
* In non-production builds a heuristic dev warning is emitted when the
|
|
13
|
+
* two inputs look like they disagree about indexing (one contains a
|
|
14
|
+
* `0` key and the other does not). The check has no runtime cost in
|
|
15
|
+
* production builds.
|
|
16
|
+
*
|
|
17
|
+
* For any line present in either map, the resulting entry is
|
|
18
|
+
* `[...input[line] ?? [], ...mine[line] ?? []]` — `input` markers come
|
|
19
|
+
* first, the transformer's own markers (`mine`) are appended.
|
|
20
|
+
*
|
|
21
|
+
* Returns `undefined` when the merge would produce no entries (both
|
|
22
|
+
* inputs absent, both empty, or every per-line array empty). Otherwise
|
|
23
|
+
* returns a fresh object whose per-line arrays are also fresh copies,
|
|
24
|
+
* so callers may safely mutate the result without affecting either
|
|
25
|
+
* input.
|
|
26
|
+
*
|
|
27
|
+
* Intended to be called by `SourceTransformer` implementations that
|
|
28
|
+
* receive an upstream `comments` map as their 3rd argument and want to
|
|
29
|
+
* preserve those entries alongside the markers they themselves emit.
|
|
30
|
+
*
|
|
31
|
+
* @param input - Comments map received by the transformer (may be
|
|
32
|
+
* `undefined` when no upstream comments exist).
|
|
33
|
+
* @param mine - Comments map the transformer wants to emit (may be
|
|
34
|
+
* `undefined` when the transformer has none of its own). Must use
|
|
35
|
+
* the same line-indexing convention as `input`.
|
|
36
|
+
*/
|
|
37
|
+
export function mergeComments(input, mine) {
|
|
38
|
+
if (!input && !mine) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (process.env.NODE_ENV !== 'production' && input && mine && Object.keys(input).length > 0 && Object.keys(mine).length > 0) {
|
|
42
|
+
warnOnIndexingMismatch(input, mine);
|
|
43
|
+
}
|
|
44
|
+
const result = {};
|
|
45
|
+
const lines = new Set();
|
|
46
|
+
if (input) {
|
|
47
|
+
for (const key of Object.keys(input)) {
|
|
48
|
+
lines.add(Number(key));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (mine) {
|
|
52
|
+
for (const key of Object.keys(mine)) {
|
|
53
|
+
lines.add(Number(key));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let hasAny = false;
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const merged = [...(input?.[line] ?? []), ...(mine?.[line] ?? [])];
|
|
59
|
+
if (merged.length > 0) {
|
|
60
|
+
result[line] = merged;
|
|
61
|
+
hasAny = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return hasAny ? result : undefined;
|
|
65
|
+
}
|
|
66
|
+
function warnOnIndexingMismatch(input, mine) {
|
|
67
|
+
const inputHasZero = Object.prototype.hasOwnProperty.call(input, 0);
|
|
68
|
+
const mineHasZero = Object.prototype.hasOwnProperty.call(mine, 0);
|
|
69
|
+
if (inputHasZero === mineHasZero) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Only warn when the side without a `0` key has at least one entry
|
|
74
|
+
// — otherwise we can't tell whether it would have included one.
|
|
75
|
+
const other = inputHasZero ? mine : input;
|
|
76
|
+
if (Object.keys(other).length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.warn('mergeComments: inputs appear to use different line-indexing conventions ' + '(one contains a `0` key, the other does not). Both inputs must use the ' + 'same convention or markers will land on the wrong lines. The repository ' + "convention is 1-indexed; convert with `convertCommentsToOneIndexed` if you're emitting 0-indexed comments.");
|
|
80
|
+
}
|