@jfrader/storybook-docs 0.6.0 → 0.7.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,3 @@
1
+ import ts from 'typescript';
2
+ export declare function snippetProgram(file: ts.SourceFile): ts.Program;
3
+ export declare function unresolvedSnippetBindings(source: string, localTags?: Set<string>): string[];
package/dist/index.js CHANGED
@@ -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 example = source?.trim() || (compound && !tag.includes(".") ? null : usageSnippet(tag, args));
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 = docsOverride?.source?.code ?? docsMeta?.source?.code;
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 ?? `${entry.name} \u2014 every story in ${entry.storybookTitle} is rendered below, in sections.` }),
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
 
@@ -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;
@@ -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 (ts2.isParenthesizedExpression(node) || ts2.isAsExpression(node) || ts2.isSatisfiesExpression(node) || ts2.isTypeAssertionExpression(node)) node = node.expression;
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 = ts2.createSourceFile("stories.tsx", source, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TSX);
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 = ts2.canHaveModifiers(statement) && ts2.getModifiers(statement)?.some((modifier) => modifier.kind === ts2.SyntaxKind.ExportKeyword);
624
- if (ts2.isVariableStatement(statement)) {
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 (!ts2.isIdentifier(declaration.name) || !declaration.initializer) continue;
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 (ts2.isFunctionDeclaration(statement) && statement.name) {
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 (ts2.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
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 (ts2.isFunctionDeclaration(node)) return node;
692
+ if (ts3.isFunctionDeclaration(node)) return node;
641
693
  node = unwrap(node);
642
- if (ts2.isIdentifier(node)) {
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 (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;
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 (ts2.isSpreadAssignment(property)) {
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 && (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;
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) => ts2.isJsxElement(node) || ts2.isJsxSelfClosingElement(node) || ts2.isJsxFragment(node) || (ts2.forEachChild(node, hasJsx) ?? false);
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 (ts2.isBlock(render.body)) {
724
+ if (ts3.isBlock(render.body)) {
673
725
  const visit = (node) => {
674
- if (ts2.isFunctionLike(node)) return;
675
- if (ts2.isReturnStatement(node)) {
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
- ts2.forEachChild(node, visit);
732
+ ts3.forEachChild(node, visit);
681
733
  };
682
- ts2.forEachChild(render.body, visit);
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 (ts2.isFunctionDeclaration(declaration)) return declaration;
748
+ if (ts3.isFunctionDeclaration(declaration)) return declaration;
697
749
  const expression = unwrap(declaration);
698
- if (ts2.isIdentifier(expression)) return helperFor(expression.text, seen);
699
- if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression)) return expression;
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) => ts2.isIdentifier(binding) ? binding.text === name : binding.elements.some((element) => ts2.isBindingElement(element) && bindingContains(element.name, 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 (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;
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, ts2.isFunctionDeclaration(declaration) ? void 0 : declaration);
767
+ bindings.set(name, ts3.isFunctionDeclaration(declaration) ? void 0 : declaration);
716
768
  }
717
769
  const bind = (scope2, name, initializer) => {
718
- if (ts2.isIdentifier(name)) scope2.set(name.text, initializer);
770
+ if (ts3.isIdentifier(name)) scope2.set(name.text, initializer);
719
771
  else for (const element of name.elements) {
720
- if (ts2.isBindingElement(element)) bind(scope2, element.name, element.initializer);
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 (ts2.isFunctionLike(node)) {
777
+ if (ts3.isFunctionLike(node)) {
726
778
  for (const parameter of node.parameters) bind(scope2, parameter.name, parameter.initializer);
727
779
  }
728
- if (ts2.isBlock(node)) {
780
+ if (ts3.isBlock(node)) {
729
781
  for (const statement of node.statements) {
730
- if (ts2.isVariableStatement(statement)) {
782
+ if (ts3.isVariableStatement(statement)) {
731
783
  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);
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 (ts2.isArrayLiteralExpression(expression)) {
744
- return expression.elements.every((element) => !ts2.isSpreadElement(element) || knownCollection(element.expression, bindings2, new Set(seen)));
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 (ts2.isIdentifier(expression)) {
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 (ts2.isCallExpression(expression) && ts2.isPropertyAccessExpression(expression.expression) && ["slice", "filter", "toSorted", "toReversed"].includes(expression.expression.name.text)) {
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 (ts2.isFunctionLike(node) || ts2.isBlock(node)) bindings2 = addScope(node, bindings2);
759
- if (ts2.isCallExpression(node)) {
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 = ts2.isPropertyAccessExpression(target) && target.name.text === "map" || ts2.isElementAccessExpression(target) && ts2.isStringLiteral(target.argumentExpression) && target.argumentExpression.text === "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 ts2.forEachChild(node, (child) => visit(child, bindings2)) ?? false;
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 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;
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.getText(file);
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.getText(file)}`;
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 || !(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;
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.getText(file)}`;
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 jsx = expressions.map((expression) => inlineRoot(expression, detail)).join("\n\n");
803
- sources[name] = expressions.length > 1 ? `// JSX return branches as authored in the story.
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(options) {
829
- const { config } = options;
830
- let projectRoot = options.root ?? process.cwd();
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 = options.root ?? resolved.root;
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.6.0",
3
+ "version": "0.7.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": {