@forgebook/svelte 0.0.0-stage → 0.2.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,66 @@
1
+ import { snippetOfArg } from "./snippetArgs.js";
2
+ /** Whether a value is a component: Svelte 5 compiles every component to a function. */
3
+ const isComponent = (value) => typeof value === "function";
4
+ /** Whether a value is an object of props. */
5
+ const isProps = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ /**
7
+ * Props with each `#name` text as its snippet; a `#name` with nothing to show sets nothing.
8
+ *
9
+ * @param props - the props, as the args or a render give them.
10
+ * @param snippetOf - makes a text's snippet.
11
+ */
12
+ const withSnippets = (props, snippetOf) => Object.fromEntries(Object.entries(props).flatMap(([name, value]) => {
13
+ const snippet = snippetOfArg(name);
14
+ if (snippet === undefined)
15
+ return [[name, value]];
16
+ if (value === undefined || value === null || value === false)
17
+ return [];
18
+ if (typeof value !== "string" && typeof value !== "number")
19
+ throw new Error(`${name} takes a text, which its snippet draws; pass a snippet as ${snippet}`);
20
+ return [[snippet, snippetOf(snippet, String(value))]];
21
+ }));
22
+ /**
23
+ * What a render or a decorator returned, as a node.
24
+ *
25
+ * @param result - the component, or the component with props.
26
+ * @param props - the props a bare component is drawn with.
27
+ * @param snippetOf - makes a text's snippet.
28
+ * @param who - what returned it, for the error.
29
+ * @throws when it is neither, which the frame shows in place of the story.
30
+ */
31
+ const nodeOf = (result, props, snippetOf, who) => {
32
+ if (isComponent(result))
33
+ return { component: result, props: withSnippets(props, snippetOf) };
34
+ // `{ Component, props }`, as Storybook's Svelte stories write it
35
+ if (isProps(result) && isComponent(result["Component"])) {
36
+ const chosen = result["props"];
37
+ return { component: result["Component"], props: withSnippets(isProps(chosen) ? chosen : {}, snippetOf) };
38
+ }
39
+ throw new Error(`${who} returned neither a component nor { Component, props }`);
40
+ };
41
+ /**
42
+ * The story with these args: its render (or the meta's component), inside its decorators.
43
+ *
44
+ * @param loaded - the story.
45
+ * @param context - the args and the canvas its render, play and decorators read.
46
+ * @param outer - the catalogue's decorators, outside the story's and the meta's.
47
+ * @param snippetOf - makes the snippet of a `#name` text.
48
+ * @throws when the story has nothing to draw, or a render or a decorator returns what cannot be drawn.
49
+ */
50
+ export const storyNode = (loaded, context, outer, snippetOf) => {
51
+ const { args } = context;
52
+ const story = () => {
53
+ if (loaded.render !== undefined)
54
+ return nodeOf(loaded.render(args, context), args, snippetOf, "A render");
55
+ if (loaded.component !== undefined)
56
+ return nodeOf(loaded.component, args, snippetOf, "The meta's component");
57
+ throw new Error("The story has neither a render nor a component to draw");
58
+ };
59
+ return [...loaded.decorators, ...outer].reduce((inner, decorator) => {
60
+ const result = decorator(() => inner, context);
61
+ // a decorator that only reads the context, or sets one, returns the story as it was given
62
+ if (result === inner)
63
+ return inner;
64
+ return { ...nodeOf(result, {}, snippetOf, "A decorator"), inner };
65
+ }, story());
66
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * What the frame hands the components inside a story through Svelte's context: the shown language's texts and the story
3
+ * clock, read when they are read, so a template that reads them follows a change.
4
+ *
5
+ * @remarks
6
+ * The renderer's host sets it (`StoryHost.svelte`); `texts.ts` and `clock.ts` read it. Outside a catalogue there is
7
+ * none, and each reader says what it falls back to.
8
+ *
9
+ * @module
10
+ */
11
+ import type { StoryClock } from "@forgebook/story-format/clock";
12
+ import type { StoryTextTable } from "@forgebook/story-format/texts";
13
+ /** What a story's components read of the frame, each value of the moment. */
14
+ export type StoryView = {
15
+ /** The texts of the shown language. */
16
+ readonly texts: StoryTextTable;
17
+ /** The story's clock: replaced when the story starts over. */
18
+ readonly clock: StoryClock;
19
+ };
20
+ /** The context's key. */
21
+ export declare const STORY_VIEW: unique symbol;
22
+ /**
23
+ * The view the frame set, for a component inside a story; called while the component is initialised, as Svelte's
24
+ * context is.
25
+ *
26
+ * @returns the view, or none outside a catalogue.
27
+ */
28
+ export declare const storyView: () => StoryView | undefined;
@@ -0,0 +1,10 @@
1
+ import { getContext, hasContext } from "svelte";
2
+ /** The context's key. */
3
+ export const STORY_VIEW = Symbol("forgebook story view");
4
+ /**
5
+ * The view the frame set, for a component inside a story; called while the component is initialised, as Svelte's
6
+ * context is.
7
+ *
8
+ * @returns the view, or none outside a catalogue.
9
+ */
10
+ export const storyView = () => hasContext(STORY_VIEW) ? getContext(STORY_VIEW) : undefined;
@@ -0,0 +1,19 @@
1
+ import type { StoryRenderer } from "forgebook/renderer";
2
+ import { type StoryNode } from "./storyNode.ts";
3
+ /** What the host draws: the story's tree of the moment, and whether it failed. */
4
+ export type StoryScene = {
5
+ /** The story's tree, made when it is read: what the story throws is thrown here, inside the host's boundary. */
6
+ readonly node: StoryNode;
7
+ /** Grows at each new try after an error, so the story is drawn anew. */
8
+ readonly attempt: number;
9
+ /** Whether the story threw, and is not drawn until it is tried again. */
10
+ readonly isFailed: boolean;
11
+ /**
12
+ * Called by the host's boundary with what the story threw.
13
+ *
14
+ * @param error - the error.
15
+ */
16
+ readonly fail: (error: unknown) => void;
17
+ };
18
+ /** Svelte as the frame draws with it. */
19
+ export declare const svelteRenderer: StoryRenderer;
@@ -0,0 +1,196 @@
1
+ import { storyTextOf } from "@forgebook/story-format/texts";
2
+ import { storyContextOf, untracedStep } from "forgebook/renderer";
3
+ import { flushSync, mount, settled, unmount } from "svelte";
4
+ import StoryHost from "./StoryHost.svelte";
5
+ import { storyCell } from "./storyCell.js";
6
+ import { storyNode } from "./storyNode.js";
7
+ import { STORY_VIEW } from "./storyView.js";
8
+ import { textSnippets } from "./textSnippet.js";
9
+ /** An error as the frame takes it. */
10
+ const toError = (error) => (error instanceof Error ? error : new Error(String(error)));
11
+ /**
12
+ * Whether two drawings draw the same story with the same decorators.
13
+ *
14
+ * @param current - the drawing on screen.
15
+ * @param next - the new one.
16
+ */
17
+ const isSameStory = (current, next) => current.story === next.story &&
18
+ current.decorators.length === next.decorators.length &&
19
+ current.decorators.every((decorator, index) => decorator === next.decorators[index]);
20
+ /**
21
+ * What a render and a decorator receive, reading the moment's values when they are read: a render follows what it read
22
+ * and nothing else.
23
+ *
24
+ * @param args - the args.
25
+ * @param view - the rest of what the story reads.
26
+ * @param scene - the drawing on screen.
27
+ * @param canvas - the story's canvas.
28
+ */
29
+ const liveContext = (args, view, scene, canvas) => ({
30
+ get args() {
31
+ return args.current;
32
+ },
33
+ step: untracedStep,
34
+ canvasElement: canvas,
35
+ get text() {
36
+ return storyTextOf(view.texts.current);
37
+ },
38
+ get clock() {
39
+ return view.clock.current;
40
+ },
41
+ get globals() {
42
+ return view.globals.current;
43
+ },
44
+ get loaded() {
45
+ return view.loaded.current;
46
+ },
47
+ get id() {
48
+ return scene.current.identity.id;
49
+ },
50
+ get name() {
51
+ return scene.current.identity.name;
52
+ },
53
+ get title() {
54
+ return scene.current.identity.title;
55
+ },
56
+ get parameters() {
57
+ return scene.current.story.parameters;
58
+ }
59
+ });
60
+ /**
61
+ * The context the story's components read, as a map for Svelte's `mount` and `render`.
62
+ *
63
+ * @param view - the texts and the clock, read when read.
64
+ */
65
+ const viewContext = (view) => new Map([[STORY_VIEW, view]]);
66
+ /**
67
+ * Brings the view to a new drawing's values; the switches only when one changed, since they come as a new object.
68
+ *
69
+ * @param view - the view's values.
70
+ * @param next - the new drawing.
71
+ */
72
+ const assignView = (view, next) => {
73
+ if (JSON.stringify(view.globals.current) !== JSON.stringify(next.globals))
74
+ view.globals.set(next.globals);
75
+ view.texts.set(next.texts);
76
+ view.clock.set(next.clock);
77
+ view.loaded.set(next.loaded);
78
+ };
79
+ /** Svelte as the frame draws with it. */
80
+ export const svelteRenderer = {
81
+ mount: (canvas, first, onError) => {
82
+ const scene = storyCell(first);
83
+ const args = storyCell(first.args);
84
+ const view = {
85
+ texts: storyCell(first.texts),
86
+ clock: storyCell(first.clock),
87
+ globals: storyCell(first.globals),
88
+ loaded: storyCell(first.loaded)
89
+ };
90
+ const attempt = storyCell(0);
91
+ const failed = storyCell(false);
92
+ // the same as `failed`, read where nothing must follow it
93
+ let hasFailed = false;
94
+ // a new try after an error, not settled yet, and how many drawings came since it started: its error, which Svelte
95
+ // reports after drawing, may be of args no longer shown
96
+ let isTrying = false;
97
+ let updatesSinceTry = 0;
98
+ const context = liveContext(args, view, scene, canvas);
99
+ const snippetOf = textSnippets();
100
+ let markDrawn = () => undefined;
101
+ const drawn = new Promise((resolve) => {
102
+ markDrawn = resolve;
103
+ });
104
+ const host = {
105
+ get node() {
106
+ return storyNode(scene.current.story, context, scene.current.decorators, snippetOf);
107
+ },
108
+ get attempt() {
109
+ return attempt.current;
110
+ },
111
+ get isFailed() {
112
+ return failed.current;
113
+ },
114
+ fail: (error) => {
115
+ if (isTrying && updatesSinceTry > 0) {
116
+ // the args changed while the try was drawn: tried again with them, the failed try said nothing of
117
+ tryAgain();
118
+ return;
119
+ }
120
+ if (!hasFailed)
121
+ onError(toError(error));
122
+ hasFailed = true;
123
+ failed.set(true);
124
+ }
125
+ };
126
+ const instance = mount(StoryHost, {
127
+ target: canvas,
128
+ props: { scene: host },
129
+ context: viewContext({
130
+ get texts() {
131
+ return view.texts.current;
132
+ },
133
+ get clock() {
134
+ return view.clock.current;
135
+ }
136
+ })
137
+ });
138
+ // once Svelte has settled, unless the story threw: tried again after an error, a story drawn at last is drawn
139
+ const markDrawnOnceSettled = () => {
140
+ void settled().then(() => {
141
+ if (!hasFailed)
142
+ markDrawn();
143
+ });
144
+ };
145
+ /** Draws the story anew with the args of the moment, after an error. */
146
+ const tryAgain = () => {
147
+ hasFailed = false;
148
+ failed.set(false);
149
+ attempt.set(attempt.current + 1);
150
+ const tried = attempt.current;
151
+ isTrying = true;
152
+ updatesSinceTry = 0;
153
+ void settled().then(() => {
154
+ if (attempt.current === tried)
155
+ isTrying = false;
156
+ });
157
+ markDrawnOnceSettled();
158
+ };
159
+ // the effects run now, `onMount` among them: a story that throws there is not drawn
160
+ flushSync();
161
+ markDrawnOnceSettled();
162
+ return {
163
+ drawn,
164
+ update: (next) => {
165
+ args.set(next.args);
166
+ assignView(view, next);
167
+ if (!isSameStory(scene.current, next))
168
+ scene.set(next);
169
+ updatesSinceTry += 1;
170
+ if (hasFailed)
171
+ tryAgain();
172
+ },
173
+ unmount: () => {
174
+ void unmount(instance);
175
+ }
176
+ };
177
+ },
178
+ renderToString: async (drawing) => {
179
+ const { render } = await import("svelte/server");
180
+ const context = storyContextOf(drawing, drawing.args, () => document.body);
181
+ const snippetOf = textSnippets();
182
+ const scene = {
183
+ get node() {
184
+ return storyNode(drawing.story, context, drawing.decorators, snippetOf);
185
+ },
186
+ attempt: 0,
187
+ isFailed: false,
188
+ fail: () => undefined
189
+ };
190
+ const output = await render(StoryHost, {
191
+ props: { scene },
192
+ context: viewContext({ texts: drawing.texts, clock: drawing.clock })
193
+ });
194
+ return output.body;
195
+ }
196
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A snippet that draws a text: what a `#name` arg becomes, so a component's `{@render children()}` draws the text the
3
+ * story or the props panel wrote.
4
+ *
5
+ * @remarks
6
+ * A snippet made outside a component is a raw one, which Svelte draws from HTML and which must be one element: the text
7
+ * sits in a `span`. Each snippet prop keeps the snippet of its text, so a story drawn again with the same text keeps what it drew,
8
+ * and a text typed in the panel replaces the one before.
9
+ *
10
+ * @module
11
+ */
12
+ import { type Snippet } from "svelte";
13
+ /**
14
+ * Makes the snippet of a snippet prop's text, the same one while the text stays the same.
15
+ *
16
+ * @param prop - the snippet prop (`children`).
17
+ * @param text - its text.
18
+ */
19
+ export type TextSnippets = (prop: string, text: string) => Snippet;
20
+ /**
21
+ * The snippets of a story's texts, kept for as long as the story is drawn.
22
+ *
23
+ * @returns the function that makes a text's snippet.
24
+ */
25
+ export declare const textSnippets: () => TextSnippets;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A snippet that draws a text: what a `#name` arg becomes, so a component's `{@render children()}` draws the text the
3
+ * story or the props panel wrote.
4
+ *
5
+ * @remarks
6
+ * A snippet made outside a component is a raw one, which Svelte draws from HTML and which must be one element: the text
7
+ * sits in a `span`. Each snippet prop keeps the snippet of its text, so a story drawn again with the same text keeps what it drew,
8
+ * and a text typed in the panel replaces the one before.
9
+ *
10
+ * @module
11
+ */
12
+ import { createRawSnippet } from "svelte";
13
+ /** A text as HTML holds it. */
14
+ const escapedText = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
15
+ /**
16
+ * The snippets of a story's texts, kept for as long as the story is drawn.
17
+ *
18
+ * @returns the function that makes a text's snippet.
19
+ */
20
+ export const textSnippets = () => {
21
+ const kept = new Map();
22
+ return (prop, text) => {
23
+ const known = kept.get(prop);
24
+ if (known?.text === text)
25
+ return known.snippet;
26
+ const snippet = createRawSnippet(() => ({ render: () => `<span>${escapedText(text)}</span>` }));
27
+ kept.set(prop, { text, snippet });
28
+ return snippet;
29
+ };
30
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The texts inside the stories for Svelte components inside a story: the shown language's table, as the frame sets it,
3
+ * read with `getStoryText()`.
4
+ *
5
+ * @remarks
6
+ * The table and the text function are the format's (`@forgebook/story-format/texts`); this module is how a Svelte
7
+ * component reads them. The text function reads the language when it is called, so a template that calls it follows a
8
+ * change; outside a catalogue there is no table, and a text is its key in brackets.
9
+ *
10
+ * @module
11
+ */
12
+ import { type StoryTextFunction } from "@forgebook/story-format/texts";
13
+ /**
14
+ * The text function of the shown language, for a component inside a story; called while the component is initialised.
15
+ *
16
+ * @returns `text(key, values?)`, reading the language when called.
17
+ */
18
+ export declare const getStoryText: () => StoryTextFunction;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The texts inside the stories for Svelte components inside a story: the shown language's table, as the frame sets it,
3
+ * read with `getStoryText()`.
4
+ *
5
+ * @remarks
6
+ * The table and the text function are the format's (`@forgebook/story-format/texts`); this module is how a Svelte
7
+ * component reads them. The text function reads the language when it is called, so a template that calls it follows a
8
+ * change; outside a catalogue there is no table, and a text is its key in brackets.
9
+ *
10
+ * @module
11
+ */
12
+ import { storyTextOf } from "@forgebook/story-format/texts";
13
+ import { storyView } from "./storyView.js";
14
+ /**
15
+ * The text function of the shown language, for a component inside a story; called while the component is initialised.
16
+ *
17
+ * @returns `text(key, values?)`, reading the language when called.
18
+ */
19
+ export const getStoryText = () => {
20
+ const view = storyView();
21
+ return (...parameters) => storyTextOf(view?.texts ?? {})(...parameters);
22
+ };
@@ -0,0 +1,23 @@
1
+ import type { PropDocs } from "forgebook/component-docs";
2
+ import ts from "typescript-6";
3
+ /** What a component takes. */
4
+ export type ComponentProps = {
5
+ /** Its props, a snippet as `#name`. */
6
+ readonly props: readonly PropDocs[];
7
+ /** Its parts: the keys of its `classNames`, `styles` and `refs` props. */
8
+ readonly parts: readonly string[];
9
+ /** The files its own props are declared in, a component as its `.svelte` file. */
10
+ readonly sources: readonly string[];
11
+ };
12
+ /** A file as the docs name it: a component's `.svelte` file rather than the TypeScript written for it. */
13
+ export declare const sourceFileOf: (fileName: string) => string;
14
+ /**
15
+ * What a component takes.
16
+ *
17
+ * @param componentSymbol - the component's declared symbol.
18
+ * @param checker - the program's checker.
19
+ * @param node - where the code names the component.
20
+ * @param componentFile - the component's file, for which props are its own.
21
+ * @throws when its type is not a component's.
22
+ */
23
+ export declare const componentProps: (componentSymbol: ts.Symbol, checker: ts.TypeChecker, node: ts.Node, componentFile: string) => ComponentProps;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * What a Svelte component takes, read from its type: its props, a snippet as the arg that fills it with a text
3
+ * (`#children`), a callback prop as an event (`onclick`, as Svelte 5 names them), each prop's default from its
4
+ * `$props()`.
5
+ *
6
+ * @remarks
7
+ * A component's type is `Component<Props>`, as `svelte2tsx` writes a `.svelte` file and as a package declares one, so its
8
+ * props are what it is called with. They read as a React component's do (`forgebook/vite/prop-docs`), from the checker's
9
+ * symbols; what it passes on to an element comes from `node_modules` (`svelte/elements`) and is left out. A default is
10
+ * what the component's `$props()` destructuring gives, `$bindable(value)` included, since the type does not hold it.
11
+ *
12
+ * @module
13
+ */
14
+ import { dirname } from "node:path";
15
+ import { declarationOrder, isForeign, partsOf, propDocs } from "forgebook/vite/prop-docs";
16
+ import ts from "typescript-6";
17
+ import { componentOfScriptFile } from "./svelteSources.js";
18
+ /** A callback prop: `on` and a letter, as Svelte names an element's (`onclick`) and a component's own (`onRemove`). */
19
+ const EVENT_NAME = /^on[A-Za-z]/;
20
+ /** A file as the docs name it: a component's `.svelte` file rather than the TypeScript written for it. */
21
+ export const sourceFileOf = (fileName) => componentOfScriptFile(fileName) ?? fileName;
22
+ /**
23
+ * The type of a component's props: what its function is called with, or, for a component declared as a class, what its
24
+ * constructor's `props` take.
25
+ *
26
+ * @param type - the component's type.
27
+ * @param checker - the program's checker.
28
+ * @param node - where the code names it.
29
+ */
30
+ const propsTypeOf = (type, checker, node) => {
31
+ const called = type.getCallSignatures().find((signature) => signature.parameters.length >= 2);
32
+ const props = called?.parameters[1];
33
+ if (props !== undefined)
34
+ return checker.getTypeOfSymbolAtLocation(props, node);
35
+ const options = type.getConstructSignatures()[0]?.parameters[0];
36
+ if (options === undefined)
37
+ return undefined;
38
+ const fromOptions = checker.getTypeOfSymbolAtLocation(options, node).getProperty("props");
39
+ return fromOptions === undefined ? undefined : checker.getTypeOfSymbolAtLocation(fromOptions, node);
40
+ };
41
+ /** Whether a type is Svelte's `Snippet`. */
42
+ const isSnippet = (type) => {
43
+ const symbol = type.aliasSymbol ?? type.getSymbol();
44
+ return (symbol?.getName() === "Snippet" &&
45
+ (symbol.declarations ?? []).some((declaration) => /[\\/]svelte[\\/]types[\\/]/.test(declaration.getSourceFile().fileName)));
46
+ };
47
+ /** Whether a declaration is the component's `$props()` destructuring. */
48
+ const isPropsDeclaration = (node, source) => ts.isVariableDeclaration(node) &&
49
+ ts.isObjectBindingPattern(node.name) &&
50
+ node.initializer !== undefined &&
51
+ ts.isCallExpression(node.initializer) &&
52
+ node.initializer.expression.getText(source) === "$props";
53
+ /**
54
+ * What a prop's destructuring gives it when it is not passed: its initialiser, or what `$bindable(value)` holds.
55
+ *
56
+ * @param element - the prop's element of the destructuring.
57
+ * @param source - the file.
58
+ * @returns the default as the source writes it, or none.
59
+ */
60
+ const defaultOf = (element, source) => {
61
+ const written = element.initializer;
62
+ if (element.dotDotDotToken !== undefined || written === undefined)
63
+ return undefined;
64
+ const isBindable = ts.isCallExpression(written) && written.expression.getText(source) === "$bindable";
65
+ const value = isBindable ? written.arguments[0] : written;
66
+ return value?.getText(source).replace(/\s+/g, " ");
67
+ };
68
+ /**
69
+ * The defaults a component's `$props()` gives, by prop: `let { tone = "neutral", value = $bindable("") } = $props()`.
70
+ *
71
+ * @param source - the file that declares the component.
72
+ */
73
+ const propsDefaults = (source) => {
74
+ const find = (node) => isPropsDeclaration(node, source) ? node.name : ts.forEachChild(node, find);
75
+ const pattern = find(source);
76
+ return new Map((pattern?.elements ?? []).flatMap((element) => {
77
+ const value = defaultOf(element, source);
78
+ return value === undefined ? [] : [[(element.propertyName ?? element.name).getText(source), value]];
79
+ }));
80
+ };
81
+ /**
82
+ * One prop of a component: a snippet as the arg that fills it with a text, a callback prop as an event.
83
+ *
84
+ * @param symbol - the prop.
85
+ * @param checker - the program's checker.
86
+ * @param node - where the code names the component.
87
+ * @param defaults - the defaults its `$props()` gives.
88
+ */
89
+ const sveltePropDocs = (symbol, checker, node, defaults) => {
90
+ const read = propDocs(symbol, checker, node, { eventName: EVENT_NAME });
91
+ // a component without TypeScript writes its props in JSDoc, whose `@property {T} name - text` keeps the dash
92
+ const docs = { ...read, description: read.description.replace(/^-\s+/, "") };
93
+ const type = checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(symbol, node));
94
+ if (isSnippet(type)) {
95
+ // any text can fill it: the panel offers a field, and the renderer makes the snippet
96
+ return {
97
+ name: `#${docs.name}`,
98
+ kind: "other",
99
+ type: docs.type,
100
+ description: docs.description,
101
+ isRequired: false,
102
+ canTakeText: true
103
+ };
104
+ }
105
+ const defaultValue = docs.defaultValue ?? defaults.get(docs.name);
106
+ return { ...docs, ...(defaultValue === undefined ? {} : { defaultValue }) };
107
+ };
108
+ /**
109
+ * What a component takes.
110
+ *
111
+ * @param componentSymbol - the component's declared symbol.
112
+ * @param checker - the program's checker.
113
+ * @param node - where the code names the component.
114
+ * @param componentFile - the component's file, for which props are its own.
115
+ * @throws when its type is not a component's.
116
+ */
117
+ export const componentProps = (componentSymbol, checker, node, componentFile) => {
118
+ const propsType = propsTypeOf(checker.getTypeOfSymbolAtLocation(componentSymbol, node), checker, node);
119
+ if (propsType === undefined)
120
+ throw new Error(`${componentSymbol.getName()} is not a Svelte component`);
121
+ const declaration = componentSymbol.declarations?.[0]?.getSourceFile();
122
+ const defaults = declaration === undefined ? new Map() : propsDefaults(declaration);
123
+ const symbols = declarationOrder(propsType.getProperties().filter((prop) => (prop.declarations ?? []).length === 0 || !isForeign(prop)), dirname(componentFile));
124
+ return {
125
+ props: symbols.map((symbol) => sveltePropDocs(symbol, checker, node, defaults)),
126
+ parts: partsOf(propsType, checker, node),
127
+ sources: [
128
+ componentFile,
129
+ ...symbols.flatMap((symbol) => (symbol.declarations ?? []).map((each) => sourceFileOf(each.getSourceFile().fileName)))
130
+ ]
131
+ };
132
+ };
@@ -0,0 +1,37 @@
1
+ import type { ComponentDocs, NoComponentDocs } from "forgebook/component-docs";
2
+ import type { StoryComponentResult, StoryFileShape } from "forgebook/vite/adapter";
3
+ /** The module a Svelte story file imports its types from (`Meta`, `Story`), for the stories `forgebook new` writes. */
4
+ export declare const storyTypesModule = "@forgebook/svelte";
5
+ /**
6
+ * A story file's component with the file that declares it.
7
+ *
8
+ * @param storyFile - the story file's absolute path.
9
+ * @returns the component, `{ docs: { name: null } }` when the meta names none, or a message saying why the file
10
+ * cannot be read.
11
+ */
12
+ export declare const extractStoryComponent: (storyFile: string) => StoryComponentResult;
13
+ /**
14
+ * The docs of a story file's component.
15
+ *
16
+ * @param storyFile - the story file's absolute path.
17
+ * @returns the component's docs, `{ name: null }` when the meta names no component, or a message saying why the file
18
+ * cannot be read.
19
+ */
20
+ export declare const extractComponentDocs: (storyFile: string) => ComponentDocs | NoComponentDocs | string;
21
+ /**
22
+ * A component a file exports, for writing its first stories (`forgebook new`): a `.svelte` file is its default export,
23
+ * named after its file.
24
+ *
25
+ * @param file - the component's file, absolute.
26
+ * @param exportName - the export to read; the file's only component when none.
27
+ * @returns the component, or a message saying why it cannot be read (no component, several, a name not exported).
28
+ */
29
+ export declare const extractFileComponent: (file: string, exportName?: string) => StoryComponentResult;
30
+ /**
31
+ * How `forgebook new` writes a Svelte component's stories: `.ts`, since a story needs no markup, a `.svelte` file
32
+ * imported by its default export and with its extension.
33
+ *
34
+ * @param name - the component's name.
35
+ * @param file - the component's file, absolute.
36
+ */
37
+ export declare const storyFileShape: (name: string, file: string) => StoryFileShape;