@mui/internal-docs-infra 0.2.3-canary.0 → 0.2.3-canary.10

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.
@@ -36,8 +36,13 @@ function processFlatMode(importResult, resolvedPathsMap) {
36
36
  var file = _fileMapping[_i];
37
37
  var fileName = file.segments[file.segments.length - 1];
38
38
  var isIndexFile = fileName.startsWith('index.');
39
+ var isUnderscoreIndexFile = fileName.startsWith('_index.');
39
40
  var candidateName = void 0;
40
- if (isIndexFile) {
41
+ if (isUnderscoreIndexFile) {
42
+ // Files starting with "_index." should be treated as direct index imports
43
+ // e.g., "../../dir/_index.module.css" -> "index.module.css"
44
+ candidateName = "index".concat(file.extension);
45
+ } else if (isIndexFile) {
41
46
  // Check if the original import was a direct index file (e.g., "./index.ext")
42
47
  var originalImportParts = file.originalImportPath.split('/');
43
48
  var isDirectIndexImport = originalImportParts.length === 2 && originalImportParts[0] === '.' && originalImportParts[1].startsWith('index.');
@@ -131,9 +136,13 @@ function processFlatMode(importResult, resolvedPathsMap) {
131
136
  var _file = _step.value;
132
137
  var _fileName = _file.segments[_file.segments.length - 1];
133
138
  var _isIndexFile = _fileName.startsWith('index.');
139
+ var _isUnderscoreIndexFile = _fileName.startsWith('_index.');
134
140
  var distinguishingSegment = _file.segments[_distinguishingIndex];
135
141
  var finalName = void 0;
136
- if (_isIndexFile) {
142
+ if (_isUnderscoreIndexFile) {
143
+ // Files starting with "_index." should always use "index" as the base name
144
+ finalName = "".concat(distinguishingSegment, "/index").concat(_file.extension);
145
+ } else if (_isIndexFile) {
137
146
  // Check if this was a direct index import
138
147
  var _originalImportParts = _file.originalImportPath.split('/');
139
148
  var _isDirectIndexImport = _originalImportParts.length === 2 && _originalImportParts[0] === '.' && _originalImportParts[1].startsWith('index.');
@@ -202,9 +211,13 @@ function processFlatMode(importResult, resolvedPathsMap) {
202
211
  var _file2 = _step3.value;
203
212
  var _fileName2 = _file2.segments[_file2.segments.length - 1];
204
213
  var _isIndexFile2 = _fileName2.startsWith('index.');
214
+ var _isUnderscoreIndexFile2 = _fileName2.startsWith('_index.');
205
215
  var _distinguishingSegment = _file2.segments[distinguishingIndex];
206
216
  var _finalName = void 0;
207
- if (_isIndexFile2) {
217
+ if (_isUnderscoreIndexFile2) {
218
+ // Files starting with "_index." should always use "index" as the base name
219
+ _finalName = "".concat(_distinguishingSegment, "/index").concat(_file2.extension);
220
+ } else if (_isIndexFile2) {
208
221
  // Check if this was a direct index import
209
222
  var _originalImportParts2 = _file2.originalImportPath.split('/');
210
223
  var _isDirectIndexImport2 = _originalImportParts2.length === 2 && _originalImportParts2[0] === '.' && _originalImportParts2[1].startsWith('index.');
@@ -9,6 +9,35 @@ import { decompressSync, strFromU8 } from 'fflate';
9
9
  import { decode } from 'uint8-to-base64';
10
10
  import { hastToJsx } from "../pipeline/hastUtils/index.js";
11
11
  import { jsx as _jsx } from "react/jsx-runtime";
12
+ var hastChildrenCache = new WeakMap();
13
+ var textChildrenCache = new WeakMap();
14
+ function renderCode(hastChildren, renderHast, text) {
15
+ if (renderHast) {
16
+ var jsx = hastChildrenCache.get(hastChildren);
17
+ if (!jsx) {
18
+ jsx = hastToJsx({
19
+ type: 'root',
20
+ children: hastChildren
21
+ });
22
+ hastChildrenCache.set(hastChildren, jsx);
23
+ }
24
+ return jsx;
25
+ }
26
+ if (text !== undefined) {
27
+ return text;
28
+ }
29
+ var txt = textChildrenCache.get(hastChildren);
30
+ if (!txt) {
31
+ txt = toText({
32
+ type: 'root',
33
+ children: hastChildren
34
+ }, {
35
+ whitespace: 'pre'
36
+ });
37
+ textChildrenCache.set(hastChildren, txt);
38
+ }
39
+ return txt;
40
+ }
12
41
  export function Pre(_ref) {
13
42
  var children = _ref.children,
14
43
  className = _ref.className,
@@ -96,49 +125,6 @@ export function Pre(_ref) {
96
125
  observer.current.observe(node);
97
126
  }
98
127
  }, []);
99
- var hastChildrenCache = React.useMemo(function () {
100
- return hast == null ? void 0 : hast.children.map(function () {
101
- return null;
102
- });
103
- }, [hast]);
104
- var textChildrenCache = React.useMemo(function () {
105
- return hast == null ? void 0 : hast.children.map(function () {
106
- return null;
107
- });
108
- }, [hast]);
109
- var renderCode = React.useCallback(function (index, hastChildren, renderHast, text) {
110
- if (renderHast) {
111
- var _cached = hastChildrenCache == null ? void 0 : hastChildrenCache[index];
112
- if (_cached) {
113
- return _cached;
114
- }
115
- var jsx = hastToJsx({
116
- type: 'root',
117
- children: hastChildren
118
- });
119
- if (hastChildrenCache) {
120
- hastChildrenCache[index] = jsx;
121
- }
122
- return jsx;
123
- }
124
- if (text !== undefined) {
125
- return text;
126
- }
127
- var cached = textChildrenCache == null ? void 0 : textChildrenCache[index];
128
- if (cached) {
129
- return cached;
130
- }
131
- var txt = toText({
132
- type: 'root',
133
- children: hastChildren
134
- }, {
135
- whitespace: 'pre'
136
- });
137
- if (textChildrenCache) {
138
- textChildrenCache[index] = txt;
139
- }
140
- return txt;
141
- }, [hastChildrenCache, textChildrenCache]);
142
128
  var frames = React.useMemo(function () {
143
129
  return hast == null ? void 0 : hast.children.map(function (child, index) {
144
130
  if (child.type !== 'element') {
@@ -151,7 +137,7 @@ export function Pre(_ref) {
151
137
  className: "frame",
152
138
  "data-frame": index,
153
139
  ref: observeFrame,
154
- children: renderCode(index, child.children, shouldHighlight && isVisible, (_child$properties = child.properties) != null && _child$properties.dataAsString ? String((_child$properties2 = child.properties) == null ? void 0 : _child$properties2.dataAsString) : undefined)
140
+ children: renderCode(child.children, shouldHighlight && isVisible, (_child$properties = child.properties) != null && _child$properties.dataAsString ? String((_child$properties2 = child.properties) == null ? void 0 : _child$properties2.dataAsString) : undefined)
155
141
  }, index);
156
142
  }
157
143
  return /*#__PURE__*/_jsx(React.Fragment, {
@@ -160,7 +146,7 @@ export function Pre(_ref) {
160
146
  })
161
147
  }, index);
162
148
  });
163
- }, [hast, renderCode, observeFrame, shouldHighlight, visibleFrames]);
149
+ }, [hast, observeFrame, shouldHighlight, visibleFrames]);
164
150
  return /*#__PURE__*/_jsx("pre", {
165
151
  ref: bindIntersectionObserver,
166
152
  className: className,
@@ -9,6 +9,19 @@ export type UseCodeOpts = {
9
9
  githubUrlPrefix?: string;
10
10
  initialVariant?: string;
11
11
  initialTransform?: string;
12
+ /**
13
+ * Controls hash removal behavior when user interacts with file tabs:
14
+ * - 'remove-hash': Remove entire hash (default)
15
+ * - 'remove-filename': Remove only filename, keep variant in hash
16
+ */
17
+ fileHashMode?: 'remove-hash' | 'remove-filename';
18
+ /**
19
+ * Controls when to save hash variant to localStorage:
20
+ * - 'on-load': Save immediately when page loads with hash
21
+ * - 'on-interaction': Save only when user clicks a tab (default)
22
+ * - 'never': Never save hash variant to localStorage
23
+ */
24
+ saveHashVariantToLocalStorage?: 'on-load' | 'on-interaction' | 'never';
12
25
  };
13
26
  type UserProps<T extends {} = {}> = T & {
14
27
  name?: string;
@@ -18,7 +18,11 @@ export function useCode(contentProps, opts) {
18
18
  initialVariant = _ref.initialVariant,
19
19
  initialTransform = _ref.initialTransform,
20
20
  preClassName = _ref.preClassName,
21
- preRef = _ref.preRef;
21
+ preRef = _ref.preRef,
22
+ _ref$fileHashMode = _ref.fileHashMode,
23
+ fileHashMode = _ref$fileHashMode === void 0 ? 'remove-hash' : _ref$fileHashMode,
24
+ _ref$saveHashVariantT = _ref.saveHashVariantToLocalStorage,
25
+ saveHashVariantToLocalStorage = _ref$saveHashVariantT === void 0 ? 'on-interaction' : _ref$saveHashVariantT;
22
26
 
23
27
  // Safely try to get context values - will be undefined if not in context
24
28
  var context = useCodeHighlighterContextOptional();
@@ -58,9 +62,10 @@ export function useCode(contentProps, opts) {
58
62
  });
59
63
  }, [contentProps, context == null ? void 0 : context.url]);
60
64
 
61
- // Sub-hook: UI State Management
65
+ // Sub-hook: UI State Management (needs slug to check for relevant hash)
62
66
  var uiState = useUIState({
63
- defaultOpen: defaultOpen
67
+ defaultOpen: defaultOpen,
68
+ mainSlug: userProps.slug
64
69
  });
65
70
 
66
71
  // Sub-hook: Variant Selection
@@ -68,7 +73,8 @@ export function useCode(contentProps, opts) {
68
73
  effectiveCode: effectiveCode,
69
74
  initialVariant: initialVariant,
70
75
  variantType: contentProps.variantType,
71
- mainSlug: userProps.slug
76
+ mainSlug: userProps.slug,
77
+ saveHashVariantToLocalStorage: saveHashVariantToLocalStorage
72
78
  });
73
79
 
74
80
  // Sub-hook: Transform Management
@@ -89,11 +95,14 @@ export function useCode(contentProps, opts) {
89
95
  selectedVariantKey: variantSelection.selectedVariantKey,
90
96
  selectVariant: variantSelection.selectVariantProgrammatic,
91
97
  variantKeys: variantSelection.variantKeys,
92
- initialVariant: initialVariant,
93
98
  shouldHighlight: shouldHighlight,
94
99
  preClassName: preClassName,
95
100
  preRef: preRef,
96
- effectiveCode: effectiveCode
101
+ effectiveCode: effectiveCode,
102
+ fileHashMode: fileHashMode,
103
+ saveHashVariantToLocalStorage: saveHashVariantToLocalStorage,
104
+ saveVariantToLocalStorage: variantSelection.saveVariantToLocalStorage,
105
+ hashVariant: variantSelection.hashVariant
97
106
  });
98
107
 
99
108
  // Sub-hook: Copy Functionality
@@ -22,11 +22,14 @@ interface UseFileNavigationProps {
22
22
  selectedVariantKey?: string;
23
23
  variantKeys?: string[];
24
24
  shouldHighlight: boolean;
25
- initialVariant?: string;
26
25
  preClassName?: string;
27
26
  preRef?: React.Ref<HTMLPreElement>;
28
27
  effectiveCode?: Code;
29
28
  selectVariant?: React.Dispatch<React.SetStateAction<string>>;
29
+ fileHashMode?: 'remove-hash' | 'remove-filename';
30
+ saveHashVariantToLocalStorage?: 'on-load' | 'on-interaction' | 'never';
31
+ saveVariantToLocalStorage?: (variant: string) => void;
32
+ hashVariant?: string | null;
30
33
  }
31
34
  export interface UseFileNavigationResult {
32
35
  selectedFileName: string | undefined;
@@ -54,11 +57,14 @@ export declare function useFileNavigation({
54
57
  mainSlug,
55
58
  selectedVariantKey,
56
59
  variantKeys,
57
- initialVariant,
58
60
  shouldHighlight,
59
61
  preClassName,
60
62
  preRef,
61
63
  effectiveCode,
62
- selectVariant
64
+ selectVariant,
65
+ fileHashMode,
66
+ saveHashVariantToLocalStorage,
67
+ saveVariantToLocalStorage,
68
+ hashVariant
63
69
  }: UseFileNavigationProps): UseFileNavigationResult;
64
70
  export {};
@@ -37,13 +37,13 @@ export function isHashRelevantToDemo(urlHash, mainSlug) {
37
37
 
38
38
  /**
39
39
  * Generates a file slug based on main slug, file name, and variant name
40
+ * All variants except "Default" include the variant name in the hash
40
41
  * @param mainSlug - The main component/demo slug
41
42
  * @param fileName - The file name
42
43
  * @param variantName - The variant name
43
- * @param isInitialVariant - Whether this is the initial/default variant
44
44
  * @returns Generated file slug
45
45
  */
46
- function generateFileSlug(mainSlug, fileName, variantName, isInitialVariant) {
46
+ function generateFileSlug(mainSlug, fileName, variantName) {
47
47
  // Extract base name from filename (strip extension)
48
48
  var lastDotIndex = fileName.lastIndexOf('.');
49
49
  var baseName = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
@@ -62,8 +62,9 @@ function generateFileSlug(mainSlug, fileName, variantName, isInitialVariant) {
62
62
  return kebabFileName;
63
63
  }
64
64
 
65
- // Format: mainSlug:fileName.ext (for initial variant) or mainSlug:variantName:fileName.ext
66
- if (isInitialVariant) {
65
+ // Format: mainSlug:fileName.ext (for Default variant) or mainSlug:variantName:fileName.ext
66
+ // "Default" variant is treated specially and doesn't include variant name in hash
67
+ if (variantName === 'Default') {
67
68
  return "".concat(kebabMainSlug, ":").concat(kebabFileName);
68
69
  }
69
70
  return "".concat(kebabMainSlug, ":").concat(kebabVariantName, ":").concat(kebabFileName);
@@ -80,29 +81,23 @@ export function useFileNavigation(_ref) {
80
81
  selectedVariantKey = _ref$selectedVariantK === void 0 ? '' : _ref$selectedVariantK,
81
82
  _ref$variantKeys = _ref.variantKeys,
82
83
  variantKeys = _ref$variantKeys === void 0 ? [] : _ref$variantKeys,
83
- initialVariant = _ref.initialVariant,
84
84
  shouldHighlight = _ref.shouldHighlight,
85
85
  preClassName = _ref.preClassName,
86
86
  preRef = _ref.preRef,
87
87
  effectiveCode = _ref.effectiveCode,
88
- selectVariant = _ref.selectVariant;
88
+ selectVariant = _ref.selectVariant,
89
+ _ref$fileHashMode = _ref.fileHashMode,
90
+ fileHashMode = _ref$fileHashMode === void 0 ? 'remove-hash' : _ref$fileHashMode,
91
+ _ref$saveHashVariantT = _ref.saveHashVariantToLocalStorage,
92
+ saveHashVariantToLocalStorage = _ref$saveHashVariantT === void 0 ? 'on-interaction' : _ref$saveHashVariantT,
93
+ saveVariantToLocalStorage = _ref.saveVariantToLocalStorage,
94
+ hashVariant = _ref.hashVariant;
89
95
  // Keep selectedFileName as untransformed filename for internal tracking
90
96
  var _React$useState = React.useState(selectedVariant == null ? void 0 : selectedVariant.fileName),
91
97
  _React$useState2 = _slicedToArray(_React$useState, 2),
92
98
  selectedFileNameInternal = _React$useState2[0],
93
99
  setSelectedFileNameInternal = _React$useState2[1];
94
100
 
95
- // Track user interaction locally
96
- var _React$useState3 = React.useState(false),
97
- _React$useState4 = _slicedToArray(_React$useState3, 2),
98
- hasUserInteraction = _React$useState4[0],
99
- setHasUserInteraction = _React$useState4[1];
100
-
101
- // Helper to mark user interaction
102
- var markUserInteraction = React.useCallback(function () {
103
- setHasUserInteraction(true);
104
- }, []);
105
-
106
101
  // Use the simplified URL hash hook
107
102
  var _useUrlHashState = useUrlHashState(),
108
103
  _useUrlHashState2 = _slicedToArray(_useUrlHashState, 2),
@@ -113,6 +108,37 @@ export function useFileNavigation(_ref) {
113
108
  var pendingFileSelection = React.useRef(null);
114
109
  var justCompletedPendingSelection = React.useRef(false);
115
110
 
111
+ // Track the previous variant key to detect user-initiated changes
112
+ var prevVariantKeyRef = React.useRef(selectedVariantKey);
113
+ var _React$useState3 = React.useState(selectedVariantKey),
114
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
115
+ prevVariantKeyState = _React$useState4[0],
116
+ setPrevVariantKeyState = _React$useState4[1];
117
+ var isInitialMount = React.useRef(true);
118
+
119
+ // Detect if the current variant change was driven by a hash change
120
+ // A variant change is hash-driven if the hash has a variant that matches where we're going
121
+ // AND we weren't already on that variant (i.e., the hash is what triggered the change)
122
+ var _React$useState5 = React.useState(hashVariant || null),
123
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
124
+ prevHashVariant = _React$useState6[0],
125
+ setPrevHashVariant = _React$useState6[1];
126
+ var isHashDrivenVariantChange = hashVariant === selectedVariantKey && prevVariantKeyState !== selectedVariantKey;
127
+
128
+ // Update prevHashVariant when hashVariant changes
129
+ React.useEffect(function () {
130
+ if (hashVariant !== prevHashVariant) {
131
+ setPrevHashVariant(hashVariant || null);
132
+ }
133
+ }, [hashVariant, prevHashVariant]);
134
+
135
+ // Update prevVariantKeyState when variant changes
136
+ React.useEffect(function () {
137
+ if (selectedVariantKey !== prevVariantKeyState) {
138
+ setPrevVariantKeyState(selectedVariantKey);
139
+ }
140
+ }, [selectedVariantKey, prevVariantKeyState]);
141
+
116
142
  // Helper function to check URL hash and switch to matching file
117
143
  var checkUrlHashAndSelectFile = React.useCallback(function () {
118
144
  if (!hash) {
@@ -125,11 +151,9 @@ export function useFileNavigation(_ref) {
125
151
 
126
152
  // Step 1: Check current variant (if we have one)
127
153
  if (selectedVariant) {
128
- var isInitialVariant = initialVariant ? selectedVariantKey === initialVariant : variantKeys.length === 0 || selectedVariantKey === variantKeys[0];
129
-
130
154
  // Check main file
131
155
  if (selectedVariant.fileName) {
132
- var mainFileSlug = generateFileSlug(mainSlug, selectedVariant.fileName, selectedVariantKey, isInitialVariant);
156
+ var mainFileSlug = generateFileSlug(mainSlug, selectedVariant.fileName, selectedVariantKey);
133
157
  if (hash === mainFileSlug) {
134
158
  matchingFileName = selectedVariant.fileName;
135
159
  matchingVariantKey = selectedVariantKey;
@@ -140,7 +164,7 @@ export function useFileNavigation(_ref) {
140
164
  if (!matchingFileName && selectedVariant.extraFiles) {
141
165
  for (var _i = 0, _Object$keys = Object.keys(selectedVariant.extraFiles); _i < _Object$keys.length; _i++) {
142
166
  var fileName = _Object$keys[_i];
143
- var fileSlug = generateFileSlug(mainSlug, fileName, selectedVariantKey, isInitialVariant);
167
+ var fileSlug = generateFileSlug(mainSlug, fileName, selectedVariantKey);
144
168
  if (hash === fileSlug) {
145
169
  matchingFileName = fileName;
146
170
  matchingVariantKey = selectedVariantKey;
@@ -156,7 +180,7 @@ export function useFileNavigation(_ref) {
156
180
  try {
157
181
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
158
182
  var file = _step.value;
159
- var _fileSlug = generateFileSlug(mainSlug, file.originalName, selectedVariantKey, isInitialVariant);
183
+ var _fileSlug = generateFileSlug(mainSlug, file.originalName, selectedVariantKey);
160
184
  if (hash === _fileSlug) {
161
185
  matchingFileName = file.originalName;
162
186
  matchingVariantKey = selectedVariantKey;
@@ -181,11 +205,10 @@ export function useFileNavigation(_ref) {
181
205
  if (variantKey === selectedVariantKey || !variant || typeof variant === 'string') {
182
206
  continue;
183
207
  }
184
- var _isInitialVariant = initialVariant ? variantKey === initialVariant : variantKeys.length === 0 || variantKey === variantKeys[0];
185
208
 
186
209
  // Check main file
187
210
  if (variant.fileName) {
188
- var _mainFileSlug = generateFileSlug(mainSlug, variant.fileName, variantKey, _isInitialVariant);
211
+ var _mainFileSlug = generateFileSlug(mainSlug, variant.fileName, variantKey);
189
212
  if (hash === _mainFileSlug) {
190
213
  matchingFileName = variant.fileName;
191
214
  matchingVariantKey = variantKey;
@@ -197,7 +220,7 @@ export function useFileNavigation(_ref) {
197
220
  if (!matchingFileName && variant.extraFiles) {
198
221
  for (var _i3 = 0, _Object$keys2 = Object.keys(variant.extraFiles); _i3 < _Object$keys2.length; _i3++) {
199
222
  var _fileName = _Object$keys2[_i3];
200
- var _fileSlug2 = generateFileSlug(mainSlug, _fileName, variantKey, _isInitialVariant);
223
+ var _fileSlug2 = generateFileSlug(mainSlug, _fileName, variantKey);
201
224
  if (hash === _fileSlug2) {
202
225
  matchingFileName = _fileName;
203
226
  matchingVariantKey = variantKey;
@@ -223,16 +246,13 @@ export function useFileNavigation(_ref) {
223
246
  // Set the file if we're in the correct variant
224
247
  pendingFileSelection.current = null;
225
248
  setSelectedFileNameInternal(matchingFileName);
226
- markUserInteraction();
227
249
  }
228
- }, [hash, selectedVariant, selectedVariantKey, variantKeys, initialVariant, mainSlug, transformedFiles, effectiveCode, selectVariant, markUserInteraction]);
250
+ }, [hash, selectedVariant, selectedVariantKey, mainSlug, transformedFiles, effectiveCode, selectVariant]);
229
251
 
230
252
  // Run hash check when URL hash changes to select the matching file
231
- // Only depends on hash to avoid re-running when the callback recreates due to variant state changes
232
253
  React.useEffect(function () {
233
254
  checkUrlHashAndSelectFile();
234
- // eslint-disable-next-line react-hooks/exhaustive-deps
235
- }, [hash]);
255
+ }, [checkUrlHashAndSelectFile]);
236
256
 
237
257
  // When variant switches with a pending file selection, complete the file selection
238
258
  React.useEffect(function () {
@@ -241,11 +261,10 @@ export function useFileNavigation(_ref) {
241
261
  pendingFileSelection.current = null;
242
262
  justCompletedPendingSelection.current = true;
243
263
  setSelectedFileNameInternal(fileToSelect);
244
- markUserInteraction();
245
264
  } else {
246
265
  justCompletedPendingSelection.current = false;
247
266
  }
248
- }, [selectedVariantKey, selectedVariant, markUserInteraction]);
267
+ }, [selectedVariantKey, selectedVariant]);
249
268
 
250
269
  // Reset selectedFileName when variant changes
251
270
  React.useEffect(function () {
@@ -263,38 +282,49 @@ export function useFileNavigation(_ref) {
263
282
  }
264
283
  }, [selectedVariant, selectedFileNameInternal]);
265
284
 
266
- // Update URL when variant changes (to reflect new slug for current file)
285
+ // Update hash when variant changes (user-initiated variant switch)
267
286
  React.useEffect(function () {
268
- if (!selectedVariant || typeof window === 'undefined' || !selectedFileNameInternal || !hasUserInteraction) {
287
+ // Skip on initial mount - let hash-driven navigation handle it
288
+ if (isInitialMount.current) {
289
+ isInitialMount.current = false;
290
+ prevVariantKeyRef.current = selectedVariantKey;
269
291
  return;
270
292
  }
271
293
 
272
- // Determine if this is the initial variant
273
- var isInitialVariant = initialVariant ? selectedVariantKey === initialVariant : variantKeys.length === 0 || selectedVariantKey === variantKeys[0];
294
+ // Only update hash if there's already a relevant hash present
295
+ if (typeof window === 'undefined' || !isHashRelevantToDemo(hash, mainSlug)) {
296
+ prevVariantKeyRef.current = selectedVariantKey;
297
+ return;
298
+ }
274
299
 
275
- // Generate the new slug for the currently selected file
276
- var fileSlug = '';
277
- if (transformedFiles) {
278
- var file = transformedFiles.files.find(function (f) {
279
- return f.originalName === selectedFileNameInternal;
280
- });
281
- if (file) {
282
- fileSlug = generateFileSlug(mainSlug, file.originalName, selectedVariantKey, isInitialVariant);
283
- }
284
- } else {
285
- fileSlug = generateFileSlug(mainSlug, selectedFileNameInternal, selectedVariantKey, isInitialVariant);
300
+ // Skip if variant hasn't actually changed
301
+ if (prevVariantKeyRef.current === selectedVariantKey) {
302
+ return;
286
303
  }
287
304
 
288
- // Only update the URL hash if it's different from current hash
289
- if (fileSlug && hash !== fileSlug) {
290
- // Only update if current hash is for the same demo (starts with mainSlug)
291
- // Don't set hash if there's no existing hash - variant changes shouldn't add hashes
292
- if (isHashRelevantToDemo(hash, mainSlug)) {
293
- setHash(fileSlug);
305
+ // Skip if this is a hash-driven variant change (hash is driving the variant selection)
306
+ if (pendingFileSelection.current || justCompletedPendingSelection.current || isHashDrivenVariantChange) {
307
+ prevVariantKeyRef.current = selectedVariantKey;
308
+ return;
309
+ }
310
+
311
+ // User switched variants, update hash based on fileHashMode
312
+ // Note: localStorage is already saved by setSelectedVariantKeyAsUser
313
+ if (fileHashMode === 'remove-filename') {
314
+ // Keep variant in hash: mainSlug or mainSlug:variant (for non-Default variants)
315
+ var kebabMainSlug = toKebabCase(mainSlug);
316
+ if (selectedVariantKey === 'Default') {
317
+ setHash(kebabMainSlug);
318
+ } else {
319
+ var kebabVariantName = toKebabCase(selectedVariantKey);
320
+ setHash("".concat(kebabMainSlug, ":").concat(kebabVariantName));
294
321
  }
295
- // Otherwise, don't update - either no hash exists or hash is for a different demo
322
+ } else {
323
+ // Remove entire hash
324
+ setHash(null);
296
325
  }
297
- }, [selectedVariant, selectedFileNameInternal, transformedFiles, mainSlug, selectedVariantKey, variantKeys, initialVariant, hasUserInteraction, setHash, hash]);
326
+ prevVariantKeyRef.current = selectedVariantKey;
327
+ }, [selectedVariantKey, hash, mainSlug, fileHashMode, setHash, isHashDrivenVariantChange]);
298
328
 
299
329
  // Compute the displayed filename (transformed if applicable)
300
330
  var selectedFileName = React.useMemo(function () {
@@ -446,15 +476,12 @@ export function useFileNavigation(_ref) {
446
476
  return [];
447
477
  }
448
478
 
449
- // Determine if this is the initial variant
450
- var isInitialVariant = initialVariant ? selectedVariantKey === initialVariant : variantKeys.length === 0 || selectedVariantKey === variantKeys[0];
451
-
452
479
  // If we have transformed files, use them
453
480
  if (transformedFiles) {
454
481
  return transformedFiles.files.map(function (f) {
455
482
  return {
456
483
  name: f.name,
457
- slug: generateFileSlug(mainSlug, f.originalName, selectedVariantKey, isInitialVariant),
484
+ slug: generateFileSlug(mainSlug, f.originalName, selectedVariantKey),
458
485
  component: f.component
459
486
  };
460
487
  });
@@ -467,7 +494,7 @@ export function useFileNavigation(_ref) {
467
494
  if (selectedVariant.fileName && selectedVariant.source) {
468
495
  result.push({
469
496
  name: selectedVariant.fileName,
470
- slug: generateFileSlug(mainSlug, selectedVariant.fileName, selectedVariantKey, isInitialVariant),
497
+ slug: generateFileSlug(mainSlug, selectedVariant.fileName, selectedVariantKey),
471
498
  component: /*#__PURE__*/_jsx(Pre, {
472
499
  className: preClassName,
473
500
  ref: preRef,
@@ -494,7 +521,7 @@ export function useFileNavigation(_ref) {
494
521
  }
495
522
  result.push({
496
523
  name: fileName,
497
- slug: generateFileSlug(mainSlug, fileName, selectedVariantKey, isInitialVariant),
524
+ slug: generateFileSlug(mainSlug, fileName, selectedVariantKey),
498
525
  component: /*#__PURE__*/_jsx(Pre, {
499
526
  className: preClassName,
500
527
  ref: preRef,
@@ -505,7 +532,7 @@ export function useFileNavigation(_ref) {
505
532
  });
506
533
  }
507
534
  return result;
508
- }, [selectedVariant, transformedFiles, mainSlug, selectedVariantKey, variantKeys, initialVariant, shouldHighlight, preClassName, preRef]);
535
+ }, [selectedVariant, transformedFiles, mainSlug, selectedVariantKey, shouldHighlight, preClassName, preRef]);
509
536
 
510
537
  // Create a wrapper for selectFileName that handles transformed filenames and URL updates
511
538
  var selectFileName = React.useCallback(function (fileName) {
@@ -513,10 +540,6 @@ export function useFileNavigation(_ref) {
513
540
  return;
514
541
  }
515
542
  var targetFileName = fileName;
516
- var fileSlug = '';
517
-
518
- // Determine if this is the initial variant
519
- var isInitialVariant = initialVariant ? selectedVariantKey === initialVariant : variantKeys.length === 0 || selectedVariantKey === variantKeys[0];
520
543
 
521
544
  // If we have transformed files, we need to reverse-lookup the original filename
522
545
  if (transformedFiles) {
@@ -526,7 +549,6 @@ export function useFileNavigation(_ref) {
526
549
  });
527
550
  if (fileByTransformedName) {
528
551
  targetFileName = fileByTransformedName.originalName;
529
- fileSlug = generateFileSlug(mainSlug, fileByTransformedName.originalName, selectedVariantKey, isInitialVariant);
530
552
  } else {
531
553
  // Check if the fileName is already an original name
532
554
  var fileByOriginalName = transformedFiles.files.find(function (f) {
@@ -534,21 +556,32 @@ export function useFileNavigation(_ref) {
534
556
  });
535
557
  if (fileByOriginalName) {
536
558
  targetFileName = fileName;
537
- fileSlug = generateFileSlug(mainSlug, fileName, selectedVariantKey, isInitialVariant);
538
559
  }
539
560
  }
540
- } else {
541
- // No transformed files, generate slug directly
542
- fileSlug = generateFileSlug(mainSlug, fileName, selectedVariantKey, isInitialVariant);
543
561
  }
544
562
 
545
- // Update the URL hash without adding to history (replaceState)
546
- if (typeof window !== 'undefined' && fileSlug && hash !== fileSlug) {
547
- setHash(fileSlug); // Use the new URL hash hook
563
+ // Handle hash removal based on fileHashMode
564
+ if (typeof window !== 'undefined' && isHashRelevantToDemo(hash, mainSlug)) {
565
+ // Save variant to localStorage if on-interaction mode (clicking a tab counts as interaction)
566
+ if (saveVariantToLocalStorage && saveHashVariantToLocalStorage === 'on-interaction') {
567
+ saveVariantToLocalStorage(selectedVariantKey);
568
+ }
569
+ if (fileHashMode === 'remove-filename') {
570
+ // Keep variant in hash: mainSlug or mainSlug:variant (for non-Default variants)
571
+ var kebabMainSlug = toKebabCase(mainSlug);
572
+ if (selectedVariantKey === 'Default') {
573
+ setHash(kebabMainSlug);
574
+ } else {
575
+ var kebabVariantName = toKebabCase(selectedVariantKey);
576
+ setHash("".concat(kebabMainSlug, ":").concat(kebabVariantName));
577
+ }
578
+ } else {
579
+ // Remove entire hash
580
+ setHash(null);
581
+ }
548
582
  }
549
- markUserInteraction(); // Mark that user has made an explicit selection
550
583
  setSelectedFileNameInternal(targetFileName);
551
- }, [selectedVariant, transformedFiles, mainSlug, selectedVariantKey, variantKeys, initialVariant, setHash, markUserInteraction, hash]);
584
+ }, [selectedVariant, transformedFiles, mainSlug, selectedVariantKey, fileHashMode, hash, setHash, saveHashVariantToLocalStorage, saveVariantToLocalStorage]);
552
585
 
553
586
  // Memoized array of all file slugs for all variants
554
587
  var allFilesSlugs = React.useMemo(function () {
@@ -570,14 +603,24 @@ export function useFileNavigation(_ref) {
570
603
  return 1; // continue
571
604
  }
572
605
 
573
- // Determine if this is the initial variant
574
- var isInitialVariant = initialVariant ? variantKey === initialVariant : variantKeys.length === 0 || variantKey === variantKeys[0];
606
+ // Add variant-only slug (points to main file of the variant)
607
+ // Skip for Default variant since it doesn't have variant name in hash
608
+ if (variant.fileName && variantKey !== 'Default') {
609
+ var kebabMainSlug = toKebabCase(mainSlug);
610
+ var kebabVariantName = toKebabCase(variantKey);
611
+ var variantOnlySlug = "".concat(kebabMainSlug, ":").concat(kebabVariantName);
612
+ result.push({
613
+ fileName: variant.fileName,
614
+ slug: variantOnlySlug,
615
+ variantName: variantKey
616
+ });
617
+ }
575
618
 
576
619
  // Add main file if it exists
577
620
  if (variant.fileName) {
578
621
  result.push({
579
622
  fileName: variant.fileName,
580
- slug: generateFileSlug(mainSlug, variant.fileName, variantKey, isInitialVariant),
623
+ slug: generateFileSlug(mainSlug, variant.fileName, variantKey),
581
624
  variantName: variantKey
582
625
  });
583
626
  }
@@ -587,7 +630,7 @@ export function useFileNavigation(_ref) {
587
630
  Object.keys(variant.extraFiles).forEach(function (fileName) {
588
631
  result.push({
589
632
  fileName: fileName,
590
- slug: generateFileSlug(mainSlug, fileName, variantKey, isInitialVariant),
633
+ slug: generateFileSlug(mainSlug, fileName, variantKey),
591
634
  variantName: variantKey
592
635
  });
593
636
  });
@@ -602,7 +645,7 @@ export function useFileNavigation(_ref) {
602
645
  _iterator2.f();
603
646
  }
604
647
  return result;
605
- }, [effectiveCode, variantKeys, initialVariant, mainSlug]);
648
+ }, [effectiveCode, variantKeys, mainSlug]);
606
649
  return {
607
650
  selectedFileName: selectedFileName,
608
651
  selectedFile: selectedFile,
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  interface UseUIStateProps {
3
3
  defaultOpen?: boolean;
4
+ mainSlug?: string;
4
5
  }
5
6
  export interface UseUIStateResult {
6
7
  expanded: boolean;
@@ -9,8 +10,10 @@ export interface UseUIStateResult {
9
10
  }
10
11
  /**
11
12
  * Hook for managing UI state like expansion and focus
13
+ * Auto-expands if there's a relevant hash for this demo
12
14
  */
13
15
  export declare function useUIState({
14
- defaultOpen
16
+ defaultOpen,
17
+ mainSlug
15
18
  }: UseUIStateProps): UseUIStateResult;
16
19
  export {};
@@ -1,18 +1,33 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
2
  import * as React from 'react';
3
+ import { useUrlHashState } from "../useUrlHashState/index.js";
4
+ import { isHashRelevantToDemo } from "./useFileNavigation.js";
3
5
  /**
4
6
  * Hook for managing UI state like expansion and focus
7
+ * Auto-expands if there's a relevant hash for this demo
5
8
  */
6
9
  export function useUIState(_ref) {
7
10
  var _ref$defaultOpen = _ref.defaultOpen,
8
- defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen;
9
- var _React$useState = React.useState(defaultOpen),
11
+ defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen,
12
+ mainSlug = _ref.mainSlug;
13
+ var _useUrlHashState = useUrlHashState(),
14
+ _useUrlHashState2 = _slicedToArray(_useUrlHashState, 1),
15
+ hash = _useUrlHashState2[0];
16
+ var hasRelevantHash = isHashRelevantToDemo(hash, mainSlug);
17
+ var _React$useState = React.useState(defaultOpen || hasRelevantHash),
10
18
  _React$useState2 = _slicedToArray(_React$useState, 2),
11
19
  expanded = _React$useState2[0],
12
20
  setExpanded = _React$useState2[1];
13
21
  var expand = React.useCallback(function () {
14
22
  return setExpanded(true);
15
23
  }, []);
24
+
25
+ // Auto-expand if hash becomes relevant
26
+ React.useEffect(function () {
27
+ if (hasRelevantHash && !expanded) {
28
+ setExpanded(true);
29
+ }
30
+ }, [hasRelevantHash, expanded]);
16
31
  return {
17
32
  expanded: expanded,
18
33
  expand: expand,
@@ -5,6 +5,7 @@ interface UseVariantSelectionProps {
5
5
  initialVariant?: string;
6
6
  variantType?: string;
7
7
  mainSlug?: string;
8
+ saveHashVariantToLocalStorage?: 'on-load' | 'on-interaction' | 'never';
8
9
  }
9
10
  export interface UseVariantSelectionResult {
10
11
  variantKeys: string[];
@@ -12,15 +13,19 @@ export interface UseVariantSelectionResult {
12
13
  selectedVariant: VariantCode | null;
13
14
  selectVariant: React.Dispatch<React.SetStateAction<string>>;
14
15
  selectVariantProgrammatic: React.Dispatch<React.SetStateAction<string>>;
16
+ saveVariantToLocalStorage: (variant: string) => void;
17
+ hashVariant: string | null;
15
18
  }
16
19
  /**
17
20
  * Hook for managing variant selection and providing variant-related data
18
- * Uses React state as source of truth, with localStorage for persistence
21
+ * Priority: URL hash > localStorage > initialVariant > first variant
22
+ * When hash has a variant, it overrides localStorage and is saved to localStorage
19
23
  */
20
24
  export declare function useVariantSelection({
21
25
  effectiveCode,
22
26
  initialVariant,
23
27
  variantType,
24
- mainSlug
28
+ mainSlug,
29
+ saveHashVariantToLocalStorage
25
30
  }: UseVariantSelectionProps): UseVariantSelectionResult;
26
31
  export {};
@@ -3,16 +3,69 @@ import _typeof from "@babel/runtime/helpers/esm/typeof";
3
3
  import * as React from 'react';
4
4
  import { usePreference } from "../usePreference/index.js";
5
5
  import { useUrlHashState } from "../useUrlHashState/index.js";
6
- import { isHashRelevantToDemo } from "./useFileNavigation.js";
6
+ import { isHashRelevantToDemo, toKebabCase } from "./useFileNavigation.js";
7
+
8
+ /**
9
+ * Parses the variant name from a URL hash
10
+ * Hash formats:
11
+ * - slug:file.tsx -> "Default"
12
+ * - slug:variant:file.tsx -> "variant"
13
+ * - slug:variant -> "variant"
14
+ * @param urlHash - The URL hash (without '#')
15
+ * @param mainSlug - The main slug for the demo (optional, used to determine if hash is relevant for file selection)
16
+ * @param variantKeys - Available variant keys
17
+ * @returns The variant name or null if not found/parseable
18
+ */
19
+ function parseVariantFromHash(urlHash, mainSlug, variantKeys) {
20
+ if (!urlHash) {
21
+ return null;
22
+ }
23
+ var parts = urlHash.split(':');
24
+
25
+ // If there are 3 parts (slug:variant:file), the variant is in the middle
26
+ if (parts.length === 3) {
27
+ var variantPart = parts[1];
28
+ // Find matching variant key (case-insensitive kebab match)
29
+ var matchingVariant = variantKeys.find(function (key) {
30
+ return toKebabCase(key) === variantPart.toLowerCase();
31
+ });
32
+ return matchingVariant || null;
33
+ }
34
+
35
+ // If there are 2 parts, could be slug:variant or slug:file
36
+ if (parts.length === 2) {
37
+ var secondPart = parts[1];
38
+ // Try to match as a variant first
39
+ var _matchingVariant = variantKeys.find(function (key) {
40
+ return toKebabCase(key) === secondPart.toLowerCase();
41
+ });
42
+ if (_matchingVariant) {
43
+ return _matchingVariant;
44
+ }
45
+ // If no matching variant and it looks like a filename, assume Default
46
+ if (secondPart.includes('.')) {
47
+ return 'Default';
48
+ }
49
+ }
50
+
51
+ // Just the slug with no other parts, assume Default
52
+ if (parts.length === 1) {
53
+ return 'Default';
54
+ }
55
+ return null;
56
+ }
7
57
  /**
8
58
  * Hook for managing variant selection and providing variant-related data
9
- * Uses React state as source of truth, with localStorage for persistence
59
+ * Priority: URL hash > localStorage > initialVariant > first variant
60
+ * When hash has a variant, it overrides localStorage and is saved to localStorage
10
61
  */
11
62
  export function useVariantSelection(_ref) {
12
63
  var effectiveCode = _ref.effectiveCode,
13
64
  initialVariant = _ref.initialVariant,
14
65
  variantType = _ref.variantType,
15
- mainSlug = _ref.mainSlug;
66
+ mainSlug = _ref.mainSlug,
67
+ _ref$saveHashVariantT = _ref.saveHashVariantToLocalStorage,
68
+ saveHashVariantToLocalStorage = _ref$saveHashVariantT === void 0 ? 'on-interaction' : _ref$saveHashVariantT;
16
69
  // Get variant keys from effective code
17
70
  var variantKeys = React.useMemo(function () {
18
71
  return Object.keys(effectiveCode).filter(function (key) {
@@ -21,14 +74,14 @@ export function useVariantSelection(_ref) {
21
74
  });
22
75
  }, [effectiveCode]);
23
76
 
24
- // Check if there's a URL hash present that's relevant to this demo
25
- // Only override localStorage if hash starts with this demo's slug
77
+ // Get URL hash and parse variant from it
26
78
  var _useUrlHashState = useUrlHashState(),
27
- _useUrlHashState2 = _slicedToArray(_useUrlHashState, 1),
28
- urlHash = _useUrlHashState2[0];
29
- var hasRelevantUrlHash = React.useMemo(function () {
30
- return isHashRelevantToDemo(urlHash, mainSlug);
31
- }, [urlHash, mainSlug]);
79
+ _useUrlHashState2 = _slicedToArray(_useUrlHashState, 2),
80
+ urlHash = _useUrlHashState2[0],
81
+ setUrlHash = _useUrlHashState2[1];
82
+ var hashVariant = React.useMemo(function () {
83
+ return parseVariantFromHash(urlHash, mainSlug, variantKeys);
84
+ }, [urlHash, mainSlug, variantKeys]);
32
85
 
33
86
  // Use localStorage hook for variant persistence
34
87
  var _usePreference = usePreference('variant', variantType || variantKeys, function () {
@@ -38,73 +91,103 @@ export function useVariantSelection(_ref) {
38
91
  storedValue = _usePreference2[0],
39
92
  setStoredValue = _usePreference2[1];
40
93
 
41
- // Initialize state - will be updated by effect if localStorage should be used
94
+ // Track if the last change was user-initiated (to prevent hash from overriding)
95
+ var isUserInitiatedChange = React.useRef(false);
96
+ // Track previous hash variant to detect hash changes
97
+ var prevHashVariant = React.useRef(hashVariant);
98
+ // Track previous storedValue to detect localStorage changes
99
+ var prevStoredValue = React.useRef(storedValue);
100
+
101
+ // Determine initial variant: hash > localStorage > initialVariant > first variant
42
102
  var _React$useState = React.useState(function () {
43
- // Use initial variant if provided and valid
103
+ // Priority 1: Hash variant
104
+ if (hashVariant && variantKeys.includes(hashVariant)) {
105
+ return hashVariant;
106
+ }
107
+ // Priority 2: localStorage
108
+ if (storedValue && variantKeys.includes(storedValue)) {
109
+ return storedValue;
110
+ }
111
+ // Priority 3: initialVariant prop
44
112
  if (initialVariant && variantKeys.includes(initialVariant)) {
45
113
  return initialVariant;
46
114
  }
47
-
48
- // Final fallback: use first available variant
115
+ // Priority 4: First available variant
49
116
  return variantKeys[0] || '';
50
117
  }),
51
118
  _React$useState2 = _slicedToArray(_React$useState, 2),
52
119
  selectedVariantKey = _React$useState2[0],
53
120
  setSelectedVariantKeyState = _React$useState2[1];
54
121
 
55
- // On mount, check if we should restore from localStorage
56
- // This needs to be in an effect because we need to check hasRelevantUrlHash which depends on urlHash
57
- var _React$useState3 = React.useState(false),
58
- _React$useState4 = _slicedToArray(_React$useState3, 2),
59
- hasInitialized = _React$useState4[0],
60
- setHasInitialized = _React$useState4[1];
122
+ // Track selected variant key in a ref for use in effect without causing re-runs
123
+ var selectedVariantKeyRef = React.useRef(selectedVariantKey);
61
124
  React.useEffect(function () {
62
- if (hasInitialized) {
63
- return;
64
- }
65
- setHasInitialized(true);
125
+ selectedVariantKeyRef.current = selectedVariantKey;
126
+ });
66
127
 
67
- // If there's a relevant URL hash, don't use localStorage - hash takes priority
68
- if (hasRelevantUrlHash) {
128
+ // When hash changes and has a variant, override current selection
129
+ // When hash is removed, fall back to localStorage
130
+ React.useEffect(function () {
131
+ // Skip if this was a user-initiated change
132
+ if (isUserInitiatedChange.current) {
133
+ // Only reset the flag once the hash has actually been cleared
134
+ if (hashVariant === null && urlHash === null) {
135
+ isUserInitiatedChange.current = false;
136
+ }
137
+ prevHashVariant.current = hashVariant;
69
138
  return;
70
139
  }
71
140
 
72
- // If we have a stored value, use it (localStorage takes priority over initialVariant)
73
- if (storedValue && variantKeys.includes(storedValue)) {
74
- setSelectedVariantKeyState(storedValue);
75
- }
76
- }, [hasInitialized, hasRelevantUrlHash, storedValue, variantKeys]);
77
-
78
- // Sync with localStorage changes (but don't override programmatic changes or when hash is present)
79
- // Only sync when storedValue changes, not when selectedVariantKey changes
80
- var prevStoredValue = React.useRef(storedValue);
81
- React.useEffect(function () {
82
- // Don't sync from localStorage when a relevant URL hash is present - hash takes absolute priority
83
- if (hasRelevantUrlHash) {
141
+ // Only apply hash if it actually changed (not just a re-render with same hash)
142
+ var hashChanged = prevHashVariant.current !== hashVariant;
143
+ var storedValueChanged = prevStoredValue.current !== storedValue;
144
+ prevHashVariant.current = hashVariant;
145
+ prevStoredValue.current = storedValue;
146
+ if (!hashChanged && !storedValueChanged) {
84
147
  return;
85
148
  }
86
- if (storedValue !== prevStoredValue.current) {
87
- prevStoredValue.current = storedValue;
88
- if (storedValue && variantKeys.includes(storedValue) && storedValue !== selectedVariantKey) {
89
- setSelectedVariantKeyState(storedValue);
149
+ if (hashVariant && variantKeys.includes(hashVariant) && hashVariant !== selectedVariantKeyRef.current) {
150
+ // Hash has a variant - use it
151
+ setSelectedVariantKeyState(hashVariant);
152
+ // Save hash variant to localStorage based on configuration
153
+ if (saveHashVariantToLocalStorage === 'on-load' && hashVariant !== storedValue) {
154
+ setStoredValue(hashVariant);
90
155
  }
156
+ } else if (!hashVariant && !urlHash &&
157
+ // Only fall back to localStorage when hash is truly empty
158
+ storedValue && variantKeys.includes(storedValue) && storedValue !== selectedVariantKeyRef.current) {
159
+ // Hash is empty but localStorage has a variant - use it
160
+ setSelectedVariantKeyState(storedValue);
91
161
  }
92
- }, [storedValue, variantKeys, selectedVariantKey, hasRelevantUrlHash]);
162
+ }, [hashVariant, urlHash, variantKeys,
163
+ // Note: selectedVariantKey is intentionally NOT in dependencies
164
+ // to avoid effect running when user manually changes variant
165
+ storedValue, setStoredValue, saveHashVariantToLocalStorage]);
166
+
167
+ // Programmatic setter: doesn't save to localStorage (used for hash-driven changes)
93
168
  var setSelectedVariantKeyProgrammatic = React.useCallback(function (value) {
94
169
  var resolvedValue = typeof value === 'function' ? value(selectedVariantKey) : value;
95
170
  if (variantKeys.includes(resolvedValue)) {
96
- // Only update React state, not localStorage
97
- // This prevents conflicts with hash-driven navigation
98
171
  setSelectedVariantKeyState(resolvedValue);
99
172
  }
100
173
  }, [selectedVariantKey, variantKeys]);
174
+
175
+ // User setter: saves to localStorage (used for user-initiated changes like dropdown)
101
176
  var setSelectedVariantKeyAsUser = React.useCallback(function (value) {
102
177
  var resolvedValue = typeof value === 'function' ? value(selectedVariantKey) : value;
103
178
  if (variantKeys.includes(resolvedValue)) {
179
+ // Mark as user-initiated to prevent hash effect from overriding
180
+ isUserInitiatedChange.current = true;
181
+ // Clear hash if it exists and is relevant to this demo
182
+ if (urlHash && mainSlug && isHashRelevantToDemo(urlHash, mainSlug)) {
183
+ setUrlHash(null);
184
+ // Update prevHashVariant to reflect that hash is now null
185
+ prevHashVariant.current = null;
186
+ }
104
187
  setSelectedVariantKeyState(resolvedValue);
105
188
  setStoredValue(resolvedValue);
106
189
  }
107
- }, [setStoredValue, selectedVariantKey, variantKeys]);
190
+ }, [setStoredValue, selectedVariantKey, variantKeys, urlHash, mainSlug, setUrlHash]);
108
191
  var selectedVariant = React.useMemo(function () {
109
192
  var variant = effectiveCode[selectedVariantKey];
110
193
  if (variant && _typeof(variant) === 'object' && 'source' in variant) {
@@ -116,16 +199,23 @@ export function useVariantSelection(_ref) {
116
199
  // Safety check: if selectedVariant doesn't exist, fall back to first variant
117
200
  React.useEffect(function () {
118
201
  if (!selectedVariant && variantKeys.length > 0) {
119
- // Don't mark this as a user selection - it's just a fallback
120
- // Use programmatic setter to avoid localStorage save
121
202
  setSelectedVariantKeyProgrammatic(variantKeys[0]);
122
203
  }
123
204
  }, [selectedVariant, variantKeys, setSelectedVariantKeyProgrammatic]);
205
+
206
+ // Function to save variant to localStorage (used for on-interaction mode)
207
+ var saveVariantToLocalStorage = React.useCallback(function (variant) {
208
+ if (saveHashVariantToLocalStorage === 'on-interaction' && variant !== storedValue) {
209
+ setStoredValue(variant);
210
+ }
211
+ }, [saveHashVariantToLocalStorage, storedValue, setStoredValue]);
124
212
  return {
125
213
  variantKeys: variantKeys,
126
214
  selectedVariantKey: selectedVariantKey,
127
215
  selectedVariant: selectedVariant,
128
216
  selectVariant: setSelectedVariantKeyAsUser,
129
- selectVariantProgrammatic: setSelectedVariantKeyProgrammatic
217
+ selectVariantProgrammatic: setSelectedVariantKeyProgrammatic,
218
+ saveVariantToLocalStorage: saveVariantToLocalStorage,
219
+ hashVariant: hashVariant
130
220
  };
131
221
  }
@@ -1 +1,2 @@
1
- export * from "./withDocsInfra.js";
1
+ export * from "./withDocsInfra.js";
2
+ export * from "./withDeploymentConfig.js";
@@ -1 +1,2 @@
1
- export * from "./withDocsInfra.js";
1
+ export * from "./withDocsInfra.js";
2
+ export * from "./withDeploymentConfig.js";
@@ -0,0 +1,2 @@
1
+ import type { NextConfig } from 'next';
2
+ export declare function withDeploymentConfig<T extends NextConfig>(nextConfig: T): T;
@@ -0,0 +1,70 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ /**
3
+ * See the docs of the Netlify environment variables:
4
+ * https://docs.netlify.com/configure-builds/environment-variables/#build-metadata.
5
+ *
6
+ * A few comments:
7
+ * - process.env.CONTEXT === 'production' means that the branch in Netlify was configured as production.
8
+ * For example, the `master` branch of the Core team is considered a `production` build on Netlify based
9
+ * on https://app.netlify.com/sites/material-ui/settings/deploys#branches.
10
+ * - Each team has different site https://app.netlify.com/teams/mui/sites.
11
+ * The following logic must be compatible with all of them.
12
+ */
13
+ var DEPLOY_ENV = 'development';
14
+
15
+ // Same as process.env.PULL_REQUEST_ID
16
+ if (process.env.CONTEXT === 'deploy-preview') {
17
+ DEPLOY_ENV = 'pull-request';
18
+ }
19
+ if (process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-deploy') {
20
+ DEPLOY_ENV = 'production';
21
+ }
22
+
23
+ // The 'master' and 'next' branches are NEVER a production environment. We use these branches for staging.
24
+ if ((process.env.CONTEXT === 'production' || process.env.CONTEXT === 'branch-deploy') && (process.env.HEAD === 'master' || process.env.HEAD === 'next')) {
25
+ DEPLOY_ENV = 'staging';
26
+ }
27
+ /**
28
+ * ====================================================================================
29
+ */
30
+
31
+ process.env.DEPLOY_ENV = DEPLOY_ENV;
32
+ export function withDeploymentConfig(nextConfig) {
33
+ return _extends(_extends({
34
+ trailingSlash: true,
35
+ reactStrictMode: true
36
+ }, nextConfig), {}, {
37
+ env: _extends(_extends({
38
+ // production | staging | pull-request | development
39
+ DEPLOY_ENV: DEPLOY_ENV
40
+ }, nextConfig.env), {}, {
41
+ // https://docs.netlify.com/configure-builds/environment-variables/#git-metadata
42
+ // reference ID (also known as "SHA" or "hash") of the commit we're building.
43
+ COMMIT_REF: process.env.COMMIT_REF,
44
+ // ID of the PR and the Deploy Preview it generated (for example, 1211)
45
+ PULL_REQUEST_ID: process.env.REVIEW_ID,
46
+ // This can be set manually in the .env to see the ads in dev mode.
47
+ ENABLE_AD_IN_DEV_MODE: process.env.ENABLE_AD_IN_DEV_MODE,
48
+ // URL representing the unique URL for an individual deploy, e.g.
49
+ // https://5b243e66dd6a547b4fee73ae--petsof.netlify.app
50
+ SITE_DEPLOY_URL: process.env.DEPLOY_URL,
51
+ // Name of the site, its Netlify subdomain; for example, material-ui-docs
52
+ SITE_NAME: process.env.SITE_NAME,
53
+ // For template images
54
+ TEMPLATE_IMAGE_URL: ''
55
+ }),
56
+ experimental: _extends(_extends({
57
+ scrollRestoration: true,
58
+ workerThreads: false
59
+ }, process.env.CI ? {
60
+ cpus: 2
61
+ } : {}), nextConfig.experimental),
62
+ eslint: _extends({
63
+ ignoreDuringBuilds: true
64
+ }, nextConfig.eslint),
65
+ typescript: _extends({
66
+ // Motivated by https://github.com/vercel/next.js/issues/7687
67
+ ignoreBuildErrors: true
68
+ }, nextConfig.typescript)
69
+ });
70
+ }
@@ -1,18 +1,5 @@
1
- import type { Configuration as WebpackConfig, RuleSetRule } from 'webpack';
2
- export interface NextConfig {
3
- pageExtensions?: string[];
4
- output?: 'export' | 'standalone' | undefined;
5
- turbopack?: {
6
- rules?: Record<string, {
7
- loaders: {
8
- loader: string;
9
- options: Record<string, unknown>;
10
- }[] | string[];
11
- }>;
12
- };
13
- webpack?: (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
14
- [key: string]: any;
15
- }
1
+ import type { NextConfig } from 'next';
2
+ import type { RuleSetRule } from 'webpack';
16
3
  export interface WebpackOptions {
17
4
  buildId: string;
18
5
  dev: boolean;
@@ -1,8 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
3
3
  import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
4
- // Define minimal NextConfig type to avoid importing from 'next'
5
-
6
4
  // Define webpack options interface based on Next.js webpack function signature
7
5
 
8
6
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-docs-infra",
3
- "version": "0.2.3-canary.0",
3
+ "version": "0.2.3-canary.10",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "keywords": [
@@ -22,7 +22,7 @@
22
22
  "homepage": "https://github.com/mui/mui-public/tree/master/packages/docs-infra",
23
23
  "dependencies": {
24
24
  "@babel/runtime": "^7.28.4",
25
- "@babel/standalone": "^7.28.4",
25
+ "@babel/standalone": "^7.28.5",
26
26
  "@wooorm/starry-night": "^3.8.0",
27
27
  "clipboard-copy": "^4.0.1",
28
28
  "fflate": "^0.8.2",
@@ -32,7 +32,7 @@
32
32
  "kebab-case": "^2.0.2",
33
33
  "lz-string": "^1.5.0",
34
34
  "path-module": "^0.1.2",
35
- "prettier": "^3.5.3",
35
+ "prettier": "^3.6.2",
36
36
  "uint8-to-base64": "^0.2.1",
37
37
  "unist-util-visit": "^5.0.0",
38
38
  "vscode-oniguruma": "^2.0.1"
@@ -45,6 +45,9 @@
45
45
  "peerDependenciesMeta": {
46
46
  "@types/react": {
47
47
  "optional": true
48
+ },
49
+ "next": {
50
+ "optional": true
48
51
  }
49
52
  },
50
53
  "publishConfig": {
@@ -53,7 +56,6 @@
53
56
  "engines": {
54
57
  "node": ">=22.12.0"
55
58
  },
56
- "gitSha": "ac83e0cbcf905f4d7a2023de4c7dc600105f86e8",
57
59
  "type": "commonjs",
58
60
  "exports": {
59
61
  "./package.json": "./package.json",
@@ -226,5 +228,6 @@
226
228
  }
227
229
  },
228
230
  "./esm": null
229
- }
231
+ },
232
+ "gitSha": "0b104084ce1db11d486ce994342b00321073cd64"
230
233
  }