@mui/internal-docs-infra 0.11.1-canary.9 → 0.11.1
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/ChunkProvider/ChunkContext.d.mts +10 -0
- package/ChunkProvider/ChunkContext.mjs +15 -0
- package/ChunkProvider/ChunkProvider.d.mts +14 -0
- package/ChunkProvider/ChunkProvider.mjs +38 -0
- package/ChunkProvider/PreloadContext.d.mts +14 -0
- package/ChunkProvider/PreloadContext.mjs +18 -0
- package/ChunkProvider/PreloadProvider.d.mts +13 -0
- package/ChunkProvider/PreloadProvider.mjs +33 -0
- package/ChunkProvider/index.d.mts +7 -0
- package/ChunkProvider/index.mjs +7 -0
- package/ChunkProvider/types.d.mts +23 -0
- package/ChunkProvider/types.mjs +1 -0
- package/ChunkProvider/usePreload.d.mts +8 -0
- package/ChunkProvider/usePreload.mjs +21 -0
- package/CodeControllerContext/CodeControllerContext.d.mts +11 -0
- package/CodeControllerContext/CodeControllerContext.mjs +2 -1
- package/CodeHighlighter/CodeHighlighter.d.mts +15 -1
- package/CodeHighlighter/CodeHighlighter.mjs +97 -319
- package/CodeHighlighter/CodeHighlighterChunk.d.mts +42 -0
- package/CodeHighlighter/CodeHighlighterChunk.mjs +77 -0
- package/CodeHighlighter/CodeHighlighterClient.mjs +597 -128
- package/CodeHighlighter/CodeHighlighterContext.d.mts +57 -1
- package/CodeHighlighter/CodeHighlighterFallbackContext.d.mts +14 -2
- package/CodeHighlighter/CodeHighlighterFallbackContext.mjs +1 -3
- package/CodeHighlighter/CodeInitialSourceLoader.d.mts +10 -0
- package/CodeHighlighter/CodeInitialSourceLoader.mjs +108 -0
- package/CodeHighlighter/CodeSourceLoader.d.mts +11 -0
- package/CodeHighlighter/CodeSourceLoader.mjs +128 -0
- package/CodeHighlighter/buildCodeHighlighterChunkProps.d.mts +47 -0
- package/CodeHighlighter/buildCodeHighlighterChunkProps.mjs +61 -0
- package/CodeHighlighter/buildStringFallback.d.mts +29 -0
- package/CodeHighlighter/buildStringFallback.mjs +42 -0
- package/CodeHighlighter/codeToFallbackProps.d.mts +31 -2
- package/CodeHighlighter/codeToFallbackProps.mjs +347 -42
- package/CodeHighlighter/createClientProps.d.mts +17 -0
- package/CodeHighlighter/createClientProps.mjs +78 -0
- package/CodeHighlighter/errors.d.mts +6 -0
- package/CodeHighlighter/errors.mjs +10 -0
- package/CodeHighlighter/fallbackCompression.d.mts +96 -0
- package/CodeHighlighter/fallbackCompression.mjs +253 -0
- package/CodeHighlighter/fallbackFormat.d.mts +137 -0
- package/CodeHighlighter/fallbackFormat.mjs +422 -0
- package/CodeHighlighter/index.d.mts +4 -1
- package/CodeHighlighter/index.mjs +3 -1
- package/CodeHighlighter/mergeComments.d.mts +38 -0
- package/CodeHighlighter/mergeComments.mjs +80 -0
- package/CodeHighlighter/prepareInitialSource.d.mts +42 -0
- package/CodeHighlighter/prepareInitialSource.mjs +292 -0
- package/CodeHighlighter/resolveFallbackCritical.d.mts +23 -0
- package/CodeHighlighter/resolveFallbackCritical.mjs +44 -0
- package/CodeHighlighter/types.d.mts +272 -8
- package/CodeHighlighter/useCodeFallback.d.mts +94 -0
- package/CodeHighlighter/useCodeFallback.mjs +204 -0
- package/CodeHighlighter/useGrammarsReady.d.mts +18 -0
- package/CodeHighlighter/useGrammarsReady.mjs +45 -0
- package/CodeHighlighter/useSpeculativeCodePreload.d.mts +26 -0
- package/CodeHighlighter/useSpeculativeCodePreload.mjs +40 -0
- package/CodeHighlighter/useSpeculativeEditingPreload.d.mts +33 -0
- package/CodeHighlighter/useSpeculativeEditingPreload.mjs +58 -0
- package/CodeHighlighter/useSpeculativeGrammarPreload.d.mts +23 -0
- package/CodeHighlighter/useSpeculativeGrammarPreload.mjs +31 -0
- package/CodeHighlighter/useSpeculativeUseCodePreload.d.mts +22 -0
- package/CodeHighlighter/useSpeculativeUseCodePreload.mjs +41 -0
- package/CodeProvider/CodeContext.d.mts +47 -12
- package/CodeProvider/CodeContext.mjs +7 -0
- package/CodeProvider/CodeProvider.d.mts +4 -2
- package/CodeProvider/CodeProvider.mjs +40 -102
- package/CodeProvider/CodeProviderLazy.d.mts +40 -0
- package/CodeProvider/CodeProviderLazy.mjs +96 -0
- package/CodeProvider/constants.d.mts +26 -0
- package/CodeProvider/constants.mjs +24 -0
- package/CodeProvider/createParseSourceWorkerClient.d.mts +6 -0
- package/CodeProvider/createParseSourceWorkerClient.mjs +22 -2
- package/CodeProvider/index.d.mts +2 -1
- package/CodeProvider/index.mjs +9 -1
- package/CodeProvider/parseSourceWorker.mjs +33 -0
- package/CodeProvider/useCodeProviderValue.d.mts +54 -0
- package/CodeProvider/useCodeProviderValue.mjs +188 -0
- package/CoordinatedLazy/ChunkServerLoader.d.mts +25 -0
- package/CoordinatedLazy/ChunkServerLoader.mjs +97 -0
- package/CoordinatedLazy/CoordinatedContentContext.d.mts +15 -0
- package/CoordinatedLazy/CoordinatedContentContext.mjs +22 -0
- package/CoordinatedLazy/CoordinatedFallbackContext.d.mts +11 -0
- package/CoordinatedLazy/CoordinatedFallbackContext.mjs +13 -0
- package/CoordinatedLazy/CoordinatedGateContext.d.mts +14 -0
- package/CoordinatedLazy/CoordinatedGateContext.mjs +19 -0
- package/CoordinatedLazy/CoordinatedLazy.d.mts +14 -0
- package/CoordinatedLazy/CoordinatedLazy.mjs +86 -0
- package/CoordinatedLazy/CoordinatedLazyClient.d.mts +24 -0
- package/CoordinatedLazy/CoordinatedLazyClient.mjs +65 -0
- package/CoordinatedLazy/LazyContent.d.mts +26 -0
- package/CoordinatedLazy/LazyContent.mjs +80 -0
- package/CoordinatedLazy/LazyContentServer.d.mts +18 -0
- package/CoordinatedLazy/LazyContentServer.mjs +25 -0
- package/CoordinatedLazy/buildChunkRenderInputs.d.mts +8 -0
- package/CoordinatedLazy/buildChunkRenderInputs.mjs +35 -0
- package/CoordinatedLazy/createCoordinatedLazy.d.mts +32 -0
- package/CoordinatedLazy/createCoordinatedLazy.mjs +127 -0
- package/CoordinatedLazy/index.d.mts +14 -0
- package/CoordinatedLazy/index.mjs +18 -0
- package/CoordinatedLazy/resolveChunkRender.d.mts +26 -0
- package/CoordinatedLazy/resolveChunkRender.mjs +73 -0
- package/CoordinatedLazy/types.d.mts +408 -0
- package/CoordinatedLazy/types.mjs +1 -0
- package/CoordinatedLazy/useChunk.d.mts +30 -0
- package/CoordinatedLazy/useChunk.mjs +135 -0
- package/CoordinatedLazy/useCoordinatedFallback.d.mts +12 -0
- package/CoordinatedLazy/useCoordinatedFallback.mjs +40 -0
- package/CoordinatedLazy/useCoordinatedSwap.d.mts +16 -0
- package/CoordinatedLazy/useCoordinatedSwap.mjs +124 -0
- package/LICENSE +1 -1
- package/abstractCreateDemo/abstractCreateDemo.d.mts +54 -3
- package/abstractCreateDemo/abstractCreateDemo.mjs +47 -7
- package/abstractCreateDemo/resolveDemoFlag.d.mts +20 -0
- package/abstractCreateDemo/resolveDemoFlag.mjs +25 -0
- package/abstractCreateStream/abstractCreateStream.d.mts +18 -0
- package/abstractCreateStream/abstractCreateStream.mjs +45 -0
- package/abstractCreateStream/index.d.mts +2 -0
- package/abstractCreateStream/index.mjs +1 -0
- package/abstractCreateStream/types.d.mts +34 -0
- package/abstractCreateStream/types.mjs +1 -0
- package/abstractCreateTypes/TypeCode.mjs +12 -11
- package/abstractCreateTypes/typesToJsx.mjs +30 -9
- package/cli/ensureDemoClients.mjs +4 -148
- package/cli/ensureDemoPages.d.mts +45 -0
- package/cli/ensureDemoPages.mjs +99 -0
- package/cli/fileUtils/index.d.mts +11 -0
- package/cli/fileUtils/index.mjs +48 -0
- package/cli/findDemoIndexFiles.d.mts +15 -0
- package/cli/findDemoIndexFiles.mjs +121 -0
- package/cli/index.mjs +1 -1
- package/cli/loadNextConfig.d.mts +25 -0
- package/cli/loadNextConfig.mjs +60 -1
- package/cli/runBrowser.mjs +1 -1
- package/cli/runValidate.mjs +44 -1
- package/package.json +84 -4
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +30 -0
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasisLazy.d.mts +17 -0
- package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasisLazy.mjs +52 -0
- package/pipeline/hastUtils/frameFallbackFromSpans.d.mts +18 -0
- package/pipeline/hastUtils/frameFallbackFromSpans.mjs +24 -0
- package/pipeline/hastUtils/hast.d.mts +27 -0
- package/pipeline/hastUtils/hastCompression.d.mts +3 -1
- package/pipeline/hastUtils/hastCompression.mjs +9 -1
- package/pipeline/hastUtils/hastDecompress.mjs +10 -4
- package/pipeline/hastUtils/hastDictionary.mjs +9 -0
- package/pipeline/hastUtils/hastUtils.d.mts +4 -3
- package/pipeline/hastUtils/hastUtils.mjs +24 -12
- package/pipeline/hastUtils/index.d.mts +2 -1
- package/pipeline/hastUtils/index.mjs +2 -1
- package/pipeline/hastUtils/stripHighlightingSpans.d.mts +6 -2
- package/pipeline/hastUtils/stripHighlightingSpans.mjs +22 -10
- package/pipeline/lintJavascriptDemoFocus/lintJavascriptDemoFocus.mjs +10 -7
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.d.mts +31 -13
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.mjs +50 -55
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransformWithComments.d.mts +78 -0
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransformWithComments.mjs +405 -0
- package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.d.mts +5 -5
- package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.mjs +36 -66
- package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.d.mts +23 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.mjs +92 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeSource.d.mts +19 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeSource.mjs +25 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeSourceToText.d.mts +17 -0
- package/pipeline/loadIsomorphicCodeVariant/decodeSourceToText.mjs +26 -0
- package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +26 -2
- package/pipeline/loadIsomorphicCodeVariant/diffHast.mjs +563 -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/flattenCodeVariant.mjs +6 -3
- 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 +74 -0
- package/pipeline/loadIsomorphicCodeVariant/loadCodeFallback.mjs +17 -5
- package/pipeline/loadIsomorphicCodeVariant/loadIsomorphicCodeVariant.mjs +229 -15
- package/pipeline/loadIsomorphicCodeVariant/transformSource.d.mts +2 -2
- package/pipeline/loadIsomorphicCodeVariant/transformSource.mjs +56 -22
- package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +18 -0
- package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.mjs +11 -7
- package/pipeline/loadServerTypes/hastTypeUtils.d.mts +2 -2
- package/pipeline/loadServerTypes/hastTypeUtils.mjs +4 -4
- package/pipeline/loadServerTypes/loadServerTypes.mjs +1 -1
- package/pipeline/loadServerTypesMeta/extractJSDocText.d.mts +14 -0
- package/pipeline/loadServerTypesMeta/extractJSDocText.mjs +60 -0
- package/pipeline/loadServerTypesMeta/processTypes.mjs +43 -46
- package/pipeline/loadServerTypesText/order.mjs +1 -1
- package/pipeline/loadServerTypesText/parseTypesMarkdown.mjs +3 -1
- package/pipeline/loaderUtils/index.d.mts +0 -1
- package/pipeline/loaderUtils/index.mjs +0 -1
- package/pipeline/loaderUtils/parseImportsAndComments.d.mts +5 -1
- package/pipeline/loaderUtils/parseImportsAndComments.mjs +19 -9
- package/pipeline/loaderUtils/resolveModulePath.mjs +23 -1
- package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.d.mts +12 -0
- package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.mjs +17 -13
- package/pipeline/parseSource/addLineGutters.mjs +45 -11
- package/pipeline/parseSource/calculateFrameRanges.d.mts +22 -0
- package/pipeline/parseSource/calculateFrameRanges.mjs +69 -25
- package/pipeline/parseSource/detectGrammarScopes.d.mts +13 -0
- package/pipeline/parseSource/detectGrammarScopes.mjs +35 -0
- package/pipeline/parseSource/extendSyntaxTokens.mjs +501 -43
- package/pipeline/parseSource/frameVisibility.d.mts +47 -0
- package/pipeline/parseSource/frameVisibility.mjs +114 -0
- package/pipeline/parseSource/grammarCache.d.mts +33 -0
- package/pipeline/parseSource/grammarCache.mjs +73 -0
- package/pipeline/parseSource/grammarLoaders.d.mts +14 -0
- package/pipeline/parseSource/grammarLoaders.mjs +24 -0
- package/pipeline/parseSource/grammarMaps.d.mts +21 -1
- package/pipeline/parseSource/grammarMaps.mjs +36 -0
- package/pipeline/parseSource/isFrameSpan.d.mts +19 -0
- package/pipeline/parseSource/isFrameSpan.mjs +24 -0
- package/pipeline/parseSource/parseSource.d.mts +41 -6
- package/pipeline/parseSource/parseSource.mjs +184 -36
- package/pipeline/parseSource/redistributeFrameFallbacks.d.mts +40 -0
- package/pipeline/parseSource/redistributeFrameFallbacks.mjs +138 -0
- package/pipeline/parseSource/restructureFrames.d.mts +5 -0
- package/pipeline/parseSource/restructureFrames.mjs +179 -16
- package/pipeline/syncPageIndex/metadataToMarkdown.mjs +6 -2
- package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.d.mts +26 -0
- package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.mjs +181 -114
- package/pipeline/transformHtmlCodeInline/removeSuffixFromHighlightedNodes.d.mts +12 -0
- package/pipeline/transformHtmlCodeInline/removeSuffixFromHighlightedNodes.mjs +52 -0
- package/pipeline/transformHtmlCodeInline/transformHtmlCodeInline.mjs +22 -1
- package/pipeline/transformTypescriptToJavascript/removeTypes.d.mts +5 -8
- package/pipeline/transformTypescriptToJavascript/removeTypes.mjs +27 -93
- package/useCode/EditableEngine.d.mts +233 -0
- package/useCode/EditableEngine.mjs +1712 -0
- package/useCode/EditingEngine.d.mts +13 -0
- package/useCode/EditingEngine.mjs +14 -0
- package/useCode/Pre.browser.mjs +5 -1
- package/useCode/Pre.d.mts +127 -1
- package/useCode/Pre.mjs +417 -165
- package/useCode/SourceEditingEngine.d.mts +50 -0
- package/useCode/SourceEditingEngine.mjs +461 -0
- package/useCode/TransformEngine.d.mts +39 -0
- package/useCode/TransformEngine.mjs +208 -0
- package/useCode/editingEngineCache.d.mts +29 -0
- package/useCode/editingEngineCache.mjs +68 -0
- package/useCode/sourceLineCounts.d.mts +80 -0
- package/useCode/sourceLineCounts.mjs +284 -0
- package/useCode/subscribeToggleNudge.d.mts +3 -0
- package/useCode/subscribeToggleNudge.mjs +95 -0
- package/useCode/transformEngineCache.d.mts +21 -0
- package/useCode/transformEngineCache.mjs +60 -0
- package/useCode/useCode.d.mts +140 -1
- package/useCode/useCode.mjs +250 -19
- package/useCode/useCodeUtils.d.mts +131 -20
- package/useCode/useCodeUtils.mjs +267 -194
- package/useCode/useCopyFunctionality.d.mts +13 -1
- package/useCode/useCopyFunctionality.mjs +39 -9
- package/useCode/useEditable.browser.mjs +10 -2
- package/useCode/useEditable.d.mts +27 -106
- package/useCode/useEditable.integration.browser.d.mts +1 -0
- package/useCode/useEditable.integration.browser.mjs +870 -0
- package/useCode/useEditable.mjs +198 -1247
- package/useCode/useEditableUtils.d.mts +50 -1
- package/useCode/useEditableUtils.mjs +29 -0
- package/useCode/useFileNavigation.d.mts +91 -3
- package/useCode/useFileNavigation.mjs +201 -41
- package/useCode/useHighlightGate.d.mts +17 -0
- package/useCode/useHighlightGate.mjs +147 -0
- package/useCode/useSourceEditing.d.mts +8 -0
- package/useCode/useSourceEditing.mjs +158 -314
- package/useCode/useSourceEnhancing.d.mts +5 -1
- package/useCode/useSourceEnhancing.mjs +22 -36
- package/useCode/useTransformManagement.d.mts +93 -5
- package/useCode/useTransformManagement.mjs +496 -28
- package/useCode/useTransitionPhase.d.mts +24 -0
- package/useCode/useTransitionPhase.mjs +49 -0
- package/useCode/useUIState.d.mts +2 -2
- package/useCode/useUIState.mjs +8 -8
- package/useCode/useVariantSelection.d.mts +130 -6
- package/useCode/useVariantSelection.mjs +529 -93
- package/useCodeWindow/useCodeWindow.d.mts +19 -2
- package/useCodeWindow/useCodeWindow.mjs +98 -71
- package/useCoordinated/coordinatePreference.d.mts +439 -0
- package/useCoordinated/coordinatePreference.mjs +951 -0
- package/useCoordinated/coordinatePreference.testUtils.d.mts +21 -0
- package/useCoordinated/coordinatePreference.testUtils.mjs +69 -0
- package/useCoordinated/createSettleGate.d.mts +96 -0
- package/useCoordinated/createSettleGate.mjs +171 -0
- package/useCoordinated/index.d.mts +8 -0
- package/useCoordinated/index.mjs +8 -0
- package/useCoordinated/layoutShiftGate.d.mts +24 -0
- package/useCoordinated/layoutShiftGate.mjs +79 -0
- package/useCoordinated/pageSettleGate.d.mts +11 -0
- package/useCoordinated/pageSettleGate.mjs +13 -0
- package/useCoordinated/scheduleTasks.d.mts +23 -0
- package/useCoordinated/scheduleTasks.mjs +45 -0
- package/useCoordinated/useCoordinated.d.mts +193 -0
- package/useCoordinated/useCoordinated.mjs +469 -0
- package/useCoordinated/useCoordinatedLazy.d.mts +17 -0
- package/useCoordinated/useCoordinatedLazy.mjs +38 -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/useCoordinated/useSettleGate.d.mts +11 -0
- package/useCoordinated/useSettleGate.mjs +34 -0
- package/useDemo/exportVariant.d.mts +12 -5
- package/useDemo/exportVariant.mjs +59 -5
- package/useDemo/useDemo.d.mts +5 -2
- package/useScrollAnchor/useScrollAnchor.mjs +28 -5
- package/useStream/index.d.mts +6 -0
- package/useStream/index.mjs +6 -0
- package/useStream/streamChunks.d.mts +23 -0
- package/useStream/streamChunks.mjs +85 -0
- package/useStream/types.d.mts +45 -0
- package/useStream/types.mjs +1 -0
- package/useStream/useStream.d.mts +57 -0
- package/useStream/useStream.mjs +119 -0
- package/useStream/useStreamController.d.mts +15 -0
- package/useStream/useStreamController.mjs +90 -0
- package/withDocsInfra/withDocsInfra.d.mts +19 -0
- package/withDocsInfra/withDocsInfra.mjs +13 -5
- package/pipeline/loaderUtils/convertCommentsToOneIndexed.d.mts +0 -8
- package/pipeline/loaderUtils/convertCommentsToOneIndexed.mjs +0 -16
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { usePreference } from "../usePreference/index.mjs";
|
|
3
3
|
import { useUrlHashState } from "../useUrlHashState/index.mjs";
|
|
4
|
+
import { useCoordinated } from "../useCoordinated/index.mjs";
|
|
5
|
+
import { useHighlightGate } from "./useHighlightGate.mjs";
|
|
6
|
+
import { useTransitionPhase } from "./useTransitionPhase.mjs";
|
|
4
7
|
import { isHashRelevantToDemo } from "./useFileNavigation.mjs";
|
|
5
8
|
import { toKebabCase } from "../pipeline/loaderUtils/toKebabCase.mjs";
|
|
9
|
+
import { variantHasLayoutShift } from "./sourceLineCounts.mjs";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Parses the variant name from a URL hash
|
|
@@ -49,17 +53,63 @@ function parseVariantFromHash(urlHash, mainSlug, variantKeys) {
|
|
|
49
53
|
}
|
|
50
54
|
return null;
|
|
51
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a stored / hash / initial preference into a valid variant
|
|
58
|
+
* key. Priority: URL hash > localStorage > initialVariant > first
|
|
59
|
+
* variant. Returns an empty string only when no variants are
|
|
60
|
+
* available — callers should treat that as "no selection".
|
|
61
|
+
*/
|
|
62
|
+
function resolveVariantKey(hashVariant, storedValue, initialVariant, variantKeys) {
|
|
63
|
+
if (hashVariant && variantKeys.includes(hashVariant)) {
|
|
64
|
+
return hashVariant;
|
|
65
|
+
}
|
|
66
|
+
if (storedValue && variantKeys.includes(storedValue)) {
|
|
67
|
+
return storedValue;
|
|
68
|
+
}
|
|
69
|
+
if (initialVariant && variantKeys.includes(initialVariant)) {
|
|
70
|
+
return initialVariant;
|
|
71
|
+
}
|
|
72
|
+
return variantKeys[0] || '';
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
/**
|
|
53
76
|
* Hook for managing variant selection and providing variant-related data
|
|
54
77
|
* Priority: URL hash > localStorage > initialVariant > first variant
|
|
55
78
|
* When hash has a variant, it overrides localStorage and is saved to localStorage
|
|
79
|
+
*
|
|
80
|
+
* Wraps the selection in `useCoordinated` so sibling demos that share
|
|
81
|
+
* the same variant set commit variant swaps together — preventing
|
|
82
|
+
* staggered layout shifts when multiple demos on the page react to
|
|
83
|
+
* the same preference change.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* Minimum coordinator barrier wait used when `variantSwapDelay` is
|
|
87
|
+
* unset or zero but a layout-shift-prone swap still needs to land on
|
|
88
|
+
* the same frame as peer demos. One animation frame at ~60fps so the
|
|
89
|
+
* coordinated paint feels instantaneous but every peer commits
|
|
90
|
+
* together. Mirrors the same constant in `useTransformManagement`.
|
|
56
91
|
*/
|
|
92
|
+
const MIN_VARIANT_WAIT_MS = 16;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Time after an originator's announce by which all peers should have
|
|
96
|
+
* acked. Beyond this, the barrier surfaces `pendingVariantKey` so
|
|
97
|
+
* consumers can render a transient loading indicator while continuing
|
|
98
|
+
* to wait up to `ultimateTimeoutMs` (10s). Mirrors
|
|
99
|
+
* `TRANSFORM_GRACE_PERIOD_MS`.
|
|
100
|
+
*/
|
|
101
|
+
const VARIANT_GRACE_PERIOD_MS = 300;
|
|
57
102
|
export function useVariantSelection({
|
|
58
103
|
effectiveCode,
|
|
59
104
|
initialVariant,
|
|
60
105
|
variantType,
|
|
61
106
|
mainSlug,
|
|
62
|
-
saveHashVariantToLocalStorage = 'on-interaction'
|
|
107
|
+
saveHashVariantToLocalStorage = 'on-interaction',
|
|
108
|
+
variantLayoutShift = 'selected',
|
|
109
|
+
selectedFileName,
|
|
110
|
+
expanded,
|
|
111
|
+
variantSwapDelay,
|
|
112
|
+
deferHighlight
|
|
63
113
|
}) {
|
|
64
114
|
// Get variant keys from effective code
|
|
65
115
|
const variantKeys = React.useMemo(() => {
|
|
@@ -78,102 +128,374 @@ export function useVariantSelection({
|
|
|
78
128
|
return null;
|
|
79
129
|
});
|
|
80
130
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
131
|
+
// When a delay is configured, start from the boot-time value
|
|
132
|
+
// (initialVariant or first variant), then adopt the stored value on
|
|
133
|
+
// a later tick as a normal coordinated receiver swap. This allows
|
|
134
|
+
// the initial→stored transition to open `data-transforming` windows
|
|
135
|
+
// instead of resolving to the stored variant before first paint —
|
|
136
|
+
// most visibly when the full content component replaces a loading
|
|
137
|
+
// skeleton and needs to animate from the default variant to the
|
|
138
|
+
// user's saved preference.
|
|
139
|
+
//
|
|
140
|
+
// The bootstrap is gated on the *stored* variant's source being
|
|
141
|
+
// available as HAST (not a raw string). On a fresh mount, only the
|
|
142
|
+
// default variant typically has source data; non-default variants
|
|
143
|
+
// are lazy-loaded as URL refs and then parsed into HAST by the
|
|
144
|
+
// highlighter pipeline. Firing the bootstrap before that lands
|
|
145
|
+
// commits a swap to a variant whose `<Pre>` has no HAST to render,
|
|
146
|
+
// producing the user-visible sequence: unhighlighted initial paint
|
|
147
|
+
// → swap-and-animate against still-unhighlighted content → content
|
|
148
|
+
// snaps to highlighted text mid-animation. Waiting for the stored
|
|
149
|
+
// variant's HAST guarantees the receiver-flow animation plays once,
|
|
150
|
+
// against a fully-highlighted target tree, so the visible order is
|
|
151
|
+
// swap → highlight → animate.
|
|
152
|
+
//
|
|
153
|
+
// We also gate on the parent's `deferHighlight` flipping to `false`.
|
|
154
|
+
// `deferHighlight` reflects the current variant's highlight pipeline
|
|
155
|
+
// state (parsing + transforms). Without this wait the combobox
|
|
156
|
+
// pending value flips to the stored variant as soon as its HAST is
|
|
157
|
+
// available, but the receiver flow's `preload` then blocks on
|
|
158
|
+
// `awaitHighlight` (which tracks the *current* variant's
|
|
159
|
+
// `deferHighlight`). The visible result is a large gap where the
|
|
160
|
+
// combo says "Tailwind" but the content is still CSS Modules with
|
|
161
|
+
// `data-transforming` running against the stale tree. Waiting for
|
|
162
|
+
// `deferHighlight=false` before flipping `allowStoredBootstrap`
|
|
163
|
+
// keeps the combo and the content swap in lockstep — and, paired
|
|
164
|
+
// with the unconditional `storedValueForResolve` gate below, means
|
|
165
|
+
// the swap commit lands on an already-highlighted destination
|
|
166
|
+
// instead of flashing the stored variant through its raw-source
|
|
167
|
+
// fallback while its parse completes.
|
|
168
|
+
const storedVariantSourceLoaded = React.useMemo(() => {
|
|
169
|
+
// When there's no stored preference (or it's not a valid variant key),
|
|
170
|
+
// the bootstrap doesn't change the resolved variant, so no wait needed.
|
|
171
|
+
if (!storedValue || !variantKeys.includes(storedValue)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const variantEntry = effectiveCode[storedValue];
|
|
175
|
+
if (!variantEntry || typeof variantEntry === 'string') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
// Require HAST (not a raw string source) so the receiver-flow
|
|
179
|
+
// animation runs against the already-highlighted target tree.
|
|
180
|
+
return variantEntry.source != null && typeof variantEntry.source !== 'string';
|
|
181
|
+
}, [storedValue, variantKeys, effectiveCode]);
|
|
182
|
+
// One-way latch: opens after the stored variant's HAST is
|
|
183
|
+
// available and the parent highlighter is no longer deferring
|
|
184
|
+
// highlights. Driven by an effect (not a render-time set-state)
|
|
185
|
+
// so the first render always resolves to `initialVariant`; the
|
|
186
|
+
// effect then flips the latch on a later tick, the resolved value
|
|
187
|
+
// swings to `storedValue`, and the change drives the coordinated
|
|
188
|
+
// receiver-flow swap (with its `data-transforming` animation).
|
|
189
|
+
// Adopting the stored value synchronously on the first render
|
|
190
|
+
// would skip the swap entirely — no animation, and
|
|
191
|
+
// `pendingBootstrap` would never latch so `useCode` wouldn't
|
|
192
|
+
// suppress highlight on the outgoing initial variant.
|
|
193
|
+
const [allowStoredBootstrap, setAllowStoredBootstrap] = React.useState(false);
|
|
194
|
+
React.useEffect(() => {
|
|
195
|
+
if (!storedVariantSourceLoaded) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (deferHighlight) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Intentional later-tick latch: see the bootstrap-gate comment above.
|
|
202
|
+
// Flipping this during render skips the receiver-flow swap animation and
|
|
203
|
+
// prevents `pendingBootstrap` from ever latching.
|
|
204
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
205
|
+
setAllowStoredBootstrap(true);
|
|
206
|
+
}, [storedVariantSourceLoaded, deferHighlight]);
|
|
207
|
+
|
|
208
|
+
// Barrier wait length. Falls back to one frame when
|
|
209
|
+
// `variantSwapDelay` isn't configured so peers still align on the
|
|
210
|
+
// same paint without making the click feel sluggish.
|
|
211
|
+
const hasDelay = typeof variantSwapDelay === 'number' && variantSwapDelay > 0;
|
|
212
|
+
const effectiveSwapWindowMs = hasDelay ? variantSwapDelay : MIN_VARIANT_WAIT_MS;
|
|
213
|
+
|
|
214
|
+
// Hold the resolved value on the initial variant until the
|
|
215
|
+
// bootstrap gate (`allowStoredBootstrap`) releases whenever there
|
|
216
|
+
// is a highlight pipeline to wait for — either because a
|
|
217
|
+
// `variantSwapDelay` is configured (delayed swaps must always
|
|
218
|
+
// settle on HAST) or because a `CodeHighlighter` parent is
|
|
219
|
+
// publishing a `deferHighlight` signal (in which case the stored
|
|
220
|
+
// variant's parse hasn't necessarily completed even though the
|
|
221
|
+
// coordinator's `minWaitMs` is zero). Without this second clause,
|
|
222
|
+
// the no-delay path would commit the swap on the very first
|
|
223
|
+
// render and the stored variant would flash through its raw-source
|
|
224
|
+
// fallback while its parse completed. When neither condition
|
|
225
|
+
// applies (bare `useCode` consumers in tests / non-highlighted
|
|
226
|
+
// contexts) we skip the gate so raw-string sources continue to
|
|
227
|
+
// bootstrap synchronously.
|
|
228
|
+
const shouldGateBootstrap = hasDelay || deferHighlight !== undefined;
|
|
229
|
+
const storedValueForResolve = shouldGateBootstrap && !allowStoredBootstrap ? null : storedValue;
|
|
230
|
+
|
|
231
|
+
// Resolved underlying value combining hash and localStorage (and
|
|
232
|
+
// the initial/first-variant fallbacks). This is what `useCoordinated`
|
|
233
|
+
// observes as its external source of truth — any change to hash or
|
|
234
|
+
// storage opens a receiver-flow barrier so peer demos commit
|
|
235
|
+
// together.
|
|
236
|
+
const resolvedValue = React.useMemo(() => resolveVariantKey(hashVariant, storedValueForResolve, initialVariant, variantKeys), [hashVariant, storedValueForResolve, initialVariant, variantKeys]);
|
|
237
|
+
|
|
238
|
+
// Stable underlying tuple. The setter is intentionally a no-op:
|
|
239
|
+
// localStorage writes are performed *eagerly* by
|
|
240
|
+
// `setSelectedVariantAsUser` (so user intent is persisted and
|
|
241
|
+
// broadcast to peer demos on the same tick as the click), not
|
|
242
|
+
// lazily on barrier commit. The engine sees the eager write echo
|
|
243
|
+
// back through `usePreference` and dedupes it via its
|
|
244
|
+
// `inFlightTargetRef` guard so the receiver flow doesn't double-fire.
|
|
245
|
+
const underlying = React.useMemo(() => [resolvedValue, () => {}], [resolvedValue]);
|
|
246
|
+
|
|
247
|
+
// Coordinator key. Demos sharing the same variant set belong to the
|
|
248
|
+
// same coordination group. Use the variant-type bucket when set so
|
|
249
|
+
// unrelated variant sets that share a type (e.g. Yarn/Npm/Pnpm
|
|
250
|
+
// installs) still coordinate even if their key list differs.
|
|
251
|
+
const channelKey = React.useMemo(() => {
|
|
252
|
+
// Single-variant demos have nothing to coordinate (no choice to
|
|
253
|
+
// swap to) — skip the coordinator entirely so we don't trigger
|
|
254
|
+
// its localStorage reads. Mirrors `usePreference`'s
|
|
255
|
+
// single-element-array short-circuit.
|
|
256
|
+
if (variantKeys.length < 2) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
if (variantType) {
|
|
260
|
+
return `variant:${variantType}`;
|
|
261
|
+
}
|
|
262
|
+
return `variant:${[...variantKeys].sort().join(':')}`;
|
|
263
|
+
}, [variantKeys, variantType]);
|
|
264
|
+
|
|
265
|
+
// Stable per-hook identity used by the coordinator to track which
|
|
266
|
+
// demos have acked the current barrier. `React.useId` gives us a
|
|
267
|
+
// unique-per-mount string without the impure `Math.random()` /
|
|
268
|
+
// `Date.now()` dance, and stays stable across re-renders.
|
|
269
|
+
const demoId = React.useId();
|
|
270
|
+
|
|
271
|
+
// Latest props read by the engine's `causesLayoutShift` callback.
|
|
272
|
+
// Kept in a ref so the callback itself can be referentially stable.
|
|
273
|
+
const layoutShiftPropsRef = React.useRef({
|
|
274
|
+
effectiveCode,
|
|
275
|
+
variantLayoutShift,
|
|
276
|
+
selectedFileName,
|
|
277
|
+
expanded
|
|
104
278
|
});
|
|
279
|
+
// eslint-disable-next-line react-hooks/refs
|
|
280
|
+
layoutShiftPropsRef.current = {
|
|
281
|
+
effectiveCode,
|
|
282
|
+
variantLayoutShift,
|
|
283
|
+
selectedFileName,
|
|
284
|
+
expanded
|
|
285
|
+
};
|
|
105
286
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
287
|
+
// Latest committed variant key — read by `causesLayoutShift` to
|
|
288
|
+
// compare against the swap target. Initialized to `''` (rather than
|
|
289
|
+
// `resolvedValue`) because the post-`useCoordinated` assignment
|
|
290
|
+
// below is the single source of truth: relying on the init argument
|
|
291
|
+
// would leave the ref stale if `resolvedValue` later changed
|
|
292
|
+
// outside of a commit. The empty string is a sentinel —
|
|
293
|
+
// `variantHasLayoutShift` treats a falsy `from` key as "no shift".
|
|
294
|
+
const committedRef = React.useRef('');
|
|
295
|
+
const causesLayoutShift = React.useCallback(target => {
|
|
296
|
+
const props = layoutShiftPropsRef.current;
|
|
297
|
+
return variantHasLayoutShift(props.effectiveCode, committedRef.current, target, {
|
|
298
|
+
mode: props.variantLayoutShift,
|
|
299
|
+
selectedFileName: props.selectedFileName,
|
|
300
|
+
expanded: props.expanded
|
|
301
|
+
});
|
|
302
|
+
}, []);
|
|
303
|
+
|
|
304
|
+
// Track whether the most recent commit landed on `hashVariant` so
|
|
305
|
+
// we can opt-in save to localStorage under `'on-load'` semantics.
|
|
306
|
+
const lastStoredValueRef = React.useRef(storedValue);
|
|
307
|
+
// eslint-disable-next-line react-hooks/refs
|
|
308
|
+
lastStoredValueRef.current = storedValue;
|
|
309
|
+
const lastHashVariantRef = React.useRef(hashVariant);
|
|
310
|
+
// eslint-disable-next-line react-hooks/refs
|
|
311
|
+
lastHashVariantRef.current = hashVariant;
|
|
312
|
+
const onCommit = React.useCallback(target => {
|
|
313
|
+
// Mirror the historical `'on-load'` behavior: when a swap
|
|
314
|
+
// commits to whatever the hash currently points at, persist
|
|
315
|
+
// that variant to localStorage so a subsequent visit without a
|
|
316
|
+
// hash still lands on the same variant.
|
|
317
|
+
if (saveHashVariantToLocalStorage === 'on-load' && lastHashVariantRef.current && lastHashVariantRef.current === target && target !== lastStoredValueRef.current) {
|
|
318
|
+
setStoredValue(target);
|
|
319
|
+
}
|
|
320
|
+
}, [saveHashVariantToLocalStorage, setStoredValue]);
|
|
321
|
+
|
|
322
|
+
// Tracks the previous render's committed variant so we can decide
|
|
323
|
+
// the originator's `minWaitMs` synchronously inside
|
|
324
|
+
// `selectVariantDispatch`: leaving a non-empty variant needs the
|
|
325
|
+
// pre-swap expand window. Initialised to the resolved boot value
|
|
326
|
+
// (mirroring `useTransformManagement`'s `prevCommittedTransformRef`)
|
|
327
|
+
// so the very first user-driven dispatch already sees a non-empty
|
|
328
|
+
// ref and applies `variantSwapDelay`. The empty-string sentinel is
|
|
329
|
+
// kept as a fallback for the (rare) case where the variant list
|
|
330
|
+
// resolves empty during boot.
|
|
331
|
+
const prevCommittedVariantKeyRef = React.useRef(resolvedValue);
|
|
332
|
+
|
|
333
|
+
// Hold the originator's coordinator barrier open while the
|
|
334
|
+
// highlighter pipeline is still working on the incoming variant.
|
|
335
|
+
// Without this, an interactive variant swap can commit after
|
|
336
|
+
// `variantSwapDelay` even when the new variant's `parseCode` /
|
|
337
|
+
// `computeHastDeltas` hasn't landed — the incoming `<Pre>` paints
|
|
338
|
+
// from raw source then snaps to highlighted text a frame later.
|
|
339
|
+
// See `useHighlightGate` for the gate plumbing.
|
|
340
|
+
const awaitHighlight = useHighlightGate(!!deferHighlight);
|
|
341
|
+
const preload = React.useCallback((_target, signal) => {
|
|
342
|
+
const wait = awaitHighlight(signal);
|
|
343
|
+
if (wait === null) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
return wait;
|
|
347
|
+
}, [awaitHighlight]);
|
|
348
|
+
const [committedVariantKey, selectVariantDispatch, coordinationExtras] = useCoordinated(underlying, {
|
|
349
|
+
channelKey,
|
|
350
|
+
peerId: demoId,
|
|
351
|
+
causesLayoutShift,
|
|
352
|
+
preload,
|
|
353
|
+
onCommit,
|
|
354
|
+
// eslint-disable-next-line react-hooks/refs
|
|
355
|
+
minWaitMs: hasDelay && prevCommittedVariantKeyRef.current !== '' ? variantSwapDelay : 0,
|
|
356
|
+
multiPeerExtraMinWaitMs: hasDelay ? 0 : MIN_VARIANT_WAIT_MS,
|
|
357
|
+
lazyMinWaitMs: hasDelay ? variantSwapDelay : 0,
|
|
358
|
+
gracePeriodMs: VARIANT_GRACE_PERIOD_MS
|
|
110
359
|
});
|
|
111
360
|
|
|
112
|
-
//
|
|
113
|
-
|
|
361
|
+
// eslint-disable-next-line react-hooks/refs
|
|
362
|
+
prevCommittedVariantKeyRef.current = committedVariantKey;
|
|
363
|
+
|
|
364
|
+
// Keep the outgoing-tree probe in sync with whatever the engine
|
|
365
|
+
// just committed. Mutating a ref during render is safe — React
|
|
366
|
+
// tolerates it as long as the value derives deterministically from
|
|
367
|
+
// inputs of the current render.
|
|
368
|
+
// eslint-disable-next-line react-hooks/refs
|
|
369
|
+
committedRef.current = committedVariantKey;
|
|
370
|
+
|
|
371
|
+
// User-facing selected variant key. Prefer the pending value so UI
|
|
372
|
+
// controls (tabs, dropdowns) react immediately to a click, even if
|
|
373
|
+
// the engine is briefly holding the visible value back for a
|
|
374
|
+
// coordinated barrier.
|
|
375
|
+
const selectedVariantKey = coordinationExtras.pendingValue;
|
|
376
|
+
|
|
377
|
+
// Track the initial committed variant so we can detect the very
|
|
378
|
+
// first commit (whether driven by bootstrap or a user click that
|
|
379
|
+
// races bootstrap). `pendingBootstrap` derives from this so callers
|
|
380
|
+
// can suppress highlighting of the outgoing initial variant when a
|
|
381
|
+
// stored-preference swap is known to be in flight — without it,
|
|
382
|
+
// the initial variant briefly paints fully highlighted right at
|
|
383
|
+
// the moment the combobox flips to the stored value, then flashes
|
|
384
|
+
// through the animation against stale content before the incoming
|
|
385
|
+
// tree commits. Latching on first commit (not on
|
|
386
|
+
// `committedVariantKey === storedValue`) keeps the gate honest if
|
|
387
|
+
// the user clicks during the bootstrap window: their click commits
|
|
388
|
+
// a different variant and `pendingBootstrap` releases so the new
|
|
389
|
+
// selection lights up normally.
|
|
390
|
+
const [initialCommittedVariantKey, setInitialCommittedVariantKey] = React.useState(null);
|
|
391
|
+
const [hasCommittedPastInitial, setHasCommittedPastInitial] = React.useState(false);
|
|
114
392
|
React.useEffect(() => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
393
|
+
if (hasCommittedPastInitial || !committedVariantKey) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Intentional later-tick latch: see the bootstrap-gate comment above.
|
|
397
|
+
// This freezes the first non-empty committed value and only later detects
|
|
398
|
+
// moving past it; moving the detection into render shifts exactly when
|
|
399
|
+
// `pendingBootstrap` releases relative to paint, which the
|
|
400
|
+
// highlight-suppression sequence was tuned around.
|
|
401
|
+
/* eslint-disable react-hooks/set-state-in-effect */
|
|
402
|
+
if (initialCommittedVariantKey === null) {
|
|
403
|
+
setInitialCommittedVariantKey(committedVariantKey);
|
|
404
|
+
} else if (committedVariantKey !== initialCommittedVariantKey) {
|
|
405
|
+
setHasCommittedPastInitial(true);
|
|
406
|
+
}
|
|
407
|
+
/* eslint-enable react-hooks/set-state-in-effect */
|
|
408
|
+
}, [committedVariantKey, hasCommittedPastInitial, initialCommittedVariantKey]);
|
|
409
|
+
|
|
410
|
+
// Reset both bootstrap latches whenever the storage bucket
|
|
411
|
+
// identity changes (the consumer swaps in a new lesson with a
|
|
412
|
+
// different variant set, or `variantType` switches the
|
|
413
|
+
// `usePreference` bucket). Without this, a second payload
|
|
414
|
+
// inherits the first payload's "already bootstrapped" state and
|
|
415
|
+
// its newly resolved stored preference is never adopted.
|
|
416
|
+
//
|
|
417
|
+
// The identity is derived from the bucket coordinates — the same
|
|
418
|
+
// values `usePreference` keys off — rather than `effectiveCode`'s
|
|
419
|
+
// object identity. `CodeHighlighterClient` rebuilds and republishes
|
|
420
|
+
// the code object during ordinary parse / transform progress, so
|
|
421
|
+
// keying on `effectiveCode` would re-arm the bootstrap path in
|
|
422
|
+
// the middle of a user-driven swap (whose stored value is
|
|
423
|
+
// persisted eagerly by `setSelectedVariantAsUser`), making an
|
|
424
|
+
// interactive variant change look like an initial-mount stored
|
|
425
|
+
// bootstrap and replaying it again.
|
|
426
|
+
//
|
|
427
|
+
// Tracking the previous identity in state (rather than a ref)
|
|
428
|
+
// follows React's "adjusting state when a prop changes" pattern
|
|
429
|
+
// so the reset stays a synchronous render-time decision without
|
|
430
|
+
// violating the refs-during-render rule.
|
|
431
|
+
const bootstrapIdentity = React.useMemo(() => `${variantType ?? ''}\u0000${[...variantKeys].sort().join('\u0001')}`, [variantType, variantKeys]);
|
|
432
|
+
const [prevBootstrapIdentity, setPrevBootstrapIdentity] = React.useState(bootstrapIdentity);
|
|
433
|
+
if (prevBootstrapIdentity !== bootstrapIdentity) {
|
|
434
|
+
setPrevBootstrapIdentity(bootstrapIdentity);
|
|
435
|
+
// Reset to `false` so the effect re-runs and the resolved value
|
|
436
|
+
// swings from initial → stored on a later tick. Setting it true
|
|
437
|
+
// synchronously here would skip the receiver-flow swap animation
|
|
438
|
+
// for the new bucket — same regression class as bootstrapping
|
|
439
|
+
// synchronously on first render.
|
|
440
|
+
setAllowStoredBootstrap(false);
|
|
441
|
+
setHasCommittedPastInitial(false);
|
|
442
|
+
setInitialCommittedVariantKey(null);
|
|
443
|
+
}
|
|
444
|
+
// A stored-preference bootstrap is only actually pending when no
|
|
445
|
+
// higher-precedence source (the URL hash) is already winning. When
|
|
446
|
+
// the hash takes precedence, the resolved value matches the hash
|
|
447
|
+
// forever and no bootstrap swap will ever fire — gating only on
|
|
448
|
+
// `storedValue !== committedVariantKey` here would leave
|
|
449
|
+
// `pendingBootstrap` latched forever, which `useCode` translates
|
|
450
|
+
// into "never highlight". The hash precedence guard keeps
|
|
451
|
+
// permalinked / hash-selected demos highlighting normally even when
|
|
452
|
+
// the user's saved preference points at a different variant.
|
|
453
|
+
const hashOverridesStorage = !!hashVariant && variantKeys.includes(hashVariant);
|
|
454
|
+
const pendingBootstrap = !hasCommittedPastInitial && !hashOverridesStorage && !!storedValue && variantKeys.includes(storedValue) && storedValue !== committedVariantKey;
|
|
455
|
+
|
|
456
|
+
// User setter: persists to localStorage (and clears any relevant
|
|
457
|
+
// URL hash) before dispatching the coordinator so peer demos
|
|
458
|
+
// observe the new preference on the same tick as the click.
|
|
459
|
+
// Validation differs from `resolveVariantKey`: an explicit `null`
|
|
460
|
+
// means "fall back to the first variant" — never re-resolved to
|
|
461
|
+
// `initialVariant` (which is only consulted for the
|
|
462
|
+
// never-set-storage hydration path).
|
|
463
|
+
const setSelectedVariantAsUser = React.useCallback(value => {
|
|
464
|
+
const resolved = typeof value === 'function' ? value(selectedVariantKey) : value;
|
|
465
|
+
const effectiveValue = resolved ?? variantKeys[0];
|
|
466
|
+
if (!effectiveValue || !variantKeys.includes(effectiveValue)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (effectiveValue === selectedVariantKey && effectiveValue === committedVariantKey) {
|
|
122
470
|
return;
|
|
123
471
|
}
|
|
472
|
+
// Clear hash first so the receiver flow doesn't observe a
|
|
473
|
+
// stale hash → variant mapping after the storage write echoes
|
|
474
|
+
// back through `usePreference`. Only clear when the current
|
|
475
|
+
// hash is one this demo cares about.
|
|
476
|
+
if (urlHash && mainSlug && isHashRelevantToDemo(urlHash, mainSlug)) {
|
|
477
|
+
setUrlHash(null);
|
|
478
|
+
}
|
|
479
|
+
// Start local coordination from the user action first so this
|
|
480
|
+
// demo is always treated as the originator (matching the
|
|
481
|
+
// pattern in `useTransformManagement`), then persist.
|
|
482
|
+
selectVariantDispatch(effectiveValue);
|
|
483
|
+
setStoredValue(effectiveValue);
|
|
484
|
+
}, [selectedVariantKey, committedVariantKey, variantKeys, urlHash, mainSlug, setUrlHash, selectVariantDispatch, setStoredValue]);
|
|
124
485
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!
|
|
486
|
+
// Programmatic setter: doesn't save to localStorage and doesn't
|
|
487
|
+
// clear the hash. Used for hash-driven changes routed through
|
|
488
|
+
// `useFileNavigation`.
|
|
489
|
+
const setSelectedVariantProgrammatic = React.useCallback(value => {
|
|
490
|
+
const resolved = typeof value === 'function' ? value(selectedVariantKey) : value;
|
|
491
|
+
if (!variantKeys.includes(resolved)) {
|
|
131
492
|
return;
|
|
132
493
|
}
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
setStoredValue(hashVariant);
|
|
139
|
-
}
|
|
140
|
-
} else if (!hashVariant && !urlHash &&
|
|
141
|
-
// Only fall back to localStorage when hash is truly empty
|
|
142
|
-
storedValue && variantKeys.includes(storedValue) && storedValue !== selectedVariantKeyRef.current) {
|
|
143
|
-
// Hash is empty but localStorage has a variant - use it
|
|
144
|
-
setSelectedVariantKeyState(storedValue);
|
|
145
|
-
}
|
|
146
|
-
}, [hashVariant, urlHash, variantKeys,
|
|
147
|
-
// Note: selectedVariantKey is intentionally NOT in dependencies
|
|
148
|
-
// to avoid effect running when user manually changes variant
|
|
149
|
-
storedValue, setStoredValue, saveHashVariantToLocalStorage]);
|
|
150
|
-
|
|
151
|
-
// Programmatic setter: doesn't save to localStorage (used for hash-driven changes)
|
|
152
|
-
const setSelectedVariantKeyProgrammatic = React.useCallback(value => {
|
|
153
|
-
const resolvedValue = typeof value === 'function' ? value(selectedVariantKey) : value;
|
|
154
|
-
if (variantKeys.includes(resolvedValue)) {
|
|
155
|
-
setSelectedVariantKeyState(resolvedValue);
|
|
156
|
-
}
|
|
157
|
-
}, [selectedVariantKey, variantKeys]);
|
|
158
|
-
|
|
159
|
-
// User setter: saves to localStorage (used for user-initiated changes like dropdown)
|
|
160
|
-
const setSelectedVariantKeyAsUser = React.useCallback(value => {
|
|
161
|
-
const resolvedValue = typeof value === 'function' ? value(selectedVariantKey) : value;
|
|
162
|
-
// If value is null, select the first variant (default)
|
|
163
|
-
const effectiveValue = resolvedValue ?? variantKeys[0];
|
|
164
|
-
if (effectiveValue && variantKeys.includes(effectiveValue)) {
|
|
165
|
-
// Mark as user-initiated to prevent hash effect from overriding
|
|
166
|
-
isUserInitiatedChange.current = true;
|
|
167
|
-
// Clear hash if it exists and is relevant to this demo
|
|
168
|
-
if (urlHash && mainSlug && isHashRelevantToDemo(urlHash, mainSlug)) {
|
|
169
|
-
setUrlHash(null);
|
|
170
|
-
// Update prevHashVariant to reflect that hash is now null
|
|
171
|
-
prevHashVariant.current = null;
|
|
172
|
-
}
|
|
173
|
-
setSelectedVariantKeyState(effectiveValue);
|
|
174
|
-
setStoredValue(effectiveValue);
|
|
175
|
-
}
|
|
176
|
-
}, [setStoredValue, selectedVariantKey, variantKeys, urlHash, mainSlug, setUrlHash]);
|
|
494
|
+
if (resolved === selectedVariantKey && resolved === committedVariantKey) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
selectVariantDispatch(resolved);
|
|
498
|
+
}, [selectedVariantKey, committedVariantKey, variantKeys, selectVariantDispatch]);
|
|
177
499
|
const selectedVariant = React.useMemo(() => {
|
|
178
500
|
const variant = effectiveCode[selectedVariantKey];
|
|
179
501
|
if (variant && typeof variant === 'object' && 'source' in variant) {
|
|
@@ -182,12 +504,119 @@ export function useVariantSelection({
|
|
|
182
504
|
return null;
|
|
183
505
|
}, [effectiveCode, selectedVariantKey]);
|
|
184
506
|
|
|
185
|
-
//
|
|
507
|
+
// Variant resolved from the *committed* key — lags `selectedVariant`
|
|
508
|
+
// by `variantSwapDelay` ms when a delay is configured. Used by the
|
|
509
|
+
// renderer so the outgoing tree stays put for the pre-swap window.
|
|
510
|
+
const committedVariant = React.useMemo(() => {
|
|
511
|
+
if (committedVariantKey === selectedVariantKey) {
|
|
512
|
+
return selectedVariant;
|
|
513
|
+
}
|
|
514
|
+
const variant = effectiveCode[committedVariantKey];
|
|
515
|
+
if (variant && typeof variant === 'object' && 'source' in variant) {
|
|
516
|
+
return variant;
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
}, [effectiveCode, committedVariantKey, selectedVariantKey, selectedVariant]);
|
|
520
|
+
|
|
521
|
+
// Post-swap `data-transforming="expanded"`/`"collapsing"` window. Mirrors the
|
|
522
|
+
// equivalent in `useTransformManagement`: fires after the engine
|
|
523
|
+
// commits a swap so the incoming tree has a chance to enter-animate
|
|
524
|
+
// any bridge `.collapse` placeholder appended by `<Pre>`. Only
|
|
525
|
+
// armed when `variantSwapDelay` is configured.
|
|
526
|
+
//
|
|
527
|
+
// `collapseSourceVariantKey` captures the variant we just left at
|
|
528
|
+
// the moment of commit so the post-swap window has a stable bridge
|
|
529
|
+
// target even if `selectedVariantKey` keeps changing.
|
|
530
|
+
const [postSwapWindowActive, setPostSwapWindowActive] = React.useState(false);
|
|
531
|
+
const [collapseSourceVariantKey, setCollapseSourceVariantKey] = React.useState(null);
|
|
532
|
+
const [prevAppliedVariant, setPrevAppliedVariant] = React.useState(committedVariantKey);
|
|
533
|
+
if (prevAppliedVariant !== committedVariantKey) {
|
|
534
|
+
if (hasDelay && prevAppliedVariant !== '') {
|
|
535
|
+
setPostSwapWindowActive(true);
|
|
536
|
+
setCollapseSourceVariantKey(prevAppliedVariant);
|
|
537
|
+
}
|
|
538
|
+
setPrevAppliedVariant(committedVariantKey);
|
|
539
|
+
}
|
|
540
|
+
// Tear down a stale window synchronously at render time: no animation
|
|
541
|
+
// window should exist without a delay, so a window left open after
|
|
542
|
+
// `hasDelay` flips to false is cleared a tick earlier than an effect would,
|
|
543
|
+
// with no animation to disrupt (there is no delay).
|
|
544
|
+
if (postSwapWindowActive && !hasDelay) {
|
|
545
|
+
setPostSwapWindowActive(false);
|
|
546
|
+
setCollapseSourceVariantKey(null);
|
|
547
|
+
}
|
|
548
|
+
React.useEffect(() => {
|
|
549
|
+
if (!postSwapWindowActive) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
const timerId = setTimeout(() => {
|
|
553
|
+
setPostSwapWindowActive(false);
|
|
554
|
+
setCollapseSourceVariantKey(null);
|
|
555
|
+
}, effectiveSwapWindowMs);
|
|
556
|
+
return () => clearTimeout(timerId);
|
|
557
|
+
}, [postSwapWindowActive, effectiveSwapWindowMs, committedVariantKey]);
|
|
558
|
+
|
|
559
|
+
// If both phases are technically eligible, the pending pre-swap
|
|
560
|
+
// takes priority — the visible tree IS the just-applied one and it
|
|
561
|
+
// needs to expand out for the next swap. When `variantSwapDelay`
|
|
562
|
+
// isn't configured, no animation window is opening (the coordinator
|
|
563
|
+
// wait is the one-frame `MIN_VARIANT_WAIT_MS`, too short to
|
|
564
|
+
// animate) so the phase stays `null`.
|
|
565
|
+
//
|
|
566
|
+
// Each phase enters a "paused" value first (`'collapsed'` for the
|
|
567
|
+
// pre-swap window, `'expanded'` for the post-swap window). The
|
|
568
|
+
// rendered `<Pre>` calls `notifyVariantTransitionReady` once it has
|
|
569
|
+
// painted the new tree at that paused value, flipping
|
|
570
|
+
// `variantTransitionReady` to `true` which advances the phase to
|
|
571
|
+
// the matching active value (`'expanding'` / `'collapsing'`). The
|
|
572
|
+
// readiness flag is keyed on `(committedVariantKey,
|
|
573
|
+
// selectedVariantKey)` so each new paused window starts with a
|
|
574
|
+
// fresh wait.
|
|
575
|
+
const variantTransitionWindowKey = `${committedVariantKey}|${selectedVariantKey}|${postSwapWindowActive ? '1' : '0'}`;
|
|
576
|
+
const {
|
|
577
|
+
ready: variantTransitionReady,
|
|
578
|
+
notify: notifyVariantTransitionReady
|
|
579
|
+
} = useTransitionPhase(variantTransitionWindowKey);
|
|
580
|
+
const variantSwappingPhase = (() => {
|
|
581
|
+
if (!hasDelay) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
if (committedVariantKey !== selectedVariantKey) {
|
|
585
|
+
return variantTransitionReady ? 'expanding' : 'collapsed';
|
|
586
|
+
}
|
|
587
|
+
if (postSwapWindowActive) {
|
|
588
|
+
return variantTransitionReady ? 'collapsing' : 'expanded';
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
})();
|
|
592
|
+
const swapPartnerVariantKey = (() => {
|
|
593
|
+
if (variantSwappingPhase === 'collapsed' || variantSwappingPhase === 'expanding') {
|
|
594
|
+
return selectedVariantKey || null;
|
|
595
|
+
}
|
|
596
|
+
if (variantSwappingPhase === 'expanded' || variantSwappingPhase === 'collapsing') {
|
|
597
|
+
return collapseSourceVariantKey;
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
})();
|
|
601
|
+
const pendingVariantKey = coordinationExtras.isWaitingForPeers ? coordinationExtras.pendingValue : undefined;
|
|
602
|
+
|
|
603
|
+
// Safety check: if the selected variant truly disappears from the
|
|
604
|
+
// code map (e.g. variant keys re-resolved), fall back to the first
|
|
605
|
+
// variant. We deliberately *don't* trip on a key whose entry exists
|
|
606
|
+
// but is still a lazy placeholder (string / partial object without
|
|
607
|
+
// `source`): during incremental loading the variant key is valid,
|
|
608
|
+
// the source just hasn't arrived yet, and bouncing through the
|
|
609
|
+
// coordinator here would round-trip the selection
|
|
610
|
+
// stored → first-variant → stored (the receiver flow re-resolves
|
|
611
|
+
// back to the underlying value the moment the placeholder swaps
|
|
612
|
+
// for a real `VariantCode`). `variantKeys` itself only includes
|
|
613
|
+
// fully-loaded variants, so it's not a reliable signal here.
|
|
614
|
+
const keyExistsInCode = selectedVariantKey ? Object.prototype.hasOwnProperty.call(effectiveCode, selectedVariantKey) : false;
|
|
186
615
|
React.useEffect(() => {
|
|
187
|
-
if (!
|
|
188
|
-
|
|
616
|
+
if (!keyExistsInCode && variantKeys.length > 0) {
|
|
617
|
+
setSelectedVariantProgrammatic(variantKeys[0]);
|
|
189
618
|
}
|
|
190
|
-
}, [
|
|
619
|
+
}, [keyExistsInCode, variantKeys, setSelectedVariantProgrammatic]);
|
|
191
620
|
|
|
192
621
|
// Function to save variant to localStorage (used for on-interaction mode)
|
|
193
622
|
const saveVariantToLocalStorage = React.useCallback(variant => {
|
|
@@ -199,8 +628,15 @@ export function useVariantSelection({
|
|
|
199
628
|
variantKeys,
|
|
200
629
|
selectedVariantKey,
|
|
201
630
|
selectedVariant,
|
|
202
|
-
|
|
203
|
-
|
|
631
|
+
committedVariantKey,
|
|
632
|
+
committedVariant,
|
|
633
|
+
variantSwappingPhase,
|
|
634
|
+
swapPartnerVariantKey,
|
|
635
|
+
pendingVariantKey,
|
|
636
|
+
pendingBootstrap,
|
|
637
|
+
notifyVariantTransitionReady,
|
|
638
|
+
selectVariant: setSelectedVariantAsUser,
|
|
639
|
+
selectVariantProgrammatic: setSelectedVariantProgrammatic,
|
|
204
640
|
saveVariantToLocalStorage,
|
|
205
641
|
hashVariant
|
|
206
642
|
};
|