@mui/internal-docs-infra 0.12.1-canary.12 → 0.12.1-canary.13

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.
@@ -18,7 +18,7 @@ export interface CodeControllerContext {
18
18
  * code is always a string to simplify use. It will be highlighted when it is passed as `code`.
19
19
  * This behavior depends on client-side highlighting and the CodeProvider component.
20
20
  */
21
- code?: ControlledCode;
21
+ code?: ControlledCode | null;
22
22
  /**
23
23
  * Controls the state for displaying the given code. This works with build-time and client-side
24
24
  * loading. If using server loading, the selection won't work for fallback loading and would
@@ -30,7 +30,7 @@ export interface CodeControllerContext {
30
30
  * called when the user interacts with the code highlighting. It's recommended to only set `code`
31
31
  * after the first `setCode` event fires to benefit from server or build-time rendering.
32
32
  */
33
- setCode?: React.Dispatch<React.SetStateAction<ControlledCode | undefined>>;
33
+ setCode?: React.Dispatch<React.SetStateAction<ControlledCode | null>>;
34
34
  /**
35
35
  * Setter function for updating the selection state. When provided in the context, this function
36
36
  * will be called when the user interacts with the code highlighting interface.
@@ -107,9 +107,9 @@ export declare const CodeControllerContext: React.Context<CodeControllerContext
107
107
  * - components: Override components for the preview
108
108
  */
109
109
  export declare function useControlledCode(): {
110
- code: ControlledCode | undefined;
110
+ code: ControlledCode | null;
111
111
  selection: Selection | undefined;
112
- setCode: React.Dispatch<React.SetStateAction<ControlledCode | undefined>> | undefined;
112
+ setCode: React.Dispatch<React.SetStateAction<ControlledCode | null>> | undefined;
113
113
  setSelection: React.Dispatch<React.SetStateAction<Selection>> | undefined;
114
114
  components: Record<string, React.ReactNode> | undefined;
115
115
  errors: Record<string, string | null> | undefined;
@@ -44,7 +44,7 @@ if (process.env.NODE_ENV !== "production") CodeControllerContext.displayName = "
44
44
  export function useControlledCode() {
45
45
  const context = React.useContext(CodeControllerContext);
46
46
  return {
47
- code: context?.code,
47
+ code: context?.code ?? null,
48
48
  selection: context?.selection,
49
49
  setCode: context?.setCode,
50
50
  setSelection: context?.setSelection,
@@ -1372,9 +1372,7 @@ export function CodeHighlighterClient(props) {
1372
1372
  setSelection: controlled?.setSelection || setSelection,
1373
1373
  components: bridgedComponents,
1374
1374
  errors: controlled?.errors,
1375
- // Only suppress when an external CodeController owns the code; static
1376
- // `props.code` still needs the locally-computed list.
1377
- availableTransforms: controlled?.code ? [] : availableTransforms,
1375
+ availableTransforms,
1378
1376
  url: props.url,
1379
1377
  deferHighlight,
1380
1378
  fallbacks: activeFallbacks,
@@ -1384,7 +1382,7 @@ export function CodeHighlighterClient(props) {
1384
1382
  onEditingActivated: handleEditingActivated,
1385
1383
  refresh,
1386
1384
  preParsedCache
1387
- }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, bridgedComponents, controlled?.errors, controlled?.code, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, editActivation, handleEditingActivated, refresh, preParsedCache]);
1385
+ }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, bridgedComponents, controlled?.errors, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, editActivation, handleEditingActivated, refresh, preParsedCache]);
1388
1386
  if (!props.variants && !props.components && !activeCode) {
1389
1387
  throw new Errors.ErrorCodeHighlighterClientMissingData();
1390
1388
  }
@@ -14,7 +14,7 @@ export interface PreParsedCacheEntry {
14
14
  }
15
15
  export interface CodeHighlighterContextType {
16
16
  code?: Code;
17
- setCode?: React.Dispatch<React.SetStateAction<ControlledCode | undefined>>;
17
+ setCode?: React.Dispatch<React.SetStateAction<ControlledCode | null>>;
18
18
  selection?: Selection;
19
19
  setSelection?: React.Dispatch<React.SetStateAction<Selection>>;
20
20
  components?: Record<string, React.ReactNode>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-docs-infra",
3
- "version": "0.12.1-canary.12",
3
+ "version": "0.12.1-canary.13",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "license": "MIT",
@@ -804,5 +804,5 @@
804
804
  "bin": {
805
805
  "docs-infra": "./cli/index.mjs"
806
806
  },
807
- "gitSha": "cd265618d793d94a8126db916013526ce860e836"
807
+ "gitSha": "61f66652a9d92b4ab95273edecf1a19351c89e18"
808
808
  }
@@ -385,6 +385,17 @@ export function useCode(contentProps, opts) {
385
385
  // is in scope and the block isn't hard-`disabled`. Otherwise `setEditable` is omitted,
386
386
  // so a host renders no toggle.
387
387
  const canToggleEditing = Boolean(controllerContext?.setCode) && !disabled;
388
+
389
+ // Discard live edits when the reader switches language.
390
+ const rawSelectTransform = transformManagement.selectTransform;
391
+ const resetSource = sourceEditing.reset;
392
+ const hasControlledEdits = Boolean(controllerContext?.code);
393
+ const selectTransform = React.useCallback(transformName => {
394
+ if (hasControlledEdits) {
395
+ resetSource?.();
396
+ }
397
+ rawSelectTransform(transformName);
398
+ }, [hasControlledEdits, resetSource, rawSelectTransform]);
388
399
  return {
389
400
  variants: variantSelection.variantKeys,
390
401
  selectedVariant: variantSelection.selectedVariantKey,
@@ -404,7 +415,7 @@ export function useCode(contentProps, opts) {
404
415
  copyMarkdown: copyFunctionality.copyMarkdown,
405
416
  availableTransforms: transformManagement.availableTransforms,
406
417
  selectedTransform: transformManagement.selectedTransform,
407
- selectTransform: transformManagement.selectTransform,
418
+ selectTransform,
408
419
  pendingTransform: transformManagement.pendingTransform,
409
420
  editable: uiState.editable,
410
421
  setEditable: canToggleEditing ? uiState.setEditable : undefined,
@@ -246,7 +246,7 @@ export function useSourceEditing({
246
246
  // survive the reset and the source viewer could reuse it for the rebuilt original.
247
247
  context?.preParsedCache?.clear();
248
248
  // Back to an empty controlled state, so the next edit re-tags `.original`.
249
- contextSetCode(undefined);
249
+ contextSetCode(null);
250
250
  }, [contextSetCode, context?.preParsedCache]);
251
251
  const isEditable = !disabled && Boolean(contextSetCode) && Boolean(selectedVariant);
252
252
  const canReset = !disabled && Boolean(contextSetCode);
@@ -122,7 +122,19 @@ export function useTransformManagement({
122
122
  // here, otherwise rename-only entries would be dropped from
123
123
  // resolution and the storage key would shift whenever a transform's
124
124
  // visibility changed between sibling demos.
125
- const applicableTransforms = React.useMemo(() => getApplicableTransforms(effectiveCode, selectedVariantKey), [effectiveCode, selectedVariantKey]);
125
+ const contextAvailableTransforms = context?.availableTransforms;
126
+ // What the CURRENT (possibly edited) code can actually apply. Empty while
127
+ // live-editing, since controlled code carries no transform manifest.
128
+ const applicableFromCode = React.useMemo(() => getApplicableTransforms(effectiveCode, selectedVariantKey), [effectiveCode, selectedVariantKey]);
129
+ const applicableTransforms = React.useMemo(() => {
130
+ // Fall back to the controller's visible list while editing so a language
131
+ // switch still validates — it discards the edit and re-applies to the
132
+ // pristine build-time source (see `useCode.selectTransform`).
133
+ if (applicableFromCode.length === 0 && contextAvailableTransforms?.length) {
134
+ return contextAvailableTransforms;
135
+ }
136
+ return applicableFromCode;
137
+ }, [applicableFromCode, contextAvailableTransforms]);
126
138
 
127
139
  // Coordinator key. Demos sharing the same applicable transform set
128
140
  // belong to the same coordination group: a user click in one demo
@@ -509,6 +521,11 @@ export function useTransformManagement({
509
521
  // `onCommit` when its `(variant, transform)` keys match the values
510
522
  // about to be rendered.
511
523
  const transformedFiles = React.useMemo(() => {
524
+ // While live-editing, `selectedTransform` may still be e.g. `js`, so
525
+ // return `undefined` to render the edited source as-is (see `useFileNavigation`).
526
+ if (delayedAppliedTransform && !applicableFromCode.includes(delayedAppliedTransform)) {
527
+ return undefined;
528
+ }
512
529
  if (precomputed && precomputed.variant === selectedVariant && precomputed.transform === delayedAppliedTransform) {
513
530
  return precomputed.result;
514
531
  }
@@ -521,7 +538,7 @@ export function useTransformManagement({
521
538
  return undefined;
522
539
  }
523
540
  return transformEngine(selectedVariant, delayedAppliedTransform, transformRuntimeDeps, context?.fallbacks);
524
- }, [precomputed, selectedVariant, delayedAppliedTransform, context?.fallbacks, transformEngine]);
541
+ }, [precomputed, selectedVariant, delayedAppliedTransform, applicableFromCode, context?.fallbacks, transformEngine]);
525
542
  const result = {
526
543
  availableTransforms,
527
544
  selectedTransform,
@@ -1,26 +1,15 @@
1
1
  import * as React from 'react';
2
2
  import type { ControlledCode } from '@mui/internal-docs-infra/CodeHighlighter/types';
3
- export interface UseDemoControllerOptions {
4
- /**
5
- * Keep the controlled code in sync across same-origin tabs/windows of the same page
6
- * (e.g. a Chrome split view) via a `BroadcastChannel`. On by default; pass `false`
7
- * to opt a demo out. SSR-safe — the channel only opens in the browser.
8
- */
9
- crossTabSync?: boolean;
3
+ import type { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerContext';
4
+ export interface UseDemoControllerResult {
5
+ /** The controlled source, keyed by variant. `null` until the first edit. */
6
+ code: ControlledCode | null;
10
7
  /**
11
- * The demo's source url (`import.meta.url`) the demo factory passes it through, so
12
- * a `DemoController` can forward its props straight to this hook. Used as the
13
- * per-demo `crossTabSync` key (the page path scopes it further) so each demo syncs
14
- * only with its counterpart in the other tab. Without it, sync falls back to
15
- * page-level — correct only when the page has a single demo.
8
+ * Updates the controlled source (e.g. as a reader edits a variant). Typed as the
9
+ * context's `setCode` (`Dispatch<SetStateAction>`) so the whole result drops straight
10
+ * into a `CodeControllerContext.Provider` with no cast.
16
11
  */
17
- url?: string;
18
- }
19
- export interface UseDemoControllerResult {
20
- /** The controlled source, keyed by variant. `undefined` until the first edit. */
21
- code: ControlledCode | undefined;
22
- /** Updates the controlled source (e.g. as a reader edits a variant). */
23
- setCode: React.Dispatch<React.SetStateAction<ControlledCode | undefined>>;
12
+ setCode: NonNullable<CodeControllerContext['setCode']>;
24
13
  /**
25
14
  * One live preview node per variant, keyed by variant — for the variants that
26
15
  * have finished building. `undefined` until at least one is ready, so a host can
@@ -62,4 +51,4 @@ export interface UseDemoControllerResult {
62
51
  * page (via the `crossTabSync` option), so a reader editing a demo in a Chrome split
63
52
  * view sees it update in both panes.
64
53
  */
65
- export declare function useDemoController(options?: UseDemoControllerOptions): UseDemoControllerResult;
54
+ export declare function useDemoController(): UseDemoControllerResult;
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as React from 'react';
4
4
  import { useCodeExternals } from '@mui/internal-docs-infra/CodeExternalsContext';
5
- import { useCrossTabState } from '@mui/internal-docs-infra/useCrossTabState';
6
5
  import { getTranspile } from "./transpileClientSingleton.mjs";
7
6
  import { useVariantBuilds } from "./useVariantBuilds.mjs";
8
7
 
@@ -49,22 +48,8 @@ const DemoRunner = /*#__PURE__*/React.lazy(() => preloadBuildEngine().then(modul
49
48
  * page (via the `crossTabSync` option), so a reader editing a demo in a Chrome split
50
49
  * view sees it update in both panes.
51
50
  */
52
- export function useDemoController(options = {}) {
53
- const {
54
- crossTabSync = true,
55
- url
56
- } = options;
57
- // The controlled code, owned here and mirrored across same-origin tabs of this page.
58
- // The channel name combines the page path with the per-demo `url`, so split-view tabs
59
- // of one page sync each demo independently; `null` (disabled, or on the server) opens
60
- // no channel and `useCrossTabState` behaves like a plain `useState`.
61
- const syncChannel = React.useMemo(() => {
62
- if (!crossTabSync || typeof window === 'undefined') {
63
- return null;
64
- }
65
- return `mui-docs-infra:demo-controller:${window.location.pathname}\u0000${url ?? ''}`;
66
- }, [crossTabSync, url]);
67
- const [code, setControlledCode] = useCrossTabState(syncChannel, undefined);
51
+ export function useDemoController() {
52
+ const [code, setControlledCode] = React.useState(null);
68
53
  // Build errors (transpile/CSS failures) and render errors (the entry throwing) live
69
54
  // in SEPARATE maps so they never clobber each other: a build error is owned by
70
55
  // `useVariantBuilds` (set on failure, cleared on the next good build); a render
@@ -102,11 +87,10 @@ export function useDemoController(options = {}) {
102
87
  // effect; the length guards keep each clear a no-op once its map is empty.
103
88
  const setCode = React.useCallback(action => {
104
89
  setControlledCode(action);
105
- // A reset passes the cleared value directly (never a functional update), so a
106
- // falsy argument is the reset — drop stale errors alongside it.
107
- if (typeof action !== 'function' && !action) {
108
- setBuildErrors(previous => Object.keys(previous).length > 0 ? {} : previous);
109
- setRenderErrors(previous => Object.keys(previous).length > 0 ? {} : previous);
90
+ // Drop stale errors alongside a reset (`code` cleared to `null`).
91
+ if (!action) {
92
+ setBuildErrors({});
93
+ setRenderErrors({});
110
94
  }
111
95
  }, [setControlledCode]);
112
96
 
@@ -28,4 +28,4 @@ export interface VariantBuild {
28
28
  * first-build-done set, and the deferred edit) lives in refs since it never
29
29
  * affects render; only the resolved `built` map is state.
30
30
  */
31
- export declare function useVariantBuilds(code: ControlledCode | undefined, transpile: Transpile | null, externals: Record<string, unknown>, report: (variant: string, message: string | null) => void): Record<string, VariantBuild>;
31
+ export declare function useVariantBuilds(code: ControlledCode | null | undefined, transpile: Transpile | null, externals: Record<string, unknown>, report: (variant: string, message: string | null) => void): Record<string, VariantBuild>;
@@ -139,7 +139,7 @@ export function useVariantBuilds(code, transpile, externals, report) {
139
139
  }
140
140
  // The attempt finished (failed) — later edits may now cancel.
141
141
  firstBuildDone.current.add(variant);
142
- report(variant, thrown instanceof Error ? thrown.message : String(thrown));
142
+ report(variant, thrown instanceof Error ? thrown.toString() : String(thrown));
143
143
  buildDeferred();
144
144
  });
145
145
  };