@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.
@@ -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 {};
@@ -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 useEffect7, useRef as useRef5, useState as useState8 } from "react";
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 useEffect6, useMemo as useMemo4, useRef as useRef4, useState as useState7 } from "react";
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] = useState7("");
1632
- const [open, setOpen] = useState7(false);
1633
- const [active, setActive] = useState7(0);
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
- useEffect6(() => {
1656
+ useEffect7(() => {
1641
1657
  setActive(0);
1642
1658
  }, [query]);
1643
- useEffect6(() => {
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
- useEffect6(() => {
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] = useState8(() => parseRoute(window.location.hash));
1803
- const [mode, setMode] = useState8(theme.values?.[0] ?? "dark");
1804
- const [navOpen, setNavOpen] = useState8(false);
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
- useEffect7(() => {
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
- useEffect7(() => {
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
- useEffect7(() => {
1850
+ useEffect8(() => {
1835
1851
  document.documentElement.setAttribute(theme.attribute ?? "data-theme", mode);
1836
1852
  }, [mode]);
1837
1853
  const [darkMode, lightMode] = theme.values ?? ["dark", "light"];
@@ -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
  }
@@ -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.4.4",
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": {