@mui/internal-docs-infra 0.12.1-canary.2 → 0.12.1-canary.21
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/PreloadProvider.mjs +1 -2
- package/ChunkProvider/usePreload.d.mts +1 -1
- package/ChunkProvider/usePreload.mjs +0 -1
- package/CodeControllerContext/CodeControllerContext.d.mts +45 -10
- package/CodeControllerContext/CodeControllerContext.mjs +15 -1
- package/CodeHighlighter/CodeHighlighterClient.d.mts +3 -2
- package/CodeHighlighter/CodeHighlighterClient.mjs +113 -18
- package/CodeHighlighter/CodeHighlighterContext.d.mts +9 -3
- package/CodeHighlighter/buildStringFallback.d.mts +1 -1
- package/CodeHighlighter/codeToFallbackProps.d.mts +1 -1
- package/CodeHighlighter/createClientProps.mjs +3 -0
- package/CodeHighlighter/fallbackCompression.d.mts +1 -1
- package/CodeHighlighter/parseControlledCode.d.mts +13 -4
- package/CodeHighlighter/parseControlledCode.mjs +41 -49
- package/CodeHighlighter/types.d.mts +21 -0
- package/CodeHighlighter/useGrammarsReady.mjs +44 -9
- package/CodeProvider/CodeProvider.d.mts +1 -1
- package/CodeProvider/CodeProviderLazy.d.mts +1 -1
- package/CodeProvider/createParseSourceWorkerClient.d.mts +6 -0
- package/CodeProvider/createParseSourceWorkerClient.mjs +83 -15
- package/CodeProvider/useCodeProviderValue.mjs +50 -4
- package/abstractCreateDemo/abstractCreateDemo.d.mts +15 -1
- package/abstractCreateDemo/abstractCreateDemo.mjs +8 -0
- package/abstractCreateDemoClient/abstractCreateDemoClient.mjs +4 -0
- package/abstractCreateTypes/abstractCreateTypes.d.mts +12 -1
- package/abstractCreateTypes/abstractCreateTypes.mjs +4 -0
- package/abstractCreateTypes/typesToJsx.d.mts +8 -0
- package/abstractCreateTypes/typesToJsx.mjs +2 -1
- package/cli/loadNextConfig.d.mts +9 -0
- package/cli/loadNextConfig.mjs +11 -2
- package/cli/runValidate.mjs +15 -7
- package/cli/validateWorker.d.mts +1 -0
- package/cli/validateWorker.mjs +2 -1
- package/createDemoData/createDemoData.d.mts +1 -1
- package/package.json +34 -5
- package/pipeline/cacheUtils/constants.d.mts +5 -0
- package/pipeline/cacheUtils/constants.mjs +5 -0
- package/pipeline/cacheUtils/hashCacheContent.d.mts +5 -0
- package/pipeline/cacheUtils/hashCacheContent.mjs +9 -0
- package/pipeline/cacheUtils/index.d.mts +9 -0
- package/pipeline/cacheUtils/index.mjs +7 -0
- package/pipeline/cacheUtils/loadFileCache.d.mts +13 -0
- package/pipeline/cacheUtils/loadFileCache.mjs +20 -0
- package/pipeline/cacheUtils/loadFileCacheEntry.d.mts +13 -0
- package/pipeline/cacheUtils/loadFileCacheEntry.mjs +47 -0
- package/pipeline/cacheUtils/resolveCachePath.d.mts +16 -0
- package/pipeline/cacheUtils/resolveCachePath.mjs +24 -0
- package/pipeline/cacheUtils/saveFileCache.d.mts +17 -0
- package/pipeline/cacheUtils/saveFileCache.mjs +54 -0
- package/pipeline/cacheUtils/types.d.mts +23 -0
- package/pipeline/cacheUtils/types.mjs +1 -0
- package/pipeline/cacheUtils/withFileCache.d.mts +40 -0
- package/pipeline/cacheUtils/withFileCache.mjs +81 -0
- package/pipeline/hastUtils/hastUtils.d.mts +1 -1
- package/pipeline/hastUtils/hastUtils.mjs +0 -1
- package/pipeline/loadIsomorphicCodeSource/loadIsomorphicCodeSource.d.mts +1 -1
- package/pipeline/loadIsomorphicCodeSource/loadIsomorphicCodeSource.mjs +1 -1
- package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.mjs +0 -1
- package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +1 -1
- package/pipeline/loadIsomorphicCodeVariant/hasAllCodeVariants.d.mts +1 -1
- package/pipeline/loadIsomorphicCodeVariant/maybeCodeInitialData.d.mts +1 -1
- package/pipeline/loadPrecomputedSitemap/loadPrecomputedSitemap.d.mts +6 -0
- package/pipeline/loadPrecomputedSitemap/loadPrecomputedSitemap.mjs +2 -1
- package/pipeline/loadPrecomputedTypes/loadPrecomputedTypes.d.mts +6 -0
- package/pipeline/loadPrecomputedTypes/loadPrecomputedTypes.mjs +5 -1
- package/pipeline/loadServerCodeMeta/resolveModulePathWithFs.d.mts +1 -1
- package/pipeline/loadServerCodeMeta/resolveModulePathWithFs.mjs +0 -1
- package/pipeline/loadServerPageIndex/enrichPageIndex.d.mts +15 -0
- package/pipeline/loadServerPageIndex/enrichPageIndex.mjs +50 -0
- package/pipeline/loadServerPageIndex/extractPrefixAndTitle.d.mts +19 -0
- package/pipeline/loadServerPageIndex/extractPrefixAndTitle.mjs +80 -0
- package/pipeline/loadServerPageIndex/index.d.mts +5 -2
- package/pipeline/loadServerPageIndex/index.mjs +4 -1
- package/pipeline/loadServerPageIndex/loadServerPageIndex.d.mts +13 -22
- package/pipeline/loadServerPageIndex/loadServerPageIndex.mjs +32 -124
- package/pipeline/loadServerPageIndex/resolvePageIndexCacheKey.d.mts +13 -0
- package/pipeline/loadServerPageIndex/resolvePageIndexCacheKey.mjs +23 -0
- package/pipeline/loadServerTypes/highlightTypes.d.mts +3 -3
- package/pipeline/loadServerTypes/highlightTypesMeta.d.mts +4 -4
- package/pipeline/loadServerTypes/loadServerTypes.d.mts +2 -2
- package/pipeline/loadServerTypes/loadServerTypes.mjs +67 -7
- package/pipeline/loadServerTypesMeta/createOptimizedProgram.d.mts +2 -1
- package/pipeline/loadServerTypesMeta/formatClass.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/formatComponent.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/formatFunction.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/formatHook.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/formatRaw.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/formatType.d.mts +1 -1
- package/pipeline/loadServerTypesMeta/loadServerTypesMeta.d.mts +7 -7
- package/pipeline/loadServerTypesMeta/loadServerTypesMeta.mjs +1 -1
- package/pipeline/loadServerTypesMeta/processTypes.d.mts +2 -2
- package/pipeline/loadServerTypesMeta/worker.d.mts +1 -1
- package/pipeline/loadServerTypesText/loadServerTypesText.d.mts +16 -2
- package/pipeline/loadServerTypesText/loadServerTypesText.mjs +30 -8
- package/pipeline/loadServerTypesText/normalizeTypesSourceDataForCache.d.mts +2 -0
- package/pipeline/loadServerTypesText/normalizeTypesSourceDataForCache.mjs +144 -0
- package/pipeline/loadServerTypesText/resolveTypesCacheKey.d.mts +19 -0
- package/pipeline/loadServerTypesText/resolveTypesCacheKey.mjs +29 -0
- package/pipeline/loaderUtils/parseImportsAndComments.d.mts +5 -3
- package/pipeline/loaderUtils/parseImportsAndComments.mjs +284 -40
- package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.d.mts +2 -2
- package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.mjs +2 -3
- package/pipeline/parseSource/detectFileTypes.d.mts +16 -0
- package/pipeline/parseSource/detectFileTypes.mjs +44 -0
- package/pipeline/syncPageIndex/mergeMetadataMarkdown.d.mts +25 -1
- package/pipeline/syncPageIndex/mergeMetadataMarkdown.mjs +79 -49
- package/pipeline/syncPageIndex/metadataToMarkdown.d.mts +15 -1
- package/pipeline/syncPageIndex/metadataToMarkdown.mjs +32 -10
- package/pipeline/syncPageIndex/syncPageIndex.d.mts +12 -0
- package/pipeline/syncPageIndex/syncPageIndex.mjs +154 -40
- package/pipeline/syncTypes/generateTypesMarkdown.d.mts +2 -2
- package/pipeline/syncTypes/syncTypes.d.mts +7 -0
- package/pipeline/syncTypes/syncTypes.mjs +31 -1
- package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.mjs +1 -1
- package/pipeline/transformMarkdownCode/transformMarkdownCode.mjs +25 -15
- package/pipeline/transformMarkdownMetadata/transformMarkdownMetadata.mjs +3 -0
- package/pipeline/transformMarkdownMetadata/types.d.mts +8 -1
- package/useCode/EditableEngine.d.mts +1 -1
- package/useCode/Pre.d.mts +9 -2
- package/useCode/Pre.mjs +4 -3
- package/useCode/TransformEngine.d.mts +1 -1
- package/useCode/useCode.d.mts +17 -1
- package/useCode/useCode.mjs +22 -1
- package/useCode/useCopyFunctionality.d.mts +2 -2
- package/useCode/useEditable.browser.mjs +0 -1
- package/useCode/useFileNavigation.d.mts +3 -0
- package/useCode/useFileNavigation.mjs +3 -1
- package/useCode/useSourceEditing.mjs +45 -10
- package/useCode/useSourceEnhancing.mjs +41 -9
- package/useCode/useTransformManagement.d.mts +2 -2
- package/useCode/useTransformManagement.mjs +19 -3
- package/useCode/useUIState.d.mts +4 -0
- package/useCode/useUIState.mjs +8 -1
- package/useCode/useVariantSelection.d.mts +1 -1
- package/useCoordinated/useCoordinated.d.mts +1 -1
- package/useCoordinated/useCoordinatedLocalStorage.d.mts +1 -1
- package/useCoordinated/useCoordinatedLocalStorage.mjs +0 -1
- package/useCoordinated/useCoordinatedPreference.d.mts +1 -1
- package/useCoordinated/useCoordinatedPreference.mjs +0 -1
- package/useCrossTabState/index.d.mts +2 -0
- package/useCrossTabState/index.mjs +2 -0
- package/useCrossTabState/useCrossTabMirror.d.mts +26 -0
- package/useCrossTabState/useCrossTabMirror.mjs +108 -0
- package/useCrossTabState/useCrossTabState.d.mts +21 -0
- package/useCrossTabState/useCrossTabState.mjs +31 -0
- package/useDemo/exportVariant.mjs +2 -4
- package/useDemo/exportVariantAsCra.d.mts +1 -1
- package/useDemo/exportVariantAsCra.mjs +0 -1
- package/useDemo/useDemo.d.mts +4 -1
- package/useDemo/useDemo.mjs +5 -0
- package/useDemoController/BuildEngine.d.mts +2 -0
- package/useDemoController/BuildEngine.mjs +10 -0
- package/useDemoController/DemoRunner.d.mts +34 -0
- package/useDemoController/DemoRunner.mjs +83 -0
- package/useDemoController/ErrorBoundary.d.mts +34 -0
- package/useDemoController/ErrorBoundary.mjs +49 -0
- package/useDemoController/Runner.d.mts +44 -0
- package/useDemoController/Runner.mjs +71 -0
- package/useDemoController/absolutizeImports.d.mts +13 -0
- package/useDemoController/absolutizeImports.mjs +73 -0
- package/useDemoController/buildScope.d.mts +48 -0
- package/useDemoController/buildScope.mjs +270 -0
- package/useDemoController/collectSources.d.mts +67 -0
- package/useDemoController/collectSources.mjs +92 -0
- package/useDemoController/compileCssModule.d.mts +64 -0
- package/useDemoController/compileCssModule.mjs +40 -0
- package/useDemoController/compileCssWithPostcss.d.mts +17 -0
- package/useDemoController/compileCssWithPostcss.mjs +89 -0
- package/useDemoController/compileModule.d.mts +14 -0
- package/useDemoController/compileModule.mjs +17 -0
- package/useDemoController/constants.d.mts +13 -0
- package/useDemoController/constants.mjs +13 -0
- package/useDemoController/createRequire.d.mts +10 -0
- package/useDemoController/createRequire.mjs +17 -0
- package/useDemoController/createTranspileWorkerClient.d.mts +27 -0
- package/useDemoController/createTranspileWorkerClient.mjs +139 -0
- package/useDemoController/cssModules.d.mts +47 -0
- package/useDemoController/currentBrowserTarget.d.mts +16 -0
- package/useDemoController/currentBrowserTarget.mjs +36 -0
- package/useDemoController/evalCode.d.mts +12 -0
- package/useDemoController/evalCode.mjs +35 -0
- package/useDemoController/generateCssModuleSource.d.mts +11 -0
- package/useDemoController/generateCssModuleSource.mjs +55 -0
- package/useDemoController/generateElement.d.mts +21 -0
- package/useDemoController/generateElement.mjs +26 -0
- package/useDemoController/hashString.d.mts +11 -0
- package/useDemoController/hashString.mjs +20 -0
- package/useDemoController/importCode.d.mts +11 -0
- package/useDemoController/importCode.mjs +15 -0
- package/useDemoController/index.d.mts +1 -0
- package/useDemoController/index.mjs +1 -0
- package/useDemoController/instantiateElement.d.mts +24 -0
- package/useDemoController/instantiateElement.mjs +57 -0
- package/useDemoController/instantiateModule.d.mts +17 -0
- package/useDemoController/instantiateModule.mjs +24 -0
- package/useDemoController/resolveCssImports.d.mts +27 -0
- package/useDemoController/resolveCssImports.mjs +79 -0
- package/useDemoController/transformCode.d.mts +14 -0
- package/useDemoController/transformCode.mjs +42 -0
- package/useDemoController/transpileClientSingleton.d.mts +10 -0
- package/useDemoController/transpileClientSingleton.mjs +83 -0
- package/useDemoController/transpileSource.d.mts +29 -0
- package/useDemoController/transpileSource.mjs +36 -0
- package/useDemoController/transpileWorker.d.mts +14 -0
- package/useDemoController/transpileWorker.mjs +41 -0
- package/useDemoController/types.d.mts +30 -0
- package/useDemoController/types.mjs +1 -0
- package/useDemoController/useDemoController.d.mts +54 -0
- package/useDemoController/useDemoController.mjs +186 -0
- package/useDemoController/useRunner.d.mts +53 -0
- package/useDemoController/useRunner.mjs +118 -0
- package/useDemoController/useVariantBuilds.d.mts +31 -0
- package/useDemoController/useVariantBuilds.mjs +188 -0
- package/useSearch/types.d.mts +1 -1
- package/useType/TypesDataProvider.d.mts +1 -1
- package/useType/TypesDataProvider.mjs +1 -2
- package/useType/useType.d.mts +1 -1
- package/useType/useTypeProp.d.mts +1 -1
- package/withDocsInfra/withDeploymentConfig.mjs +57 -0
- package/withDocsInfra/withDocsInfra.d.mts +14 -0
- package/withDocsInfra/withDocsInfra.mjs +25 -11
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirrors a value you already own across same-origin browser tabs/windows through a
|
|
3
|
+
* `BroadcastChannel` — so, for example, two Chrome split-view tabs of the same page
|
|
4
|
+
* keep a live demo's edits in sync. The lower-level building block behind
|
|
5
|
+
* `useCrossTabState`: use it when the state lives elsewhere (a reducer, a store, a
|
|
6
|
+
* controller's own `useState`) and you only want to keep it in sync; reach for
|
|
7
|
+
* `useCrossTabState` when you just want a synced `useState`.
|
|
8
|
+
*
|
|
9
|
+
* Pass a `key` (the channel name) that is identical across the tabs meant to share
|
|
10
|
+
* state, and `null` to disable — no channel is opened, which also makes it SSR-safe
|
|
11
|
+
* and gives the caller a clean off switch. `value` is broadcast whenever it changes
|
|
12
|
+
* locally; an incoming remote value is handed to `applyRemote` WITHOUT being echoed
|
|
13
|
+
* back, so two tabs can't ping-pong. Last write wins.
|
|
14
|
+
*
|
|
15
|
+
* A tab opened mid-edit catches up: on mount it asks peers for the current state, and
|
|
16
|
+
* any tab already holding shared state replies. A fresh tab with nothing to share
|
|
17
|
+
* stays quiet, so it can never blank out the asker; and once a tab holds shared state
|
|
18
|
+
* it ignores further catch-up replies, keeping its own value authoritative.
|
|
19
|
+
*
|
|
20
|
+
* `value` must be structured-cloneable (plain data) — `postMessage` clones it.
|
|
21
|
+
*
|
|
22
|
+
* @param key The channel name; tabs sync only when their keys match. `null` disables syncing (and is SSR-safe).
|
|
23
|
+
* @param value The current value, broadcast to other tabs whenever it changes.
|
|
24
|
+
* @param applyRemote Called with a value received from another tab — apply it to your own state.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useCrossTabMirror<T>(key: string | null, value: T, applyRemote: (value: T) => void): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Channel protocol: a `state` push (a local change, or a `reply` catching a newcomer
|
|
7
|
+
* up), or a newcomer's `request` for the current state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mirrors a value you already own across same-origin browser tabs/windows through a
|
|
12
|
+
* `BroadcastChannel` — so, for example, two Chrome split-view tabs of the same page
|
|
13
|
+
* keep a live demo's edits in sync. The lower-level building block behind
|
|
14
|
+
* `useCrossTabState`: use it when the state lives elsewhere (a reducer, a store, a
|
|
15
|
+
* controller's own `useState`) and you only want to keep it in sync; reach for
|
|
16
|
+
* `useCrossTabState` when you just want a synced `useState`.
|
|
17
|
+
*
|
|
18
|
+
* Pass a `key` (the channel name) that is identical across the tabs meant to share
|
|
19
|
+
* state, and `null` to disable — no channel is opened, which also makes it SSR-safe
|
|
20
|
+
* and gives the caller a clean off switch. `value` is broadcast whenever it changes
|
|
21
|
+
* locally; an incoming remote value is handed to `applyRemote` WITHOUT being echoed
|
|
22
|
+
* back, so two tabs can't ping-pong. Last write wins.
|
|
23
|
+
*
|
|
24
|
+
* A tab opened mid-edit catches up: on mount it asks peers for the current state, and
|
|
25
|
+
* any tab already holding shared state replies. A fresh tab with nothing to share
|
|
26
|
+
* stays quiet, so it can never blank out the asker; and once a tab holds shared state
|
|
27
|
+
* it ignores further catch-up replies, keeping its own value authoritative.
|
|
28
|
+
*
|
|
29
|
+
* `value` must be structured-cloneable (plain data) — `postMessage` clones it.
|
|
30
|
+
*
|
|
31
|
+
* @param key The channel name; tabs sync only when their keys match. `null` disables syncing (and is SSR-safe).
|
|
32
|
+
* @param value The current value, broadcast to other tabs whenever it changes.
|
|
33
|
+
* @param applyRemote Called with a value received from another tab — apply it to your own state.
|
|
34
|
+
*/
|
|
35
|
+
export function useCrossTabMirror(key, value, applyRemote) {
|
|
36
|
+
const channelRef = React.useRef(null);
|
|
37
|
+
// The last value sent OR received on the channel. A local change whose value
|
|
38
|
+
// differs from it is broadcast; a value we just applied from a remote message
|
|
39
|
+
// matches it, so the broadcast effect below skips it — that's the echo guard.
|
|
40
|
+
const lastSyncedRef = React.useRef(value);
|
|
41
|
+
// Whether this tab holds shared state (has broadcast a change or adopted a peer's).
|
|
42
|
+
// Only a holder answers a newcomer's request, and a holder ignores catch-up replies.
|
|
43
|
+
const primedRef = React.useRef(false);
|
|
44
|
+
// Keep the latest `applyRemote` without re-opening the channel when an inline
|
|
45
|
+
// callback identity changes between renders.
|
|
46
|
+
const applyRemoteRef = React.useRef(applyRemote);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
applyRemoteRef.current = applyRemote;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Open the channel (declared before the broadcast effect, so on mount the channel
|
|
52
|
+
// exists by the time that effect first runs and skips the initial value).
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (key === null || typeof BroadcastChannel === 'undefined') {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const channel = new BroadcastChannel(key);
|
|
58
|
+
channelRef.current = channel;
|
|
59
|
+
channel.onmessage = event => {
|
|
60
|
+
const message = event.data;
|
|
61
|
+
if (message.kind === 'request') {
|
|
62
|
+
// A tab just came online and asked for the current state. Only a tab that
|
|
63
|
+
// holds shared state answers, so a fresh peer can't reply with a blank value.
|
|
64
|
+
if (primedRef.current) {
|
|
65
|
+
channel.postMessage({
|
|
66
|
+
kind: 'state',
|
|
67
|
+
value: lastSyncedRef.current,
|
|
68
|
+
reply: true
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// A catch-up reply must not overwrite state we already hold (a fast local edit,
|
|
74
|
+
// or an earlier reply we already took). A normal push — a peer's edit — always
|
|
75
|
+
// wins (last write wins).
|
|
76
|
+
if (message.reply && primedRef.current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
primedRef.current = true;
|
|
80
|
+
lastSyncedRef.current = message.value;
|
|
81
|
+
applyRemoteRef.current(message.value);
|
|
82
|
+
};
|
|
83
|
+
// Ask any existing tab to send its current state, so a tab opened mid-edit catches
|
|
84
|
+
// up instead of starting blank.
|
|
85
|
+
channel.postMessage({
|
|
86
|
+
kind: 'request'
|
|
87
|
+
});
|
|
88
|
+
return () => {
|
|
89
|
+
channel.close();
|
|
90
|
+
channelRef.current = null;
|
|
91
|
+
};
|
|
92
|
+
}, [key]);
|
|
93
|
+
|
|
94
|
+
// Broadcast local changes. A value we just received (so it equals `lastSyncedRef`)
|
|
95
|
+
// is not re-sent, which stops the echo.
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
const channel = channelRef.current;
|
|
98
|
+
if (!channel || Object.is(value, lastSyncedRef.current)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
lastSyncedRef.current = value;
|
|
102
|
+
primedRef.current = true;
|
|
103
|
+
channel.postMessage({
|
|
104
|
+
kind: 'state',
|
|
105
|
+
value
|
|
106
|
+
});
|
|
107
|
+
}, [value]);
|
|
108
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A `useState` whose value is mirrored across same-origin browser tabs/windows of the
|
|
4
|
+
* same page through a `BroadcastChannel` — set it in one tab and the others follow.
|
|
5
|
+
* For example, edit a live demo in a Chrome split view and both panes stay in sync.
|
|
6
|
+
*
|
|
7
|
+
* Drop-in for `useState`: returns `[value, setValue]` and supports lazy initializers
|
|
8
|
+
* and functional updates. The extra `key` is the channel name — tabs sync only when
|
|
9
|
+
* their keys match, and `null` disables syncing (also SSR-safe; no channel opens on
|
|
10
|
+
* the server). State is EPHEMERAL: nothing is persisted, but a tab opened mid-session
|
|
11
|
+
* catches up from a peer that already holds it instead of starting at `initialValue`.
|
|
12
|
+
* For state that must survive a reload, use `useLocalStorageState` instead.
|
|
13
|
+
*
|
|
14
|
+
* `value` must be structured-cloneable (plain data) — `postMessage` clones it. When the
|
|
15
|
+
* state lives elsewhere (a reducer, a store, a controller) and you only want to sync
|
|
16
|
+
* it, use the lower-level {@link useCrossTabMirror} instead.
|
|
17
|
+
*
|
|
18
|
+
* @param key The channel name; tabs sync only when their keys match. `null` disables syncing (and is SSR-safe).
|
|
19
|
+
* @param initialValue The initial state, or a lazy initializer — exactly like `useState`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useCrossTabState<T>(key: string | null, initialValue: T | (() => T)): [T, React.Dispatch<React.SetStateAction<T>>];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useCrossTabMirror } from "./useCrossTabMirror.mjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A `useState` whose value is mirrored across same-origin browser tabs/windows of the
|
|
8
|
+
* same page through a `BroadcastChannel` — set it in one tab and the others follow.
|
|
9
|
+
* For example, edit a live demo in a Chrome split view and both panes stay in sync.
|
|
10
|
+
*
|
|
11
|
+
* Drop-in for `useState`: returns `[value, setValue]` and supports lazy initializers
|
|
12
|
+
* and functional updates. The extra `key` is the channel name — tabs sync only when
|
|
13
|
+
* their keys match, and `null` disables syncing (also SSR-safe; no channel opens on
|
|
14
|
+
* the server). State is EPHEMERAL: nothing is persisted, but a tab opened mid-session
|
|
15
|
+
* catches up from a peer that already holds it instead of starting at `initialValue`.
|
|
16
|
+
* For state that must survive a reload, use `useLocalStorageState` instead.
|
|
17
|
+
*
|
|
18
|
+
* `value` must be structured-cloneable (plain data) — `postMessage` clones it. When the
|
|
19
|
+
* state lives elsewhere (a reducer, a store, a controller) and you only want to sync
|
|
20
|
+
* it, use the lower-level {@link useCrossTabMirror} instead.
|
|
21
|
+
*
|
|
22
|
+
* @param key The channel name; tabs sync only when their keys match. `null` disables syncing (and is SSR-safe).
|
|
23
|
+
* @param initialValue The initial state, or a lazy initializer — exactly like `useState`.
|
|
24
|
+
*/
|
|
25
|
+
export function useCrossTabState(key, initialValue) {
|
|
26
|
+
const [value, setValue] = React.useState(initialValue);
|
|
27
|
+
// `setValue` applies a peer's value directly; `useCrossTabMirror` broadcasts our
|
|
28
|
+
// resolved `value` on change and skips echoing back what it just handed us.
|
|
29
|
+
useCrossTabMirror(key, value, setValue);
|
|
30
|
+
return [value, setValue];
|
|
31
|
+
}
|
|
@@ -330,10 +330,8 @@ export function exportVariant(variantCode, config = {}) {
|
|
|
330
330
|
},
|
|
331
331
|
devDependencies: {
|
|
332
332
|
...(!isFramework && {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
'@vitejs/plugin-react': '^5',
|
|
336
|
-
vite: '^7'
|
|
333
|
+
'@vitejs/plugin-react': 'latest',
|
|
334
|
+
vite: 'latest'
|
|
337
335
|
}),
|
|
338
336
|
...(useTypescript && {
|
|
339
337
|
typescript: 'latest',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* without library-specific dependencies
|
|
5
5
|
*/
|
|
6
6
|
import type { VariantCode } from "../CodeHighlighter/types.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import type { ExportConfig } from "./exportVariant.mjs";
|
|
8
8
|
/**
|
|
9
9
|
* Export a VariantCode as a Create React App template
|
|
10
10
|
* Returns an object with the exported VariantCode and entrypoint path
|
package/useDemo/useDemo.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { UseCodeOpts } from "../useCode/index.mjs";
|
|
3
3
|
import type { ContentProps } from "../CodeHighlighter/types.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import type { ExportConfig } from "./exportVariant.mjs";
|
|
5
5
|
/**
|
|
6
6
|
* Demo templates use the exportVariant/exportVariantAsCra with flattenCodeVariant pattern:
|
|
7
7
|
*
|
|
@@ -74,6 +74,8 @@ export declare function useDemo<T extends {} = {}>(contentProps: ContentProps<T>
|
|
|
74
74
|
selectedTransform: string | null | undefined;
|
|
75
75
|
selectTransform: (transformName: string | null) => void;
|
|
76
76
|
pendingTransform: string | null | undefined;
|
|
77
|
+
editable: boolean;
|
|
78
|
+
setEditable?: (editable: boolean) => void;
|
|
77
79
|
setSource?: (source: string, fileName?: string) => void;
|
|
78
80
|
reset?: () => void;
|
|
79
81
|
refresh?: () => void;
|
|
@@ -82,6 +84,7 @@ export declare function useDemo<T extends {} = {}>(contentProps: ContentProps<T>
|
|
|
82
84
|
slug?: string;
|
|
83
85
|
};
|
|
84
86
|
component: string | number | bigint | true | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | Iterable<React.ReactNode> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ReactPortal | null | undefined> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
|
|
87
|
+
error: string | null;
|
|
85
88
|
focusRef: React.RefObject<HTMLButtonElement | null>;
|
|
86
89
|
resetFocus: () => void;
|
|
87
90
|
openStackBlitz: () => void;
|
package/useDemo/useDemo.mjs
CHANGED
|
@@ -81,6 +81,10 @@ export function useDemo(contentProps, opts) {
|
|
|
81
81
|
return effectiveComponents[code.selectedVariant] || null;
|
|
82
82
|
}, [effectiveComponents, code.selectedVariant]);
|
|
83
83
|
|
|
84
|
+
// Surface the selected variant's runtime error, reported by its preview
|
|
85
|
+
// component (e.g. `DemoRunner`) and bridged through `CodeHighlighterContext`.
|
|
86
|
+
const error = context?.errors?.[code.selectedVariant] ?? null;
|
|
87
|
+
|
|
84
88
|
// Demo-specific ref and focus management. Typed as `HTMLButtonElement` since
|
|
85
89
|
// the typical pattern is an invisible focus-target button rendered inside the
|
|
86
90
|
// demo. `resetFocus` simply calls `.focus()` on it.
|
|
@@ -181,6 +185,7 @@ export function useDemo(contentProps, opts) {
|
|
|
181
185
|
...code,
|
|
182
186
|
// Demo-specific additions
|
|
183
187
|
component,
|
|
188
|
+
error,
|
|
184
189
|
focusRef,
|
|
185
190
|
resetFocus,
|
|
186
191
|
openStackBlitz,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The lazy "build engine" chunk for the live-demo runtime: both halves bundled together
|
|
2
|
+
// so editing a demo fetches ONE chunk instead of two — the BUILD half (`buildScope` plus
|
|
3
|
+
// its scope-assembly and eval machinery) and the RENDER half (`DemoRunner` plus the
|
|
4
|
+
// react-runner reimplementation). It is imported DYNAMICALLY (never statically) from
|
|
5
|
+
// `useVariantBuilds` (which needs `buildScope`) and `useDemoController` (which needs
|
|
6
|
+
// `DemoRunner`), both under the `BuildEngine` webpack chunk name; see those files for
|
|
7
|
+
// why the engine is deferred to the first edit. Keeping it out of the eager
|
|
8
|
+
// `useDemoController` chunk is the whole point — don't add a static import of this file.
|
|
9
|
+
export { buildScope } from "./buildScope.mjs";
|
|
10
|
+
export { DemoRunner } from "./DemoRunner.mjs";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Scope } from "./types.mjs";
|
|
3
|
+
export interface DemoRunnerProps {
|
|
4
|
+
/** The already-transpiled entry code for the variant (from the transpile worker). */
|
|
5
|
+
runnerCode: string;
|
|
6
|
+
/** The built module registry the entry evaluates against. */
|
|
7
|
+
scope: Scope;
|
|
8
|
+
/** Compiled stylesheet text collected from the variant's CSS extra files. */
|
|
9
|
+
css?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Called with the runner's current error message (or `null` when it renders
|
|
12
|
+
* cleanly). Lets the controller surface errors while the live preview keeps
|
|
13
|
+
* showing the last good render.
|
|
14
|
+
*/
|
|
15
|
+
onError?: (message: string | null) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Shown when the entry throws BEFORE any successful render (no last-good element to keep)
|
|
18
|
+
* — typically the host's build-time render, so a broken first edit shows the original
|
|
19
|
+
* instead of blanking. The host (`CodeHighlighterClient`) injects it per variant.
|
|
20
|
+
*/
|
|
21
|
+
fallback?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Renders one ready demo variant. Wraps the content in an {@link ErrorBoundary} as
|
|
25
|
+
* a backstop; runtime errors thrown while rendering the entry are already caught by
|
|
26
|
+
* the inner `Runner` and reported through `onError` the same way.
|
|
27
|
+
*/
|
|
28
|
+
export declare function DemoRunner({
|
|
29
|
+
runnerCode,
|
|
30
|
+
scope,
|
|
31
|
+
css,
|
|
32
|
+
onError,
|
|
33
|
+
fallback
|
|
34
|
+
}: DemoRunnerProps): React.JSX.Element;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useRunner } from "./useRunner.mjs";
|
|
5
|
+
import { ErrorBoundary } from "./ErrorBoundary.mjs";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Presentational preview for a single demo variant. The async transpilation +
|
|
9
|
+
// scope build happen upstream in `useDemoController` (off the main thread, in a
|
|
10
|
+
// worker), so this is mounted only once a variant is READY and stays fully
|
|
11
|
+
// synchronous: it evaluates the already-transpiled entry, renders it, and reports
|
|
12
|
+
// runtime errors. Not built for direct use — `useDemoController` owns it.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
15
|
+
/**
|
|
16
|
+
* Renders one ready demo variant. Wraps the content in an {@link ErrorBoundary} as
|
|
17
|
+
* a backstop; runtime errors thrown while rendering the entry are already caught by
|
|
18
|
+
* the inner `Runner` and reported through `onError` the same way.
|
|
19
|
+
*/
|
|
20
|
+
export function DemoRunner({
|
|
21
|
+
runnerCode,
|
|
22
|
+
scope,
|
|
23
|
+
css,
|
|
24
|
+
onError,
|
|
25
|
+
fallback
|
|
26
|
+
}) {
|
|
27
|
+
return /*#__PURE__*/_jsx(ErrorBoundary, {
|
|
28
|
+
resetKeys: [runnerCode, scope],
|
|
29
|
+
onError: caught => onError?.(caught.message),
|
|
30
|
+
children: /*#__PURE__*/_jsx(DemoRunnerContent, {
|
|
31
|
+
runnerCode: runnerCode,
|
|
32
|
+
scope: scope,
|
|
33
|
+
css: css,
|
|
34
|
+
onError: onError,
|
|
35
|
+
fallback: fallback
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function DemoRunnerContent({
|
|
40
|
+
runnerCode,
|
|
41
|
+
scope,
|
|
42
|
+
css,
|
|
43
|
+
onError,
|
|
44
|
+
fallback
|
|
45
|
+
}) {
|
|
46
|
+
const {
|
|
47
|
+
element,
|
|
48
|
+
error
|
|
49
|
+
} = useRunner({
|
|
50
|
+
transpiledCode: runnerCode,
|
|
51
|
+
scope,
|
|
52
|
+
fallback
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Report the current error (or `null` when clean) to the host without unmounting
|
|
56
|
+
// the live preview, which `useRunner` keeps showing. The latest `onError` is kept
|
|
57
|
+
// in a ref so an inline callback doesn't re-fire the report.
|
|
58
|
+
const onErrorRef = React.useRef(onError);
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
onErrorRef.current = onError;
|
|
61
|
+
});
|
|
62
|
+
// Report on every change, but clear (`null`) only on unmount — clearing on each
|
|
63
|
+
// change would blink the error off and back on as the source is edited.
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
onErrorRef.current?.(error);
|
|
66
|
+
}, [error]);
|
|
67
|
+
React.useEffect(() => () => {
|
|
68
|
+
onErrorRef.current?.(null);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
// Render the compiled module CSS as a `<style>` inside the output (rather than
|
|
72
|
+
// injecting it into `document.head`) so the scoped styles are co-located with the
|
|
73
|
+
// demo and easy to inspect. A plain `<style>` (no `precedence`) is not hoisted by
|
|
74
|
+
// React, and its rules still apply document-wide.
|
|
75
|
+
return /*#__PURE__*/_jsxs(React.Fragment, {
|
|
76
|
+
children: [css ? /*#__PURE__*/_jsx("div", {
|
|
77
|
+
"data-demo-styles": "",
|
|
78
|
+
children: /*#__PURE__*/_jsx("style", {
|
|
79
|
+
children: css
|
|
80
|
+
})
|
|
81
|
+
}) : null, element]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ErrorBoundaryProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
/** Called with the error thrown while rendering `children`. */
|
|
5
|
+
onError?: (error: Error) => void;
|
|
6
|
+
/**
|
|
7
|
+
* When any value in this array changes, a caught error is cleared and
|
|
8
|
+
* `children` render again — letting a fixed input recover from a prior crash.
|
|
9
|
+
*/
|
|
10
|
+
resetKeys?: ReadonlyArray<unknown>;
|
|
11
|
+
/** Rendered in place of `children` while an error is caught. Defaults to `null`. */
|
|
12
|
+
fallback?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
interface ErrorBoundaryState {
|
|
15
|
+
error: Error | null;
|
|
16
|
+
/** The `resetKeys` the current state was derived from, compared element-wise. */
|
|
17
|
+
resetKeys: ReadonlyArray<unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generic error boundary: catches errors thrown while rendering its subtree,
|
|
21
|
+
* reports them through `onError`, and renders `fallback` instead of tearing down
|
|
22
|
+
* the surrounding tree. Pass `resetKeys` (e.g. the live-edited source) so a fixed
|
|
23
|
+
* input retries rendering after a previous crash.
|
|
24
|
+
*
|
|
25
|
+
* Implemented as a class because error boundaries require `getDerivedStateFromError`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
28
|
+
state: ErrorBoundaryState;
|
|
29
|
+
static getDerivedStateFromError(error: unknown): Partial<ErrorBoundaryState>;
|
|
30
|
+
static getDerivedStateFromProps(props: ErrorBoundaryProps, state: ErrorBoundaryState): Partial<ErrorBoundaryState> | null;
|
|
31
|
+
componentDidCatch(error: unknown): void;
|
|
32
|
+
render(): React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
/** Normalizes an unknown thrown value into an `Error`. */
|
|
5
|
+
function toError(thrown) {
|
|
6
|
+
return thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Whether two reset-key arrays differ in length or any element (`Object.is`). */
|
|
10
|
+
function resetKeysChanged(a, b) {
|
|
11
|
+
return a.length !== b.length || a.some((value, index) => !Object.is(value, b[index]));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generic error boundary: catches errors thrown while rendering its subtree,
|
|
16
|
+
* reports them through `onError`, and renders `fallback` instead of tearing down
|
|
17
|
+
* the surrounding tree. Pass `resetKeys` (e.g. the live-edited source) so a fixed
|
|
18
|
+
* input retries rendering after a previous crash.
|
|
19
|
+
*
|
|
20
|
+
* Implemented as a class because error boundaries require `getDerivedStateFromError`.
|
|
21
|
+
*/
|
|
22
|
+
export class ErrorBoundary extends React.Component {
|
|
23
|
+
state = {
|
|
24
|
+
error: null,
|
|
25
|
+
resetKeys: this.props.resetKeys ?? []
|
|
26
|
+
};
|
|
27
|
+
static getDerivedStateFromError(error) {
|
|
28
|
+
return {
|
|
29
|
+
error: toError(error)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
static getDerivedStateFromProps(props, state) {
|
|
33
|
+
const nextKeys = props.resetKeys ?? [];
|
|
34
|
+
if (!resetKeysChanged(state.resetKeys, nextKeys)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Inputs changed — clear any caught error so the children render again.
|
|
38
|
+
return {
|
|
39
|
+
error: null,
|
|
40
|
+
resetKeys: nextKeys
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
componentDidCatch(error) {
|
|
44
|
+
this.props.onError?.(toError(error));
|
|
45
|
+
}
|
|
46
|
+
render() {
|
|
47
|
+
return this.state.error ? this.props.fallback ?? null : this.props.children;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Scope } from "./types.mjs";
|
|
3
|
+
export interface RunnerProps {
|
|
4
|
+
/**
|
|
5
|
+
* Already-transpiled entry code (CommonJS, from the transpile worker). The
|
|
6
|
+
* Runner only evaluates + renders it; transpilation happens upstream (off the
|
|
7
|
+
* main thread), so a transpile error never reaches here.
|
|
8
|
+
*/
|
|
9
|
+
transpiledCode: string;
|
|
10
|
+
/** Identifiers (and an `import` registry) exposed to the evaluated entry. */
|
|
11
|
+
scope?: Scope;
|
|
12
|
+
/**
|
|
13
|
+
* Called after every (re)render with the error that occurred while evaluating or
|
|
14
|
+
* rendering the entry, or `undefined` when it rendered cleanly.
|
|
15
|
+
*/
|
|
16
|
+
onRendered?: (error?: Error) => void;
|
|
17
|
+
}
|
|
18
|
+
interface RunnerState {
|
|
19
|
+
element: React.ReactNode;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
/** The transpiled code the current `element`/`error` was derived from. */
|
|
22
|
+
renderedCode: string | null;
|
|
23
|
+
/** The scope the current `element`/`error` was derived from (compared by reference). */
|
|
24
|
+
renderedScope: Scope | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Evaluates already-transpiled entry code and renders the React node it exports,
|
|
28
|
+
* doubling as an error boundary so a runtime error thrown while rendering that node
|
|
29
|
+
* is caught instead of tearing down the host tree. Evaluate-time errors (a throw in
|
|
30
|
+
* the module body) and render-time errors are both reported through `onRendered`.
|
|
31
|
+
*
|
|
32
|
+
* Implemented as a class because error boundaries require
|
|
33
|
+
* `getDerivedStateFromError`.
|
|
34
|
+
*/
|
|
35
|
+
export declare class Runner extends React.Component<RunnerProps, RunnerState> {
|
|
36
|
+
state: RunnerState;
|
|
37
|
+
static getDerivedStateFromProps(props: RunnerProps, state: RunnerState): Partial<RunnerState> | null;
|
|
38
|
+
static getDerivedStateFromError(error: unknown): Partial<RunnerState>;
|
|
39
|
+
componentDidMount(): void;
|
|
40
|
+
componentDidUpdate(): void;
|
|
41
|
+
shouldComponentUpdate(nextProps: RunnerProps, nextState: RunnerState): boolean;
|
|
42
|
+
render(): React.ReactNode;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { instantiateElement } from "./instantiateElement.mjs";
|
|
5
|
+
/** Normalizes an unknown thrown value into an `Error`. */
|
|
6
|
+
function toError(thrown) {
|
|
7
|
+
return thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Evaluates already-transpiled entry code and renders the React node it exports,
|
|
12
|
+
* doubling as an error boundary so a runtime error thrown while rendering that node
|
|
13
|
+
* is caught instead of tearing down the host tree. Evaluate-time errors (a throw in
|
|
14
|
+
* the module body) and render-time errors are both reported through `onRendered`.
|
|
15
|
+
*
|
|
16
|
+
* Implemented as a class because error boundaries require
|
|
17
|
+
* `getDerivedStateFromError`.
|
|
18
|
+
*/
|
|
19
|
+
export class Runner extends React.Component {
|
|
20
|
+
state = {
|
|
21
|
+
element: null,
|
|
22
|
+
error: null,
|
|
23
|
+
renderedCode: null,
|
|
24
|
+
renderedScope: undefined
|
|
25
|
+
};
|
|
26
|
+
static getDerivedStateFromProps(props, state) {
|
|
27
|
+
// Regenerate only when an input changed; unrelated re-renders keep the element.
|
|
28
|
+
if (props.transpiledCode === state.renderedCode && props.scope === state.renderedScope) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return {
|
|
33
|
+
element: instantiateElement(props.transpiledCode, props.scope),
|
|
34
|
+
error: null,
|
|
35
|
+
renderedCode: props.transpiledCode,
|
|
36
|
+
renderedScope: props.scope
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
element: null,
|
|
41
|
+
error: toError(error),
|
|
42
|
+
renderedCode: props.transpiledCode,
|
|
43
|
+
renderedScope: props.scope
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
static getDerivedStateFromError(error) {
|
|
48
|
+
// A runtime error thrown while React rendered the generated node.
|
|
49
|
+
return {
|
|
50
|
+
error: toError(error)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
componentDidMount() {
|
|
54
|
+
this.props.onRendered?.(this.state.error ?? undefined);
|
|
55
|
+
}
|
|
56
|
+
componentDidUpdate() {
|
|
57
|
+
this.props.onRendered?.(this.state.error ?? undefined);
|
|
58
|
+
}
|
|
59
|
+
shouldComponentUpdate(nextProps, nextState) {
|
|
60
|
+
return nextProps.transpiledCode !== this.props.transpiledCode || nextProps.scope !== this.props.scope || nextState.error !== this.state.error ||
|
|
61
|
+
// Re-render (so `componentDidUpdate` re-reports via `onRendered`) when the
|
|
62
|
+
// callback changes even though the code/scope didn't — e.g. restoring the
|
|
63
|
+
// source to the exact code the cached element last rendered. Without this,
|
|
64
|
+
// `getDerivedStateFromProps` short-circuits, `onRendered` never fires, and a
|
|
65
|
+
// pending error never clears. The regenerate itself is still skipped.
|
|
66
|
+
nextProps.onRendered !== this.props.onRendered;
|
|
67
|
+
}
|
|
68
|
+
render() {
|
|
69
|
+
return this.state.error ? null : this.state.element;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrites a source's relative imports (`./x`, `../x`) to absolute specifiers
|
|
3
|
+
* under the scope-import prefix, resolving each against the importing file's
|
|
4
|
+
* own directory. This lets every extra file be registered under a single absolute
|
|
5
|
+
* key that resolves regardless of where the file lives — e.g. `dir/file.ts`
|
|
6
|
+
* importing `../root` becomes `<prefix>root`, and `root.ts` importing `./dir/file`
|
|
7
|
+
* becomes `<prefix>dir/file`. External (bare) specifiers are left untouched.
|
|
8
|
+
*
|
|
9
|
+
* Only needed when extra files span subdirectories; flat demos resolve fine with
|
|
10
|
+
* their plain `./name` specifiers and should skip this. `fileName` is the file's
|
|
11
|
+
* path within the demo (e.g. `dir/file.ts`); the main source lives at the root.
|
|
12
|
+
*/
|
|
13
|
+
export declare function absolutizeImports(source: string, fileName: string, prefix?: string): string;
|