@jfrader/storybook-docs 0.4.4 → 0.5.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/story-sources.d.ts +15 -0
- package/dist/controls.d.ts +2 -1
- package/dist/index.js +36 -20
- package/dist/sections.d.ts +2 -1
- package/dist/virtual.d.ts +4 -0
- package/dist/vite-plugin.js +234 -0
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface HelperExpansion {
|
|
2
|
+
inlined: string[];
|
|
3
|
+
remaining: {
|
|
4
|
+
name: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
}[];
|
|
7
|
+
}
|
|
8
|
+
export declare function extractStorySourceDetails(source: string): {
|
|
9
|
+
sources: Record<string, string>;
|
|
10
|
+
helpers: Record<string, HelperExpansion>;
|
|
11
|
+
};
|
|
12
|
+
export declare function extractStorySources(source: string): Record<string, string>;
|
|
13
|
+
export declare function storySourcesIndexCode(slugs: Iterable<string>): string;
|
|
14
|
+
export declare function storySourcesCode(sources: Record<string, string>): string;
|
|
15
|
+
export {};
|
package/dist/controls.d.ts
CHANGED
|
@@ -40,10 +40,11 @@ export declare function getStoryControls(meta: StoryShape, story: StoryShape, co
|
|
|
40
40
|
controls: StoryControl[];
|
|
41
41
|
unavailable: string | null;
|
|
42
42
|
};
|
|
43
|
-
export declare function ControlledStoryFrame({ module, story, componentName, componentDefaults }: {
|
|
43
|
+
export declare function ControlledStoryFrame({ module, story, componentName, componentDefaults, renderSource }: {
|
|
44
44
|
module: Record<string, unknown>;
|
|
45
45
|
story: string;
|
|
46
46
|
componentName: string;
|
|
47
47
|
/** Defaults read from the component source, shown until the reader changes them. */
|
|
48
48
|
componentDefaults?: Record<string, unknown>;
|
|
49
|
+
renderSource?: string;
|
|
49
50
|
}): import("react").JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,12 @@ 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 { useEffect as
|
|
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
|
|
|
12
12
|
// src/pages/component-page.tsx
|
|
13
|
-
import { useMemo as useMemo3 } from "react";
|
|
13
|
+
import { useEffect as useEffect6, useMemo as useMemo3, useState as useState7 } from "react";
|
|
14
14
|
import { storySource as storySource2 } from "virtual:storybook-docs/catalog";
|
|
15
15
|
|
|
16
16
|
// src/guidance.tsx
|
|
@@ -374,7 +374,8 @@ function usageSnippet(componentName, args) {
|
|
|
374
374
|
const children = typeof args.children === "string" && !/[&<>{}]/.test(args.children) ? args.children : `{${JSON.stringify(args.children)}}`;
|
|
375
375
|
return `${opening}>${children}</${componentName}>`;
|
|
376
376
|
}
|
|
377
|
-
function buildUsageSnippet({ componentName, from, args, source, jsxName, component }) {
|
|
377
|
+
function buildUsageSnippet({ componentName, from, args, source, renderSource, jsxName, component }) {
|
|
378
|
+
if (!source?.trim() && renderSource?.trim()) return renderSource;
|
|
378
379
|
const rootName = componentName.split(".")[0];
|
|
379
380
|
const identity = component;
|
|
380
381
|
const tag = jsxName ?? (typeof identity?.displayName === "string" && identity.displayName.startsWith(`${rootName}.`) ? identity.displayName : componentName);
|
|
@@ -980,7 +981,7 @@ function getStoryControls(meta, story, componentDefaults = {}) {
|
|
|
980
981
|
unavailable: controls.length === 0 ? "No controls." : null
|
|
981
982
|
};
|
|
982
983
|
}
|
|
983
|
-
function ControlledStoryFrame({ module, story, componentName, componentDefaults }) {
|
|
984
|
+
function ControlledStoryFrame({ module, story, componentName, componentDefaults, renderSource }) {
|
|
984
985
|
const meta = module.default ?? {};
|
|
985
986
|
const definition = module[story] ?? {};
|
|
986
987
|
const { args: defaults, controls, unavailable } = getStoryControls(meta, definition, componentDefaults);
|
|
@@ -996,12 +997,13 @@ function ControlledStoryFrame({ module, story, componentName, componentDefaults
|
|
|
996
997
|
const storyName = definition.name ?? story;
|
|
997
998
|
const skipped = ["loaders", "beforeEach"].filter((key) => meta[key] || definition[key]);
|
|
998
999
|
if (definition.parameters?.viewport ?? meta.parameters?.viewport) skipped.push("viewport");
|
|
999
|
-
const codeLanguage = docsOverride?.source?.language ?? docsMeta?.source?.language ?? "tsx";
|
|
1000
|
+
const codeLanguage = authoredCode?.trim() ? docsOverride?.source?.language ?? docsMeta?.source?.language ?? "tsx" : "tsx";
|
|
1000
1001
|
const snippet = buildUsageSnippet({
|
|
1001
1002
|
componentName,
|
|
1002
1003
|
from: storySource,
|
|
1003
1004
|
args: { ...meta.args, ...definition.args, ...edits },
|
|
1004
1005
|
source: authoredCode,
|
|
1006
|
+
renderSource,
|
|
1005
1007
|
// A story name is not a component name: only a part-qualified name (the rare `Dialog.Content`
|
|
1006
1008
|
// story) is worth using as the JSX tag, otherwise the component's own name is the honest one.
|
|
1007
1009
|
jsxName: storyName.includes(".") ? storyName : void 0,
|
|
@@ -1049,6 +1051,7 @@ function useProps(slug) {
|
|
|
1049
1051
|
}
|
|
1050
1052
|
|
|
1051
1053
|
// src/pages/component-page.tsx
|
|
1054
|
+
import { loadStorySources } from "virtual:storybook-docs/story-sources";
|
|
1052
1055
|
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1053
1056
|
function formatValue(value) {
|
|
1054
1057
|
if (value === void 0) return "\u2014";
|
|
@@ -1175,6 +1178,19 @@ function PropsTable({
|
|
|
1175
1178
|
}
|
|
1176
1179
|
function ComponentPage({ entry }) {
|
|
1177
1180
|
const { module, error } = useStoryModule(entry);
|
|
1181
|
+
const [storySources, setStorySources] = useState7(null);
|
|
1182
|
+
useEffect6(() => {
|
|
1183
|
+
let active = true;
|
|
1184
|
+
loadStorySources(entry.slug).then(({ sources: sources2 }) => {
|
|
1185
|
+
if (active) setStorySources({ slug: entry.slug, sources: sources2 });
|
|
1186
|
+
}).catch(() => {
|
|
1187
|
+
if (active) setStorySources(null);
|
|
1188
|
+
});
|
|
1189
|
+
return () => {
|
|
1190
|
+
active = false;
|
|
1191
|
+
};
|
|
1192
|
+
}, [entry.slug]);
|
|
1193
|
+
const sources = storySources?.slug === entry.slug ? storySources.sources : {};
|
|
1178
1194
|
const props = useProps(entry.slug);
|
|
1179
1195
|
const meta = module?.default ?? {};
|
|
1180
1196
|
const argTypes = meta.argTypes ?? {};
|
|
@@ -1246,7 +1262,7 @@ function ComponentPage({ entry }) {
|
|
|
1246
1262
|
/* @__PURE__ */ jsx8(Skeleton, { height: 150 })
|
|
1247
1263
|
] }) : null,
|
|
1248
1264
|
module ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1249
|
-
example ? /* @__PURE__ */ jsx8(ControlledStoryFrame, { module, story: example, componentName: entry.name, componentDefaults }, `${entry.slug}:${example}`) : null,
|
|
1265
|
+
example ? /* @__PURE__ */ jsx8(ControlledStoryFrame, { module, story: example, componentName: entry.name, componentDefaults, renderSource: sources[example] }, `${entry.slug}:${example}`) : null,
|
|
1250
1266
|
entry.hasComponent ? /* @__PURE__ */ jsx8("h2", { children: "Usage" }) : null,
|
|
1251
1267
|
snippet ? /* @__PURE__ */ jsx8(CodeBlock, { code: snippet, language: codeLanguage }) : entry.hasComponent ? /* @__PURE__ */ jsx8("p", { children: "No usage code is authored for this component." }) : null,
|
|
1252
1268
|
usage ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
@@ -1256,7 +1272,7 @@ function ComponentPage({ entry }) {
|
|
|
1256
1272
|
/* @__PURE__ */ jsx8(Guidance, { slug: entry.slug }),
|
|
1257
1273
|
sections.map((section) => /* @__PURE__ */ jsxs5("section", { children: [
|
|
1258
1274
|
/* @__PURE__ */ jsx8("h2", { children: section.title }),
|
|
1259
|
-
section.stories.map((story) => /* @__PURE__ */ jsx8(ControlledStoryFrame, { module, story, componentName: entry.name, componentDefaults }, `${entry.slug}:${story}`))
|
|
1275
|
+
section.stories.map((story) => /* @__PURE__ */ jsx8(ControlledStoryFrame, { module, story, componentName: entry.name, componentDefaults, renderSource: sources[story] }, `${entry.slug}:${story}`))
|
|
1260
1276
|
] }, section.title))
|
|
1261
1277
|
] }) : null,
|
|
1262
1278
|
/* @__PURE__ */ jsx8("h2", { children: "Props" }),
|
|
@@ -1577,7 +1593,7 @@ function ReleasesPage() {
|
|
|
1577
1593
|
}
|
|
1578
1594
|
|
|
1579
1595
|
// src/search.tsx
|
|
1580
|
-
import { useEffect as
|
|
1596
|
+
import { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef4, useState as useState8 } from "react";
|
|
1581
1597
|
import { catalog as catalog2 } from "virtual:storybook-docs/catalog";
|
|
1582
1598
|
|
|
1583
1599
|
// src/search-match.ts
|
|
@@ -1628,19 +1644,19 @@ function renderHighlighted(text, query) {
|
|
|
1628
1644
|
);
|
|
1629
1645
|
}
|
|
1630
1646
|
function SearchBox() {
|
|
1631
|
-
const [query, setQuery] =
|
|
1632
|
-
const [open, setOpen] =
|
|
1633
|
-
const [active, setActive] =
|
|
1647
|
+
const [query, setQuery] = useState8("");
|
|
1648
|
+
const [open, setOpen] = useState8(false);
|
|
1649
|
+
const [active, setActive] = useState8(0);
|
|
1634
1650
|
const inputRef = useRef4(null);
|
|
1635
1651
|
const listId = "storybook-docs-search-results";
|
|
1636
1652
|
const optionId = (key) => `${listId}-${key}`;
|
|
1637
1653
|
const results = useMemo4(() => {
|
|
1638
1654
|
return searchCatalog(catalog2, query);
|
|
1639
1655
|
}, [query]);
|
|
1640
|
-
|
|
1656
|
+
useEffect7(() => {
|
|
1641
1657
|
setActive(0);
|
|
1642
1658
|
}, [query]);
|
|
1643
|
-
|
|
1659
|
+
useEffect7(() => {
|
|
1644
1660
|
const onGlobalKeyDown = (event) => {
|
|
1645
1661
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
1646
1662
|
event.preventDefault();
|
|
@@ -1663,7 +1679,7 @@ function SearchBox() {
|
|
|
1663
1679
|
const showDropdown = open && (hasResults || hasQuery);
|
|
1664
1680
|
const activeRow = hasResults && results[active] ? results[active] : void 0;
|
|
1665
1681
|
const activeOptionId = activeRow ? optionId(rowKey(activeRow)) : void 0;
|
|
1666
|
-
|
|
1682
|
+
useEffect7(() => {
|
|
1667
1683
|
if (showDropdown && activeOptionId) {
|
|
1668
1684
|
const el = document.getElementById(activeOptionId);
|
|
1669
1685
|
el?.scrollIntoView({ block: "nearest" });
|
|
@@ -1799,14 +1815,14 @@ function parseRoute(hash) {
|
|
|
1799
1815
|
return { kind: "overview" };
|
|
1800
1816
|
}
|
|
1801
1817
|
function DocsApp() {
|
|
1802
|
-
const [route, setRoute] =
|
|
1803
|
-
const [mode, setMode] =
|
|
1804
|
-
const [navOpen, setNavOpen] =
|
|
1818
|
+
const [route, setRoute] = useState9(() => parseRoute(window.location.hash));
|
|
1819
|
+
const [mode, setMode] = useState9(theme.values?.[0] ?? "dark");
|
|
1820
|
+
const [navOpen, setNavOpen] = useState9(false);
|
|
1805
1821
|
const navRef = useRef5(null);
|
|
1806
1822
|
useRailFollow(navRef);
|
|
1807
1823
|
const headerRef = useRef5(null);
|
|
1808
1824
|
const mainRef = useRef5(null);
|
|
1809
|
-
|
|
1825
|
+
useEffect8(() => {
|
|
1810
1826
|
const header = headerRef.current;
|
|
1811
1827
|
if (!header) return;
|
|
1812
1828
|
const root = document.documentElement;
|
|
@@ -1821,7 +1837,7 @@ function DocsApp() {
|
|
|
1821
1837
|
else root.style.removeProperty("--sd-header-height");
|
|
1822
1838
|
};
|
|
1823
1839
|
}, []);
|
|
1824
|
-
|
|
1840
|
+
useEffect8(() => {
|
|
1825
1841
|
const onHash = () => {
|
|
1826
1842
|
setRoute(parseRoute(window.location.hash));
|
|
1827
1843
|
setNavOpen(false);
|
|
@@ -1831,7 +1847,7 @@ function DocsApp() {
|
|
|
1831
1847
|
window.addEventListener("hashchange", onHash);
|
|
1832
1848
|
return () => window.removeEventListener("hashchange", onHash);
|
|
1833
1849
|
}, []);
|
|
1834
|
-
|
|
1850
|
+
useEffect8(() => {
|
|
1835
1851
|
document.documentElement.setAttribute(theme.attribute ?? "data-theme", mode);
|
|
1836
1852
|
}, [mode]);
|
|
1837
1853
|
const [darkMode, lightMode] = theme.values ?? ["dark", "light"];
|
package/dist/sections.d.ts
CHANGED
|
@@ -42,11 +42,12 @@ export interface ExampleStory {
|
|
|
42
42
|
export declare function selectExample(stories: ExampleStory[], parts?: string[], defaults?: Record<string, unknown> | null): string | null;
|
|
43
43
|
/** Only story-authored values: never synthesize children or component defaults. */
|
|
44
44
|
export declare function usageSnippet(componentName: string, args: Record<string, unknown> | undefined): string | null;
|
|
45
|
-
export declare function buildUsageSnippet({ componentName, from, args, source, jsxName, component }: {
|
|
45
|
+
export declare function buildUsageSnippet({ componentName, from, args, source, renderSource, jsxName, component }: {
|
|
46
46
|
componentName: string;
|
|
47
47
|
from: string;
|
|
48
48
|
args?: Record<string, unknown>;
|
|
49
49
|
source?: string;
|
|
50
|
+
renderSource?: string;
|
|
50
51
|
jsxName?: string;
|
|
51
52
|
component?: unknown;
|
|
52
53
|
}): string | null;
|
package/dist/virtual.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ declare module 'virtual:storybook-docs/guidance' {
|
|
|
28
28
|
export function loadGuidance(slug: string): Promise<{ markdown: string }>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
declare module 'virtual:storybook-docs/story-sources' {
|
|
32
|
+
export function loadStorySources(slug: string): Promise<{ sources: Record<string, string> }>;
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
declare module 'virtual:storybook-docs/releases' {
|
|
32
36
|
export const releases: { version: string | null; changelog: string | null };
|
|
33
37
|
}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -608,6 +608,222 @@ export const fireEvent = () => true;
|
|
|
608
608
|
export const cleanup = () => {};
|
|
609
609
|
`;
|
|
610
610
|
|
|
611
|
+
// src/build/story-sources.ts
|
|
612
|
+
import ts2 from "typescript";
|
|
613
|
+
function unwrap(node) {
|
|
614
|
+
while (ts2.isParenthesizedExpression(node) || ts2.isAsExpression(node) || ts2.isSatisfiesExpression(node) || ts2.isTypeAssertionExpression(node)) node = node.expression;
|
|
615
|
+
return node;
|
|
616
|
+
}
|
|
617
|
+
var MAX_HELPER_DEPTH = 3;
|
|
618
|
+
function extractStorySourceDetails(source) {
|
|
619
|
+
const file = ts2.createSourceFile("stories.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
|
|
620
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
621
|
+
const exports = /* @__PURE__ */ new Map();
|
|
622
|
+
for (const statement of file.statements) {
|
|
623
|
+
const exported = ts2.canHaveModifiers(statement) && ts2.getModifiers(statement)?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword);
|
|
624
|
+
if (ts2.isVariableStatement(statement)) {
|
|
625
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
626
|
+
if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
627
|
+
declarations.set(declaration.name.text, declaration.initializer);
|
|
628
|
+
if (exported) exports.set(declaration.name.text, declaration.name.text);
|
|
629
|
+
}
|
|
630
|
+
} else if (ts2.isFunctionDeclaration(statement) && statement.name) {
|
|
631
|
+
declarations.set(statement.name.text, statement);
|
|
632
|
+
if (exported) exports.set(statement.name.text, statement.name.text);
|
|
633
|
+
} else if (ts2.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
|
|
634
|
+
for (const element of statement.exportClause.elements) {
|
|
635
|
+
exports.set(element.name.text, (element.propertyName ?? element.name).text);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const renderFor = (node, seen = /* @__PURE__ */ new Set()) => {
|
|
640
|
+
if (ts2.isFunctionDeclaration(node)) return node;
|
|
641
|
+
node = unwrap(node);
|
|
642
|
+
if (ts2.isIdentifier(node)) {
|
|
643
|
+
if (seen.has(node.text)) return;
|
|
644
|
+
seen.add(node.text);
|
|
645
|
+
const declaration = declarations.get(node.text);
|
|
646
|
+
return declaration ? renderFor(declaration, seen) : void 0;
|
|
647
|
+
}
|
|
648
|
+
if (ts2.isPropertyAccessExpression(node) && node.name.text === "render" || ts2.isElementAccessExpression(node) && ts2.isStringLiteral(node.argumentExpression) && node.argumentExpression.text === "render") return renderFor(node.expression, seen);
|
|
649
|
+
if (ts2.isArrowFunction(node) || ts2.isFunctionExpression(node)) return node;
|
|
650
|
+
if (!ts2.isObjectLiteralExpression(node)) return;
|
|
651
|
+
for (const property of [...node.properties].reverse()) {
|
|
652
|
+
if (ts2.isSpreadAssignment(property)) {
|
|
653
|
+
const render = renderFor(property.expression, new Set(seen));
|
|
654
|
+
if (render !== void 0) return render;
|
|
655
|
+
} else if (property.name && (ts2.isIdentifier(property.name) || ts2.isStringLiteral(property.name)) && property.name.text === "render") {
|
|
656
|
+
if (ts2.isMethodDeclaration(property)) return property;
|
|
657
|
+
if (ts2.isPropertyAssignment(property)) return renderFor(property.initializer, seen) ?? null;
|
|
658
|
+
if (ts2.isShorthandPropertyAssignment(property)) return renderFor(property.name, seen) ?? null;
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
const hasJsx = (node) => ts2.isJsxElement(node) || ts2.isJsxSelfClosingElement(node) || ts2.isJsxFragment(node) || (ts2.forEachChild(node, hasJsx) ?? false);
|
|
664
|
+
const returnsCache = /* @__PURE__ */ new Map();
|
|
665
|
+
const returnCounts = /* @__PURE__ */ new Map();
|
|
666
|
+
const returnedJsx = (render) => {
|
|
667
|
+
const cached = returnsCache.get(render);
|
|
668
|
+
if (cached) return cached;
|
|
669
|
+
const expressions = [];
|
|
670
|
+
if (!render.body) return expressions;
|
|
671
|
+
let returnCount = 0;
|
|
672
|
+
if (ts2.isBlock(render.body)) {
|
|
673
|
+
const visit = (node) => {
|
|
674
|
+
if (ts2.isFunctionLike(node)) return;
|
|
675
|
+
if (ts2.isReturnStatement(node)) {
|
|
676
|
+
returnCount += 1;
|
|
677
|
+
if (node.expression && hasJsx(node.expression)) expressions.push(unwrap(node.expression));
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
ts2.forEachChild(node, visit);
|
|
681
|
+
};
|
|
682
|
+
ts2.forEachChild(render.body, visit);
|
|
683
|
+
} else {
|
|
684
|
+
returnCount = 1;
|
|
685
|
+
if (hasJsx(render.body)) expressions.push(unwrap(render.body));
|
|
686
|
+
}
|
|
687
|
+
returnsCache.set(render, expressions);
|
|
688
|
+
returnCounts.set(render, returnCount);
|
|
689
|
+
return expressions;
|
|
690
|
+
};
|
|
691
|
+
const helperFor = (name, seen = /* @__PURE__ */ new Set()) => {
|
|
692
|
+
if (seen.has(name)) return;
|
|
693
|
+
seen.add(name);
|
|
694
|
+
const declaration = declarations.get(name);
|
|
695
|
+
if (!declaration) return;
|
|
696
|
+
if (ts2.isFunctionDeclaration(declaration)) return declaration;
|
|
697
|
+
const expression = unwrap(declaration);
|
|
698
|
+
if (ts2.isIdentifier(expression)) return helperFor(expression.text, seen);
|
|
699
|
+
if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression)) return expression;
|
|
700
|
+
};
|
|
701
|
+
const bindingContains = (binding, name) => ts2.isIdentifier(binding) ? binding.text === name : binding.elements.some((element) => ts2.isBindingElement(element) && bindingContains(element.name, name));
|
|
702
|
+
const isShadowed = (expression, name) => {
|
|
703
|
+
for (let node = expression.parent; node; node = node.parent) {
|
|
704
|
+
if (ts2.isFunctionLike(node) && node.parameters.some((parameter) => bindingContains(parameter.name, name))) return true;
|
|
705
|
+
if (ts2.isBlock(node) && node.statements.some((statement) => ts2.isVariableStatement(statement) && statement.declarationList.declarations.some((declaration) => bindingContains(declaration.name, name)) || ts2.isFunctionDeclaration(statement) && statement.name?.text === name)) return true;
|
|
706
|
+
}
|
|
707
|
+
return false;
|
|
708
|
+
};
|
|
709
|
+
const mapCache = /* @__PURE__ */ new Map();
|
|
710
|
+
const hasUnknownMap = (helper, jsx) => {
|
|
711
|
+
const cached = mapCache.get(jsx);
|
|
712
|
+
if (cached !== void 0) return cached;
|
|
713
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
714
|
+
for (const [name, declaration] of declarations) {
|
|
715
|
+
bindings.set(name, ts2.isFunctionDeclaration(declaration) ? void 0 : declaration);
|
|
716
|
+
}
|
|
717
|
+
const bind = (scope2, name, initializer) => {
|
|
718
|
+
if (ts2.isIdentifier(name)) scope2.set(name.text, initializer);
|
|
719
|
+
else for (const element of name.elements) {
|
|
720
|
+
if (ts2.isBindingElement(element)) bind(scope2, element.name, element.initializer);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
const addScope = (node, parent) => {
|
|
724
|
+
const scope2 = new Map(parent);
|
|
725
|
+
if (ts2.isFunctionLike(node)) {
|
|
726
|
+
for (const parameter of node.parameters) bind(scope2, parameter.name, parameter.initializer);
|
|
727
|
+
}
|
|
728
|
+
if (ts2.isBlock(node)) {
|
|
729
|
+
for (const statement of node.statements) {
|
|
730
|
+
if (ts2.isVariableStatement(statement)) {
|
|
731
|
+
for (const declaration of statement.declarationList.declarations) bind(scope2, declaration.name, declaration.initializer);
|
|
732
|
+
} else if (ts2.isFunctionDeclaration(statement) && statement.name) scope2.set(statement.name.text, void 0);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return scope2;
|
|
736
|
+
};
|
|
737
|
+
const ancestors = [];
|
|
738
|
+
for (let node = jsx.parent; node !== helper; node = node.parent) ancestors.push(node);
|
|
739
|
+
let scope = addScope(helper, bindings);
|
|
740
|
+
for (const node of ancestors.reverse()) scope = addScope(node, scope);
|
|
741
|
+
const knownCollection = (expression, bindings2, seen = /* @__PURE__ */ new Set()) => {
|
|
742
|
+
expression = unwrap(expression);
|
|
743
|
+
if (ts2.isArrayLiteralExpression(expression)) {
|
|
744
|
+
return expression.elements.every((element) => !ts2.isSpreadElement(element) || knownCollection(element.expression, bindings2, new Set(seen)));
|
|
745
|
+
}
|
|
746
|
+
if (ts2.isIdentifier(expression)) {
|
|
747
|
+
if (seen.has(expression.text)) return false;
|
|
748
|
+
seen.add(expression.text);
|
|
749
|
+
const initializer = bindings2.get(expression.text);
|
|
750
|
+
return Boolean(initializer && knownCollection(initializer, bindings2, seen));
|
|
751
|
+
}
|
|
752
|
+
if (ts2.isCallExpression(expression) && ts2.isPropertyAccessExpression(expression.expression) && ["slice", "filter", "toSorted", "toReversed"].includes(expression.expression.name.text)) {
|
|
753
|
+
return knownCollection(expression.expression.expression, bindings2, seen);
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
};
|
|
757
|
+
const visit = (node, bindings2) => {
|
|
758
|
+
if (ts2.isFunctionLike(node) || ts2.isBlock(node)) bindings2 = addScope(node, bindings2);
|
|
759
|
+
if (ts2.isCallExpression(node)) {
|
|
760
|
+
const target = node.expression;
|
|
761
|
+
const map = ts2.isPropertyAccessExpression(target) && target.name.text === "map" || ts2.isElementAccessExpression(target) && ts2.isStringLiteral(target.argumentExpression) && target.argumentExpression.text === "map";
|
|
762
|
+
if (map && !knownCollection(target.expression, bindings2)) return true;
|
|
763
|
+
}
|
|
764
|
+
return ts2.forEachChild(node, (child) => visit(child, bindings2)) ?? false;
|
|
765
|
+
};
|
|
766
|
+
const unknown = visit(jsx, scope);
|
|
767
|
+
mapCache.set(jsx, unknown);
|
|
768
|
+
return unknown;
|
|
769
|
+
};
|
|
770
|
+
const inlineRoot = (expression, detail, trail = []) => {
|
|
771
|
+
const tag = ts2.isJsxElement(expression) ? expression.openingElement.tagName : ts2.isJsxSelfClosingElement(expression) ? expression.tagName : void 0;
|
|
772
|
+
const name = tag && ts2.isIdentifier(tag) && /^[A-Z]/.test(tag.text) ? tag.text : void 0;
|
|
773
|
+
const helper = name && helperFor(name);
|
|
774
|
+
if (!name || !declarations.has(name)) return expression.getText(file);
|
|
775
|
+
if (!helper) {
|
|
776
|
+
const reason2 = "local definition is not a direct function";
|
|
777
|
+
detail.remaining.push({ name, reason: reason2 });
|
|
778
|
+
return `// ${name} not expanded: ${reason2}.
|
|
779
|
+
${expression.getText(file)}`;
|
|
780
|
+
}
|
|
781
|
+
const returned = returnedJsx(helper);
|
|
782
|
+
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 || !(ts2.isJsxElement(jsx) || ts2.isJsxSelfClosingElement(jsx) || ts2.isJsxFragment(jsx)) ? "no single literal JSX return" : hasUnknownMap(helper, jsx) ? "maps over a collection without a local array definition" : void 0;
|
|
784
|
+
if (reason) {
|
|
785
|
+
detail.remaining.push({ name, reason });
|
|
786
|
+
return `// ${name} not expanded: ${reason}.
|
|
787
|
+
${expression.getText(file)}`;
|
|
788
|
+
}
|
|
789
|
+
detail.inlined.push(name);
|
|
790
|
+
return `// JSX from ${name}, as authored.
|
|
791
|
+
${inlineRoot(jsx, detail, [...trail, name])}`;
|
|
792
|
+
};
|
|
793
|
+
const sources = {};
|
|
794
|
+
const helpers = {};
|
|
795
|
+
for (const [name, local] of exports) {
|
|
796
|
+
const declaration = declarations.get(local);
|
|
797
|
+
const render = declaration && renderFor(declaration);
|
|
798
|
+
if (!render?.body) continue;
|
|
799
|
+
const expressions = returnedJsx(render);
|
|
800
|
+
if (expressions.length) {
|
|
801
|
+
const detail = { inlined: [], remaining: [] };
|
|
802
|
+
const jsx = expressions.map((expression) => inlineRoot(expression, detail)).join("\n\n");
|
|
803
|
+
sources[name] = expressions.length > 1 ? `// JSX return branches as authored in the story.
|
|
804
|
+
${jsx}` : jsx;
|
|
805
|
+
if (detail.inlined.length || detail.remaining.length) helpers[name] = detail;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return { sources, helpers };
|
|
809
|
+
}
|
|
810
|
+
function extractStorySources(source) {
|
|
811
|
+
return extractStorySourceDetails(source).sources;
|
|
812
|
+
}
|
|
813
|
+
function storySourcesIndexCode(slugs) {
|
|
814
|
+
return `
|
|
815
|
+
export function loadStorySources(slug) {
|
|
816
|
+
switch (slug) {
|
|
817
|
+
${[...slugs].map((slug) => ` case ${JSON.stringify(slug)}: return import(${JSON.stringify(`${VIRTUAL_PREFIX}story-sources/${slug}`)});`).join("\n")}
|
|
818
|
+
default: return Promise.resolve({ sources: {} });
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
`;
|
|
822
|
+
}
|
|
823
|
+
function storySourcesCode(sources) {
|
|
824
|
+
return `export const sources = ${JSON.stringify(sources)};`;
|
|
825
|
+
}
|
|
826
|
+
|
|
611
827
|
// src/vite-plugin.ts
|
|
612
828
|
function storybookDocs(options) {
|
|
613
829
|
const { config } = options;
|
|
@@ -616,6 +832,7 @@ function storybookDocs(options) {
|
|
|
616
832
|
catalog.files.clear();
|
|
617
833
|
const engineStyles = path7.join(import.meta.dirname, "styles.css");
|
|
618
834
|
const propsCache = /* @__PURE__ */ new Map();
|
|
835
|
+
const storySourcesCache = /* @__PURE__ */ new Map();
|
|
619
836
|
const propsCollector = createPropsCollector();
|
|
620
837
|
let guidanceFiles = /* @__PURE__ */ new Map();
|
|
621
838
|
const loadProps = (slug) => {
|
|
@@ -636,6 +853,7 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
|
|
|
636
853
|
"stub-storybook-test": () => STORYBOOK_TEST_STUB,
|
|
637
854
|
props: () => propsIndexCode(catalog.entries.map((entry) => entry.slug)),
|
|
638
855
|
guidance: () => guidanceIndexCode(guidanceFiles.keys()),
|
|
856
|
+
"story-sources": () => storySourcesIndexCode(catalog.files.keys()),
|
|
639
857
|
releases: () => releasesCode(readReleases(config, projectRoot)),
|
|
640
858
|
code: () => codeModuleCode(resolveCodeModule(config.code?.module, projectRoot))
|
|
641
859
|
};
|
|
@@ -665,6 +883,7 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
|
|
|
665
883
|
},
|
|
666
884
|
buildStart() {
|
|
667
885
|
propsCache.clear();
|
|
886
|
+
storySourcesCache.clear();
|
|
668
887
|
propsCollector.clearPrograms();
|
|
669
888
|
},
|
|
670
889
|
closeBundle() {
|
|
@@ -682,11 +901,26 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
|
|
|
682
901
|
const module = Object.hasOwn(modules, name) ? modules[name] : void 0;
|
|
683
902
|
if (module) return module();
|
|
684
903
|
if (name.startsWith("props/")) return loadProps(name.slice("props/".length));
|
|
904
|
+
if (name.startsWith("story-sources/")) {
|
|
905
|
+
const storyFile = catalog.files.get(name.slice("story-sources/".length));
|
|
906
|
+
if (!storyFile) return storySourcesCode({});
|
|
907
|
+
const file = realpathSync(storyFile);
|
|
908
|
+
let sources = storySourcesCache.get(file);
|
|
909
|
+
if (!sources) {
|
|
910
|
+
sources = extractStorySources(readFileSync3(file, "utf8"));
|
|
911
|
+
storySourcesCache.set(file, sources);
|
|
912
|
+
}
|
|
913
|
+
this.addWatchFile(file);
|
|
914
|
+
return storySourcesCode(sources);
|
|
915
|
+
}
|
|
685
916
|
if (name.startsWith("guidance/")) {
|
|
686
917
|
const file = guidanceFiles.get(name.slice("guidance/".length));
|
|
687
918
|
return guidanceCode(file ? readFileSync3(file, "utf8") : "");
|
|
688
919
|
}
|
|
689
920
|
return null;
|
|
921
|
+
},
|
|
922
|
+
watchChange(file) {
|
|
923
|
+
storySourcesCache.delete(file);
|
|
690
924
|
}
|
|
691
925
|
};
|
|
692
926
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jfrader/storybook-docs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": {
|