@jfrader/storybook-docs 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/snippet-bindings.d.ts +3 -0
- package/dist/index.js +28 -25
- package/dist/sections.d.ts +12 -0
- package/dist/styles.css +30 -17
- package/dist/vite-plugin.js +147 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
|
|
|
5
5
|
import { site as site3, themeCss, theme as theme2 } from "virtual:storybook-docs/site";
|
|
6
6
|
|
|
7
7
|
// src/app.tsx
|
|
8
|
-
import {
|
|
8
|
+
import { useEffect as useEffect8, useRef as useRef5, useState as useState9 } from "react";
|
|
9
9
|
import { catalog as catalog3, groups as groups2 } from "virtual:storybook-docs/catalog";
|
|
10
10
|
import { pages, site as site2, theme } from "virtual:storybook-docs/site";
|
|
11
11
|
|
|
@@ -263,6 +263,11 @@ import { useCallback as useCallback2, useEffect as useEffect4, useId, useRef as
|
|
|
263
263
|
import { Component as Component2, createElement as createElement2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
264
264
|
|
|
265
265
|
// src/sections.ts
|
|
266
|
+
function frameSource(meta, story) {
|
|
267
|
+
const unchanged = Object.entries(story.args ?? {}).every(([key, value]) => Object.hasOwn(meta.args ?? {}, key) && Object.is(value, meta.args?.[key]));
|
|
268
|
+
return story.parameters?.docs?.source?.code ?? (unchanged ? meta.parameters?.docs?.source?.code : void 0);
|
|
269
|
+
}
|
|
270
|
+
var STORY_EXCERPT = "// Excerpt from the story";
|
|
266
271
|
function mergeAnnotations(...values) {
|
|
267
272
|
const merged = {};
|
|
268
273
|
const isRecord = (value) => value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
|
|
@@ -375,13 +380,20 @@ function usageSnippet(componentName, args) {
|
|
|
375
380
|
return `${opening}>${children}</${componentName}>`;
|
|
376
381
|
}
|
|
377
382
|
function buildUsageSnippet({ componentName, from, args, source, renderSource, jsxName, component }) {
|
|
378
|
-
if (!source?.trim() && renderSource?.trim()) return renderSource;
|
|
379
383
|
const rootName = componentName.split(".")[0];
|
|
380
384
|
const identity = component;
|
|
381
385
|
const tag = jsxName ?? (typeof identity?.displayName === "string" && identity.displayName.startsWith(`${rootName}.`) ? identity.displayName : componentName);
|
|
382
386
|
const compound = component != null && Object.entries(component).some(([name, part]) => /^[A-Z]/.test(name) && (typeof part === "function" || part !== null && typeof part === "object" && "$$typeof" in part));
|
|
383
|
-
const
|
|
387
|
+
const generated = () => compound && !tag.includes(".") ? null : usageSnippet(tag, args);
|
|
388
|
+
const extracted = renderSource?.trim();
|
|
389
|
+
const excerpt = extracted?.startsWith(STORY_EXCERPT);
|
|
390
|
+
const example = source?.trim() || (excerpt ? generated() || extracted : extracted || generated());
|
|
384
391
|
if (!example) return null;
|
|
392
|
+
if (!source?.trim() && example === extracted) {
|
|
393
|
+
const rootTag = example.replace(/^\s*\/\/[^\n]*(?:\n|$)/gm, "").trim().match(/^<([A-Za-z_$][\w$]*(?:\.[\w$]+)*)[\s/>]/)?.[1]?.split(".")[0];
|
|
394
|
+
if (excerpt || rootTag !== rootName || !from.trim()) return example;
|
|
395
|
+
}
|
|
396
|
+
if (!from.trim()) return example;
|
|
385
397
|
const imports = [...example.matchAll(/import\s*\{([^}]+)\}\s*from\s*(['"])(.*?)\2/g)];
|
|
386
398
|
const imported = imports.some((match) => match[3] === from && match[1].split(",").some((name) => name.trim() === rootName));
|
|
387
399
|
return imported ? example : `import { ${rootName} } from '${from}';
|
|
@@ -1038,7 +1050,7 @@ function ControlledStoryFrame({ module, story, componentName, componentDefaults,
|
|
|
1038
1050
|
const storyLayout = requestedLayout === "centered" || requestedLayout === "fullscreen" ? requestedLayout : "padded";
|
|
1039
1051
|
const docsOverride = definition.parameters?.docs;
|
|
1040
1052
|
const docsMeta = meta.parameters?.docs;
|
|
1041
|
-
const authoredCode =
|
|
1053
|
+
const authoredCode = frameSource(meta, definition);
|
|
1042
1054
|
const authoredStory = docsOverride?.description?.story ?? docsMeta?.description?.story;
|
|
1043
1055
|
const storyName = definition.name ?? story;
|
|
1044
1056
|
const skipped = ["loaders", "beforeEach"].filter((key) => meta[key] || definition[key]);
|
|
@@ -1297,7 +1309,7 @@ function ComponentPage({ entry }) {
|
|
|
1297
1309
|
" props"
|
|
1298
1310
|
] })
|
|
1299
1311
|
] }),
|
|
1300
|
-
/* @__PURE__ */ jsx8("p", { className: "lede", children: description
|
|
1312
|
+
description ? /* @__PURE__ */ jsx8("p", { className: "lede", children: description }) : null,
|
|
1301
1313
|
error ? /* @__PURE__ */ jsxs5("div", { className: "story-failed", children: [
|
|
1302
1314
|
"Could not load the story module: ",
|
|
1303
1315
|
error
|
|
@@ -1322,9 +1334,7 @@ function ComponentPage({ entry }) {
|
|
|
1322
1334
|
] }, section.title))
|
|
1323
1335
|
] }) : null,
|
|
1324
1336
|
/* @__PURE__ */ jsx8("h2", { children: "Props" }),
|
|
1325
|
-
/* @__PURE__ */ jsx8(PropsTable, { components: props, argTypes, defaultArgs })
|
|
1326
|
-
/* @__PURE__ */ jsx8("h2", { children: "Notes" }),
|
|
1327
|
-
/* @__PURE__ */ jsx8("p", { children: "Everything above is generated from this component\u2019s story module and source files. Editorial judgement \u2014 when to reach for it, when not to \u2014 is written next to the code and rendered as guidance above." })
|
|
1337
|
+
/* @__PURE__ */ jsx8(PropsTable, { components: props, argTypes, defaultArgs })
|
|
1328
1338
|
] });
|
|
1329
1339
|
}
|
|
1330
1340
|
|
|
@@ -1847,21 +1857,14 @@ function SearchBox() {
|
|
|
1847
1857
|
import { releases as releases2 } from "virtual:storybook-docs/releases";
|
|
1848
1858
|
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1849
1859
|
var routeHref = (path) => `#${hrefFor(path)}`;
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
const [open, setOpen] = useState9(false);
|
|
1853
|
-
const [showAll, setShowAll] = useState9(false);
|
|
1854
|
-
const expanded = active || open;
|
|
1855
|
-
const links = Children.toArray(children);
|
|
1856
|
-
const capped = active && links.length > NAV_GROUP_WINDOW;
|
|
1857
|
-
const start2 = Math.min(Math.max(activeIndex - NAV_GROUP_WINDOW / 2, 0), Math.max(0, links.length - NAV_GROUP_WINDOW));
|
|
1858
|
-
const visibleLinks = capped && !showAll ? links.slice(start2, start2 + NAV_GROUP_WINDOW) : links;
|
|
1860
|
+
function NavGroup({ id, title, active, children }) {
|
|
1861
|
+
const [open, setOpen] = useState9(active);
|
|
1859
1862
|
return /* @__PURE__ */ jsxs13("div", { role: "group", "aria-labelledby": id, children: [
|
|
1860
|
-
/* @__PURE__ */
|
|
1861
|
-
|
|
1862
|
-
/* @__PURE__ */ jsx16("
|
|
1863
|
-
|
|
1864
|
-
|
|
1863
|
+
/* @__PURE__ */ jsxs13("button", { type: "button", className: "grp", id, "aria-expanded": open, "aria-controls": `${id}-links`, onClick: () => setOpen(!open), children: [
|
|
1864
|
+
/* @__PURE__ */ jsx16("span", { children: title }),
|
|
1865
|
+
/* @__PURE__ */ jsx16("svg", { className: "grp-chevron", width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: /* @__PURE__ */ jsx16("path", { d: "m6 4 4 4-4 4" }) })
|
|
1866
|
+
] }),
|
|
1867
|
+
/* @__PURE__ */ jsx16("div", { id: `${id}-links`, hidden: !open, children })
|
|
1865
1868
|
] });
|
|
1866
1869
|
}
|
|
1867
1870
|
function parseRoute(hash) {
|
|
@@ -1989,7 +1992,7 @@ function DocsApp() {
|
|
|
1989
1992
|
]
|
|
1990
1993
|
}
|
|
1991
1994
|
) : null,
|
|
1992
|
-
foundations.length > 0 ? /* @__PURE__ */ jsx16(NavGroup, { id: "nav-grp-foundations", title: "
|
|
1995
|
+
foundations.length > 0 ? /* @__PURE__ */ jsx16(NavGroup, { id: "nav-grp-foundations", title: "Design tokens", active: foundations.some((item) => route.kind === item.kind), children: foundations.map((item) => /* @__PURE__ */ jsx16(
|
|
1993
1996
|
"a",
|
|
1994
1997
|
{
|
|
1995
1998
|
href: item.href,
|
|
@@ -2001,8 +2004,8 @@ function DocsApp() {
|
|
|
2001
2004
|
)) }, `foundations-${activeSlug ?? route.kind}`) : null,
|
|
2002
2005
|
groups2.map((group, index) => {
|
|
2003
2006
|
const entries = catalog3.filter((entry) => entry.group === group);
|
|
2004
|
-
const
|
|
2005
|
-
return /* @__PURE__ */ jsx16(NavGroup, { id: `nav-grp-${index}`, title: group, active
|
|
2007
|
+
const active = entries.some((entry) => entry.slug === activeSlug);
|
|
2008
|
+
return /* @__PURE__ */ jsx16(NavGroup, { id: `nav-grp-${index}`, title: group, active, children: entries.map((entry) => /* @__PURE__ */ jsx16(
|
|
2006
2009
|
"a",
|
|
2007
2010
|
{
|
|
2008
2011
|
href: routeHref(`/components/${entry.slug}`),
|
package/dist/sections.d.ts
CHANGED
|
@@ -13,6 +13,18 @@ export type DocsParameters = {
|
|
|
13
13
|
language?: string;
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
+
export declare function frameSource(meta: {
|
|
17
|
+
args?: Record<string, unknown>;
|
|
18
|
+
parameters?: {
|
|
19
|
+
docs?: DocsParameters;
|
|
20
|
+
};
|
|
21
|
+
}, story: {
|
|
22
|
+
args?: Record<string, unknown>;
|
|
23
|
+
parameters?: {
|
|
24
|
+
docs?: DocsParameters;
|
|
25
|
+
};
|
|
26
|
+
}): string | undefined;
|
|
27
|
+
export declare const STORY_EXCERPT = "// Excerpt from the story";
|
|
16
28
|
export declare function mergeAnnotations(...values: Array<Record<string, unknown> | undefined>): Record<string, unknown>;
|
|
17
29
|
export interface StorySection {
|
|
18
30
|
title: string;
|
package/dist/styles.css
CHANGED
|
@@ -202,8 +202,7 @@ body {
|
|
|
202
202
|
background: var(--sd-surface);
|
|
203
203
|
padding: 14px 12px 12px;
|
|
204
204
|
}
|
|
205
|
-
.nav a
|
|
206
|
-
.nav-more {
|
|
205
|
+
.nav a {
|
|
207
206
|
display: flex;
|
|
208
207
|
align-items: center;
|
|
209
208
|
box-sizing: border-box;
|
|
@@ -214,16 +213,7 @@ body {
|
|
|
214
213
|
font-size: 13.5px;
|
|
215
214
|
text-decoration: none;
|
|
216
215
|
}
|
|
217
|
-
.nav
|
|
218
|
-
width: 100%;
|
|
219
|
-
background: none;
|
|
220
|
-
border: 0;
|
|
221
|
-
font-family: inherit;
|
|
222
|
-
cursor: pointer;
|
|
223
|
-
text-align: left;
|
|
224
|
-
}
|
|
225
|
-
.nav a:hover,
|
|
226
|
-
.nav-more:hover {
|
|
216
|
+
.nav a:hover {
|
|
227
217
|
background: var(--sd-surface-hover);
|
|
228
218
|
color: var(--sd-text);
|
|
229
219
|
}
|
|
@@ -234,23 +224,46 @@ body {
|
|
|
234
224
|
}
|
|
235
225
|
.grp {
|
|
236
226
|
margin: 3px 0 2px;
|
|
237
|
-
padding:
|
|
227
|
+
padding: 8px 10px;
|
|
238
228
|
font-size: 10.5px;
|
|
239
229
|
letter-spacing: 0.09em;
|
|
240
230
|
text-transform: uppercase;
|
|
241
231
|
color: var(--sd-text-muted);
|
|
242
232
|
font-weight: 700;
|
|
243
|
-
display:
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: space-between;
|
|
236
|
+
gap: 8px;
|
|
244
237
|
width: 100%;
|
|
245
238
|
text-align: left;
|
|
246
239
|
font-family: inherit;
|
|
247
240
|
background: none;
|
|
248
241
|
border: 0;
|
|
242
|
+
border-radius: 8px;
|
|
249
243
|
cursor: pointer;
|
|
250
|
-
min-height:
|
|
244
|
+
min-height: 36px;
|
|
245
|
+
}
|
|
246
|
+
.grp[aria-expanded="true"] {
|
|
247
|
+
background: var(--sd-surface-raised);
|
|
248
|
+
color: var(--sd-text);
|
|
249
|
+
}
|
|
250
|
+
.grp:hover {
|
|
251
|
+
background: var(--sd-surface-hover);
|
|
252
|
+
color: var(--sd-text);
|
|
253
|
+
}
|
|
254
|
+
.grp-chevron {
|
|
255
|
+
flex-shrink: 0;
|
|
256
|
+
transition: transform 150ms ease;
|
|
257
|
+
}
|
|
258
|
+
.grp[aria-expanded="true"] .grp-chevron {
|
|
259
|
+
transform: rotate(90deg);
|
|
260
|
+
}
|
|
261
|
+
@media (prefers-reduced-motion: reduce) {
|
|
262
|
+
.grp-chevron {
|
|
263
|
+
transition: none;
|
|
264
|
+
}
|
|
251
265
|
}
|
|
252
|
-
.grp:focus-visible
|
|
253
|
-
.nav-more:focus-visible {
|
|
266
|
+
.grp:focus-visible {
|
|
254
267
|
outline: 2px solid var(--sd-focus-ring);
|
|
255
268
|
outline-offset: 2px;
|
|
256
269
|
}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -609,58 +609,110 @@ export const cleanup = () => {};
|
|
|
609
609
|
`;
|
|
610
610
|
|
|
611
611
|
// src/build/story-sources.ts
|
|
612
|
+
import ts3 from "typescript";
|
|
613
|
+
|
|
614
|
+
// src/sections.ts
|
|
615
|
+
var STORY_EXCERPT = "// Excerpt from the story";
|
|
616
|
+
|
|
617
|
+
// src/build/snippet-bindings.ts
|
|
612
618
|
import ts2 from "typescript";
|
|
619
|
+
var options = { target: ts2.ScriptTarget.Latest, jsx: ts2.JsxEmit.Preserve, noLib: true };
|
|
620
|
+
var UNRESOLVED_NAME_DIAGNOSTICS = /* @__PURE__ */ new Set([2304, 2552, 2580, 2581, 2582, 2591, 2592, 18004]);
|
|
621
|
+
function snippetProgram(file) {
|
|
622
|
+
const host = ts2.createCompilerHost(options);
|
|
623
|
+
host.getSourceFile = (name) => name === file.fileName ? file : void 0;
|
|
624
|
+
return ts2.createProgram([file.fileName], options, host);
|
|
625
|
+
}
|
|
626
|
+
var globals;
|
|
627
|
+
function globalNames() {
|
|
628
|
+
if (!globals) {
|
|
629
|
+
const host = ts2.createCompilerHost({ target: ts2.ScriptTarget.Latest });
|
|
630
|
+
const lib = host.getDefaultLibFileName({ target: ts2.ScriptTarget.Latest });
|
|
631
|
+
const libraries = ts2.createProgram([lib], { target: ts2.ScriptTarget.Latest });
|
|
632
|
+
const file = libraries.getSourceFile(lib);
|
|
633
|
+
globals = new Set(libraries.getTypeChecker().getSymbolsInScope(file, ts2.SymbolFlags.Value).map((symbol) => symbol.name));
|
|
634
|
+
}
|
|
635
|
+
return globals;
|
|
636
|
+
}
|
|
637
|
+
function unresolvedSnippetBindings(source, localTags = /* @__PURE__ */ new Set()) {
|
|
638
|
+
const file = ts2.createSourceFile("snippet.tsx", `declare const args: any;
|
|
639
|
+
${source}`, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
640
|
+
const program = snippetProgram(file);
|
|
641
|
+
const expression = file.statements.find(ts2.isExpressionStatement)?.expression;
|
|
642
|
+
const rootTag = expression && (ts2.isJsxElement(expression) ? expression.openingElement.tagName : ts2.isJsxSelfClosingElement(expression) ? expression.tagName : void 0);
|
|
643
|
+
const publicRoot = rootTag?.getText(file).split(".")[0];
|
|
644
|
+
const identifiers = /* @__PURE__ */ new Map();
|
|
645
|
+
const visit = (node) => {
|
|
646
|
+
if (ts2.isIdentifier(node)) identifiers.set(node.getStart(file), node);
|
|
647
|
+
ts2.forEachChild(node, visit);
|
|
648
|
+
};
|
|
649
|
+
visit(file);
|
|
650
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
651
|
+
for (const diagnostic of program.getSemanticDiagnostics(file)) {
|
|
652
|
+
if (!UNRESOLVED_NAME_DIAGNOSTICS.has(diagnostic.code) || diagnostic.start === void 0) continue;
|
|
653
|
+
const node = identifiers.get(diagnostic.start);
|
|
654
|
+
if (!node) continue;
|
|
655
|
+
let tag = node;
|
|
656
|
+
while (ts2.isPropertyAccessExpression(tag.parent) && tag.parent.expression === tag) tag = tag.parent;
|
|
657
|
+
const jsxTag = (ts2.isJsxOpeningElement(tag.parent) || ts2.isJsxClosingElement(tag.parent) || ts2.isJsxSelfClosingElement(tag.parent)) && tag.parent.tagName === tag;
|
|
658
|
+
if (!(jsxTag && node.text === publicRoot && !localTags.has(node.text)) && !globalNames().has(node.text)) unresolved.add(node.text);
|
|
659
|
+
}
|
|
660
|
+
return [...unresolved].sort();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/build/story-sources.ts
|
|
613
664
|
function unwrap(node) {
|
|
614
|
-
while (
|
|
665
|
+
while (ts3.isParenthesizedExpression(node) || ts3.isAsExpression(node) || ts3.isSatisfiesExpression(node) || ts3.isTypeAssertionExpression(node)) node = node.expression;
|
|
615
666
|
return node;
|
|
616
667
|
}
|
|
617
668
|
var MAX_HELPER_DEPTH = 3;
|
|
618
669
|
function extractStorySourceDetails(source) {
|
|
619
|
-
const file =
|
|
670
|
+
const file = ts3.createSourceFile("stories.tsx", source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
|
|
671
|
+
const checker = snippetProgram(file).getTypeChecker();
|
|
620
672
|
const declarations = /* @__PURE__ */ new Map();
|
|
621
673
|
const exports = /* @__PURE__ */ new Map();
|
|
622
674
|
for (const statement of file.statements) {
|
|
623
|
-
const exported =
|
|
624
|
-
if (
|
|
675
|
+
const exported = ts3.canHaveModifiers(statement) && ts3.getModifiers(statement)?.some((modifier) => modifier.kind === ts3.SyntaxKind.ExportKeyword);
|
|
676
|
+
if (ts3.isVariableStatement(statement)) {
|
|
625
677
|
for (const declaration of statement.declarationList.declarations) {
|
|
626
|
-
if (!
|
|
678
|
+
if (!ts3.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
627
679
|
declarations.set(declaration.name.text, declaration.initializer);
|
|
628
680
|
if (exported) exports.set(declaration.name.text, declaration.name.text);
|
|
629
681
|
}
|
|
630
|
-
} else if (
|
|
682
|
+
} else if (ts3.isFunctionDeclaration(statement) && statement.name) {
|
|
631
683
|
declarations.set(statement.name.text, statement);
|
|
632
684
|
if (exported) exports.set(statement.name.text, statement.name.text);
|
|
633
|
-
} else if (
|
|
685
|
+
} else if (ts3.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
|
|
634
686
|
for (const element of statement.exportClause.elements) {
|
|
635
687
|
exports.set(element.name.text, (element.propertyName ?? element.name).text);
|
|
636
688
|
}
|
|
637
689
|
}
|
|
638
690
|
}
|
|
639
691
|
const renderFor = (node, seen = /* @__PURE__ */ new Set()) => {
|
|
640
|
-
if (
|
|
692
|
+
if (ts3.isFunctionDeclaration(node)) return node;
|
|
641
693
|
node = unwrap(node);
|
|
642
|
-
if (
|
|
694
|
+
if (ts3.isIdentifier(node)) {
|
|
643
695
|
if (seen.has(node.text)) return;
|
|
644
696
|
seen.add(node.text);
|
|
645
697
|
const declaration = declarations.get(node.text);
|
|
646
698
|
return declaration ? renderFor(declaration, seen) : void 0;
|
|
647
699
|
}
|
|
648
|
-
if (
|
|
649
|
-
if (
|
|
650
|
-
if (!
|
|
700
|
+
if (ts3.isPropertyAccessExpression(node) && node.name.text === "render" || ts3.isElementAccessExpression(node) && ts3.isStringLiteral(node.argumentExpression) && node.argumentExpression.text === "render") return renderFor(node.expression, seen);
|
|
701
|
+
if (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node)) return node;
|
|
702
|
+
if (!ts3.isObjectLiteralExpression(node)) return;
|
|
651
703
|
for (const property of [...node.properties].reverse()) {
|
|
652
|
-
if (
|
|
704
|
+
if (ts3.isSpreadAssignment(property)) {
|
|
653
705
|
const render = renderFor(property.expression, new Set(seen));
|
|
654
706
|
if (render !== void 0) return render;
|
|
655
|
-
} else if (property.name && (
|
|
656
|
-
if (
|
|
657
|
-
if (
|
|
658
|
-
if (
|
|
707
|
+
} else if (property.name && (ts3.isIdentifier(property.name) || ts3.isStringLiteral(property.name)) && property.name.text === "render") {
|
|
708
|
+
if (ts3.isMethodDeclaration(property)) return property;
|
|
709
|
+
if (ts3.isPropertyAssignment(property)) return renderFor(property.initializer, seen) ?? null;
|
|
710
|
+
if (ts3.isShorthandPropertyAssignment(property)) return renderFor(property.name, seen) ?? null;
|
|
659
711
|
return null;
|
|
660
712
|
}
|
|
661
713
|
}
|
|
662
714
|
};
|
|
663
|
-
const hasJsx = (node) =>
|
|
715
|
+
const hasJsx = (node) => ts3.isJsxElement(node) || ts3.isJsxSelfClosingElement(node) || ts3.isJsxFragment(node) || (ts3.forEachChild(node, hasJsx) ?? false);
|
|
664
716
|
const returnsCache = /* @__PURE__ */ new Map();
|
|
665
717
|
const returnCounts = /* @__PURE__ */ new Map();
|
|
666
718
|
const returnedJsx = (render) => {
|
|
@@ -669,17 +721,17 @@ function extractStorySourceDetails(source) {
|
|
|
669
721
|
const expressions = [];
|
|
670
722
|
if (!render.body) return expressions;
|
|
671
723
|
let returnCount = 0;
|
|
672
|
-
if (
|
|
724
|
+
if (ts3.isBlock(render.body)) {
|
|
673
725
|
const visit = (node) => {
|
|
674
|
-
if (
|
|
675
|
-
if (
|
|
726
|
+
if (ts3.isFunctionLike(node)) return;
|
|
727
|
+
if (ts3.isReturnStatement(node)) {
|
|
676
728
|
returnCount += 1;
|
|
677
729
|
if (node.expression && hasJsx(node.expression)) expressions.push(unwrap(node.expression));
|
|
678
730
|
return;
|
|
679
731
|
}
|
|
680
|
-
|
|
732
|
+
ts3.forEachChild(node, visit);
|
|
681
733
|
};
|
|
682
|
-
|
|
734
|
+
ts3.forEachChild(render.body, visit);
|
|
683
735
|
} else {
|
|
684
736
|
returnCount = 1;
|
|
685
737
|
if (hasJsx(render.body)) expressions.push(unwrap(render.body));
|
|
@@ -693,16 +745,16 @@ function extractStorySourceDetails(source) {
|
|
|
693
745
|
seen.add(name);
|
|
694
746
|
const declaration = declarations.get(name);
|
|
695
747
|
if (!declaration) return;
|
|
696
|
-
if (
|
|
748
|
+
if (ts3.isFunctionDeclaration(declaration)) return declaration;
|
|
697
749
|
const expression = unwrap(declaration);
|
|
698
|
-
if (
|
|
699
|
-
if (
|
|
750
|
+
if (ts3.isIdentifier(expression)) return helperFor(expression.text, seen);
|
|
751
|
+
if (ts3.isArrowFunction(expression) || ts3.isFunctionExpression(expression)) return expression;
|
|
700
752
|
};
|
|
701
|
-
const bindingContains = (binding, name) =>
|
|
753
|
+
const bindingContains = (binding, name) => ts3.isIdentifier(binding) ? binding.text === name : binding.elements.some((element) => ts3.isBindingElement(element) && bindingContains(element.name, name));
|
|
702
754
|
const isShadowed = (expression, name) => {
|
|
703
755
|
for (let node = expression.parent; node; node = node.parent) {
|
|
704
|
-
if (
|
|
705
|
-
if (
|
|
756
|
+
if (ts3.isFunctionLike(node) && node.parameters.some((parameter) => bindingContains(parameter.name, name))) return true;
|
|
757
|
+
if (ts3.isBlock(node) && node.statements.some((statement) => ts3.isVariableStatement(statement) && statement.declarationList.declarations.some((declaration) => bindingContains(declaration.name, name)) || ts3.isFunctionDeclaration(statement) && statement.name?.text === name)) return true;
|
|
706
758
|
}
|
|
707
759
|
return false;
|
|
708
760
|
};
|
|
@@ -712,24 +764,24 @@ function extractStorySourceDetails(source) {
|
|
|
712
764
|
if (cached !== void 0) return cached;
|
|
713
765
|
const bindings = /* @__PURE__ */ new Map();
|
|
714
766
|
for (const [name, declaration] of declarations) {
|
|
715
|
-
bindings.set(name,
|
|
767
|
+
bindings.set(name, ts3.isFunctionDeclaration(declaration) ? void 0 : declaration);
|
|
716
768
|
}
|
|
717
769
|
const bind = (scope2, name, initializer) => {
|
|
718
|
-
if (
|
|
770
|
+
if (ts3.isIdentifier(name)) scope2.set(name.text, initializer);
|
|
719
771
|
else for (const element of name.elements) {
|
|
720
|
-
if (
|
|
772
|
+
if (ts3.isBindingElement(element)) bind(scope2, element.name, element.initializer);
|
|
721
773
|
}
|
|
722
774
|
};
|
|
723
775
|
const addScope = (node, parent) => {
|
|
724
776
|
const scope2 = new Map(parent);
|
|
725
|
-
if (
|
|
777
|
+
if (ts3.isFunctionLike(node)) {
|
|
726
778
|
for (const parameter of node.parameters) bind(scope2, parameter.name, parameter.initializer);
|
|
727
779
|
}
|
|
728
|
-
if (
|
|
780
|
+
if (ts3.isBlock(node)) {
|
|
729
781
|
for (const statement of node.statements) {
|
|
730
|
-
if (
|
|
782
|
+
if (ts3.isVariableStatement(statement)) {
|
|
731
783
|
for (const declaration of statement.declarationList.declarations) bind(scope2, declaration.name, declaration.initializer);
|
|
732
|
-
} else if (
|
|
784
|
+
} else if (ts3.isFunctionDeclaration(statement) && statement.name) scope2.set(statement.name.text, void 0);
|
|
733
785
|
}
|
|
734
786
|
}
|
|
735
787
|
return scope2;
|
|
@@ -740,55 +792,84 @@ function extractStorySourceDetails(source) {
|
|
|
740
792
|
for (const node of ancestors.reverse()) scope = addScope(node, scope);
|
|
741
793
|
const knownCollection = (expression, bindings2, seen = /* @__PURE__ */ new Set()) => {
|
|
742
794
|
expression = unwrap(expression);
|
|
743
|
-
if (
|
|
744
|
-
return expression.elements.every((element) => !
|
|
795
|
+
if (ts3.isArrayLiteralExpression(expression)) {
|
|
796
|
+
return expression.elements.every((element) => !ts3.isSpreadElement(element) || knownCollection(element.expression, bindings2, new Set(seen)));
|
|
745
797
|
}
|
|
746
|
-
if (
|
|
798
|
+
if (ts3.isIdentifier(expression)) {
|
|
747
799
|
if (seen.has(expression.text)) return false;
|
|
748
800
|
seen.add(expression.text);
|
|
749
801
|
const initializer = bindings2.get(expression.text);
|
|
750
802
|
return Boolean(initializer && knownCollection(initializer, bindings2, seen));
|
|
751
803
|
}
|
|
752
|
-
if (
|
|
804
|
+
if (ts3.isCallExpression(expression) && ts3.isPropertyAccessExpression(expression.expression) && ["slice", "filter", "toSorted", "toReversed"].includes(expression.expression.name.text)) {
|
|
753
805
|
return knownCollection(expression.expression.expression, bindings2, seen);
|
|
754
806
|
}
|
|
755
807
|
return false;
|
|
756
808
|
};
|
|
757
809
|
const visit = (node, bindings2) => {
|
|
758
|
-
if (
|
|
759
|
-
if (
|
|
810
|
+
if (ts3.isFunctionLike(node) || ts3.isBlock(node)) bindings2 = addScope(node, bindings2);
|
|
811
|
+
if (ts3.isCallExpression(node)) {
|
|
760
812
|
const target = node.expression;
|
|
761
|
-
const map =
|
|
813
|
+
const map = ts3.isPropertyAccessExpression(target) && target.name.text === "map" || ts3.isElementAccessExpression(target) && ts3.isStringLiteral(target.argumentExpression) && target.argumentExpression.text === "map";
|
|
762
814
|
if (map && !knownCollection(target.expression, bindings2)) return true;
|
|
763
815
|
}
|
|
764
|
-
return
|
|
816
|
+
return ts3.forEachChild(node, (child) => visit(child, bindings2)) ?? false;
|
|
765
817
|
};
|
|
766
818
|
const unknown = visit(jsx, scope);
|
|
767
819
|
mapCache.set(jsx, unknown);
|
|
768
820
|
return unknown;
|
|
769
821
|
};
|
|
770
|
-
const
|
|
771
|
-
const
|
|
772
|
-
const
|
|
822
|
+
const textWithBindings = (expression, bindings) => {
|
|
823
|
+
const edits = [];
|
|
824
|
+
const visit = (node) => {
|
|
825
|
+
if (ts3.isIdentifier(node)) {
|
|
826
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
827
|
+
const text2 = symbol && bindings.get(symbol);
|
|
828
|
+
const parent = node.parent;
|
|
829
|
+
const tag = (ts3.isJsxOpeningElement(parent) || ts3.isJsxClosingElement(parent) || ts3.isJsxSelfClosingElement(parent)) && parent.tagName === node;
|
|
830
|
+
if (text2 && !tag) edits.push({
|
|
831
|
+
start: node.getStart(file) - expression.getStart(file),
|
|
832
|
+
end: node.end - expression.getStart(file),
|
|
833
|
+
text: ts3.isShorthandPropertyAssignment(parent) ? `${node.text}: ${text2}` : text2
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
ts3.forEachChild(node, visit);
|
|
837
|
+
};
|
|
838
|
+
visit(expression);
|
|
839
|
+
let text = expression.getText(file);
|
|
840
|
+
for (const edit of edits.reverse()) text = text.slice(0, edit.start) + edit.text + text.slice(edit.end);
|
|
841
|
+
return text;
|
|
842
|
+
};
|
|
843
|
+
const inlineRoot = (expression, detail, trail = [], bindings = /* @__PURE__ */ new Map()) => {
|
|
844
|
+
const tag = ts3.isJsxElement(expression) ? expression.openingElement.tagName : ts3.isJsxSelfClosingElement(expression) ? expression.tagName : void 0;
|
|
845
|
+
const name = tag && ts3.isIdentifier(tag) && /^[A-Z]/.test(tag.text) ? tag.text : void 0;
|
|
773
846
|
const helper = name && helperFor(name);
|
|
774
|
-
if (!name || !declarations.has(name)) return expression
|
|
847
|
+
if (!name || !declarations.has(name)) return textWithBindings(expression, bindings);
|
|
775
848
|
if (!helper) {
|
|
776
849
|
const reason2 = "local definition is not a direct function";
|
|
777
850
|
detail.remaining.push({ name, reason: reason2 });
|
|
778
851
|
return `// ${name} not expanded: ${reason2}.
|
|
779
|
-
${expression
|
|
852
|
+
${textWithBindings(expression, bindings)}`;
|
|
780
853
|
}
|
|
781
854
|
const returned = returnedJsx(helper);
|
|
782
855
|
const jsx = returned[0];
|
|
783
|
-
const reason = isShadowed(expression, name) ? "file helper is shadowed in this scope" : trail.includes(name) ? "recursive helper cycle" : trail.length >= MAX_HELPER_DEPTH ? `helper depth limit (${MAX_HELPER_DEPTH})` : returnCounts.get(helper) !== 1 || returned.length !== 1 || !jsx || !(
|
|
856
|
+
const reason = isShadowed(expression, name) ? "file helper is shadowed in this scope" : trail.includes(name) ? "recursive helper cycle" : trail.length >= MAX_HELPER_DEPTH ? `helper depth limit (${MAX_HELPER_DEPTH})` : returnCounts.get(helper) !== 1 || returned.length !== 1 || !jsx || !(ts3.isJsxElement(jsx) || ts3.isJsxSelfClosingElement(jsx) || ts3.isJsxFragment(jsx)) ? "no single literal JSX return" : hasUnknownMap(helper, jsx) ? "maps over a collection without a local array definition" : void 0;
|
|
784
857
|
if (reason) {
|
|
785
858
|
detail.remaining.push({ name, reason });
|
|
786
859
|
return `// ${name} not expanded: ${reason}.
|
|
787
|
-
${expression
|
|
860
|
+
${textWithBindings(expression, bindings)}`;
|
|
788
861
|
}
|
|
789
862
|
detail.inlined.push(name);
|
|
863
|
+
const attributes = ts3.isJsxElement(expression) ? expression.openingElement.attributes : ts3.isJsxSelfClosingElement(expression) ? expression.attributes : void 0;
|
|
864
|
+
const parameter = helper.parameters[0]?.name;
|
|
865
|
+
const rootAttributes = ts3.isJsxElement(jsx) ? jsx.openingElement.attributes : ts3.isJsxSelfClosingElement(jsx) ? jsx.attributes : void 0;
|
|
866
|
+
const spread = attributes?.properties.length === 1 ? attributes.properties[0] : void 0;
|
|
867
|
+
if (parameter && ts3.isIdentifier(parameter) && spread && ts3.isJsxSpreadAttribute(spread) && textWithBindings(spread.expression, bindings) === "args" && rootAttributes?.properties.some((prop) => ts3.isJsxSpreadAttribute(prop) && ts3.isIdentifier(prop.expression) && prop.expression.text === parameter.text)) {
|
|
868
|
+
const symbol = checker.getSymbolAtLocation(parameter);
|
|
869
|
+
if (symbol) bindings = new Map(bindings).set(symbol, "args");
|
|
870
|
+
}
|
|
790
871
|
return `// JSX from ${name}, as authored.
|
|
791
|
-
${inlineRoot(jsx, detail, [...trail, name])}`;
|
|
872
|
+
${inlineRoot(jsx, detail, [...trail, name], bindings)}`;
|
|
792
873
|
};
|
|
793
874
|
const sources = {};
|
|
794
875
|
const helpers = {};
|
|
@@ -799,8 +880,16 @@ ${inlineRoot(jsx, detail, [...trail, name])}`;
|
|
|
799
880
|
const expressions = returnedJsx(render);
|
|
800
881
|
if (expressions.length) {
|
|
801
882
|
const detail = { inlined: [], remaining: [] };
|
|
802
|
-
const
|
|
803
|
-
|
|
883
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
884
|
+
const parameter = render.parameters[0]?.name;
|
|
885
|
+
if (parameter && ts3.isIdentifier(parameter)) {
|
|
886
|
+
const symbol = checker.getSymbolAtLocation(parameter);
|
|
887
|
+
if (symbol) bindings.set(symbol, "args");
|
|
888
|
+
}
|
|
889
|
+
const jsx = expressions.map((expression) => inlineRoot(expression, detail, [], bindings)).join("\n\n");
|
|
890
|
+
const unresolved = unresolvedSnippetBindings(jsx, new Set(declarations.keys()));
|
|
891
|
+
const incomplete = detail.remaining.length || unresolved.length || expressions.length > 1;
|
|
892
|
+
sources[name] = incomplete ? `${STORY_EXCERPT}${unresolved.length ? `; unresolved bindings: ${unresolved.join(", ")}` : "; multiple return branches"}.
|
|
804
893
|
${jsx}` : jsx;
|
|
805
894
|
if (detail.inlined.length || detail.remaining.length) helpers[name] = detail;
|
|
806
895
|
}
|
|
@@ -825,9 +914,9 @@ function storySourcesCode(sources) {
|
|
|
825
914
|
}
|
|
826
915
|
|
|
827
916
|
// src/vite-plugin.ts
|
|
828
|
-
function storybookDocs(
|
|
829
|
-
const { config } =
|
|
830
|
-
let projectRoot =
|
|
917
|
+
function storybookDocs(options2) {
|
|
918
|
+
const { config } = options2;
|
|
919
|
+
let projectRoot = options2.root ?? process.cwd();
|
|
831
920
|
let catalog = buildCatalog(scanStories(config.stories, projectRoot), config.groups);
|
|
832
921
|
catalog.files.clear();
|
|
833
922
|
const engineStyles = path7.join(import.meta.dirname, "styles.css");
|
|
@@ -874,7 +963,7 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
|
|
|
874
963
|
},
|
|
875
964
|
configResolved(resolved) {
|
|
876
965
|
propsCollector.setBuildMode(resolved.command === "build");
|
|
877
|
-
projectRoot =
|
|
966
|
+
projectRoot = options2.root ?? resolved.root;
|
|
878
967
|
catalog = buildCatalog(scanStories(config.stories, projectRoot), config.groups);
|
|
879
968
|
guidanceFiles = indexGuidance(config.guidance, projectRoot);
|
|
880
969
|
this.info?.(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jfrader/storybook-docs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A documentation front for any design system that has Storybook: navigation, live examples and API tables are generated from the story files themselves.",
|
|
6
6
|
"exports": {
|