@pure-ds/storybook 0.1.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.
Files changed (129) hide show
  1. package/.storybook/addons/description/preview.js +15 -0
  2. package/.storybook/addons/description/register.js +60 -0
  3. package/.storybook/addons/html-preview/Panel.jsx +327 -0
  4. package/.storybook/addons/html-preview/constants.js +6 -0
  5. package/.storybook/addons/html-preview/preview.js +178 -0
  6. package/.storybook/addons/html-preview/register.js +16 -0
  7. package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
  8. package/.storybook/addons/pds-configurator/Tool.js +30 -0
  9. package/.storybook/addons/pds-configurator/constants.js +9 -0
  10. package/.storybook/addons/pds-configurator/preview.js +159 -0
  11. package/.storybook/addons/pds-configurator/register.js +24 -0
  12. package/.storybook/docs.css +35 -0
  13. package/.storybook/htmlPreview.css +103 -0
  14. package/.storybook/htmlPreview.js +271 -0
  15. package/.storybook/main.js +160 -0
  16. package/.storybook/preview-body.html +48 -0
  17. package/.storybook/preview-head.html +11 -0
  18. package/.storybook/preview.js +1563 -0
  19. package/README.md +266 -0
  20. package/bin/index.js +40 -0
  21. package/dist/pds-reference.json +2101 -0
  22. package/package.json +45 -0
  23. package/pds.config.js +6 -0
  24. package/public/assets/css/app.css +1216 -0
  25. package/public/assets/data/auto-design-advanced.json +704 -0
  26. package/public/assets/data/auto-design-simple.json +123 -0
  27. package/public/assets/img/icon-512x512.png +0 -0
  28. package/public/assets/img/logo-trans.png +0 -0
  29. package/public/assets/img/logo.png +0 -0
  30. package/public/assets/js/app.js +15088 -0
  31. package/public/assets/js/app.js.map +7 -0
  32. package/public/assets/js/lit.js +1176 -0
  33. package/public/assets/js/lit.js.map +7 -0
  34. package/public/assets/js/pds.js +9801 -0
  35. package/public/assets/js/pds.js.map +7 -0
  36. package/public/assets/pds/components/pds-calendar.js +837 -0
  37. package/public/assets/pds/components/pds-drawer.js +857 -0
  38. package/public/assets/pds/components/pds-icon.js +338 -0
  39. package/public/assets/pds/components/pds-jsonform.js +1775 -0
  40. package/public/assets/pds/components/pds-richtext.js +1035 -0
  41. package/public/assets/pds/components/pds-scrollrow.js +331 -0
  42. package/public/assets/pds/components/pds-splitpanel.js +401 -0
  43. package/public/assets/pds/components/pds-tabstrip.js +251 -0
  44. package/public/assets/pds/components/pds-toaster.js +446 -0
  45. package/public/assets/pds/components/pds-upload.js +657 -0
  46. package/public/assets/pds/custom-elements.json +2003 -0
  47. package/public/assets/pds/icons/pds-icons.svg +498 -0
  48. package/public/assets/pds/pds-css-complete.json +1861 -0
  49. package/public/assets/pds/pds-runtime-config.json +11 -0
  50. package/public/assets/pds/pds.css-data.json +2152 -0
  51. package/public/assets/pds/styles/pds-components.css +1944 -0
  52. package/public/assets/pds/styles/pds-components.css.js +3895 -0
  53. package/public/assets/pds/styles/pds-primitives.css +352 -0
  54. package/public/assets/pds/styles/pds-primitives.css.js +711 -0
  55. package/public/assets/pds/styles/pds-styles.css +3761 -0
  56. package/public/assets/pds/styles/pds-styles.css.js +7529 -0
  57. package/public/assets/pds/styles/pds-tokens.css +699 -0
  58. package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
  59. package/public/assets/pds/styles/pds-utilities.css +763 -0
  60. package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
  61. package/public/assets/pds/vscode-custom-data.json +824 -0
  62. package/scripts/build-pds-reference.mjs +807 -0
  63. package/scripts/generate-stories.js +542 -0
  64. package/scripts/package-build.js +86 -0
  65. package/src/js/app.js +17 -0
  66. package/src/js/common/ask.js +208 -0
  67. package/src/js/common/common.js +20 -0
  68. package/src/js/common/font-loader.js +200 -0
  69. package/src/js/common/msg.js +90 -0
  70. package/src/js/lit.js +40 -0
  71. package/src/js/pds-core/pds-config.js +1162 -0
  72. package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
  73. package/src/js/pds-core/pds-enhancers.js +357 -0
  74. package/src/js/pds-core/pds-enums.js +86 -0
  75. package/src/js/pds-core/pds-generator.js +5317 -0
  76. package/src/js/pds-core/pds-ontology.js +256 -0
  77. package/src/js/pds-core/pds-paths.js +109 -0
  78. package/src/js/pds-core/pds-query.js +571 -0
  79. package/src/js/pds-core/pds-registry.js +129 -0
  80. package/src/js/pds-core/pds.d.ts +129 -0
  81. package/src/js/pds.d.ts +408 -0
  82. package/src/js/pds.js +1579 -0
  83. package/src/pds-core/pds-api.js +105 -0
  84. package/stories/GettingStarted.md +96 -0
  85. package/stories/GettingStarted.stories.js +144 -0
  86. package/stories/WhatIsPDS.md +194 -0
  87. package/stories/WhatIsPDS.stories.js +144 -0
  88. package/stories/components/PdsCalendar.stories.js +263 -0
  89. package/stories/components/PdsDrawer.stories.js +623 -0
  90. package/stories/components/PdsIcon.stories.js +78 -0
  91. package/stories/components/PdsJsonform.stories.js +1444 -0
  92. package/stories/components/PdsRichtext.stories.js +367 -0
  93. package/stories/components/PdsScrollrow.stories.js +140 -0
  94. package/stories/components/PdsSplitpanel.stories.js +502 -0
  95. package/stories/components/PdsTabstrip.stories.js +442 -0
  96. package/stories/components/PdsToaster.stories.js +186 -0
  97. package/stories/components/PdsUpload.stories.js +66 -0
  98. package/stories/enhancements/Dropdowns.stories.js +185 -0
  99. package/stories/enhancements/InteractiveStates.stories.js +625 -0
  100. package/stories/enhancements/MeshGradients.stories.js +320 -0
  101. package/stories/enhancements/OpenGroups.stories.js +227 -0
  102. package/stories/enhancements/RangeSliders.stories.js +232 -0
  103. package/stories/enhancements/RequiredFields.stories.js +189 -0
  104. package/stories/enhancements/Toggles.stories.js +167 -0
  105. package/stories/foundations/Colors.stories.js +283 -0
  106. package/stories/foundations/Icons.stories.js +305 -0
  107. package/stories/foundations/SmartSurfaces.stories.js +367 -0
  108. package/stories/foundations/Spacing.stories.js +175 -0
  109. package/stories/foundations/Typography.stories.js +960 -0
  110. package/stories/foundations/ZIndex.stories.js +325 -0
  111. package/stories/patterns/BorderEffects.stories.js +72 -0
  112. package/stories/patterns/Layout.stories.js +99 -0
  113. package/stories/patterns/Utilities.stories.js +107 -0
  114. package/stories/primitives/Accordion.stories.js +359 -0
  115. package/stories/primitives/Alerts.stories.js +64 -0
  116. package/stories/primitives/Badges.stories.js +183 -0
  117. package/stories/primitives/Buttons.stories.js +229 -0
  118. package/stories/primitives/Cards.stories.js +353 -0
  119. package/stories/primitives/FormGroups.stories.js +569 -0
  120. package/stories/primitives/Forms.stories.js +131 -0
  121. package/stories/primitives/Media.stories.js +203 -0
  122. package/stories/primitives/Tables.stories.js +232 -0
  123. package/stories/reference/ReferenceCatalog.stories.js +28 -0
  124. package/stories/reference/reference-catalog.js +413 -0
  125. package/stories/reference/reference-docs.js +302 -0
  126. package/stories/reference/reference-helpers.js +310 -0
  127. package/stories/utilities/GridSystem.stories.js +208 -0
  128. package/stories/utils/PdsAsk.stories.js +420 -0
  129. package/stories/utils/toast-utils.js +148 -0
@@ -0,0 +1,807 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Consolidated reference data generator for Pure Design System Storybook
4
+ // Combines metadata from custom-elements.json, the PDS ontology, and Storybook stories
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { fileURLToPath, pathToFileURL } from 'url';
9
+ import ts from 'typescript';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ // Determine if we are in the monorepo or installed as a package
15
+ // If we are in node_modules, we are a package.
16
+ // OR if we are running from the package-build script, we might be in the package folder but not in node_modules yet.
17
+ // But this script is run by the user (via npm run storybook) or by the build script.
18
+
19
+ // If we are in the monorepo, ROOT_DIR is ../../..
20
+ // If we are in the package, ROOT_DIR is ..
21
+ // We can check for the existence of 'packages/pds-storybook' in the parent directories to guess.
22
+
23
+ let ROOT_DIR;
24
+ let STORIES_ROOT;
25
+ let OUTPUT_DIR;
26
+
27
+ // Check if we are in the monorepo structure
28
+ if (fs.existsSync(path.join(__dirname, '../../packages/pds-storybook'))) {
29
+ // We are in packages/pds-storybook/scripts
30
+ ROOT_DIR = path.join(__dirname, '../../..');
31
+ STORIES_ROOT = path.join(ROOT_DIR, 'packages/pds-storybook/stories');
32
+ OUTPUT_DIR = path.join(ROOT_DIR, 'packages/pds-storybook/dist');
33
+ } else {
34
+ // We are likely in the package structure (node_modules/@pure-ds/storybook/scripts or just dist/scripts)
35
+ ROOT_DIR = path.join(__dirname, '..');
36
+ STORIES_ROOT = path.join(ROOT_DIR, 'stories');
37
+ OUTPUT_DIR = path.join(ROOT_DIR, 'dist');
38
+ }
39
+
40
+ const CUSTOM_ELEMENTS_PATH = path.join(ROOT_DIR, 'custom-elements.json');
41
+ const ONTOLOGY_PATH = path.join(ROOT_DIR, 'src/js/pds-core/pds-ontology.js');
42
+ const ENHANCERS_PATH = path.join(ROOT_DIR, 'src/js/pds-core/pds-enhancers.js');
43
+ const ENHANCERS_SOURCE_LABEL = path.relative(ROOT_DIR, ENHANCERS_PATH);
44
+ const ONTOLOGY_SOURCE_LABEL = path.relative(ROOT_DIR, ONTOLOGY_PATH);
45
+ const OUTPUT_PATH = path.join(OUTPUT_DIR, 'pds-reference.json');
46
+
47
+ const TYPE_METADATA_CACHE = new Map();
48
+
49
+ const SPECIAL_COMPONENT_OVERRIDES = {
50
+ 'pds-jsonform': {
51
+ title: 'PDS JSON Schema Form',
52
+ notes: [
53
+ 'Typed interfaces for `JsonFormOptions`, `UISchema`, and related events live in `src/js/pds.d.ts`.',
54
+ 'The form accepts standard JSON Schema (Draft 7) documents via the `jsonSchema` property. `uiSchema` and `options` fine-tune layout, widgets, and behaviors.'
55
+ ],
56
+ properties: {
57
+ jsonSchema: {
58
+ type: 'JSONSchema7 (object)',
59
+ description: 'Primary schema input. Provide a JSON Schema object to generate the form layout and validation rules.'
60
+ },
61
+ uiSchema: {
62
+ type: 'UISchema',
63
+ description: 'Optional UI overrides keyed by JSON Pointer. Controls layout, widgets, surfaces, dialogs, and per-field hints.'
64
+ },
65
+ options: {
66
+ type: 'JsonFormOptions',
67
+ description: 'Toolkit-level options that adjust widget families, layouts, and validation behavior. Supports path-specific overrides.'
68
+ },
69
+ values: {
70
+ type: 'Record<string, any>',
71
+ description: 'Initial data that pre-populates the generated form. Shape must match the JSON Schema.'
72
+ }
73
+ },
74
+ events: [
75
+ {
76
+ name: 'pw:submit',
77
+ detail: 'JsonFormSubmitDetail',
78
+ description: 'Emitted after submission. `detail` includes `{ json, formData, valid, issues }` for server hand-off or additional processing.'
79
+ },
80
+ {
81
+ name: 'pw:value-change',
82
+ detail: 'JsonFormValueChangeDetail',
83
+ description: 'Live value updates whenever a field changes. `detail.name` contains the JSON Pointer-compatible path.'
84
+ },
85
+ {
86
+ name: 'pw:array-add',
87
+ detail: 'JsonFormArrayEventDetail',
88
+ description: 'Triggered when an array item is appended. Carries the path and index metadata.'
89
+ },
90
+ {
91
+ name: 'pw:array-remove',
92
+ detail: 'JsonFormArrayEventDetail',
93
+ description: 'Triggered when an array item is removed.'
94
+ },
95
+ {
96
+ name: 'pw:array-reorder',
97
+ detail: 'JsonFormArrayEventDetail',
98
+ description: 'Triggered when array items are reordered (drag & drop scenarios).'
99
+ },
100
+ {
101
+ name: 'pw:dialog-open',
102
+ detail: 'JsonFormDialogEventDetail',
103
+ description: 'Fires before a dialog-driven field opens (e.g. complex editors).'
104
+ },
105
+ {
106
+ name: 'pw:dialog-submit',
107
+ detail: 'JsonFormDialogEventDetail',
108
+ description: 'Fires when a dialog-driven field is saved.'
109
+ }
110
+ ]
111
+ }
112
+ };
113
+
114
+ function slugifySegment(input = '') {
115
+ return input
116
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
117
+ .replace(/[^a-z0-9]+/gi, '-')
118
+ .replace(/^-+|-+$/g, '')
119
+ .toLowerCase();
120
+ }
121
+
122
+ function slugifyTitle(input = '') {
123
+ return slugifySegment(input.replace(/\//g, '-'));
124
+ }
125
+
126
+ function humanizeTag(tag = '') {
127
+ if (!tag) return '';
128
+ return tag
129
+ .replace(/^pds-/, 'PDS ')
130
+ .replace(/-/g, ' ')
131
+ .replace(/\b([a-z])/g, (m) => m.toUpperCase());
132
+ }
133
+
134
+ function dedupe(values = []) {
135
+ return Array.from(new Set(values.filter(Boolean)));
136
+ }
137
+
138
+ function trimOrNull(value) {
139
+ if (typeof value !== 'string') {
140
+ return value ?? null;
141
+ }
142
+ const trimmed = value.trim();
143
+ return trimmed.length ? trimmed : null;
144
+ }
145
+
146
+ function normalizeMemberName(name = '') {
147
+ if (typeof name !== 'string') {
148
+ return '';
149
+ }
150
+ return name.replace(/^['"]|['"]$/g, '');
151
+ }
152
+
153
+ function isPrivateMemberName(name) {
154
+ const normalized = normalizeMemberName(name);
155
+ if (!normalized) return false;
156
+ return (
157
+ normalized.startsWith('#') ||
158
+ normalized.startsWith('_') ||
159
+ normalized.includes('#private@')
160
+ );
161
+ }
162
+
163
+ async function readJson(filePath) {
164
+ const raw = await fs.promises.readFile(filePath, 'utf-8');
165
+ return JSON.parse(raw);
166
+ }
167
+
168
+ async function loadOntology() {
169
+ const mod = await import(pathToFileURL(ONTOLOGY_PATH).href);
170
+ return mod.ontology || mod.default || {};
171
+ }
172
+
173
+ function normalizeEnhancerDemo(demo) {
174
+ if (!demo) return null;
175
+ let markup = demo;
176
+ if (typeof markup === 'function') {
177
+ try {
178
+ markup = markup();
179
+ } catch (error) {
180
+ console.warn('[pds-reference] Failed to evaluate enhancer demo', error);
181
+ return null;
182
+ }
183
+ }
184
+ if (typeof markup !== 'string') return null;
185
+ const trimmed = markup.trim();
186
+ return trimmed.length ? trimmed : null;
187
+ }
188
+
189
+ async function loadEnhancers() {
190
+ try {
191
+ const mod = await import(pathToFileURL(ENHANCERS_PATH).href);
192
+ if (Array.isArray(mod.defaultPDSEnhancerMetadata)) {
193
+ return mod.defaultPDSEnhancerMetadata;
194
+ }
195
+ if (Array.isArray(mod.defaultPDSEnhancers)) {
196
+ return mod.defaultPDSEnhancers.map((enhancer) => ({
197
+ selector: enhancer.selector,
198
+ description: enhancer.description || null,
199
+ demoHtml: normalizeEnhancerDemo(enhancer.demoHtml)
200
+ }));
201
+ }
202
+ if (Array.isArray(mod.default)) {
203
+ return mod.default;
204
+ }
205
+ } catch (error) {
206
+ console.warn(`[pds-reference] Unable to import enhancers metadata: ${error.message}`);
207
+ }
208
+ return [];
209
+ }
210
+
211
+ async function walkStories(dir) {
212
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
213
+ const files = [];
214
+ for (const entry of entries) {
215
+ const fullPath = path.join(dir, entry.name);
216
+ if (entry.isDirectory()) {
217
+ files.push(...await walkStories(fullPath));
218
+ } else if (entry.isFile() && entry.name.endsWith('.stories.js')) {
219
+ files.push(fullPath);
220
+ }
221
+ }
222
+ return files;
223
+ }
224
+
225
+ async function collectStoryMetadata() {
226
+ const storyFiles = await walkStories(STORIES_ROOT);
227
+ const index = new Map();
228
+
229
+ for (const file of storyFiles) {
230
+ if (file.includes(`${path.sep}reference${path.sep}`)) continue;
231
+
232
+ const fileUrl = pathToFileURL(file).href;
233
+ let mod;
234
+ try {
235
+ mod = await import(fileUrl);
236
+ } catch (error) {
237
+ const rel = path.relative(ROOT_DIR, file);
238
+ if (error.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
239
+ console.warn(`[pds-reference] Skipped ${rel} (non-JS dependency: ${error.message.split(' for ').pop() || error.message})`);
240
+ } else {
241
+ console.warn(`[pds-reference] Skipped ${rel}: ${error.message}`);
242
+ }
243
+ continue;
244
+ }
245
+
246
+ const meta = mod.default;
247
+ if (!meta || !meta.title) {
248
+ continue;
249
+ }
250
+
251
+ const segments = meta.title.split('/');
252
+ const nameSegment = segments[segments.length - 1] || segments[0];
253
+ const slug = slugifySegment(nameSegment);
254
+ if (!slug) continue;
255
+
256
+ const idBase = slugifyTitle(meta.title);
257
+ const relPath = path.relative(ROOT_DIR, file);
258
+
259
+ const entry = index.get(slug) || {
260
+ slug,
261
+ storyTitle: meta.title,
262
+ category: segments.slice(0, -1).join('/') || null,
263
+ name: nameSegment,
264
+ description: null,
265
+ tags: new Set(),
266
+ pdsParameters: {},
267
+ stories: [],
268
+ files: new Set()
269
+ };
270
+
271
+ entry.files.add(relPath);
272
+
273
+ if (Array.isArray(meta.tags)) {
274
+ meta.tags.forEach((tag) => tag && entry.tags.add(tag));
275
+ }
276
+ if (Array.isArray(meta.parameters?.pds?.tags)) {
277
+ meta.parameters.pds.tags.forEach((tag) => tag && entry.tags.add(tag));
278
+ }
279
+
280
+ if (meta.parameters?.pds) {
281
+ entry.pdsParameters = { ...entry.pdsParameters, ...meta.parameters.pds };
282
+ }
283
+
284
+ if (meta.parameters?.docs?.description?.component) {
285
+ entry.description = meta.parameters.docs.description.component;
286
+ }
287
+
288
+ for (const [exportName, story] of Object.entries(mod)) {
289
+ if (exportName === 'default' || exportName === '__namedExportsOrder') continue;
290
+
291
+ const storyItem = typeof story === 'object' ? story : null;
292
+ if (!storyItem) continue;
293
+
294
+ const storyTags = [];
295
+ if (Array.isArray(storyItem.tags)) storyTags.push(...storyItem.tags);
296
+ if (Array.isArray(storyItem.parameters?.pds?.tags)) storyTags.push(...storyItem.parameters.pds.tags);
297
+
298
+ entry.stories.push({
299
+ exportName,
300
+ name: storyItem.storyName || storyItem.name || exportName,
301
+ id: `${idBase}--${slugifySegment(exportName)}`,
302
+ tags: dedupe(storyTags),
303
+ description: trimOrNull(storyItem.parameters?.docs?.description?.story || storyItem.parameters?.docs?.description?.component),
304
+ source: relPath
305
+ });
306
+ }
307
+
308
+ index.set(slug, entry);
309
+ }
310
+
311
+ const result = {};
312
+ for (const [slug, entry] of index.entries()) {
313
+ result[slug] = {
314
+ slug,
315
+ storyTitle: entry.storyTitle,
316
+ category: entry.category,
317
+ name: entry.name,
318
+ description: trimOrNull(entry.description),
319
+ tags: Array.from(entry.tags).sort(),
320
+ pdsParameters: entry.pdsParameters,
321
+ stories: entry.stories.sort((a, b) => a.name.localeCompare(b.name)),
322
+ files: Array.from(entry.files)
323
+ };
324
+ }
325
+ return result;
326
+ }
327
+
328
+ function extractJsDocComment(node, sourceFile) {
329
+ const docs = node?.jsDoc;
330
+ if (!docs?.length) return null;
331
+ const parts = [];
332
+ for (const doc of docs) {
333
+ if (typeof doc.comment === 'string') {
334
+ parts.push(doc.comment);
335
+ } else if (Array.isArray(doc.comment)) {
336
+ parts.push(
337
+ doc.comment
338
+ .map((segment) => (typeof segment === 'string' ? segment : segment.getText(sourceFile)))
339
+ .join('')
340
+ );
341
+ }
342
+ }
343
+ return trimOrNull(parts.join('\n\n'));
344
+ }
345
+
346
+ function resolveDtsPath(modulePath) {
347
+ if (!modulePath) return null;
348
+ const normalized = modulePath.replace(/\\/g, '/');
349
+ const candidate = normalized.replace(/\.(mjs|cjs|js|ts)$/i, '.d.ts');
350
+ return path.join(ROOT_DIR, 'dist/types', candidate);
351
+ }
352
+
353
+ async function loadTypeMetadata(modulePath) {
354
+ if (!modulePath) return null;
355
+ if (TYPE_METADATA_CACHE.has(modulePath)) {
356
+ return TYPE_METADATA_CACHE.get(modulePath);
357
+ }
358
+
359
+ const dtsPath = resolveDtsPath(modulePath);
360
+ if (!dtsPath) {
361
+ TYPE_METADATA_CACHE.set(modulePath, null);
362
+ return null;
363
+ }
364
+
365
+ let source;
366
+ try {
367
+ source = await fs.promises.readFile(dtsPath, 'utf8');
368
+ } catch {
369
+ TYPE_METADATA_CACHE.set(modulePath, null);
370
+ return null;
371
+ }
372
+
373
+ if (!source || !source.trim()) {
374
+ TYPE_METADATA_CACHE.set(modulePath, null);
375
+ return null;
376
+ }
377
+
378
+ const sourceFile = ts.createSourceFile(dtsPath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
379
+
380
+ const moduleInfo = {
381
+ classes: {}
382
+ };
383
+
384
+ sourceFile.forEachChild((node) => {
385
+ if (!ts.isClassDeclaration(node) && !ts.isClassExpression(node)) return;
386
+ const className = node.name?.text;
387
+ if (!className) return;
388
+
389
+ const classInfo = {
390
+ name: className,
391
+ description: trimOrNull(extractJsDocComment(node, sourceFile)),
392
+ properties: {},
393
+ methods: {}
394
+ };
395
+
396
+ for (const member of node.members || []) {
397
+ const rawMemberName = member.name?.getText?.(sourceFile);
398
+ const memberName = normalizeMemberName(rawMemberName);
399
+ if (!memberName || isPrivateMemberName(memberName)) continue;
400
+
401
+ if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
402
+ classInfo.properties[memberName] = {
403
+ type: member.type ? member.type.getText(sourceFile) : null,
404
+ description: trimOrNull(extractJsDocComment(member, sourceFile)),
405
+ optional: Boolean(member.questionToken),
406
+ readonly: Boolean(member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ReadonlyKeyword))
407
+ };
408
+ } else if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
409
+ classInfo.methods[memberName] = {
410
+ description: trimOrNull(extractJsDocComment(member, sourceFile)),
411
+ returnType: member.type ? member.type.getText(sourceFile) : 'void',
412
+ parameters:
413
+ member.parameters?.map((param) => ({
414
+ name: param.name.getText(sourceFile),
415
+ type: param.type ? param.type.getText(sourceFile) : 'any',
416
+ optional: Boolean(param.questionToken),
417
+ description: trimOrNull(extractJsDocComment(param, sourceFile))
418
+ })) || []
419
+ };
420
+ }
421
+ }
422
+
423
+ moduleInfo.classes[className] = classInfo;
424
+ });
425
+
426
+ TYPE_METADATA_CACHE.set(modulePath, moduleInfo);
427
+ return moduleInfo;
428
+ }
429
+
430
+ function mapCustomElement(
431
+ declaration,
432
+ moduleEntry,
433
+ storyMeta = {},
434
+ ontologyLookup = new Map(),
435
+ explicitTag,
436
+ tsClassInfo = null
437
+ ) {
438
+ const tag = (explicitTag || declaration.tagName || '').toLowerCase();
439
+ if (!tag) return null;
440
+ const ontologyEntry = ontologyLookup.get(tag) || ontologyLookup.get(declaration.name) || null;
441
+
442
+ const attributes = (declaration.attributes || []).map((attr) => ({
443
+ name: attr.name,
444
+ description: trimOrNull(attr.description),
445
+ type: attr.type?.text || null,
446
+ default: attr.default ?? null,
447
+ fieldName: attr.fieldName || null
448
+ }));
449
+
450
+ const attributeByField = new Map(attributes.map((attr) => [attr.fieldName || attr.name, attr]));
451
+
452
+ let properties = (declaration.members || [])
453
+ .filter((member) => member.kind === 'field' && member.privacy !== 'private' && !isPrivateMemberName(member.name || ''))
454
+ .map((member) => ({
455
+ name: member.name,
456
+ attribute: member.attribute || attributeByField.get(member.name)?.name || null,
457
+ description: trimOrNull(member.description || attributeByField.get(member.name)?.description),
458
+ type: member.type?.text || null,
459
+ default: member.default ?? null,
460
+ reflects: member.reflects || false,
461
+ privacy: member.privacy || 'public'
462
+ }));
463
+
464
+ let methods = (declaration.members || [])
465
+ .filter((member) => member.kind === 'method' && member.privacy !== 'private' && !isPrivateMemberName(member.name || ''))
466
+ .map((member) => ({
467
+ name: member.name,
468
+ description: trimOrNull(member.description),
469
+ parameters: (member.parameters || []).map((param) => ({
470
+ name: param.name,
471
+ type: param.type?.text || null,
472
+ description: param.description || null
473
+ })),
474
+ return: member.return?.type?.text || null
475
+ }));
476
+
477
+ const events = (declaration.events || [])
478
+ .filter((event) => event.name && event.name !== 'name')
479
+ .map((event) => ({
480
+ name: event.name,
481
+ description: trimOrNull(event.description),
482
+ type: event.type?.text || null
483
+ }));
484
+
485
+ const slots = (declaration.slots || []).map((slot) => ({
486
+ name: slot.name,
487
+ description: trimOrNull(slot.description)
488
+ }));
489
+
490
+ const cssParts = (declaration.cssParts || []).map((part) => ({
491
+ name: part.name,
492
+ description: trimOrNull(part.description)
493
+ }));
494
+
495
+ if (tsClassInfo) {
496
+ const propertyMap = new Map(properties.map((prop) => [prop.name, prop]));
497
+ for (const prop of properties) {
498
+ const tsProp = tsClassInfo.properties?.[prop.name];
499
+ if (!tsProp) continue;
500
+ prop.type = tsProp.type || prop.type;
501
+ prop.description = trimOrNull([prop.description, tsProp.description].filter(Boolean).join('\n\n')) || prop.description;
502
+ if (tsProp.optional) prop.optional = true;
503
+ if (tsProp.readonly) prop.readonly = true;
504
+ }
505
+
506
+ for (const [propName, tsProp] of Object.entries(tsClassInfo.properties || {})) {
507
+ if (isPrivateMemberName(propName) || propertyMap.has(propName)) continue;
508
+ const entry = {
509
+ name: propName,
510
+ attribute: null,
511
+ description: trimOrNull(tsProp.description),
512
+ type: tsProp.type || null,
513
+ default: null,
514
+ reflects: false,
515
+ privacy: 'public'
516
+ };
517
+ if (tsProp.optional) entry.optional = true;
518
+ if (tsProp.readonly) entry.readonly = true;
519
+ propertyMap.set(propName, entry);
520
+ }
521
+ properties = Array.from(propertyMap.values());
522
+
523
+ const methodMap = new Map(methods.map((method) => [method.name, method]));
524
+ for (const method of methods) {
525
+ const tsMethod = tsClassInfo.methods?.[method.name];
526
+ if (!tsMethod) continue;
527
+ method.description = trimOrNull([method.description, tsMethod.description].filter(Boolean).join('\n\n')) || method.description;
528
+ if ((!method.parameters || method.parameters.length === 0) && tsMethod.parameters?.length) {
529
+ method.parameters = tsMethod.parameters.map((param) => ({
530
+ name: param.name,
531
+ type: param.type,
532
+ description: param.description,
533
+ optional: param.optional || false
534
+ }));
535
+ } else if (method.parameters?.length && tsMethod.parameters?.length) {
536
+ method.parameters = method.parameters.map((param) => {
537
+ const tsParam = tsMethod.parameters.find((p) => p.name === param.name);
538
+ if (!tsParam) return param;
539
+ return {
540
+ ...param,
541
+ type: tsParam.type || param.type,
542
+ description: trimOrNull([param.description, tsParam.description].filter(Boolean).join('\n\n')) || param.description,
543
+ optional: tsParam.optional || param.optional || false
544
+ };
545
+ });
546
+ }
547
+ method.return = tsMethod.returnType || method.return;
548
+ }
549
+
550
+ for (const [methodName, tsMethod] of Object.entries(tsClassInfo.methods || {})) {
551
+ if (isPrivateMemberName(methodName) || methodMap.has(methodName)) continue;
552
+ methodMap.set(methodName, {
553
+ name: methodName,
554
+ description: trimOrNull(tsMethod.description),
555
+ parameters: (tsMethod.parameters || []).map((param) => ({
556
+ name: param.name,
557
+ type: param.type,
558
+ description: param.description,
559
+ optional: param.optional || false
560
+ })),
561
+ return: tsMethod.returnType || null
562
+ });
563
+ }
564
+ methods = Array.from(methodMap.values());
565
+ }
566
+
567
+ const overrides = tag ? SPECIAL_COMPONENT_OVERRIDES[tag] || {} : {};
568
+
569
+ if (overrides.properties) {
570
+ for (const [propName, override] of Object.entries(overrides.properties)) {
571
+ const property = properties.find((prop) => prop.name === propName);
572
+ if (property) {
573
+ property.type = override.type || property.type;
574
+ property.description = trimOrNull([override.description, property.description].filter(Boolean).join('\n\n'));
575
+ } else {
576
+ properties.push({
577
+ name: propName,
578
+ attribute: null,
579
+ description: trimOrNull(override.description),
580
+ type: override.type || null,
581
+ default: null,
582
+ reflects: false,
583
+ privacy: 'public'
584
+ });
585
+ }
586
+ }
587
+ }
588
+
589
+ if (overrides.events) {
590
+ for (const event of overrides.events) {
591
+ const existing = events.find((item) => item.name === event.name);
592
+ if (existing) {
593
+ existing.type = event.detail ? `CustomEvent<${event.detail}>` : existing.type;
594
+ existing.description = trimOrNull([existing.description, event.description].filter(Boolean).join('\n\n'));
595
+ } else {
596
+ events.push({
597
+ name: event.name,
598
+ type: event.detail ? `CustomEvent<${event.detail}>` : null,
599
+ description: trimOrNull(event.description)
600
+ });
601
+ }
602
+ }
603
+ }
604
+
605
+ const combinedDescription = trimOrNull([
606
+ declaration.description,
607
+ tsClassInfo?.description
608
+ ].filter(Boolean).join('\n\n'));
609
+
610
+ const className = tsClassInfo?.name || declaration.name;
611
+
612
+ return {
613
+ tag,
614
+ className,
615
+ displayName: overrides.title || storyMeta.name || ontologyEntry?.name || humanizeTag(tag),
616
+ storyTitle: storyMeta.storyTitle || null,
617
+ category: storyMeta.category || null,
618
+ description: combinedDescription,
619
+ docsDescription: storyMeta.description || null,
620
+ pdsTags: storyMeta.tags || [],
621
+ ontology: ontologyEntry ? { ...ontologyEntry } : null,
622
+ stories: storyMeta.stories || [],
623
+ sourceModule: moduleEntry.path,
624
+ superclass: declaration.superclass?.name || null,
625
+ attributes,
626
+ properties: properties.sort((a, b) => a.name.localeCompare(b.name)),
627
+ methods: methods.sort((a, b) => a.name.localeCompare(b.name)),
628
+ events: events.sort((a, b) => a.name.localeCompare(b.name)),
629
+ slots,
630
+ cssParts,
631
+ notes: overrides.notes || []
632
+ };
633
+ }
634
+
635
+ async function buildComponents(customElements, ontology, storyIndex) {
636
+ const ontologyLookup = new Map();
637
+ for (const item of ontology.components || []) {
638
+ ontologyLookup.set(item.id?.toLowerCase(), item);
639
+ if (item.selectors) {
640
+ item.selectors.forEach((sel) => {
641
+ if (sel && sel.startsWith('pds-')) {
642
+ ontologyLookup.set(sel.toLowerCase(), item);
643
+ }
644
+ });
645
+ }
646
+ }
647
+
648
+ const components = {};
649
+
650
+ for (const moduleEntry of customElements.modules || []) {
651
+ const moduleTypeInfo = await loadTypeMetadata(moduleEntry.path);
652
+ const customElementDefs = (moduleEntry.exports || []).filter((exp) => exp.kind === 'custom-element-definition');
653
+
654
+ if (customElementDefs.length) {
655
+ for (const definition of customElementDefs) {
656
+ const tag = definition.name?.toLowerCase();
657
+ if (!tag) continue;
658
+ const declarationRefName = definition.declaration?.name;
659
+ const declaration = (moduleEntry.declarations || []).find((decl) => decl.name === declarationRefName) ||
660
+ (moduleEntry.declarations || []).find((decl) => decl.tagName && decl.tagName.toLowerCase() === tag) ||
661
+ (moduleEntry.declarations || []).find((decl) => decl.customElement);
662
+ if (!declaration) continue;
663
+
664
+ const tagWithoutPrefix = tag.startsWith('pds-') ? tag.slice(4) : tag;
665
+ const storyMeta =
666
+ storyIndex[tag] ||
667
+ storyIndex[slugifySegment(tag)] ||
668
+ storyIndex[tagWithoutPrefix] ||
669
+ storyIndex[slugifySegment(tagWithoutPrefix)] ||
670
+ storyIndex[slugifySegment(declaration.name)] || {};
671
+
672
+ const tsClassInfo = moduleTypeInfo?.classes?.[declaration.name] ||
673
+ moduleTypeInfo?.classes?.[declarationRefName] ||
674
+ null;
675
+
676
+ const component = mapCustomElement(declaration, moduleEntry, storyMeta, ontologyLookup, tag, tsClassInfo);
677
+ if (component?.tag) {
678
+ components[component.tag] = component;
679
+ }
680
+ }
681
+ continue;
682
+ }
683
+
684
+ for (const declaration of moduleEntry.declarations || []) {
685
+ if (!declaration.tagName) continue;
686
+ const tag = declaration.tagName.toLowerCase();
687
+ const tagWithoutPrefix = tag.startsWith('pds-') ? tag.slice(4) : tag;
688
+ const storyMeta =
689
+ storyIndex[tag] ||
690
+ storyIndex[slugifySegment(tag)] ||
691
+ storyIndex[tagWithoutPrefix] ||
692
+ storyIndex[slugifySegment(tagWithoutPrefix)] ||
693
+ storyIndex[slugifySegment(declaration.name)] || {};
694
+ const tsClassInfo = moduleTypeInfo?.classes?.[declaration.name] || null;
695
+ const component = mapCustomElement(declaration, moduleEntry, storyMeta, ontologyLookup, tag, tsClassInfo);
696
+ if (component?.tag) {
697
+ components[component.tag] = component;
698
+ }
699
+ }
700
+ }
701
+
702
+ return components;
703
+ }
704
+
705
+ function buildPrimitives(ontology) {
706
+ return {
707
+ primitives: (ontology.primitives || []).map((item) => ({
708
+ id: item.id,
709
+ name: item.name || humanizeTag(item.id),
710
+ selectors: item.selectors || []
711
+ })),
712
+ layoutPatterns: (ontology.layoutPatterns || []).map((item) => ({
713
+ id: item.id,
714
+ name: item.name,
715
+ description: item.description || null,
716
+ selectors: item.selectors || []
717
+ })),
718
+ utilities: Array.isArray(ontology.utilities) ? ontology.utilities : []
719
+ };
720
+ }
721
+
722
+ function buildEnhancements(ontology, enhancerMetadata = []) {
723
+ const entries = [];
724
+ const seen = new Set();
725
+
726
+ for (const meta of enhancerMetadata) {
727
+ if (!meta || !meta.selector) continue;
728
+ const selector = String(meta.selector).trim();
729
+ if (!selector) continue;
730
+ seen.add(selector);
731
+ entries.push({
732
+ id: slugifySegment(selector),
733
+ selector,
734
+ description: trimOrNull(meta.description),
735
+ demoHtml: trimOrNull(meta.demoHtml),
736
+ source: ENHANCERS_SOURCE_LABEL
737
+ });
738
+ }
739
+
740
+ for (const selector of ontology.enhancements || []) {
741
+ const normalized = String(selector || '').trim();
742
+ if (!normalized || seen.has(normalized)) continue;
743
+ entries.push({
744
+ id: slugifySegment(normalized),
745
+ selector: normalized,
746
+ description: null,
747
+ demoHtml: null,
748
+ source: ONTOLOGY_SOURCE_LABEL
749
+ });
750
+ }
751
+
752
+ return entries.sort((a, b) => a.selector.localeCompare(b.selector));
753
+ }
754
+
755
+ function buildTokens(ontology) {
756
+ const tokens = ontology.tokens || {};
757
+ const result = {};
758
+ for (const [group, value] of Object.entries(tokens)) {
759
+ if (Array.isArray(value)) {
760
+ result[group] = value;
761
+ } else {
762
+ result[group] = value;
763
+ }
764
+ }
765
+ return result;
766
+ }
767
+
768
+ async function main() {
769
+ try {
770
+ const [customElements, ontology, storyIndex, enhancerMetadata] = await Promise.all([
771
+ readJson(CUSTOM_ELEMENTS_PATH),
772
+ loadOntology(),
773
+ collectStoryMetadata(),
774
+ loadEnhancers()
775
+ ]);
776
+
777
+ const components = await buildComponents(customElements, ontology, storyIndex);
778
+ const primitives = buildPrimitives(ontology);
779
+ const enhancements = buildEnhancements(ontology, enhancerMetadata);
780
+ const tokens = buildTokens(ontology);
781
+
782
+ const reference = {
783
+ generatedAt: new Date().toISOString(),
784
+ sources: {
785
+ customElements: path.relative(ROOT_DIR, CUSTOM_ELEMENTS_PATH),
786
+ ontology: path.relative(ROOT_DIR, ONTOLOGY_PATH),
787
+ storiesRoot: path.relative(ROOT_DIR, STORIES_ROOT),
788
+ enhancers: path.relative(ROOT_DIR, ENHANCERS_PATH)
789
+ },
790
+ components,
791
+ primitives,
792
+ enhancements,
793
+ tokens
794
+ };
795
+
796
+ await fs.promises.mkdir(OUTPUT_DIR, { recursive: true });
797
+ await fs.promises.writeFile(OUTPUT_PATH, JSON.stringify(reference, null, 2));
798
+
799
+ console.log(`[pds-reference] Wrote ${path.relative(ROOT_DIR, OUTPUT_PATH)}`);
800
+ } catch (error) {
801
+ console.error('[pds-reference] Failed to build reference data');
802
+ console.error(error);
803
+ process.exitCode = 1;
804
+ }
805
+ }
806
+
807
+ main();