@jfrader/storybook-docs 0.0.0-stage → 0.4.3

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -2
  3. package/dist/app.d.ts +1 -0
  4. package/dist/base.d.ts +3 -0
  5. package/dist/build/catalog.d.ts +20 -0
  6. package/dist/build/code.d.ts +2 -0
  7. package/dist/build/global-styles.d.ts +1 -0
  8. package/dist/build/guidance.d.ts +4 -0
  9. package/dist/build/module-ids.d.ts +2 -0
  10. package/dist/build/props.d.ts +10 -0
  11. package/dist/build/releases.d.ts +7 -0
  12. package/dist/build/site.d.ts +2 -0
  13. package/dist/build/storybook-test.d.ts +2 -0
  14. package/dist/build/tokens.d.ts +2 -0
  15. package/dist/code.d.ts +16 -0
  16. package/dist/component-demo.d.ts +23 -0
  17. package/dist/config.d.ts +98 -0
  18. package/dist/controls.d.ts +49 -0
  19. package/dist/guidance-model.d.ts +74 -0
  20. package/dist/guidance.d.ts +9 -0
  21. package/dist/index.css +424 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +1930 -0
  24. package/dist/pages/component-page.d.ts +4 -0
  25. package/dist/pages/foundations-color.d.ts +3 -0
  26. package/dist/pages/foundations-effects.d.ts +3 -0
  27. package/dist/pages/foundations-spacing.d.ts +3 -0
  28. package/dist/pages/foundations-type.d.ts +3 -0
  29. package/dist/pages/overview.d.ts +3 -0
  30. package/dist/pages/releases.d.ts +3 -0
  31. package/dist/prop-docs.d.ts +20 -0
  32. package/dist/props.d.ts +4 -0
  33. package/dist/scan.d.ts +19 -0
  34. package/dist/search-match.d.ts +21 -0
  35. package/dist/search.d.ts +5 -0
  36. package/dist/sections.d.ts +52 -0
  37. package/dist/skeleton.d.ts +11 -0
  38. package/dist/start.d.ts +4 -0
  39. package/dist/story-view.d.ts +20 -0
  40. package/dist/styles.css +688 -0
  41. package/dist/toc.d.ts +10 -0
  42. package/dist/virtual.d.ts +42 -0
  43. package/dist/vite-plugin.d.ts +10 -0
  44. package/dist/vite-plugin.js +697 -0
  45. package/docs/CONFIG.md +287 -0
  46. package/docs/RELEASING.md +127 -0
  47. package/package.json +83 -4
@@ -0,0 +1,697 @@
1
+ // src/vite-plugin.ts
2
+ import { readFileSync as readFileSync3, realpathSync } from "node:fs";
3
+ import path7 from "node:path";
4
+
5
+ // src/build/catalog.ts
6
+ import { existsSync } from "node:fs";
7
+ import path2 from "node:path";
8
+
9
+ // src/scan.ts
10
+ import { readdirSync, readFileSync } from "node:fs";
11
+ import path from "node:path";
12
+ function matchGlob(pattern, candidate) {
13
+ const patternParts = pattern.split("/");
14
+ const candidateParts = candidate.split("/");
15
+ const segmentRegExp = (segment) => new RegExp(`^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")}$`);
16
+ const walk = (patternIndex, candidateIndex) => {
17
+ if (patternIndex === patternParts.length) return candidateIndex === candidateParts.length;
18
+ const patternPart = patternParts[patternIndex];
19
+ if (patternPart === "**") {
20
+ for (let skip = candidateIndex; skip <= candidateParts.length; skip += 1) {
21
+ if (walk(patternIndex + 1, skip)) return true;
22
+ }
23
+ return false;
24
+ }
25
+ if (candidateIndex >= candidateParts.length) return false;
26
+ if (segmentRegExp(patternPart ?? "").test(candidateParts[candidateIndex] ?? "")) {
27
+ return walk(patternIndex + 1, candidateIndex + 1);
28
+ }
29
+ return false;
30
+ };
31
+ return walk(0, 0);
32
+ }
33
+ function collectMatches(rootDir, pattern) {
34
+ const matches = [];
35
+ const visit = (directory) => {
36
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
37
+ const full = path.join(directory, entry.name);
38
+ if (entry.isDirectory()) {
39
+ visit(full);
40
+ continue;
41
+ }
42
+ const relative = path.relative(rootDir, full).split(path.sep).join("/");
43
+ if (matchGlob(pattern, relative)) matches.push(full);
44
+ }
45
+ };
46
+ visit(rootDir);
47
+ return matches.sort();
48
+ }
49
+ function extractTitle(source) {
50
+ const metaStart = source.search(/\bconst\s+meta\b/);
51
+ if (metaStart >= 0) {
52
+ const rest = source.slice(metaStart);
53
+ const end = rest.search(/export\s+default\s+meta|satisfies\s+Meta|\)\s*;\s*$/m);
54
+ const scope = end >= 0 ? rest.slice(0, end) : rest.slice(0, 6e3);
55
+ const scoped = scope.match(/title:\s*'([^']+)'/) ?? scope.match(/title:\s*"([^"]+)"/);
56
+ if (scoped?.[1]) return scoped[1];
57
+ }
58
+ const anywhere = source.match(/title:\s*'([^']+)'/) ?? source.match(/title:\s*"([^"]+)"/);
59
+ return anywhere?.[1] ?? null;
60
+ }
61
+ function readStoryFile(file) {
62
+ const source = readFileSync(file, "utf8");
63
+ const title = extractTitle(source);
64
+ if (!title) return null;
65
+ const stories = [...source.matchAll(/^export const ([A-Za-z0-9_]+)/gm)].map((match) => match[1] ?? "");
66
+ return { title, stories, hasComponent: /component:\s*\w+/.test(source) };
67
+ }
68
+ var toKebab = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
69
+ function storyIdentity(file, title) {
70
+ const [group = "Components", ...leaf] = title.split("/");
71
+ const filename = path.basename(file).replace(/\.stories\.[^.]+$/, "");
72
+ return { group, name: leaf.join("/") || filename, slug: toKebab(filename) };
73
+ }
74
+
75
+ // src/build/module-ids.ts
76
+ var VIRTUAL_PREFIX = "virtual:storybook-docs/";
77
+ var FILE_PREFIX = "storybook-docs-file:";
78
+
79
+ // src/build/catalog.ts
80
+ var DEFAULT_GROUP_ORDER = [
81
+ "Getting started",
82
+ "Foundations",
83
+ "Actions",
84
+ "Forms",
85
+ "Inputs",
86
+ "Data display",
87
+ "Feedback",
88
+ "Navigation",
89
+ "Surfaces",
90
+ "Pickers",
91
+ "Layout",
92
+ "Content",
93
+ "Components"
94
+ ];
95
+ function resolveStoryRoot(from, root) {
96
+ if (from.startsWith(".") || path2.isAbsolute(from)) {
97
+ const resolved = path2.resolve(root, from);
98
+ if (!existsSync(resolved)) {
99
+ throw new Error(`storybook-docs: story root "${from}" does not exist (looked in ${resolved})`);
100
+ }
101
+ return resolved;
102
+ }
103
+ let dir = root;
104
+ for (; ; ) {
105
+ const candidate = path2.join(dir, "node_modules", from);
106
+ if (existsSync(candidate)) return candidate;
107
+ const parent = path2.dirname(dir);
108
+ if (parent === dir) {
109
+ throw new Error(`storybook-docs: package "${from}" is not installed from ${root}`);
110
+ }
111
+ dir = parent;
112
+ }
113
+ }
114
+ function scanStories(config, root) {
115
+ const stories = [];
116
+ for (const file of collectMatches(resolveStoryRoot(config.from, root), config.pattern)) {
117
+ const facts = readStoryFile(file);
118
+ if (facts) stories.push({ file, facts });
119
+ }
120
+ return stories;
121
+ }
122
+ function buildCatalog(stories, groupsConfig) {
123
+ const rename = groupsConfig?.rename ?? {};
124
+ const order = groupsConfig?.order ?? DEFAULT_GROUP_ORDER;
125
+ const entries = [];
126
+ const files = /* @__PURE__ */ new Map();
127
+ for (const { file, facts } of stories) {
128
+ const { group: authoredGroup, name, slug } = storyIdentity(file, facts.title);
129
+ if (files.has(slug)) {
130
+ throw new Error(`storybook-docs: duplicate story slug "${slug}" in "${files.get(slug)}" and "${file}"`);
131
+ }
132
+ files.set(slug, file);
133
+ entries.push({
134
+ slug,
135
+ name,
136
+ group: rename[authoredGroup] ?? authoredGroup,
137
+ storybookTitle: facts.title,
138
+ stories: facts.stories,
139
+ hasComponent: facts.hasComponent,
140
+ load: () => import(
141
+ /* @vite-ignore */
142
+ `${FILE_PREFIX}${file}`
143
+ )
144
+ });
145
+ }
146
+ entries.sort((a, b) => {
147
+ const indexA = order.indexOf(a.group);
148
+ const indexB = order.indexOf(b.group);
149
+ if (indexA !== indexB) return (indexA === -1 ? 99 : indexA) - (indexB === -1 ? 99 : indexB);
150
+ return a.name.localeCompare(b.name);
151
+ });
152
+ const groups = [...new Set(entries.map((entry) => entry.group))].sort((a, b) => {
153
+ const indexA = order.indexOf(a);
154
+ const indexB = order.indexOf(b);
155
+ return (indexA === -1 ? 99 : indexA) - (indexB === -1 ? 99 : indexB);
156
+ });
157
+ return {
158
+ entries,
159
+ groups,
160
+ files,
161
+ stats: {
162
+ components: entries.length,
163
+ stories: entries.reduce((total, entry) => total + entry.stories.length, 0),
164
+ groups: groups.length
165
+ }
166
+ };
167
+ }
168
+ function catalogCode(catalog) {
169
+ const resolveStoryFile = (entry) => {
170
+ const found = catalog.files.get(entry.slug);
171
+ if (!found) throw new Error(`storybook-docs: no file resolved for "${entry.slug}"`);
172
+ return found;
173
+ };
174
+ return `
175
+ export const catalog = [
176
+ ${catalog.entries.map(
177
+ (entry) => ` { slug: ${JSON.stringify(entry.slug)}, name: ${JSON.stringify(entry.name)}, group: ${JSON.stringify(entry.group)}, storybookTitle: ${JSON.stringify(entry.storybookTitle)}, hasComponent: ${entry.hasComponent}, stories: ${JSON.stringify(entry.stories)}, load: () => import(${JSON.stringify(`${FILE_PREFIX}${resolveStoryFile(entry)}`)}) },`
178
+ ).join("\n")}
179
+ ];
180
+ export const groups = ${JSON.stringify(catalog.groups)};
181
+ export const stats = ${JSON.stringify(catalog.stats)};
182
+ `;
183
+ }
184
+
185
+ // src/config.ts
186
+ var THEME_ROLES = [
187
+ "bg",
188
+ "surface",
189
+ "surfaceRaised",
190
+ "surfaceHover",
191
+ "border",
192
+ "borderSubtle",
193
+ "text",
194
+ "textMuted",
195
+ "textSubtle",
196
+ "accent",
197
+ "accentText",
198
+ "accentSoft",
199
+ "accentContrast",
200
+ "danger",
201
+ "dangerText",
202
+ "success",
203
+ "focusRing"
204
+ ];
205
+
206
+ // src/build/site.ts
207
+ function siteCode(config) {
208
+ const hasTokens = Boolean(config.tokens?.varsModule);
209
+ const pages = {
210
+ overview: config.pages?.overview ?? true,
211
+ api: config.pages?.api ?? true,
212
+ color: (config.pages?.color ?? true) && hasTokens,
213
+ typography: (config.pages?.typography ?? true) && hasTokens,
214
+ spacing: (config.pages?.spacing ?? true) && hasTokens,
215
+ effects: (config.pages?.effects ?? true) && hasTokens
216
+ };
217
+ const theme = {
218
+ attribute: config.theme?.attribute ?? "data-theme",
219
+ values: config.theme?.values ?? ["dark", "light"],
220
+ vars: config.theme?.vars ?? {}
221
+ };
222
+ const themeCss = Object.entries(theme.vars).filter(([role]) => THEME_ROLES.includes(role)).map(([role, reference]) => ` --sd-${role.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}: ${reference};`).join("\n");
223
+ return `
224
+ export const site = ${JSON.stringify(config.site)};
225
+ export const theme = ${JSON.stringify(theme)};
226
+ export const pages = ${JSON.stringify(pages)};
227
+ export const hasTokens = ${JSON.stringify(hasTokens)};
228
+ export const themeCss = ${JSON.stringify(themeCss ? `:root {
229
+ ${themeCss}
230
+ }` : "")};
231
+ `;
232
+ }
233
+
234
+ // src/build/tokens.ts
235
+ function tokensCode(tokens) {
236
+ if (!tokens?.varsModule) return `export const vars = {};`;
237
+ const named = tokens.varsExport ?? "vars";
238
+ return `export { ${named} as vars } from ${JSON.stringify(tokens.varsModule)};`;
239
+ }
240
+
241
+ // src/build/global-styles.ts
242
+ function globalStylesCode(engineStyles, css = []) {
243
+ const imports = [
244
+ `import ${JSON.stringify(`${FILE_PREFIX}${engineStyles}`)};`,
245
+ ...css.map((specifier) => `import ${JSON.stringify(specifier)};`)
246
+ ];
247
+ return imports.join("\n");
248
+ }
249
+
250
+ // src/build/props.ts
251
+ import { readdirSync as readdirSync2 } from "node:fs";
252
+ import path3 from "node:path";
253
+ import { withCompilerOptions } from "react-docgen-typescript";
254
+ import ts from "typescript";
255
+ function compareProps(a, b) {
256
+ return Number(Boolean(a.inheritedFrom)) - Number(Boolean(b.inheritedFrom)) || Number(b.required) - Number(a.required) || a.name.localeCompare(b.name);
257
+ }
258
+ var isExternal = (fileName) => /(^|\/)node_modules\/(@types|typescript)(\/|$)/.test(fileName);
259
+ var DOCGEN_COMPILER_OPTIONS = {
260
+ jsx: ts.JsxEmit.React,
261
+ module: ts.ModuleKind.CommonJS,
262
+ target: ts.ScriptTarget.Latest,
263
+ esModuleInterop: true
264
+ };
265
+ var TYPE_PROPS_COMPILER_OPTIONS = {
266
+ jsx: ts.JsxEmit.ReactJSX,
267
+ target: ts.ScriptTarget.ES2022,
268
+ module: ts.ModuleKind.ESNext,
269
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
270
+ skipLibCheck: true,
271
+ strict: true
272
+ };
273
+ var docgen = withCompilerOptions(DOCGEN_COMPILER_OPTIONS, {
274
+ savePropValueAsString: true,
275
+ shouldExtractLiteralValuesFromEnum: true,
276
+ shouldRemoveUndefinedFromOptional: true,
277
+ skipChildrenPropWithoutDoc: false,
278
+ // Keep the component's own props (and its local types); drop inherited DOM/React props and
279
+ // the utility-type plumbing that TypeScript's own libs and DefinitelyTyped add. The check is
280
+ // by *package*, not by "contains node_modules" — when a design system is consumed from
281
+ // node_modules, that path test would drop every prop.
282
+ propFilter: (prop) => !isExternal(prop.parent?.fileName ?? "")
283
+ });
284
+ function toPropDoc(prop) {
285
+ const value = prop;
286
+ const declared = value.defaultValue;
287
+ const raw = declared !== null && typeof declared === "object" ? declared.value : declared;
288
+ const text = declared === null || raw === void 0 || raw === "undefined" ? null : typeof raw === "object" ? JSON.stringify(raw) ?? null : String(raw);
289
+ return {
290
+ name: value.name,
291
+ type: value.type?.name === "enum" && Array.isArray(value.type.value) ? value.type.value.map((option) => option.value).join(" | ") : value.type?.name ?? "unknown",
292
+ typeUnavailable: !value.type?.name,
293
+ required: Boolean(value.required),
294
+ defaultValue: text !== null && (value.type?.name === "string" || value.type?.value?.some((option) => option.value === JSON.stringify(text))) ? JSON.stringify(text) : text,
295
+ description: value.description ?? ""
296
+ };
297
+ }
298
+ function createPropsCollector() {
299
+ let isBuild = false;
300
+ const programCaches = /* @__PURE__ */ new Map();
301
+ const createPropsProgram = (sources, compilerOptions) => {
302
+ if (!isBuild) return ts.createProgram(sources, compilerOptions);
303
+ let cache = programCaches.get(compilerOptions);
304
+ if (!cache) {
305
+ const host = ts.createCompilerHost(compilerOptions);
306
+ const getSourceFile = host.getSourceFile;
307
+ const sourceFiles = /* @__PURE__ */ new Map();
308
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
309
+ const key = host.getCanonicalFileName(fileName);
310
+ const cached = sourceFiles.get(key);
311
+ if (cached && !shouldCreateNewSourceFile) return cached;
312
+ const source = getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
313
+ if (source) sourceFiles.set(key, source);
314
+ return source;
315
+ };
316
+ cache = { host };
317
+ programCaches.set(compilerOptions, cache);
318
+ }
319
+ cache.program = ts.createProgram(sources, compilerOptions, cache.host, cache.program);
320
+ return cache.program;
321
+ };
322
+ const collect = (storyFile, entryName, slug) => {
323
+ if (storyFile) {
324
+ const directory = path3.dirname(storyFile);
325
+ const sources = readdirSync2(directory).filter((file) => /\.tsx?$/.test(file) && !file.endsWith(".stories.tsx")).map((file) => path3.join(directory, file));
326
+ if (sources.length > 0) {
327
+ try {
328
+ const parsed = docgen.parseWithProgramProvider(sources, () => createPropsProgram(sources, DOCGEN_COMPILER_OPTIONS)).map((result) => ({
329
+ name: result.displayName,
330
+ props: Object.values(result.props ?? {}).map(toPropDoc).sort(compareProps)
331
+ })).filter((component) => component.props.length > 0);
332
+ const matchesOwnName = (name) => entryName.length > 0 && name.toLowerCase().startsWith(entryName.toLowerCase());
333
+ const relevant = parsed.some((component) => matchesOwnName(component.name)) ? parsed.filter((component) => matchesOwnName(component.name)) : parsed;
334
+ const typed = collectTypeProps(sources, entryName);
335
+ return mergePropsTables(typed.length > 0 ? [...relevant.filter((table) => typed.some((item) => item.name === table.name)), ...typed] : relevant);
336
+ } catch (cause) {
337
+ console.warn(`storybook-docs: could not read props for "${slug}" (${String(cause)})`);
338
+ }
339
+ }
340
+ }
341
+ return [];
342
+ };
343
+ const collectTypeProps = (sources, entryName) => {
344
+ const program = createPropsProgram(sources, TYPE_PROPS_COMPILER_OPTIONS);
345
+ const checker = program.getTypeChecker();
346
+ const tables = [];
347
+ const unresolved = /* @__PURE__ */ Symbol("unresolved");
348
+ const literalValue = (node, seen = /* @__PURE__ */ new Set()) => {
349
+ if (seen.has(node)) return unresolved;
350
+ const visited = new Set(seen).add(node);
351
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
352
+ if (ts.isNumericLiteral(node)) return Number(node.text);
353
+ if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
354
+ if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
355
+ if (node.kind === ts.SyntaxKind.NullKeyword) return null;
356
+ if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) return literalValue(node.expression, visited);
357
+ if (ts.isIdentifier(node)) {
358
+ const symbol = checker.getSymbolAtLocation(node);
359
+ const target = symbol && (symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol);
360
+ const declaration = target?.valueDeclaration;
361
+ if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer && ts.isVariableDeclarationList(declaration.parent) && declaration.parent.flags & ts.NodeFlags.Const) {
362
+ return literalValue(declaration.initializer, visited);
363
+ }
364
+ }
365
+ if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand)) {
366
+ if (node.operator === ts.SyntaxKind.MinusToken) return -Number(node.operand.text);
367
+ if (node.operator === ts.SyntaxKind.PlusToken) return Number(node.operand.text);
368
+ }
369
+ if (ts.isArrayLiteralExpression(node)) {
370
+ const values = node.elements.map((element) => literalValue(element, visited));
371
+ return values.includes(unresolved) ? unresolved : values;
372
+ }
373
+ if (ts.isObjectLiteralExpression(node)) {
374
+ const entries = [];
375
+ for (const property of node.properties) {
376
+ if (!ts.isPropertyAssignment(property) || ts.isComputedPropertyName(property.name)) return unresolved;
377
+ const value = literalValue(property.initializer, visited);
378
+ if (value === unresolved) return unresolved;
379
+ entries.push([property.name.text, value]);
380
+ }
381
+ return Object.fromEntries(entries);
382
+ }
383
+ return unresolved;
384
+ };
385
+ const describeProps = (type, location, depth = 0) => checker.getPropertiesOfType(type).filter((symbol) => symbol.getName() !== "key").map((symbol) => {
386
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
387
+ const propType = checker.getTypeOfSymbolAtLocation(symbol, declaration ?? location);
388
+ const optional = (symbol.flags & ts.SymbolFlags.Optional) !== 0;
389
+ const typeFlags = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
390
+ const literalUnion = propType.isUnion() && propType.types.every((part) => Boolean(part.flags & (ts.TypeFlags.Literal | ts.TypeFlags.Null | ts.TypeFlags.Undefined)));
391
+ const printedType = checker.typeToString(propType, void 0, typeFlags);
392
+ const displayType = literalUnion && propType.isUnion() ? propType.types.filter((part) => !optional || !(part.flags & ts.TypeFlags.Undefined)).map((part) => checker.typeToString(part, void 0, typeFlags)).join(" | ").replace(/false \| true/g, "boolean") : optional ? printedType.replace(/ \| undefined$/, "") : printedType;
393
+ const parent = declaration?.parent;
394
+ const inheritedFrom = declaration && (isExternal(declaration.getSourceFile().fileName) || !sources.includes(declaration.getSourceFile().fileName)) ? parent && (ts.isInterfaceDeclaration(parent) || ts.isTypeAliasDeclaration(parent)) ? parent.name.text : path3.basename(declaration.getSourceFile().fileName) : void 0;
395
+ const memberType = checker.getNonNullableType(propType);
396
+ const hasLocalMembers = !checker.isArrayType(memberType) && !checker.isTupleType(memberType) && !memberType.getCallSignatures().length && checker.getPropertiesOfType(memberType).some((member) => member.declarations?.some((node) => !isExternal(node.getSourceFile().fileName)));
397
+ return {
398
+ name: symbol.getName(),
399
+ type: displayType || "unknown",
400
+ typeUnavailable: false,
401
+ required: !optional,
402
+ defaultValue: null,
403
+ description: ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim(),
404
+ inheritedFrom,
405
+ tags: symbol.getJsDocTags(checker).filter((tag) => ["deprecated", "since", "example"].includes(tag.name)).map((tag) => ({ name: tag.name, text: ts.displayPartsToString(tag.text) })),
406
+ members: depth === 0 && (symbol.getName() === "slotProps" || !inheritedFrom && hasLocalMembers) ? describeProps(memberType, location, depth + 1) : void 0
407
+ };
408
+ }).sort(compareProps);
409
+ const visitComponent = (type, label, location, depth = 0) => {
410
+ const signature = type.getCallSignatures()[0];
411
+ if (signature) {
412
+ const parameter = signature.getParameters()[0];
413
+ if (parameter) {
414
+ const declaration = signature.getDeclaration();
415
+ const propsType = checker.getTypeOfSymbolAtLocation(parameter, declaration ?? location);
416
+ const props = describeProps(propsType, declaration ?? location);
417
+ const binding = declaration?.parameters[0]?.name;
418
+ const readDefaults = (elements) => {
419
+ for (const element of elements) {
420
+ const prop = props.find((item) => item.name === (element.propertyName ?? element.name).getText());
421
+ if (prop && element.initializer && element.initializer.getText() !== "undefined") {
422
+ const value = literalValue(element.initializer);
423
+ prop.defaultValue = value === unresolved ? element.initializer.getText() : JSON.stringify(value) ?? null;
424
+ }
425
+ }
426
+ };
427
+ if (binding && ts.isObjectBindingPattern(binding)) readDefaults(binding.elements);
428
+ const parameterNames = binding && ts.isIdentifier(binding) ? [binding.text] : binding && ts.isObjectBindingPattern(binding) ? binding.elements.filter((element) => element.dotDotDotToken && ts.isIdentifier(element.name)).map((element) => element.name.getText()) : [];
429
+ const readBodyDefaults = (node) => {
430
+ if (ts.isFunctionLike(node) && node !== declaration) return;
431
+ if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer) {
432
+ const input = ts.isCallExpression(node.initializer) ? node.initializer.arguments : [node.initializer];
433
+ if (input.some((argument) => ts.isIdentifier(argument) && parameterNames.includes(argument.text))) {
434
+ const inputType = checker.getTypeAtLocation(node.initializer);
435
+ const elements = node.name.elements.filter((element) => {
436
+ const name = (element.propertyName ?? element.name).getText();
437
+ const original = propsType.getProperty(name);
438
+ const projected = inputType.getProperty(name);
439
+ return original?.declarations?.some((item) => projected?.declarations?.includes(item));
440
+ });
441
+ readDefaults(elements);
442
+ }
443
+ }
444
+ ts.forEachChild(node, readBodyDefaults);
445
+ };
446
+ if (declaration) {
447
+ ts.forEachChild(declaration, readBodyDefaults);
448
+ }
449
+ tables.push({ name: label, props });
450
+ }
451
+ }
452
+ if (depth >= 2) return;
453
+ for (const member of checker.getPropertiesOfType(type)) {
454
+ const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration ?? location);
455
+ if (memberType.getCallSignatures().length > 0 && !["bind", "call", "apply"].includes(member.getName())) {
456
+ visitComponent(memberType, `${label}.${member.getName()}`, location, depth + 1);
457
+ }
458
+ }
459
+ };
460
+ for (const file of sources) {
461
+ const source = program.getSourceFile(file);
462
+ const module = source && checker.getSymbolAtLocation(source);
463
+ if (!source || !module) continue;
464
+ for (const symbol of checker.getExportsOfModule(module)) {
465
+ if (symbol.getName() !== entryName && symbol.getName() !== "default") continue;
466
+ const target = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
467
+ visitComponent(checker.getTypeOfSymbolAtLocation(target, target.valueDeclaration ?? source), entryName, source);
468
+ }
469
+ }
470
+ return tables;
471
+ };
472
+ return {
473
+ collect,
474
+ setBuildMode(value) {
475
+ isBuild = value;
476
+ },
477
+ clearPrograms() {
478
+ programCaches.clear();
479
+ }
480
+ };
481
+ }
482
+ function mergePropsTables(tables) {
483
+ const byName = /* @__PURE__ */ new Map();
484
+ for (const table of tables) {
485
+ const existing = byName.get(table.name);
486
+ if (!existing) byName.set(table.name, table);
487
+ else {
488
+ const props = new Map(existing.props.filter((prop) => prop.name !== "key").map((prop) => [prop.name, prop]));
489
+ for (const prop of table.props) {
490
+ const previous = props.get(prop.name);
491
+ props.set(prop.name, { ...previous, ...prop, defaultValue: prop.defaultValue ?? previous?.defaultValue ?? null });
492
+ }
493
+ byName.set(table.name, { ...existing, ...table, props: [...props.values()].sort(compareProps) });
494
+ }
495
+ }
496
+ return [...byName.values()].sort((a, b) => a.name.split(".").length - b.name.split(".").length || a.name.localeCompare(b.name));
497
+ }
498
+ function propsCode(components) {
499
+ return `export const components = ${JSON.stringify(components)};`;
500
+ }
501
+ function propsIndexCode(slugs) {
502
+ return `
503
+ export function loadProps(slug) {
504
+ switch (slug) {
505
+ ${[...slugs].map((slug) => ` case ${JSON.stringify(slug)}: return import(${JSON.stringify(`${VIRTUAL_PREFIX}props/${slug}`)});`).join("\n")}
506
+ default: return Promise.resolve({ components: [] });
507
+ }
508
+ }
509
+ `;
510
+ }
511
+
512
+ // src/build/guidance.ts
513
+ import { existsSync as existsSync2, readdirSync as readdirSync3 } from "node:fs";
514
+ import path4 from "node:path";
515
+ function indexGuidance(guidance, root) {
516
+ const files = /* @__PURE__ */ new Map();
517
+ for (const dir of [guidance?.dir ?? []].flat()) {
518
+ const directory = path4.resolve(root, dir);
519
+ if (!existsSync2(directory)) continue;
520
+ for (const file of readdirSync3(directory)) {
521
+ if (!file.endsWith(".md")) continue;
522
+ files.set(file.slice(0, -".md".length), path4.join(directory, file));
523
+ }
524
+ }
525
+ return files;
526
+ }
527
+ function guidanceIndexCode(slugs) {
528
+ return `
529
+ export function loadGuidance(slug) {
530
+ switch (slug) {
531
+ ${[...slugs].map((slug) => ` case ${JSON.stringify(slug)}: return import(${JSON.stringify(`${VIRTUAL_PREFIX}guidance/${slug}`)});`).join("\n")}
532
+ default: return Promise.resolve({ markdown: '' });
533
+ }
534
+ }
535
+ `;
536
+ }
537
+ function guidanceCode(markdown) {
538
+ return `export const markdown = ${JSON.stringify(markdown)};`;
539
+ }
540
+
541
+ // src/build/releases.ts
542
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
543
+ import path5 from "node:path";
544
+ function readReleases(config, root) {
545
+ const readIfPresent = (candidate) => {
546
+ if (!candidate) return null;
547
+ const resolved = path5.resolve(root, candidate);
548
+ return existsSync3(resolved) ? readFileSync2(resolved, "utf8") : null;
549
+ };
550
+ let version = config.site.version ?? null;
551
+ if (!version && config.releases?.versionFrom) {
552
+ const manifest = readIfPresent(config.releases.versionFrom);
553
+ if (manifest) {
554
+ try {
555
+ version = JSON.parse(manifest).version ?? null;
556
+ } catch {
557
+ version = null;
558
+ }
559
+ }
560
+ }
561
+ const changelog = readIfPresent(config.releases?.changelog);
562
+ return { version, changelog };
563
+ }
564
+ function releasesCode(releases) {
565
+ return `export const releases = ${JSON.stringify(releases)};`;
566
+ }
567
+
568
+ // src/build/code.ts
569
+ import { existsSync as existsSync4 } from "node:fs";
570
+ import path6 from "node:path";
571
+ function resolveCodeModule(module, root) {
572
+ if (!module) return void 0;
573
+ const resolved = path6.resolve(root, module);
574
+ if (!existsSync4(resolved)) {
575
+ throw new Error(`storybook-docs: code.module "${module}" does not exist (looked in ${resolved})`);
576
+ }
577
+ return resolved;
578
+ }
579
+ function codeModuleCode(resolved) {
580
+ if (!resolved) return "export const provided = false;\nexport const Code = undefined;\n";
581
+ return `import { lazy } from 'react';
582
+ export const Code = lazy(() => import(${JSON.stringify(resolved)}));
583
+ export const provided = true;
584
+ `;
585
+ }
586
+
587
+ // src/build/storybook-test.ts
588
+ var STORYBOOK_TEST_STUB = `
589
+ const chainable = (impl) => {
590
+ const callable = (...args) => (typeof impl === 'function' ? impl(...args) : undefined);
591
+ const chain = () => callable;
592
+ for (const name of [
593
+ 'mockName', 'mockImplementation', 'mockImplementationOnce', 'mockResolvedValue',
594
+ 'mockResolvedValueOnce', 'mockRejectedValue', 'mockReturnValue', 'mockReturnThis',
595
+ 'mockReset', 'mockClear', 'mockRestore',
596
+ ]) callable[name] = chain;
597
+ callable.mock = { calls: [], results: [] };
598
+ return callable;
599
+ };
600
+ export const fn = (impl) => chainable(impl);
601
+ export const spyOn = () => chainable();
602
+ export const mocked = (value) => value;
603
+ export const expect = () => new Proxy({}, { get: () => () => undefined });
604
+ export const within = () => new Proxy({}, { get: () => () => ({}) });
605
+ export const waitFor = (callback) => Promise.resolve(typeof callback === 'function' ? callback() : undefined);
606
+ export const userEvent = new Proxy({}, { get: () => () => Promise.resolve() });
607
+ export const fireEvent = () => true;
608
+ export const cleanup = () => {};
609
+ `;
610
+
611
+ // src/vite-plugin.ts
612
+ function storybookDocs(options) {
613
+ const { config } = options;
614
+ let projectRoot = options.root ?? process.cwd();
615
+ let catalog = buildCatalog(scanStories(config.stories, projectRoot), config.groups);
616
+ catalog.files.clear();
617
+ const engineStyles = path7.join(import.meta.dirname, "styles.css");
618
+ const propsCache = /* @__PURE__ */ new Map();
619
+ const propsCollector = createPropsCollector();
620
+ let guidanceFiles = /* @__PURE__ */ new Map();
621
+ const loadProps = (slug) => {
622
+ let components = propsCache.get(slug);
623
+ if (!components) {
624
+ const name = catalog.entries.find((entry) => entry.slug === slug)?.name ?? "";
625
+ components = propsCollector.collect(catalog.files.get(slug), name, slug);
626
+ propsCache.set(slug, components);
627
+ }
628
+ return propsCode(components);
629
+ };
630
+ const modules = {
631
+ catalog: () => `${catalogCode(catalog)}
632
+ export const storySource = ${JSON.stringify(config.stories.from)};`,
633
+ site: () => siteCode(config),
634
+ tokens: () => tokensCode(config.tokens),
635
+ "global-styles": () => globalStylesCode(engineStyles, config.tokens?.css),
636
+ "stub-storybook-test": () => STORYBOOK_TEST_STUB,
637
+ props: () => propsIndexCode(catalog.entries.map((entry) => entry.slug)),
638
+ guidance: () => guidanceIndexCode(guidanceFiles.keys()),
639
+ releases: () => releasesCode(readReleases(config, projectRoot)),
640
+ code: () => codeModuleCode(resolveCodeModule(config.code?.module, projectRoot))
641
+ };
642
+ return {
643
+ name: "storybook-docs",
644
+ enforce: "pre",
645
+ config() {
646
+ const entries = [];
647
+ try {
648
+ const storyRoot = resolveStoryRoot(config.stories.from, process.cwd());
649
+ entries.push(path7.join(storyRoot, config.stories.pattern).split(path7.sep).join("/"));
650
+ } catch {
651
+ }
652
+ return {
653
+ resolve: { dedupe: ["react", "react-dom"] },
654
+ optimizeDeps: { entries: ["index.html", ...entries] }
655
+ };
656
+ },
657
+ configResolved(resolved) {
658
+ propsCollector.setBuildMode(resolved.command === "build");
659
+ projectRoot = options.root ?? resolved.root;
660
+ catalog = buildCatalog(scanStories(config.stories, projectRoot), config.groups);
661
+ guidanceFiles = indexGuidance(config.guidance, projectRoot);
662
+ this.info?.(
663
+ `storybook-docs: ${catalog.stats.components} components, ${catalog.stats.stories} stories, ${catalog.stats.groups} groups from ${config.stories.from}`
664
+ );
665
+ },
666
+ buildStart() {
667
+ propsCache.clear();
668
+ propsCollector.clearPrograms();
669
+ },
670
+ closeBundle() {
671
+ propsCollector.clearPrograms();
672
+ },
673
+ resolveId(id) {
674
+ if (id.startsWith(VIRTUAL_PREFIX)) return `\0${id}`;
675
+ if (id.startsWith(FILE_PREFIX)) return realpathSync(id.slice(FILE_PREFIX.length));
676
+ if (id === "storybook/test") return `\0${VIRTUAL_PREFIX}stub-storybook-test`;
677
+ return null;
678
+ },
679
+ load(id) {
680
+ if (!id.startsWith(`\0${VIRTUAL_PREFIX}`)) return null;
681
+ const name = id.slice(`\0${VIRTUAL_PREFIX}`.length);
682
+ const module = Object.hasOwn(modules, name) ? modules[name] : void 0;
683
+ if (module) return module();
684
+ if (name.startsWith("props/")) return loadProps(name.slice("props/".length));
685
+ if (name.startsWith("guidance/")) {
686
+ const file = guidanceFiles.get(name.slice("guidance/".length));
687
+ return guidanceCode(file ? readFileSync3(file, "utf8") : "");
688
+ }
689
+ return null;
690
+ }
691
+ };
692
+ }
693
+ var vite_plugin_default = storybookDocs;
694
+ export {
695
+ vite_plugin_default as default,
696
+ storybookDocs
697
+ };