@vanilla-bean/components 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Component/Component.js +226 -40
- package/Component/Component.scenarios.js +2 -2
- package/Component/Component.test.js +427 -40
- package/Component/README.md +480 -0
- package/Component/observeElementConnection.js +1 -1
- package/Elem/README.md +373 -0
- package/README.md +2 -2
- package/components/BottomSheet/BottomSheet.js +8 -16
- package/components/BottomSheet/README.md +1 -1
- package/components/Button/Button.js +12 -18
- package/components/Button/Button.lld.md +1 -1
- package/components/Calendar/Calendar.js +139 -59
- package/components/Calendar/CalendarEvent.js +9 -4
- package/components/Calendar/Toolbar.js +28 -20
- package/components/Calendar/index.js +1 -0
- package/components/Code/Code.js +26 -30
- package/components/Code/Code.lld.md +1 -1
- package/components/ColorPicker/ColorPicker.js +58 -58
- package/components/Dialog/Dialog.js +79 -73
- package/components/Dialog/Dialog.lld.md +1 -1
- package/components/Dialog/README.md +10 -10
- package/components/Form/Form.js +21 -17
- package/components/Form/Form.lld.md +2 -2
- package/components/Form/README.md +3 -3
- package/components/Icon/Icon.js +19 -11
- package/components/Input/Input.js +85 -67
- package/components/Input/README.md +7 -9
- package/components/Keyboard/Key.js +7 -10
- package/components/Keyboard/Keyboard.js +38 -46
- package/components/Keyboard/Keyboard.lld.md +1 -1
- package/components/Label/Label.js +52 -50
- package/components/Link/Link.js +14 -20
- package/components/List/List.js +28 -30
- package/components/Menu/Menu.js +19 -10
- package/components/Menu/Menu.lld.md +2 -2
- package/components/Notify/Notify.js +29 -19
- package/components/Notify/Notify.lld.md +2 -2
- package/components/Page/Page.lld.md +1 -1
- package/components/Popover/Popover.js +49 -44
- package/components/RadioButton/RadioButton.js +33 -29
- package/components/RadioButton/RadioButton.lld.md +1 -1
- package/components/Router/README.md +12 -12
- package/components/Router/Router.js +19 -21
- package/components/Select/README.md +5 -5
- package/components/Select/Select.js +25 -31
- package/components/Table/README.md +4 -4
- package/components/Table/Table.js +55 -36
- package/components/Table/Table.lld.md +1 -1
- package/components/TagList/Tag.js +16 -14
- package/components/TagList/TagList.js +15 -8
- package/components/TagList/TagList.lld.md +1 -1
- package/components/Tooltip/Tooltip.js +12 -25
- package/components/TooltipWrapper/TooltipWrapper.js +55 -49
- package/components/TooltipWrapper/TooltipWrapper.lld.md +2 -2
- package/components/Whiteboard/Whiteboard.js +45 -43
- package/components/Whiteboard/Whiteboard.lld.md +1 -1
- package/devTools/build.js +43 -0
- package/devTools/buildTypes.js +322 -0
- package/devTools/createComponent.js +155 -0
- package/devTools/extractJSDoc.js +395 -0
- package/devTools/processTemplate.js +500 -0
- package/devTools/updateComponentIndex.js +16 -0
- package/devTools/updateDemoViewIndex.js +90 -0
- package/eslint.config.cjs +5 -2
- package/index.d.ts +120 -62
- package/package.json +23 -22
- package/spellcheck.config.cjs +3 -0
- package/styled/README.md +329 -0
- package/theme/.test.js +2 -2
- package/theme/README.md +8 -8
- package/theme/colors.js +16 -13
- package/theme/colors.test.js +28 -0
- package/utils/browser.js +1 -1
- package/utils/element.js +2 -2
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { extractJSDoc } from './extractJSDoc.js';
|
|
4
|
+
|
|
5
|
+
const codeCell = str => {
|
|
6
|
+
const escaped = (str || '')
|
|
7
|
+
.replace(/&/g, '&')
|
|
8
|
+
.replace(/</g, '<')
|
|
9
|
+
.replace(/>/g, '>')
|
|
10
|
+
.replace(/\|/g, '|');
|
|
11
|
+
return `<code>${escaped}</code>`;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Processes README templates by replacing extraction commands with JSDoc-derived content.
|
|
16
|
+
* @param {string} templateContent - Template content with extraction commands
|
|
17
|
+
* @param {string} componentPath - Path to component directory for resolving relative paths
|
|
18
|
+
* @returns {string} Processed template with commands replaced by generated content
|
|
19
|
+
*/
|
|
20
|
+
export function processTemplate(templateContent, componentPath = '.') {
|
|
21
|
+
// Replace extraction commands that start with known command words
|
|
22
|
+
return templateContent.replace(/\[\[(extract-[\w-]+|import)\s+([^\]]+)\]\]/g, (match, command, args) => {
|
|
23
|
+
try {
|
|
24
|
+
return processCommand(`${command} ${args}`.trim(), componentPath);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.warn(`Failed to process command "${command} ${args}":`, error.message);
|
|
27
|
+
return match; // Return original if processing fails
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Processes individual template commands and returns generated markdown content.
|
|
34
|
+
* @param {string} command - Command string (e.g., "extract-description Button.js")
|
|
35
|
+
* @param {string} basePath - Base path for resolving file references
|
|
36
|
+
* @returns {string} Generated markdown content for the command
|
|
37
|
+
*/
|
|
38
|
+
function processCommand(command, basePath) {
|
|
39
|
+
const [action, ...args] = command.split(/\s+/);
|
|
40
|
+
const fileName = args[0];
|
|
41
|
+
|
|
42
|
+
if (!fileName) {
|
|
43
|
+
throw new Error(`No filename provided for command: ${action}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const filePath = join(basePath, fileName);
|
|
47
|
+
|
|
48
|
+
switch (action) {
|
|
49
|
+
case 'extract-description':
|
|
50
|
+
return extractDescriptionMarkdown(filePath);
|
|
51
|
+
|
|
52
|
+
case 'extract-options':
|
|
53
|
+
return extractOptionsMarkdown(filePath);
|
|
54
|
+
|
|
55
|
+
case 'extract-methods':
|
|
56
|
+
return extractMethodsMarkdown(filePath);
|
|
57
|
+
|
|
58
|
+
case 'extract-properties':
|
|
59
|
+
return extractPropertiesMarkdown(filePath);
|
|
60
|
+
|
|
61
|
+
case 'extract-events':
|
|
62
|
+
return extractEventsMarkdown(filePath);
|
|
63
|
+
|
|
64
|
+
case 'extract-imports':
|
|
65
|
+
return extractImportsMarkdown(filePath);
|
|
66
|
+
|
|
67
|
+
case 'extract-examples':
|
|
68
|
+
return extractExamplesMarkdown(filePath);
|
|
69
|
+
|
|
70
|
+
case 'extract-enums':
|
|
71
|
+
return extractEnumsMarkdown(filePath);
|
|
72
|
+
|
|
73
|
+
case 'extract-custom-events':
|
|
74
|
+
return extractCustomEventsMarkdown(filePath);
|
|
75
|
+
|
|
76
|
+
case 'extract-design':
|
|
77
|
+
return extractDesignMarkdown(filePath, basePath);
|
|
78
|
+
|
|
79
|
+
case 'import':
|
|
80
|
+
return importFileContent(filePath, args[1]); // args[1] might be regex pattern
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`Unknown extraction command: ${action}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extracts and formats component description as markdown.
|
|
89
|
+
* @param {string} filePath - Path to component file
|
|
90
|
+
* @returns {string} Formatted description markdown
|
|
91
|
+
*/
|
|
92
|
+
function extractDescriptionMarkdown(filePath) {
|
|
93
|
+
const { description } = extractJSDoc(filePath);
|
|
94
|
+
|
|
95
|
+
if (!description) {
|
|
96
|
+
return '*No description available.*';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return description;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extracts and formats component options as markdown table.
|
|
104
|
+
* @param {string} filePath - Path to component file
|
|
105
|
+
* @returns {string} Formatted options table markdown
|
|
106
|
+
*/
|
|
107
|
+
function extractOptionsMarkdown(filePath) {
|
|
108
|
+
const { options } = extractJSDoc(filePath);
|
|
109
|
+
|
|
110
|
+
if (!options.length) {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let markdown = '| Option | Type | Default | Description |\n';
|
|
115
|
+
markdown += '|--------|------|---------|-------------|\n';
|
|
116
|
+
|
|
117
|
+
options.forEach(option => {
|
|
118
|
+
const name = option.optional ? `[${option.name}]` : option.name;
|
|
119
|
+
const type = codeCell(option.type);
|
|
120
|
+
let defaultValue;
|
|
121
|
+
if (option.default) defaultValue = codeCell(option.default);
|
|
122
|
+
else if (option.optional) defaultValue = codeCell('undefined');
|
|
123
|
+
else defaultValue = '*required*';
|
|
124
|
+
const description = (option.description || '*No description*').replace(/\|/g, '|');
|
|
125
|
+
|
|
126
|
+
markdown += `| ${name} | ${type} | ${defaultValue} | ${description} |\n`;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return markdown;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extracts and formats component methods as markdown sections.
|
|
134
|
+
* @param {string} filePath - Path to component file
|
|
135
|
+
* @returns {string} Formatted methods markdown
|
|
136
|
+
*/
|
|
137
|
+
function extractMethodsMarkdown(filePath) {
|
|
138
|
+
const { methods } = extractJSDoc(filePath);
|
|
139
|
+
|
|
140
|
+
if (!methods.length) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let markdown = '';
|
|
145
|
+
|
|
146
|
+
methods.forEach((method, index) => {
|
|
147
|
+
if (index > 0) markdown += '\n';
|
|
148
|
+
|
|
149
|
+
// Method signature
|
|
150
|
+
const params = method.parameters
|
|
151
|
+
.map(p => {
|
|
152
|
+
const name = p.optional ? `[${p.name}]` : p.name;
|
|
153
|
+
return p.default ? `${name}=${p.default}` : name;
|
|
154
|
+
})
|
|
155
|
+
.join(', ');
|
|
156
|
+
|
|
157
|
+
markdown += `### \`${method.name}(${params})\`\n\n`;
|
|
158
|
+
|
|
159
|
+
// Description
|
|
160
|
+
if (method.description) {
|
|
161
|
+
markdown += `${method.description}\n\n`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Parameters
|
|
165
|
+
if (method.parameters.length > 0) {
|
|
166
|
+
markdown += '**Parameters:**\n\n';
|
|
167
|
+
method.parameters.forEach(param => {
|
|
168
|
+
const name = param.optional ? `[${param.name}]` : param.name;
|
|
169
|
+
markdown += `- \`${name}\` (\`${param.type}\`) - ${param.description}`;
|
|
170
|
+
if (param.default) markdown += ` (default: \`${param.default}\`)`;
|
|
171
|
+
markdown += '\n';
|
|
172
|
+
});
|
|
173
|
+
markdown += '\n';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Returns
|
|
177
|
+
if (method.returns) {
|
|
178
|
+
markdown += `**Returns:** \`${method.returns.type}\` - ${method.returns.description}\n\n`;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return markdown.trim();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extracts and formats component properties as markdown table.
|
|
187
|
+
* @param {string} filePath - Path to component file
|
|
188
|
+
* @returns {string} Formatted properties table markdown
|
|
189
|
+
*/
|
|
190
|
+
function extractPropertiesMarkdown(filePath) {
|
|
191
|
+
const { properties } = extractJSDoc(filePath);
|
|
192
|
+
|
|
193
|
+
if (!properties.length) {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let markdown = '| Property | Type | Access | Description |\n';
|
|
198
|
+
markdown += '|----------|------|--------|-------------|\n';
|
|
199
|
+
|
|
200
|
+
properties.forEach(prop => {
|
|
201
|
+
const access = prop.access === 'readonly' ? 'Read-only' : 'Read/Write';
|
|
202
|
+
const description = (prop.description || '*No description*').replace(/\|/g, '|');
|
|
203
|
+
|
|
204
|
+
markdown += `| ${codeCell(prop.name)} | ${codeCell(prop.type)} | ${access} | ${description} |\n`;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return markdown;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extracts and formats component events as markdown list.
|
|
212
|
+
* @param {string} filePath - Path to component file
|
|
213
|
+
* @returns {string} Formatted events markdown
|
|
214
|
+
*/
|
|
215
|
+
function extractEventsMarkdown(filePath) {
|
|
216
|
+
const { events } = extractJSDoc(filePath);
|
|
217
|
+
|
|
218
|
+
if (!events.length) {
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let markdown = '';
|
|
223
|
+
|
|
224
|
+
events.forEach(event => {
|
|
225
|
+
markdown += `- **\`${event.name}\`** - ${event.description}\n`;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return markdown;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Extracts and formats component dependencies as markdown list.
|
|
233
|
+
* @param {string} filePath - Path to component file
|
|
234
|
+
* @returns {string} Formatted imports markdown
|
|
235
|
+
*/
|
|
236
|
+
const COMPONENT_DEMO_ROUTES = {
|
|
237
|
+
EventTarget: 'https://developer.mozilla.org/docs/Web/API/EventTarget',
|
|
238
|
+
Elem: '#/documentation/Elem',
|
|
239
|
+
Component: '#/documentation/Component',
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const MODULE_ROUTES = {
|
|
243
|
+
utils: '#/documentation/utils',
|
|
244
|
+
styled: '#/documentation/styled',
|
|
245
|
+
'@vanilla-bean/oxject': '#/dependencies/oxject',
|
|
246
|
+
'@ctrl/tinycolor': '#/dependencies/tinycolor',
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Extracts and formats component dependencies as markdown list.
|
|
251
|
+
* @param {string} filePath - Path to component file
|
|
252
|
+
* @returns {string} Formatted imports markdown
|
|
253
|
+
*/
|
|
254
|
+
function extractImportsMarkdown(filePath) {
|
|
255
|
+
const { imports } = extractJSDoc(filePath);
|
|
256
|
+
|
|
257
|
+
if (!imports.length) {
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const seen = new Set();
|
|
262
|
+
let markdown = '';
|
|
263
|
+
|
|
264
|
+
imports.forEach(imp => {
|
|
265
|
+
const moduleRoot = imp.module.replace(/\/.*$/, '');
|
|
266
|
+
const moduleRoute = MODULE_ROUTES[moduleRoot] || MODULE_ROUTES[imp.module];
|
|
267
|
+
|
|
268
|
+
imp.imports.forEach(name => {
|
|
269
|
+
if (seen.has(name)) return;
|
|
270
|
+
seen.add(name);
|
|
271
|
+
|
|
272
|
+
const route = moduleRoute ?? COMPONENT_DEMO_ROUTES[name] ?? (/^[A-Z]/.test(name) ? `#/${name}` : null);
|
|
273
|
+
markdown += route ? `- [${name}](${route})\n` : `- \`${name}\`\n`;
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return markdown;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Extracts and formats JSDoc examples as markdown code blocks.
|
|
282
|
+
* @param {string} filePath - Path to component file
|
|
283
|
+
* @returns {string} Formatted examples markdown
|
|
284
|
+
*/
|
|
285
|
+
function extractExamplesMarkdown(filePath) {
|
|
286
|
+
const { examples } = extractJSDoc(filePath);
|
|
287
|
+
|
|
288
|
+
if (!examples.length) {
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let markdown = '';
|
|
293
|
+
|
|
294
|
+
examples.forEach((example, index) => {
|
|
295
|
+
if (index > 0) markdown += '\n';
|
|
296
|
+
|
|
297
|
+
if (example.description) {
|
|
298
|
+
markdown += `${example.description}\n\n`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
markdown += '```js\n';
|
|
302
|
+
markdown += example.code;
|
|
303
|
+
markdown += '\n```\n';
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return markdown.trim();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Extracts usage examples from demo file.
|
|
311
|
+
* @param {string} filePath - Path to component file
|
|
312
|
+
* @param {string} basePath - Base path for resolving demo file
|
|
313
|
+
* @returns {string} Formatted usage examples markdown
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extracts enum definitions from JSDoc comments.
|
|
318
|
+
* @param {string} filePath - Path to component file
|
|
319
|
+
* @returns {string} Formatted enums markdown
|
|
320
|
+
*/
|
|
321
|
+
function extractEnumsMarkdown(filePath) {
|
|
322
|
+
try {
|
|
323
|
+
const content = readFileSync(filePath, 'utf8');
|
|
324
|
+
|
|
325
|
+
// Look for @enum JSDoc tags
|
|
326
|
+
const enumMatches = [
|
|
327
|
+
...content.matchAll(/\/\*\*\s*\n([\s\S]*?)@enum\s*{([^}]+)}\s*([\s\S]*?)\*\/\s*(const|let|var)\s+(\w+)/g),
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
if (!enumMatches.length) {
|
|
331
|
+
return '';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let markdown = '';
|
|
335
|
+
enumMatches.forEach(match => {
|
|
336
|
+
const [, preEnum, enumType, , , enumName] = match;
|
|
337
|
+
|
|
338
|
+
const description = preEnum.match(/^\s*\*\s*(.+?)(?=\s*^\s*\*\s*@|\s*$)/ms);
|
|
339
|
+
const desc = description
|
|
340
|
+
? description[1]
|
|
341
|
+
.split('\n')
|
|
342
|
+
.map(line => line.replace(/^\s*\*\s?/, ''))
|
|
343
|
+
.join(' ')
|
|
344
|
+
.trim()
|
|
345
|
+
: '';
|
|
346
|
+
|
|
347
|
+
markdown += `### \`${enumName}\` (\`${enumType}\`)\n\n`;
|
|
348
|
+
if (desc) {
|
|
349
|
+
markdown += `${desc}\n\n`;
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
return markdown.trim();
|
|
354
|
+
} catch {
|
|
355
|
+
return '*Error extracting enums.*';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Extracts custom event definitions from JSDoc `@fires` tags and CustomEvent calls.
|
|
361
|
+
* @param {string} filePath - Path to component file
|
|
362
|
+
* @returns {string} Formatted custom events markdown
|
|
363
|
+
*/
|
|
364
|
+
function extractCustomEventsMarkdown(filePath) {
|
|
365
|
+
try {
|
|
366
|
+
const content = readFileSync(filePath, 'utf8');
|
|
367
|
+
const customEvents = [];
|
|
368
|
+
|
|
369
|
+
// Extract from @fires JSDoc tags
|
|
370
|
+
const firesMatches = [...content.matchAll(/^\s*\*\s*@fires\s+(\w+)\s*-?\s*(.*)$/gm)];
|
|
371
|
+
firesMatches.forEach(match => {
|
|
372
|
+
const [, eventName, description] = match;
|
|
373
|
+
customEvents.push({
|
|
374
|
+
name: eventName,
|
|
375
|
+
description: description || `Custom event: ${eventName}`,
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Extract from dispatchEvent calls
|
|
380
|
+
const dispatchMatches = [
|
|
381
|
+
...content.matchAll(/dispatchEvent\s*\(\s*new\s+CustomEvent\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*{([^}]*)}\s*\)/g),
|
|
382
|
+
];
|
|
383
|
+
dispatchMatches.forEach(match => {
|
|
384
|
+
const [, eventName] = match;
|
|
385
|
+
if (!customEvents.find(e => e.name === eventName)) {
|
|
386
|
+
customEvents.push({
|
|
387
|
+
name: eventName,
|
|
388
|
+
description: `Custom event: ${eventName}`,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (!customEvents.length) {
|
|
394
|
+
return '';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let markdown = '';
|
|
398
|
+
customEvents.forEach(event => {
|
|
399
|
+
markdown += `- **\`${event.name}\`** - ${event.description}\n`;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
return markdown.trim();
|
|
403
|
+
} catch {
|
|
404
|
+
return '*Error extracting custom events.*';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Extracts design information from DESIGN.md file or JSDoc `@design` tags.
|
|
410
|
+
* @param {string} filePath - Path to component file
|
|
411
|
+
* @param {string} basePath - Base path for resolving design file
|
|
412
|
+
* @returns {string} Formatted design information markdown
|
|
413
|
+
*/
|
|
414
|
+
function extractDesignMarkdown(filePath, basePath) {
|
|
415
|
+
try {
|
|
416
|
+
// First try to find DESIGN.md file in component directory
|
|
417
|
+
const designPath = join(basePath, 'DESIGN.md');
|
|
418
|
+
try {
|
|
419
|
+
const designContent = readFileSync(designPath, 'utf8');
|
|
420
|
+
return designContent;
|
|
421
|
+
} catch {
|
|
422
|
+
// DESIGN.md doesn't exist, try extracting from JSDoc
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Extract from JSDoc @design tags
|
|
426
|
+
const content = readFileSync(filePath, 'utf8');
|
|
427
|
+
const designMatches = [...content.matchAll(/^\s*\*\s*@design\s+(.+?)(?=\s*^\s*\*\s*@|\s*^\s*\*\s*$)/gms)];
|
|
428
|
+
|
|
429
|
+
if (!designMatches.length) {
|
|
430
|
+
return '';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let markdown = '';
|
|
434
|
+
designMatches.forEach(match => {
|
|
435
|
+
const designText = match[1]
|
|
436
|
+
.split('\n')
|
|
437
|
+
.map(line => line.replace(/^\s*\*\s?/, ''))
|
|
438
|
+
.join(' ')
|
|
439
|
+
.trim();
|
|
440
|
+
|
|
441
|
+
markdown += designText + '\n\n';
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return markdown.trim();
|
|
445
|
+
} catch {
|
|
446
|
+
return '*Error extracting design information.*';
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Imports file content with optional regex pattern matching.
|
|
452
|
+
* @param {string} filePath - Path to file to import
|
|
453
|
+
* @param {string} [pattern] - Optional regex pattern to extract specific content
|
|
454
|
+
* @returns {string} File content or pattern-matched content
|
|
455
|
+
*/
|
|
456
|
+
function importFileContent(filePath, pattern) {
|
|
457
|
+
try {
|
|
458
|
+
const content = readFileSync(filePath, 'utf8');
|
|
459
|
+
|
|
460
|
+
if (!pattern) {
|
|
461
|
+
return content;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Parse regex pattern from string
|
|
465
|
+
const regexMatch = pattern.match(/^\/(.+)\/([gimsu]*)$/);
|
|
466
|
+
if (regexMatch) {
|
|
467
|
+
const [, regexPattern, flags] = regexMatch;
|
|
468
|
+
const regex = new RegExp(regexPattern, flags);
|
|
469
|
+
const match = content.match(regex);
|
|
470
|
+
|
|
471
|
+
if (match) {
|
|
472
|
+
return match[0];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return content;
|
|
477
|
+
} catch {
|
|
478
|
+
return `*Error importing ${filePath}: file not found*`;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// CLI usage
|
|
483
|
+
if (import.meta.main) {
|
|
484
|
+
const templatePath = process.argv[2];
|
|
485
|
+
const componentDir = process.argv[3] || '.';
|
|
486
|
+
|
|
487
|
+
if (!templatePath) {
|
|
488
|
+
console.error('Usage: bun processTemplate.js <template-file> [component-directory]');
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const templateContent = readFileSync(templatePath, 'utf8');
|
|
494
|
+
const processedContent = processTemplate(templateContent, componentDir);
|
|
495
|
+
console.log(processedContent);
|
|
496
|
+
} catch {
|
|
497
|
+
console.error('Error processing template: template processing failed');
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { Glob } from 'bun';
|
|
3
|
+
|
|
4
|
+
import { orderBy } from '../utils/data';
|
|
5
|
+
|
|
6
|
+
const components = [...new Glob('components/*/index.js').scanSync('.')].sort(orderBy([{ direction: 'asc' }]));
|
|
7
|
+
|
|
8
|
+
let result = `/* This file is automatically managed (bun build:index) (${basename(process.argv[1])}), do not edit by hand */\n\n`;
|
|
9
|
+
|
|
10
|
+
components.forEach(file => {
|
|
11
|
+
const [, , name] = file.match(/(.+?)\/(.+)\/(.+?)(\..+)?$/);
|
|
12
|
+
|
|
13
|
+
result += `export * from './${name}';\n`;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await Bun.write('components/index.js', result);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { Glob } from 'bun';
|
|
4
|
+
|
|
5
|
+
import { orderBy } from '../utils/data';
|
|
6
|
+
|
|
7
|
+
const demos = [...new Glob('components/*/demo.js').scanSync('.')].sort(orderBy([{ direction: 'asc' }]));
|
|
8
|
+
const examples = [...new Glob('demo/examples/*.js').scanSync('.')]
|
|
9
|
+
.filter(file => !file.endsWith('.test.js'))
|
|
10
|
+
.sort(orderBy([{ direction: 'asc' }]));
|
|
11
|
+
const documentation = [...new Glob('./*/README.md').scanSync('.')].sort(orderBy([{ direction: 'asc' }]));
|
|
12
|
+
|
|
13
|
+
let result = `/* This file is automatically managed (bun build:index) (${basename(process.argv[1])}), do not edit by hand */\n\n`;
|
|
14
|
+
|
|
15
|
+
result += `import { styled } from '../styled/styled.js';\n\n`;
|
|
16
|
+
|
|
17
|
+
demos.forEach(file => {
|
|
18
|
+
const match = file.match(/(.+?)\/(.+)\/(.+?)(\..+)?$/);
|
|
19
|
+
result += `import ${match[2]}Demo from '../components/${match[2]}/demo.js';\n`;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
result += '\n';
|
|
23
|
+
|
|
24
|
+
examples.forEach(file => {
|
|
25
|
+
const match = file.match(/(.+)\/(.+?)(\..+)?$/);
|
|
26
|
+
result += `import ${match[2]}Example from './examples/${match[2]}';\n`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
result += `\nimport DocumentationView from './DemoView/DocumentationView.js';\n\n`;
|
|
30
|
+
|
|
31
|
+
result += `const startDocumentation = styled(DocumentationView, () => '', { fileName: 'docs/GETTING_STARTED.md', nextLabel: 'Elem', nextUrl: '#/documentation/Elem' });\nconst MarkdownDocumentation = styled(DocumentationView, () => '', { folderName: 'markdown' });\n`;
|
|
32
|
+
|
|
33
|
+
const documentationChain = {
|
|
34
|
+
demo: `, nextLabel: 'Getting Started', nextUrl: '#/documentation/start'`,
|
|
35
|
+
Elem: `, nextLabel: 'Component', nextUrl: '#/documentation/Component'`,
|
|
36
|
+
Component: `, nextLabel: 'styled', nextUrl: '#/documentation/styled'`,
|
|
37
|
+
styled: `, nextLabel: 'theme', nextUrl: '#/documentation/theme'`,
|
|
38
|
+
theme: `, nextLabel: 'utils', nextUrl: '#/documentation/utils'`,
|
|
39
|
+
utils: ``,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
documentation.forEach(file => {
|
|
43
|
+
const [, , name] = file.match(/(.+?)\/(.+)\/(.+?)(\..+)?$/);
|
|
44
|
+
result += `const ${name}Documentation = styled(DocumentationView, () => '', { folderName: '${name}'${documentationChain[name] || ''} });\n`;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { dependencies = {} } = JSON.parse(await Bun.file('./package.json').text());
|
|
48
|
+
const npmDocs = Object.keys(dependencies)
|
|
49
|
+
.map(pkg => {
|
|
50
|
+
const name = pkg.replace(/^@[^/]+\//, '');
|
|
51
|
+
const readmePath = `node_modules/${pkg}/README.md`;
|
|
52
|
+
return existsSync(readmePath) ? { name, pkg } : null;
|
|
53
|
+
})
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
56
|
+
|
|
57
|
+
npmDocs.forEach(({ name }) => {
|
|
58
|
+
const varName = name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
59
|
+
result += `const dep_${varName} = styled(DocumentationView, () => '', { folderName: 'dependencies/${name}' });\n`;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
result += '\nexport default {\n';
|
|
63
|
+
|
|
64
|
+
demos.forEach(file => {
|
|
65
|
+
const match = file.match(/(.+?)\/(.+)\/(.+?)(\..+)?$/);
|
|
66
|
+
result += `\t['/${match[2]}']: ${match[2]}Demo,\n`;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
result += '\n';
|
|
70
|
+
|
|
71
|
+
examples.forEach(file => {
|
|
72
|
+
const match = file.match(/(.+)\/(.+?)(\..+)?$/);
|
|
73
|
+
result += `\t['/examples/${match[2]}']: ${match[2]}Example,\n`;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
result += `\n\t['/documentation/start']: startDocumentation,\n\t['/documentation/markdown']: MarkdownDocumentation,\n`;
|
|
77
|
+
|
|
78
|
+
documentation.forEach(file => {
|
|
79
|
+
const match = file.match(/(.+?)\/(.+)\/(.+?)(\..+)?$/);
|
|
80
|
+
result += `\t['/documentation/${match[2]}']: ${match[2]}Documentation,\n`;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
npmDocs.forEach(({ name }) => {
|
|
84
|
+
const varName = name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
85
|
+
result += `\t['/dependencies/${name}']: dep_${varName},\n`;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
result += '};\n';
|
|
89
|
+
|
|
90
|
+
await Bun.write('demo/views.js', result);
|
package/eslint.config.cjs
CHANGED
|
@@ -27,8 +27,8 @@ module.exports = [
|
|
|
27
27
|
files: ['**/*.js', '**/*.mjs', '**/*.cjs'],
|
|
28
28
|
plugins: {
|
|
29
29
|
import: importPlugin,
|
|
30
|
-
'write-good-comments': writeGoodComments,
|
|
31
|
-
spellcheck,
|
|
30
|
+
'write-good-comments': fixupPluginRules(writeGoodComments),
|
|
31
|
+
spellcheck: fixupPluginRules(spellcheck),
|
|
32
32
|
},
|
|
33
33
|
rules: {
|
|
34
34
|
'no-console': 'warn',
|
|
@@ -52,6 +52,9 @@ module.exports = [
|
|
|
52
52
|
|
|
53
53
|
'spellcheck/spell-checker': ['warn', require('./spellcheck.config.cjs')],
|
|
54
54
|
'jsdoc/no-undefined-types': ['warn', { definedTypes: ['Component', 'TemplateStringsArray'] }],
|
|
55
|
+
// {*} and {Function} are deliberate here - buildTypes maps them to TS types
|
|
56
|
+
'jsdoc/reject-any-type': 'off',
|
|
57
|
+
'jsdoc/reject-function-type': 'off',
|
|
55
58
|
},
|
|
56
59
|
},
|
|
57
60
|
{
|