sdocs 0.0.1
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/README.md +43 -0
- package/bin/sdocs.js +2 -0
- package/dist/Sdocs.svelte +1210 -0
- package/dist/Sdocs.svelte.d.ts +5 -0
- package/dist/cli/app-plugin.d.ts +7 -0
- package/dist/cli/app-plugin.js +69 -0
- package/dist/cli/config.d.ts +12 -0
- package/dist/cli/config.js +34 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +72 -0
- package/dist/cli/server.d.ts +2 -0
- package/dist/cli/server.js +62 -0
- package/dist/docgen.d.ts +47 -0
- package/dist/docgen.js +463 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/internal/ComponentPreview.svelte +58 -0
- package/dist/internal/ComponentPreview.svelte.d.ts +17 -0
- package/dist/internal/CssPropsTable.svelte +239 -0
- package/dist/internal/CssPropsTable.svelte.d.ts +11 -0
- package/dist/internal/Home.svelte +92 -0
- package/dist/internal/Home.svelte.d.ts +9 -0
- package/dist/internal/MethodsTable.svelte +72 -0
- package/dist/internal/MethodsTable.svelte.d.ts +7 -0
- package/dist/internal/PropsTable.svelte +342 -0
- package/dist/internal/PropsTable.svelte.d.ts +12 -0
- package/dist/internal/Showcase.svelte +130 -0
- package/dist/internal/Showcase.svelte.d.ts +21 -0
- package/dist/types.d.ts +162 -0
- package/dist/types.js +1 -0
- package/dist/ui/Badge/Badge.docs.svelte +46 -0
- package/dist/ui/Badge/Badge.docs.svelte.d.ts +26 -0
- package/dist/ui/Badge/Badge.svelte +59 -0
- package/dist/ui/Badge/Badge.svelte.d.ts +17 -0
- package/dist/ui/Badge/index.d.ts +1 -0
- package/dist/ui/Badge/index.js +1 -0
- package/dist/ui/Checkbox/Checkbox.docs.svelte +51 -0
- package/dist/ui/Checkbox/Checkbox.docs.svelte.d.ts +27 -0
- package/dist/ui/Checkbox/Checkbox.svelte +169 -0
- package/dist/ui/Checkbox/Checkbox.svelte.d.ts +18 -0
- package/dist/ui/Checkbox/index.d.ts +1 -0
- package/dist/ui/Checkbox/index.js +1 -0
- package/dist/ui/CodeBlock/CodeBlock.docs.svelte +28 -0
- package/dist/ui/CodeBlock/CodeBlock.docs.svelte.d.ts +24 -0
- package/dist/ui/CodeBlock/CodeBlock.svelte +101 -0
- package/dist/ui/CodeBlock/CodeBlock.svelte.d.ts +7 -0
- package/dist/ui/CodeBlock/index.d.ts +1 -0
- package/dist/ui/CodeBlock/index.js +1 -0
- package/dist/ui/Frame/Frame.docs.svelte +140 -0
- package/dist/ui/Frame/Frame.docs.svelte.d.ts +26 -0
- package/dist/ui/Frame/Frame.svelte +88 -0
- package/dist/ui/Frame/Frame.svelte.d.ts +15 -0
- package/dist/ui/Frame/index.d.ts +1 -0
- package/dist/ui/Frame/index.js +1 -0
- package/dist/ui/InputNumber/InputNumber.docs.svelte +50 -0
- package/dist/ui/InputNumber/InputNumber.docs.svelte.d.ts +26 -0
- package/dist/ui/InputNumber/InputNumber.svelte +275 -0
- package/dist/ui/InputNumber/InputNumber.svelte.d.ts +26 -0
- package/dist/ui/InputNumber/index.d.ts +1 -0
- package/dist/ui/InputNumber/index.js +1 -0
- package/dist/ui/InputText/InputText.docs.svelte +43 -0
- package/dist/ui/InputText/InputText.docs.svelte.d.ts +26 -0
- package/dist/ui/InputText/InputText.svelte +116 -0
- package/dist/ui/InputText/InputText.svelte.d.ts +22 -0
- package/dist/ui/InputText/index.d.ts +1 -0
- package/dist/ui/InputText/index.js +1 -0
- package/dist/ui/Panel/CollapsiblePanel.docs.svelte +45 -0
- package/dist/ui/Panel/CollapsiblePanel.docs.svelte.d.ts +25 -0
- package/dist/ui/Panel/CollapsiblePanel.svelte +93 -0
- package/dist/ui/Panel/CollapsiblePanel.svelte.d.ts +14 -0
- package/dist/ui/Panel/index.d.ts +1 -0
- package/dist/ui/Panel/index.js +1 -0
- package/dist/ui/Placeholder/Placeholder.docs.svelte +49 -0
- package/dist/ui/Placeholder/Placeholder.docs.svelte.d.ts +26 -0
- package/dist/ui/Placeholder/Placeholder.svelte +99 -0
- package/dist/ui/Placeholder/Placeholder.svelte.d.ts +21 -0
- package/dist/ui/Placeholder/index.d.ts +1 -0
- package/dist/ui/Placeholder/index.js +1 -0
- package/dist/ui/Radio/Radio.docs.svelte +67 -0
- package/dist/ui/Radio/Radio.docs.svelte.d.ts +27 -0
- package/dist/ui/Radio/Radio.svelte +165 -0
- package/dist/ui/Radio/Radio.svelte.d.ts +22 -0
- package/dist/ui/Radio/RadioGroup.docs.svelte +70 -0
- package/dist/ui/Radio/RadioGroup.docs.svelte.d.ts +27 -0
- package/dist/ui/Radio/RadioGroup.svelte +98 -0
- package/dist/ui/Radio/RadioGroup.svelte.d.ts +27 -0
- package/dist/ui/Radio/index.d.ts +2 -0
- package/dist/ui/Radio/index.js +2 -0
- package/dist/ui/SegmentControl/SegmentControl.docs.svelte +54 -0
- package/dist/ui/SegmentControl/SegmentControl.docs.svelte.d.ts +25 -0
- package/dist/ui/SegmentControl/SegmentControl.svelte +120 -0
- package/dist/ui/SegmentControl/SegmentControl.svelte.d.ts +18 -0
- package/dist/ui/SegmentControl/index.d.ts +1 -0
- package/dist/ui/SegmentControl/index.js +1 -0
- package/dist/ui/Stack/Stack.docs.svelte +63 -0
- package/dist/ui/Stack/Stack.docs.svelte.d.ts +26 -0
- package/dist/ui/Stack/Stack.svelte +45 -0
- package/dist/ui/Stack/Stack.svelte.d.ts +19 -0
- package/dist/ui/Stack/index.d.ts +1 -0
- package/dist/ui/Stack/index.js +1 -0
- package/dist/ui/Table/Body.svelte +17 -0
- package/dist/ui/Table/Body.svelte.d.ts +11 -0
- package/dist/ui/Table/Caption.svelte +17 -0
- package/dist/ui/Table/Caption.svelte.d.ts +11 -0
- package/dist/ui/Table/Cell.svelte +24 -0
- package/dist/ui/Table/Cell.svelte.d.ts +15 -0
- package/dist/ui/Table/Foot.svelte +17 -0
- package/dist/ui/Table/Foot.svelte.d.ts +11 -0
- package/dist/ui/Table/Head.svelte +17 -0
- package/dist/ui/Table/Head.svelte.d.ts +11 -0
- package/dist/ui/Table/Header.svelte +27 -0
- package/dist/ui/Table/Header.svelte.d.ts +17 -0
- package/dist/ui/Table/Row.svelte +19 -0
- package/dist/ui/Table/Row.svelte.d.ts +13 -0
- package/dist/ui/Table/Table.docs.svelte +197 -0
- package/dist/ui/Table/Table.docs.svelte.d.ts +28 -0
- package/dist/ui/Table/Table.svelte +140 -0
- package/dist/ui/Table/Table.svelte.d.ts +27 -0
- package/dist/ui/Table/index.js +10 -0
- package/dist/ui/css/colors.css +377 -0
- package/dist/ui/css/global.css +10 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +12 -0
- package/dist/virtual-sdocs.d.ts +20 -0
- package/dist/vite-plugin.d.ts +18 -0
- package/dist/vite-plugin.js +206 -0
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +2 -0
- package/package.json +76 -0
package/dist/docgen.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/** Types that can't be serialized as control values — component uses its own defaults */
|
|
2
|
+
const NON_SERIALIZABLE_TYPES = ['bigint', 'symbol', 'object', 'null', 'undefined'];
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Parsing - Extract props from component source
|
|
5
|
+
// ============================================================================
|
|
6
|
+
/**
|
|
7
|
+
* Parse Svelte 5 component props from source code.
|
|
8
|
+
* Supports both interface Props {} and inline type annotations.
|
|
9
|
+
*/
|
|
10
|
+
export function parseProps(source) {
|
|
11
|
+
const props = [];
|
|
12
|
+
const cssProps = [];
|
|
13
|
+
let description;
|
|
14
|
+
// Try to find Props interface with optional JSDoc comment before it
|
|
15
|
+
const interfaceMatch = source.match(/(?:\/\*\*\s*([\s\S]*?)\s*\*\/\s*)?interface\s+Props\s*\{([^}]+)\}/);
|
|
16
|
+
if (interfaceMatch) {
|
|
17
|
+
// Extract component description and @cssvar tags from JSDoc before interface
|
|
18
|
+
if (interfaceMatch[1]) {
|
|
19
|
+
const { description: desc, cssVars } = parseJsDocBlock(interfaceMatch[1]);
|
|
20
|
+
description = desc;
|
|
21
|
+
cssProps.push(...cssVars);
|
|
22
|
+
}
|
|
23
|
+
const interfaceBody = interfaceMatch[2];
|
|
24
|
+
props.push(...parseInterfaceBody(interfaceBody));
|
|
25
|
+
}
|
|
26
|
+
// Also check for inline type in $props destructuring
|
|
27
|
+
// let { foo = 'bar' }: { foo?: string } = $props();
|
|
28
|
+
const inlineMatch = source.match(/\$props\s*<\s*\{([^}]+)\}\s*>\s*\(\s*\)/s);
|
|
29
|
+
if (inlineMatch && props.length === 0) {
|
|
30
|
+
props.push(...parseInterfaceBody(inlineMatch[1]));
|
|
31
|
+
}
|
|
32
|
+
// Support JSDoc @type for JavaScript components
|
|
33
|
+
// /** @type {{ label?: string, children?: import('svelte').Snippet }} */
|
|
34
|
+
// let { label = 'default' } = $props();
|
|
35
|
+
if (props.length === 0) {
|
|
36
|
+
const jsdocTypeMatch = source.match(/\/\*\*\s*@type\s*\{\s*\{([^}]+)\}\s*\}\s*\*\/[\s\S]*?\$props\s*\(\s*\)/s);
|
|
37
|
+
if (jsdocTypeMatch) {
|
|
38
|
+
props.push(...parseJsDocTypeProps(jsdocTypeMatch[1]));
|
|
39
|
+
}
|
|
40
|
+
// For JS components, look for a separate JSDoc block with description/@cssvar
|
|
41
|
+
// This is the block before the @type block (or before $props)
|
|
42
|
+
if (!description) {
|
|
43
|
+
const jsDocBlocks = source.match(/\/\*\*\s*([\s\S]*?)\s*\*\//g);
|
|
44
|
+
if (jsDocBlocks) {
|
|
45
|
+
for (const block of jsDocBlocks) {
|
|
46
|
+
// Skip @type blocks
|
|
47
|
+
if (block.includes('@type'))
|
|
48
|
+
continue;
|
|
49
|
+
const content = block.replace(/^\/\*\*\s*/, '').replace(/\s*\*\/$/, '');
|
|
50
|
+
const { description: desc, cssVars } = parseJsDocBlock(content);
|
|
51
|
+
if (desc || cssVars.length > 0) {
|
|
52
|
+
description = desc;
|
|
53
|
+
cssProps.push(...cssVars);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Extract default values from destructuring
|
|
61
|
+
// let { foo = 'default', bar = 123 }: Props = $props();
|
|
62
|
+
const destructureMatch = source.match(/let\s*\{([\s\S]*?)\}[^=]*=\s*\$props\s*\(\s*\)/s);
|
|
63
|
+
if (destructureMatch) {
|
|
64
|
+
const destructure = destructureMatch[1];
|
|
65
|
+
const defaults = parseDestructureDefaults(destructure);
|
|
66
|
+
// Merge defaults into props
|
|
67
|
+
for (const prop of props) {
|
|
68
|
+
if (defaults[prop.name] !== undefined) {
|
|
69
|
+
prop.defaultValue = defaults[prop.name];
|
|
70
|
+
prop.required = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Extract exported functions
|
|
75
|
+
const methods = parseExportedFunctions(source);
|
|
76
|
+
return { props, cssProps, methods, description };
|
|
77
|
+
}
|
|
78
|
+
function parseInterfaceBody(body) {
|
|
79
|
+
const props = [];
|
|
80
|
+
// Match lines like:
|
|
81
|
+
// propName?: type;
|
|
82
|
+
// propName: type;
|
|
83
|
+
// /** description */ propName?: type;
|
|
84
|
+
const propRegex = /(?:\/\*\*\s*([^*]*)\s*\*\/\s*)?(\w+)(\?)?\s*:\s*([^;]+);/g;
|
|
85
|
+
let match;
|
|
86
|
+
while ((match = propRegex.exec(body)) !== null) {
|
|
87
|
+
const [, description, name, optional, type] = match;
|
|
88
|
+
// Skip internal Svelte props only
|
|
89
|
+
if (name.startsWith('$$')) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
props.push({
|
|
93
|
+
name,
|
|
94
|
+
type: type.trim(),
|
|
95
|
+
required: !optional,
|
|
96
|
+
description: description?.trim()
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return props;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse props from JSDoc @type annotation
|
|
103
|
+
* Format: { propName?: type, propName2: type2 }
|
|
104
|
+
*/
|
|
105
|
+
function parseJsDocTypeProps(typeBody) {
|
|
106
|
+
const props = [];
|
|
107
|
+
// Match: propName?: type or propName: type
|
|
108
|
+
// Type can include import('svelte').Snippet or simple types
|
|
109
|
+
const propRegex = /(\w+)(\?)?\s*:\s*([^,}]+)/g;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = propRegex.exec(typeBody)) !== null) {
|
|
112
|
+
const [, name, optional, type] = match;
|
|
113
|
+
// Skip internal props
|
|
114
|
+
if (name.startsWith('$$')) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
props.push({
|
|
118
|
+
name,
|
|
119
|
+
type: type.trim(),
|
|
120
|
+
required: !optional
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return props;
|
|
124
|
+
}
|
|
125
|
+
function parseDestructureDefaults(destructure) {
|
|
126
|
+
const defaults = {};
|
|
127
|
+
// Match: name = value
|
|
128
|
+
// Handle strings, numbers, booleans
|
|
129
|
+
const defaultRegex = /(\w+)\s*=\s*(['"`]?)([^,}]+?)\2(?:\s*,|\s*$)/g;
|
|
130
|
+
let match;
|
|
131
|
+
while ((match = defaultRegex.exec(destructure)) !== null) {
|
|
132
|
+
const [, name, quote, value] = match;
|
|
133
|
+
// Clean up the value
|
|
134
|
+
let cleanValue = value.trim();
|
|
135
|
+
// If it was quoted, add quotes back for string identification
|
|
136
|
+
if (quote) {
|
|
137
|
+
cleanValue = quote + cleanValue + quote;
|
|
138
|
+
}
|
|
139
|
+
defaults[name] = cleanValue;
|
|
140
|
+
}
|
|
141
|
+
return defaults;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Parse exported functions from component source code.
|
|
145
|
+
* Supports both TS and JS: export function name(params): returnType
|
|
146
|
+
* With optional JSDoc comment before.
|
|
147
|
+
*/
|
|
148
|
+
function parseExportedFunctions(source) {
|
|
149
|
+
const methods = [];
|
|
150
|
+
// Match optional JSDoc followed by export function
|
|
151
|
+
const methodRegex = /(?:\/\*\*\s*([\s\S]*?)\s*\*\/\s*)?export\s+function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([\w<>[\]|&\s,]+))?\s*\{/g;
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = methodRegex.exec(source)) !== null) {
|
|
154
|
+
const [, jsDoc, name, params, returnType] = match;
|
|
155
|
+
// Extract description from JSDoc (just the text, skip tags)
|
|
156
|
+
let description;
|
|
157
|
+
if (jsDoc) {
|
|
158
|
+
const lines = jsDoc.split('\n').map(line => line.replace(/^\s*\*\s?/, '').trim());
|
|
159
|
+
const descLines = lines.filter(l => l.length > 0 && !l.startsWith('@'));
|
|
160
|
+
description = descLines.length > 0 ? descLines.join(' ').trim() : undefined;
|
|
161
|
+
}
|
|
162
|
+
methods.push({
|
|
163
|
+
name,
|
|
164
|
+
params: params.trim() || undefined,
|
|
165
|
+
returnType: returnType?.trim() || 'void',
|
|
166
|
+
description
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return methods;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Parse JSDoc block to extract description and @cssvar tags
|
|
173
|
+
* Format: @cssvar {type} --name - description (default: value)
|
|
174
|
+
*/
|
|
175
|
+
function parseJsDocBlock(comment) {
|
|
176
|
+
const cssVars = [];
|
|
177
|
+
const descriptionLines = [];
|
|
178
|
+
const lines = comment.split('\n').map(line => line.replace(/^\s*\*\s?/, '').trim());
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
// Match @cssvar {type} --name - description (default: value)
|
|
181
|
+
// Type is optional: @cssvar --name - description
|
|
182
|
+
const cssvarMatch = line.match(/@cssvar\s+(?:\{(\w+)\}\s+)?(--[\w-]+)\s*(?:-\s*(.+))?/);
|
|
183
|
+
if (cssvarMatch) {
|
|
184
|
+
const [, type, name, rest] = cssvarMatch;
|
|
185
|
+
let description;
|
|
186
|
+
let defaultValue;
|
|
187
|
+
if (rest) {
|
|
188
|
+
// Extract default value from "(default: value)" at the end
|
|
189
|
+
const defaultMatch = rest.match(/^(.+?)\s*\(default:\s*([^)]+)\)\s*$/);
|
|
190
|
+
if (defaultMatch) {
|
|
191
|
+
description = defaultMatch[1].trim();
|
|
192
|
+
defaultValue = defaultMatch[2].trim();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
description = rest.trim();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
cssVars.push({
|
|
199
|
+
name,
|
|
200
|
+
type: type,
|
|
201
|
+
description,
|
|
202
|
+
default: defaultValue
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (!line.startsWith('@') && line.length > 0) {
|
|
206
|
+
// Regular description line (not a tag)
|
|
207
|
+
descriptionLines.push(line);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
description: descriptionLines.length > 0 ? descriptionLines.join(' ').trim() : undefined,
|
|
212
|
+
cssVars
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Conversion - Transform parsed props to runtime types
|
|
217
|
+
// ============================================================================
|
|
218
|
+
/**
|
|
219
|
+
* Convert extracted component props to argTypes for controls
|
|
220
|
+
*/
|
|
221
|
+
export function propsToArgTypes(docgen) {
|
|
222
|
+
if (!docgen?.props)
|
|
223
|
+
return {};
|
|
224
|
+
const argTypes = {};
|
|
225
|
+
for (const prop of docgen.props) {
|
|
226
|
+
// Skip internal Svelte props
|
|
227
|
+
if (prop.name.startsWith('$$')) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const argType = inferArgType(prop);
|
|
231
|
+
argTypes[prop.name] = argType;
|
|
232
|
+
}
|
|
233
|
+
return argTypes;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Extract default args from component docgen
|
|
237
|
+
*/
|
|
238
|
+
export function propsToDefaultArgs(docgen) {
|
|
239
|
+
if (!docgen?.props)
|
|
240
|
+
return {};
|
|
241
|
+
const args = {};
|
|
242
|
+
for (const prop of docgen.props) {
|
|
243
|
+
if (prop.name === 'children' || prop.name === 'class' || prop.name.startsWith('$$')) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Skip types that can't be represented as control values
|
|
247
|
+
const typeLower = prop.type.toLowerCase();
|
|
248
|
+
if (NON_SERIALIZABLE_TYPES.includes(typeLower) || typeLower.includes('=>') || typeLower.includes('function')) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (prop.defaultValue !== undefined) {
|
|
252
|
+
args[prop.name] = parseDefaultValue(prop.defaultValue, prop.type);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return args;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Convert extracted CSS props to CssProps config
|
|
259
|
+
*/
|
|
260
|
+
export function cssVarsToCssProps(docgen) {
|
|
261
|
+
if (!docgen?.cssProps)
|
|
262
|
+
return {};
|
|
263
|
+
const cssProps = {};
|
|
264
|
+
for (const cssProp of docgen.cssProps) {
|
|
265
|
+
cssProps[cssProp.name] = {
|
|
266
|
+
description: cssProp.description,
|
|
267
|
+
default: cssProp.default,
|
|
268
|
+
control: cssProp.type ?? 'color' // default to color picker
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return cssProps;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Convert extracted methods to MethodType[] for the UI
|
|
275
|
+
*/
|
|
276
|
+
export function docgenToMethods(docgen) {
|
|
277
|
+
if (!docgen?.methods)
|
|
278
|
+
return [];
|
|
279
|
+
return docgen.methods.map((m) => ({
|
|
280
|
+
name: m.name,
|
|
281
|
+
params: m.params,
|
|
282
|
+
returnType: m.returnType,
|
|
283
|
+
description: m.description
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// Helpers
|
|
288
|
+
// ============================================================================
|
|
289
|
+
function inferArgType(prop) {
|
|
290
|
+
const type = prop.type.toLowerCase();
|
|
291
|
+
const originalType = prop.type;
|
|
292
|
+
const defaultValue = prop.defaultValue;
|
|
293
|
+
// Snippet types - show in table but not controllable
|
|
294
|
+
// Normalize display: import('svelte').Snippet -> Snippet
|
|
295
|
+
if (type.includes('snippet')) {
|
|
296
|
+
const snippetType = originalType.includes('import(') ? 'Snippet' : originalType;
|
|
297
|
+
return {
|
|
298
|
+
control: false,
|
|
299
|
+
type: { name: snippetType },
|
|
300
|
+
description: prop.description,
|
|
301
|
+
required: prop.required,
|
|
302
|
+
defaultValue
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
// Function types (callbacks, event handlers) - show but not controllable
|
|
306
|
+
if (type.includes('=>') || type.includes('function')) {
|
|
307
|
+
return {
|
|
308
|
+
control: false,
|
|
309
|
+
type: { name: 'Function' },
|
|
310
|
+
description: prop.description,
|
|
311
|
+
required: prop.required,
|
|
312
|
+
defaultValue
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// Non-serializable types - show but not controllable
|
|
316
|
+
if (NON_SERIALIZABLE_TYPES.includes(type)) {
|
|
317
|
+
return {
|
|
318
|
+
control: false,
|
|
319
|
+
type: { name: originalType },
|
|
320
|
+
description: prop.description,
|
|
321
|
+
required: prop.required,
|
|
322
|
+
defaultValue
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// children prop - show but not controllable
|
|
326
|
+
if (prop.name === 'children') {
|
|
327
|
+
return {
|
|
328
|
+
control: false,
|
|
329
|
+
type: { name: originalType },
|
|
330
|
+
description: prop.description,
|
|
331
|
+
required: prop.required,
|
|
332
|
+
defaultValue
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// class prop - text input
|
|
336
|
+
if (prop.name === 'class') {
|
|
337
|
+
return {
|
|
338
|
+
control: 'text',
|
|
339
|
+
type: { name: 'string' },
|
|
340
|
+
description: prop.description,
|
|
341
|
+
required: prop.required,
|
|
342
|
+
defaultValue
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
// Boolean
|
|
346
|
+
if (type === 'boolean' || type === 'bool') {
|
|
347
|
+
return { control: 'boolean', type: { name: 'boolean' }, description: prop.description, required: prop.required, defaultValue };
|
|
348
|
+
}
|
|
349
|
+
// Number
|
|
350
|
+
if (type === 'number' || type === 'int' || type === 'float') {
|
|
351
|
+
return { control: 'number', type: { name: 'number' }, description: prop.description, required: prop.required, defaultValue };
|
|
352
|
+
}
|
|
353
|
+
// Union types (string literals) -> select or radio
|
|
354
|
+
const unionMatch = prop.type.match(/^['"]([^'"]+)['"](?:\s*\|\s*['"]([^'"]+)['"])+$/);
|
|
355
|
+
if (unionMatch) {
|
|
356
|
+
const options = extractUnionOptions(prop.type);
|
|
357
|
+
if (options.length <= 4) {
|
|
358
|
+
return {
|
|
359
|
+
control: { type: 'radio', options },
|
|
360
|
+
type: { name: originalType },
|
|
361
|
+
description: prop.description,
|
|
362
|
+
required: prop.required,
|
|
363
|
+
defaultValue
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
control: { type: 'select', options },
|
|
368
|
+
type: { name: originalType },
|
|
369
|
+
description: prop.description,
|
|
370
|
+
required: prop.required,
|
|
371
|
+
defaultValue
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// Explicit union check with pipe (string literals)
|
|
375
|
+
if (prop.type.includes('|') && prop.type.includes("'")) {
|
|
376
|
+
const options = extractUnionOptions(prop.type);
|
|
377
|
+
if (options.length > 0) {
|
|
378
|
+
if (options.length <= 4) {
|
|
379
|
+
return {
|
|
380
|
+
control: { type: 'radio', options },
|
|
381
|
+
type: { name: originalType },
|
|
382
|
+
description: prop.description,
|
|
383
|
+
required: prop.required,
|
|
384
|
+
defaultValue
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
control: { type: 'select', options },
|
|
389
|
+
type: { name: originalType },
|
|
390
|
+
description: prop.description,
|
|
391
|
+
required: prop.required,
|
|
392
|
+
defaultValue
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Number literal unions (e.g., 20 | 24 | 28 | 32 | 40)
|
|
397
|
+
if (prop.type.includes('|')) {
|
|
398
|
+
const numberOptions = extractNumberUnionOptions(prop.type);
|
|
399
|
+
if (numberOptions.length > 0) {
|
|
400
|
+
if (numberOptions.length <= 5) {
|
|
401
|
+
return {
|
|
402
|
+
control: { type: 'radio', options: numberOptions },
|
|
403
|
+
type: { name: originalType },
|
|
404
|
+
description: prop.description,
|
|
405
|
+
required: prop.required,
|
|
406
|
+
defaultValue
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
control: { type: 'select', options: numberOptions },
|
|
411
|
+
type: { name: originalType },
|
|
412
|
+
description: prop.description,
|
|
413
|
+
required: prop.required,
|
|
414
|
+
defaultValue
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// String (fallback for most text types)
|
|
419
|
+
if (type === 'string' || type.includes('string')) {
|
|
420
|
+
return { control: 'text', type: { name: 'string' }, description: prop.description, required: prop.required, defaultValue };
|
|
421
|
+
}
|
|
422
|
+
// Default to text for unknown types
|
|
423
|
+
return { control: 'text', type: { name: originalType }, description: prop.description, required: prop.required, defaultValue };
|
|
424
|
+
}
|
|
425
|
+
function extractUnionOptions(typeStr) {
|
|
426
|
+
const matches = typeStr.match(/['"]([^'"]+)['"]/g);
|
|
427
|
+
if (!matches)
|
|
428
|
+
return [];
|
|
429
|
+
return matches.map((m) => m.replace(/['"]/g, ''));
|
|
430
|
+
}
|
|
431
|
+
function extractNumberUnionOptions(typeStr) {
|
|
432
|
+
// Match number literals in unions like "20 | 24 | 28 | 32 | 40"
|
|
433
|
+
const parts = typeStr.split('|').map((s) => s.trim());
|
|
434
|
+
const numbers = [];
|
|
435
|
+
for (const part of parts) {
|
|
436
|
+
if (/^\d+$/.test(part)) {
|
|
437
|
+
numbers.push(part);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Only return if all parts are numbers
|
|
441
|
+
return numbers.length === parts.length ? numbers : [];
|
|
442
|
+
}
|
|
443
|
+
function parseDefaultValue(value, type) {
|
|
444
|
+
// Handle boolean
|
|
445
|
+
if (value === 'true')
|
|
446
|
+
return true;
|
|
447
|
+
if (value === 'false')
|
|
448
|
+
return false;
|
|
449
|
+
// Handle number (including number literal unions like "20 | 24 | 32")
|
|
450
|
+
const num = Number(value);
|
|
451
|
+
if (!isNaN(num)) {
|
|
452
|
+
const isNumberType = type.toLowerCase().includes('number');
|
|
453
|
+
const isNumberUnion = extractNumberUnionOptions(type).length > 0;
|
|
454
|
+
if (isNumberType || isNumberUnion) {
|
|
455
|
+
return num;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Handle string with quotes
|
|
459
|
+
if (value.startsWith("'") || value.startsWith('"')) {
|
|
460
|
+
return value.slice(1, -1);
|
|
461
|
+
}
|
|
462
|
+
return value;
|
|
463
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A container for previewing components with consistent styling.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Padding size */
|
|
9
|
+
padding?: 's' | 'm' | 'l';
|
|
10
|
+
/** Whether to center content */
|
|
11
|
+
centered?: boolean;
|
|
12
|
+
/** CSS custom properties to apply */
|
|
13
|
+
cssVars?: Record<string, string>;
|
|
14
|
+
/** Children content */
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { padding = 'm', centered = true, cssVars = {}, children }: Props = $props();
|
|
19
|
+
|
|
20
|
+
// Build style string from CSS vars
|
|
21
|
+
let styleString = $derived(
|
|
22
|
+
Object.entries(cssVars)
|
|
23
|
+
.filter(([_, v]) => v)
|
|
24
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
25
|
+
.join('; '),
|
|
26
|
+
);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="ComponentPreview {padding}" class:centered style={styleString || undefined}>
|
|
30
|
+
{#if children}
|
|
31
|
+
{@render children()}
|
|
32
|
+
{/if}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.ComponentPreview {
|
|
37
|
+
background: var(--color-bg-elevated);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.centered {
|
|
41
|
+
display: flex;
|
|
42
|
+
/* align-items: center; */
|
|
43
|
+
/* justify-content: center; */
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Padding sizes */
|
|
47
|
+
.s {
|
|
48
|
+
padding: 16px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.m {
|
|
52
|
+
padding: 32px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.l {
|
|
56
|
+
padding: 40px;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* A container for previewing components with consistent styling.
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Padding size */
|
|
7
|
+
padding?: 's' | 'm' | 'l';
|
|
8
|
+
/** Whether to center content */
|
|
9
|
+
centered?: boolean;
|
|
10
|
+
/** CSS custom properties to apply */
|
|
11
|
+
cssVars?: Record<string, string>;
|
|
12
|
+
/** Children content */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
}
|
|
15
|
+
declare const ComponentPreview: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type ComponentPreview = ReturnType<typeof ComponentPreview>;
|
|
17
|
+
export default ComponentPreview;
|