@vanilla-bean/components 1.1.1 → 2.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.
Files changed (52) hide show
  1. package/Component/Component.js +222 -36
  2. package/Component/Component.test.js +425 -38
  3. package/Component/README.md +480 -0
  4. package/Elem/README.md +373 -0
  5. package/README.md +1 -1
  6. package/components/BottomSheet/BottomSheet.js +8 -16
  7. package/components/Button/Button.js +12 -18
  8. package/components/Calendar/Calendar.js +139 -59
  9. package/components/Calendar/CalendarEvent.js +9 -4
  10. package/components/Calendar/Toolbar.js +28 -20
  11. package/components/Calendar/index.js +1 -0
  12. package/components/Code/Code.js +26 -30
  13. package/components/ColorPicker/ColorPicker.js +57 -57
  14. package/components/Dialog/Dialog.js +78 -72
  15. package/components/Form/Form.js +12 -8
  16. package/components/Icon/Icon.js +19 -11
  17. package/components/Input/Input.js +85 -67
  18. package/components/Input/README.md +0 -2
  19. package/components/Keyboard/Key.js +7 -10
  20. package/components/Keyboard/Keyboard.js +38 -46
  21. package/components/Label/Label.js +52 -50
  22. package/components/Link/Link.js +14 -20
  23. package/components/List/List.js +28 -30
  24. package/components/Menu/Menu.js +19 -10
  25. package/components/Menu/Menu.lld.md +2 -2
  26. package/components/Notify/Notify.js +29 -19
  27. package/components/Popover/Popover.js +49 -44
  28. package/components/RadioButton/RadioButton.js +33 -29
  29. package/components/Router/Router.js +19 -21
  30. package/components/Select/Select.js +24 -30
  31. package/components/Table/Table.js +55 -36
  32. package/components/TagList/Tag.js +16 -14
  33. package/components/TagList/TagList.js +15 -8
  34. package/components/Tooltip/Tooltip.js +12 -25
  35. package/components/TooltipWrapper/TooltipWrapper.js +55 -49
  36. package/components/Whiteboard/Whiteboard.js +45 -43
  37. package/devTools/build.js +43 -0
  38. package/devTools/buildTypes.js +322 -0
  39. package/devTools/createComponent.js +155 -0
  40. package/devTools/extractJSDoc.js +395 -0
  41. package/devTools/processTemplate.js +500 -0
  42. package/devTools/updateComponentIndex.js +16 -0
  43. package/devTools/updateDemoViewIndex.js +90 -0
  44. package/eslint.config.cjs +6 -3
  45. package/index.d.ts +117 -59
  46. package/package.json +67 -22
  47. package/spellcheck.config.cjs +4 -0
  48. package/styled/README.md +329 -0
  49. package/theme/colors.js +15 -12
  50. package/theme/colors.test.js +28 -0
  51. package/utils/element.js +2 -2
  52. package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
@@ -0,0 +1,395 @@
1
+ import { readFileSync } from 'fs';
2
+
3
+ /**
4
+ * Extracts JSDoc information from JavaScript files for documentation generation.
5
+ *
6
+ * Parses JSDoc comments and extracts component descriptions, parameters, methods,
7
+ * properties, and events for automated README generation.
8
+ * @param {string} filePath - Path to JavaScript file to parse
9
+ * @returns {object} Extracted JSDoc information structured for documentation templates
10
+ */
11
+ export function extractJSDoc(filePath) {
12
+ try {
13
+ const content = readFileSync(filePath, 'utf8');
14
+
15
+ return {
16
+ description: extractDescription(content),
17
+ options: extractOptions(content),
18
+ methods: extractMethods(content),
19
+ properties: extractProperties(content),
20
+ events: extractEvents(content),
21
+ imports: extractImports(content),
22
+ examples: extractExamples(content),
23
+ };
24
+ } catch (error) {
25
+ console.warn(`Failed to extract JSDoc from ${filePath}:`, error.message);
26
+ return {
27
+ description: '',
28
+ options: [],
29
+ methods: [],
30
+ properties: [],
31
+ events: [],
32
+ imports: [],
33
+ examples: [],
34
+ };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Extracts component description from class-level JSDoc comment.
40
+ * @param {string} content - File content to parse
41
+ * @returns {string} Component description text
42
+ */
43
+ function extractDescription(content) {
44
+ // Match class JSDoc comment - handle both export default class and class patterns
45
+ const classMatches = [
46
+ content.match(/\/\*\*\s*\n([\s\S]*?)\*\/\s*export\s+default\s+class\s+\w+/),
47
+ content.match(/\/\*\*\s*\n([\s\S]*?)\*\/\s*(?:export\s+)?class\s+\w+/),
48
+ ];
49
+
50
+ const classMatch = classMatches.find(match => match);
51
+ if (!classMatch) return '';
52
+
53
+ const jsdoc = classMatch[1];
54
+
55
+ // Extract description (everything before first @tag)
56
+ const descMatch = jsdoc.match(/^\s*\*\s*(.+?)(?=\s*^\s*\*\s*@|\s*$)/ms);
57
+
58
+ if (!descMatch) return '';
59
+
60
+ // Clean up the description text - handle multi-line descriptions
61
+ const lines = descMatch[1].split('\n');
62
+ const cleanedLines = lines.map(line => line.replace(/^\s*\*\s?/, '')).filter(line => line.trim() !== '');
63
+
64
+ return cleanedLines.join(' ').replace(/\s+/g, ' ').trim();
65
+ }
66
+
67
+ /**
68
+ * Extracts constructor options from JSDoc `@param` tags.
69
+ * @param {string} content - File content to parse
70
+ * @returns {Array} Array of option objects with name, type, description, and default
71
+ */
72
+ function extractOptions(content) {
73
+ // Find class-level JSDoc (which contains constructor info) or constructor JSDoc
74
+ const jsDocMatches = [
75
+ content.match(/\/\*\*\s*\n([\s\S]*?)\*\/\s*export\s+default\s+class\s+\w+/),
76
+ content.match(/\/\*\*\s*\n([\s\S]*?)\*\/\s*constructor\s*\(/),
77
+ content.match(/\/\*\*\s*\n([\s\S]*?)\*\/\s*(?:export\s+)?class\s+\w+/),
78
+ ];
79
+
80
+ const jsDocMatch = jsDocMatches.find(match => match);
81
+ if (!jsDocMatch) return [];
82
+
83
+ const jsdoc = jsDocMatch[1];
84
+
85
+ // Extract @param tags for options (handle both options.prop and direct param patterns)
86
+ const paramMatches = [
87
+ // options.property pattern
88
+ ...jsdoc.matchAll(
89
+ /^\s*\*\s*@param\s+{([^}]+)}\s+(\[?options\.(\w+)(?:=([^\]]+))?\]?)\s*-\s*(.+?)(?=\s*^\s*\*\s*@|\s*^\s*\*\s*$|\s*\*\/)/gms,
90
+ ),
91
+ // Direct parameter pattern (for constructor params like options={})
92
+ ...jsdoc.matchAll(
93
+ /^\s*\*\s*@param\s+{([^}]+)}\s+(\[?(\w+)(?:=([^\]]+))?\]?)\s*-\s*(.+?)(?=\s*^\s*\*\s*@|\s*^\s*\*\s*$|\s*\*\/)/gms,
94
+ ),
95
+ ];
96
+
97
+ const codeDefaults = extractDefaultOptionsFromCode(content);
98
+
99
+ return paramMatches
100
+ .filter(match => match[2].includes('options.'))
101
+ .map(match => {
102
+ const [, type, fullParam, name, defaultValue, description] = match;
103
+ const optional = fullParam.startsWith('[') && fullParam.endsWith(']');
104
+ const optionName = name.replace('options.', '');
105
+
106
+ return {
107
+ name: optionName,
108
+ type: type.trim(),
109
+ description: description.trim().replace(/\s+/g, ' '),
110
+ optional,
111
+ default: defaultValue || codeDefaults[optionName] || null,
112
+ isSubProperty: fullParam.includes('options.'),
113
+ };
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Reads the `defaultOptions` object literal from source and extracts simple key→value pairs.
119
+ * Only captures string, number, and boolean literals - skips getters and complex values.
120
+ * @param {string} content - File content
121
+ * @returns {object} Map of option name → default value string
122
+ */
123
+ function extractDefaultOptionsFromCode(content) {
124
+ const defaults = {};
125
+ const block = content.match(/const\s+defaultOptions\s*=\s*\{([\s\S]*?)\};/);
126
+ if (!block) return defaults;
127
+
128
+ const pairs = [
129
+ ...block[1].matchAll(/\b(\w+):\s*(?:'([^']*)'|"([^"]*)"|(`[^`]*`)|(-?\d+\.?\d*(?:e[+-]?\d+)?)|true|false)/g),
130
+ ];
131
+
132
+ pairs.forEach(match => {
133
+ const [full, key, singleQuoted, doubleQuoted, template, number] = match;
134
+ if (singleQuoted !== undefined) defaults[key] = `'${singleQuoted}'`;
135
+ else if (doubleQuoted !== undefined) defaults[key] = `"${doubleQuoted}"`;
136
+ else if (template !== undefined) defaults[key] = template;
137
+ else if (number !== undefined) defaults[key] = number;
138
+ else if (full.endsWith('true')) defaults[key] = 'true';
139
+ else if (full.endsWith('false')) defaults[key] = 'false';
140
+ });
141
+
142
+ return defaults;
143
+ }
144
+
145
+ /**
146
+ * Extracts method information from JSDoc comments.
147
+ * @param {string} content - File content to parse
148
+ * @returns {Array} Array of method objects with name, description, parameters, and return info
149
+ */
150
+ function extractMethods(content) {
151
+ // Match method JSDoc comments (excluding constructor and private methods)
152
+ // Match JSDoc comments that are within the class body, not at class level
153
+
154
+ // Find class body content (everything between class declaration and closing brace)
155
+ const classBodyMatch = content.match(/class\s+\w+[^{]*\{([\s\S]*)\}[^}]*$/);
156
+ if (!classBodyMatch) return [];
157
+
158
+ const classBody = classBodyMatch[1];
159
+
160
+ // Match each JSDoc block with the method that IMMEDIATELY follows it.
161
+ // `(?:(?!\/\*\*)[\s\S])*?` - non-greedy content that cannot cross into a new `/**`,
162
+ // so the captured block can never span multiple JSDoc comments or method bodies.
163
+ // get/set accessors are excluded; underscore-prefixed names are excluded.
164
+ const matches = [
165
+ ...classBody.matchAll(
166
+ /\/\*\*((?:(?!\/\*\*)[\s\S])*?)\*\/[ \t]*\n[ \t]*(?!constructor\b)(?!get\b)(?!set\b)(?!_)(\w+)\s*\([^)]*\)\s*\{/g,
167
+ ),
168
+ ];
169
+
170
+ return matches.map(match => {
171
+ const [, jsdoc, name] = match;
172
+
173
+ // Extract description
174
+ const descMatch = jsdoc.match(/^\s*\*\s*(.+?)(?=\s*^\s*\*\s*@|\s*$)/ms);
175
+ const description = descMatch
176
+ ? descMatch[1]
177
+ .split('\n')
178
+ .map(line => line.replace(/^\s*\*\s?/, ''))
179
+ .join(' ')
180
+ .replace(/\s+/g, ' ')
181
+ .trim()
182
+ : '';
183
+
184
+ // Extract parameters
185
+ const paramMatches = [
186
+ ...jsdoc.matchAll(
187
+ /^\s*\*\s*@param\s+{([^}]+)}\s+(\[?(\w+)(?:=([^\]]+))?\]?)\s*-\s*(.+?)(?=\s*^\s*\*\s*@|\s*^\s*\*\s*$)/gms,
188
+ ),
189
+ ];
190
+
191
+ const parameters = paramMatches.map(paramMatch => {
192
+ const [, type, fullParam, name, defaultValue, desc] = paramMatch;
193
+ const optional = fullParam.startsWith('[') && fullParam.endsWith(']');
194
+
195
+ return {
196
+ name,
197
+ type: type.trim(),
198
+ description: desc.trim().replace(/\s+/g, ' '),
199
+ optional,
200
+ default: defaultValue || null,
201
+ };
202
+ });
203
+
204
+ // Extract return information - type only; description is not used in type generation.
205
+ const returnMatch = jsdoc.match(/^\s*\*\s*@returns?\s+\{([^}]+)\}/m);
206
+ const returnInfo = returnMatch ? { type: returnMatch[1].trim() } : null;
207
+
208
+ return {
209
+ name,
210
+ description,
211
+ parameters,
212
+ returns: returnInfo,
213
+ };
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Extracts property information from JSDoc comments and getter methods.
219
+ * @param {string} content - File content to parse
220
+ * @returns {Array} Array of property objects with name, type, description, and access info
221
+ */
222
+ function extractProperties(content) {
223
+ const properties = [];
224
+
225
+ // Extract getter properties - same anti-span pattern as extractMethods.
226
+ const getterMatches = [
227
+ ...content.matchAll(/\/\*\*((?:(?!\/\*\*)[\s\S])*?)\*\/[ \t]*\n[ \t]*get\s+(\w+)\s*\(\)\s*\{/g),
228
+ ];
229
+
230
+ getterMatches.forEach(match => {
231
+ const [, jsdoc, name] = match;
232
+
233
+ const descMatch = jsdoc.match(/^\s*\*\s*(.+?)(?=\s*^\s*\*\s*@|\s*$)/ms);
234
+ const description = descMatch
235
+ ? descMatch[1]
236
+ .split('\n')
237
+ .map(line => line.replace(/^\s*\*\s?/, ''))
238
+ .join(' ')
239
+ .replace(/\s+/g, ' ')
240
+ .trim()
241
+ : '';
242
+
243
+ const returnMatch = jsdoc.match(/^\s*\*\s*@returns?\s+\{([^}]+)\}/m);
244
+ const type = returnMatch ? returnMatch[1].trim() : 'unknown';
245
+
246
+ properties.push({
247
+ name,
248
+ type,
249
+ description,
250
+ access: 'readonly',
251
+ });
252
+ });
253
+
254
+ // Extract documented properties (this.property assignments with JSDoc)
255
+ const propMatches = [...content.matchAll(/\/\*\*((?:(?!\/\*\*)[\s\S])*?)\*\/[ \t]*\n[ \t]*(?:this\.)?(\w+)\s*=/g)];
256
+
257
+ propMatches.forEach(match => {
258
+ const [, jsdoc, name] = match;
259
+
260
+ const descMatch = jsdoc.match(/^\s*\*\s*(.+?)(?=\s*^\s*\*\s*@|\s*$)/ms);
261
+ const description = descMatch
262
+ ? descMatch[1]
263
+ .split('\n')
264
+ .map(line => line.replace(/^\s*\*\s?/, ''))
265
+ .join(' ')
266
+ .replace(/\s+/g, ' ')
267
+ .trim()
268
+ : '';
269
+
270
+ const typeMatch = jsdoc.match(/^\s*\*\s*@type\s+{([^}]+)}/m);
271
+ const type = typeMatch ? typeMatch[1].trim() : 'unknown';
272
+
273
+ properties.push({
274
+ name,
275
+ type,
276
+ description,
277
+ access: 'read-write',
278
+ });
279
+ });
280
+
281
+ return properties;
282
+ }
283
+
284
+ /**
285
+ * Extracts event information from emit() calls and JSDoc `@fires` tags.
286
+ * @param {string} content - File content to parse
287
+ * @returns {Array} Array of event objects with name and description
288
+ */
289
+ function extractEvents(content) {
290
+ const events = [];
291
+
292
+ // Extract from emit() calls
293
+ const emitMatches = [...content.matchAll(/\.emit\s*\(\s*['"`]([^'"`]+)['"`]/g)];
294
+
295
+ emitMatches.forEach(match => {
296
+ const [, eventName] = match;
297
+ events.push({
298
+ name: eventName,
299
+ description: `Emitted when ${eventName} occurs`,
300
+ });
301
+ });
302
+
303
+ // Extract from dispatchEvent calls
304
+ const dispatchMatches = [...content.matchAll(/\.dispatchEvent\s*\(\s*new\s+CustomEvent\s*\(\s*['"`]([^'"`]+)['"`]/g)];
305
+
306
+ dispatchMatches.forEach(match => {
307
+ const [, eventName] = match;
308
+ if (!events.find(e => e.name === eventName)) {
309
+ events.push({
310
+ name: eventName,
311
+ description: `Custom event: ${eventName}`,
312
+ });
313
+ }
314
+ });
315
+
316
+ return events;
317
+ }
318
+
319
+ /**
320
+ * Extracts import statements to determine component dependencies.
321
+ * @param {string} content - File content to parse
322
+ * @returns {Array} Array of import objects with module and imported items
323
+ */
324
+ function extractImports(content) {
325
+ const importMatches = [
326
+ ...content.matchAll(/^import\s+(?:{([^}]+)}|\*\s+as\s+(\w+)|(\w+))\s+from\s+['"`]([^'"`]+)['"`];/gm),
327
+ ];
328
+
329
+ return importMatches
330
+ .filter(([, , , , module]) => /^\.\.\//.test(module)) // external relative only, not same-dir ./
331
+ .map(match => {
332
+ const [, namedImports, namespaceImport, defaultImport, module] = match;
333
+
334
+ let imports = [];
335
+
336
+ if (namedImports) {
337
+ imports = namedImports.split(',').map(imp => imp.trim());
338
+ } else if (namespaceImport) {
339
+ imports = [`* as ${namespaceImport}`];
340
+ } else if (defaultImport) {
341
+ imports = [defaultImport];
342
+ }
343
+
344
+ return {
345
+ module: module.replace(/^(\.\.?\/)+/, ''),
346
+ imports,
347
+ };
348
+ });
349
+ }
350
+
351
+ /**
352
+ * Extracts example code from JSDoc `@example` tags.
353
+ * @param {string} content - File content to parse
354
+ * @returns {Array} Array of example objects with code and optional description
355
+ */
356
+ function extractExamples(content) {
357
+ const examples = [];
358
+
359
+ // Find all JSDoc blocks with @example tags
360
+ const jsdocBlocks = [...content.matchAll(/\/\*\*\s*\n([\s\S]*?)\*\//g)];
361
+
362
+ jsdocBlocks.forEach(([, jsdoc]) => {
363
+ const exampleMatches = [...jsdoc.matchAll(/^\s*\*\s*@example\s*\n([\s\S]*?)(?=^\s*\*\s*@|\s*$)/gms)];
364
+
365
+ exampleMatches.forEach(([, exampleContent]) => {
366
+ const code = exampleContent
367
+ .split('\n')
368
+ .map(line => line.replace(/^\s*\*\s?/, ''))
369
+ .join('\n')
370
+ .trim();
371
+
372
+ if (code) {
373
+ examples.push({
374
+ code,
375
+ description: '', // Could extract description if needed
376
+ });
377
+ }
378
+ });
379
+ });
380
+
381
+ return examples;
382
+ }
383
+
384
+ // CLI usage
385
+ if (import.meta.main) {
386
+ const filePath = process.argv[2];
387
+
388
+ if (!filePath) {
389
+ console.error('Usage: bun extractJSDoc.js <path-to-js-file>');
390
+ process.exit(1);
391
+ }
392
+
393
+ const result = extractJSDoc(filePath);
394
+ console.log(JSON.stringify(result, null, 2));
395
+ }