@pure-ds/storybook 0.4.17 → 0.4.19
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/.storybook/addons/html-preview/Panel.jsx +21 -21
- package/.storybook/addons/html-preview/preview.js +4 -5
- package/.storybook/manager.js +337 -49
- package/.storybook/preview-head.html +2 -2
- package/.storybook/preview.js +2 -2
- package/README.md +2 -2
- package/dist/pds-reference.json +1915 -261
- package/package.json +2 -2
- package/public/assets/css/app.css +2 -2
- package/public/assets/js/app.js +41 -22
- package/public/assets/js/pds.js +60 -41
- package/public/assets/pds/components/{pds-jsonform.js → pds-form.js} +536 -45
- package/public/assets/pds/custom-elements.json +8 -8
- package/public/assets/pds/vscode-custom-data.json +63 -63
- package/scripts/build-pds-reference.mjs +112 -38
- package/scripts/generate-stories.js +2 -2
- package/src/js/common/ask.js +48 -21
- package/src/js/pds-configurator/pds-config-form.js +9 -9
- package/src/js/pds-configurator/pds-demo.js +2 -2
- package/src/js/pds-core/pds-config.js +14 -14
- package/src/js/pds-core/pds-generator.js +25 -12
- package/src/js/pds-core/pds-ontology.js +5 -5
- package/src/js/pds.d.ts +2 -2
- package/stories/GettingStarted.stories.js +3 -0
- package/stories/WhatIsPDS.stories.js +3 -0
- package/stories/components/PdsForm.stories.js +4356 -0
- package/stories/components/{PdsJsonformUiSchema.md → PdsFormUiSchema.md} +2 -2
- package/stories/foundations/Spacing.stories.js +5 -5
- package/stories/layout/LayoutOverview.stories.js +13 -11
- package/stories/primitives/{Forms.stories.js → FormElements.stories.js} +3 -3
- package/stories/primitives/HtmlFormElements.stories.js +128 -0
- package/stories/primitives/{FormGroups.stories.js → HtmlFormGroups.stories.js} +70 -21
- package/stories/utils/PdsAsk.stories.js +14 -13
- package/stories/components/PdsJsonform.stories.js +0 -2007
- /package/src/{pds-core → node-api}/pds-api.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { useChannel } from '@storybook/manager-api';
|
|
3
3
|
import { IconButton } from '@storybook/components';
|
|
4
4
|
import { EVENTS } from './constants.js';
|
|
@@ -109,10 +109,10 @@ const CheckIcon = () => (
|
|
|
109
109
|
);
|
|
110
110
|
|
|
111
111
|
export const Panel = ({ active }) => {
|
|
112
|
-
const [source, setSource] = useState({ markup: '',
|
|
112
|
+
const [source, setSource] = useState({ markup: '', forms: [] });
|
|
113
113
|
const [copied, setCopied] = useState(false);
|
|
114
114
|
const [highlightedMarkup, setHighlightedMarkup] = useState('');
|
|
115
|
-
const [
|
|
115
|
+
const [highlightedforms, setHighlightedforms] = useState([]);
|
|
116
116
|
const shikiRef = useRef(null);
|
|
117
117
|
|
|
118
118
|
// Get Storybook theme to detect light/dark mode
|
|
@@ -161,10 +161,10 @@ export const Panel = ({ active }) => {
|
|
|
161
161
|
}
|
|
162
162
|
}, [source.markup, shikiTheme]);
|
|
163
163
|
|
|
164
|
-
// Highlight
|
|
164
|
+
// Highlight forms schemas when source or theme changes
|
|
165
165
|
useEffect(() => {
|
|
166
|
-
if (!source.
|
|
167
|
-
|
|
166
|
+
if (!source.forms || source.forms.length === 0) {
|
|
167
|
+
setHighlightedforms([]);
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -181,9 +181,9 @@ export const Panel = ({ active }) => {
|
|
|
181
181
|
return `<pre><code>${escapeHtml(code)}</code></pre>`;
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
const
|
|
184
|
+
const processforms = async () => {
|
|
185
185
|
const highlighted = await Promise.all(
|
|
186
|
-
source.
|
|
186
|
+
source.forms.map(async (form, index) => ({
|
|
187
187
|
id: form.id ?? index,
|
|
188
188
|
label: form.label,
|
|
189
189
|
jsonSchema: await highlightJsonCode(form.jsonSchema),
|
|
@@ -191,34 +191,34 @@ export const Panel = ({ active }) => {
|
|
|
191
191
|
options: await highlightJsonCode(form.options)
|
|
192
192
|
}))
|
|
193
193
|
);
|
|
194
|
-
|
|
194
|
+
setHighlightedforms(highlighted);
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
}, [source.
|
|
197
|
+
processforms();
|
|
198
|
+
}, [source.forms, shikiTheme]);
|
|
199
199
|
|
|
200
200
|
useChannel({
|
|
201
201
|
[EVENTS.UPDATE_HTML]: (payload) => {
|
|
202
202
|
if (typeof payload === 'string') {
|
|
203
|
-
setSource({ markup: payload || '',
|
|
203
|
+
setSource({ markup: payload || '', forms: [] });
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
if (payload && typeof payload === 'object') {
|
|
208
208
|
setSource({
|
|
209
209
|
markup: payload.markup || '',
|
|
210
|
-
|
|
210
|
+
forms: Array.isArray(payload.forms) ? payload.forms : []
|
|
211
211
|
});
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
setSource({ markup: '',
|
|
215
|
+
setSource({ markup: '', forms: [] });
|
|
216
216
|
}
|
|
217
217
|
});
|
|
218
218
|
|
|
219
219
|
// Request HTML update when panel becomes active
|
|
220
220
|
React.useEffect(() => {
|
|
221
|
-
if (active && !source.markup && source.
|
|
221
|
+
if (active && !source.markup && source.forms.length === 0) {
|
|
222
222
|
// Trigger a re-extraction by emitting a request event
|
|
223
223
|
// The decorator will pick this up on the next render cycle
|
|
224
224
|
const container = document.querySelector('#storybook-root');
|
|
@@ -230,7 +230,7 @@ export const Panel = ({ active }) => {
|
|
|
230
230
|
}, 100);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
-
}, [active, source.markup, source.
|
|
233
|
+
}, [active, source.markup, source.forms.length]);
|
|
234
234
|
|
|
235
235
|
const copyToClipboard = useCallback(async () => {
|
|
236
236
|
try {
|
|
@@ -244,9 +244,9 @@ export const Panel = ({ active }) => {
|
|
|
244
244
|
}, [source.markup]);
|
|
245
245
|
|
|
246
246
|
const hasMarkup = Boolean(source.markup);
|
|
247
|
-
const
|
|
247
|
+
const hasforms = source.forms.length > 0;
|
|
248
248
|
|
|
249
|
-
if (!hasMarkup && !
|
|
249
|
+
if (!hasMarkup && !hasforms) {
|
|
250
250
|
return (
|
|
251
251
|
<Container>
|
|
252
252
|
<EmptyState>
|
|
@@ -266,10 +266,10 @@ export const Panel = ({ active }) => {
|
|
|
266
266
|
</SectionWrapper>
|
|
267
267
|
)}
|
|
268
268
|
|
|
269
|
-
{
|
|
269
|
+
{hasforms && source.forms.map((sourceForm, index) => {
|
|
270
270
|
const key = sourceForm.id ?? index;
|
|
271
|
-
const highlightedForm =
|
|
272
|
-
const heading = source.
|
|
271
|
+
const highlightedForm = highlightedforms[index];
|
|
272
|
+
const heading = source.forms.length > 1 ? (sourceForm.label || `Form ${index + 1}`) : 'pds-form';
|
|
273
273
|
|
|
274
274
|
return (
|
|
275
275
|
<SectionWrapper key={key}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from '@storybook/preview-api';
|
|
1
|
+
import { useEffect } from '@storybook/preview-api';
|
|
2
2
|
import { addons } from '@storybook/preview-api';
|
|
3
3
|
import { EVENTS } from './constants.js';
|
|
4
4
|
import { render as litRender } from 'lit';
|
|
@@ -166,13 +166,12 @@ export const withHTMLExtractor = (storyFn, context) => {
|
|
|
166
166
|
html = formatHTML(extractHTML(container));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
const forms = Array.from(container.querySelectorAll('pds-
|
|
170
|
-
const jsonForms = forms
|
|
169
|
+
const forms = Array.from(container.querySelectorAll('pds-form'))
|
|
171
170
|
.map((form, index) => {
|
|
172
171
|
const label =
|
|
173
172
|
form.getAttribute?.('id') ||
|
|
174
173
|
form.getAttribute?.('name') ||
|
|
175
|
-
(
|
|
174
|
+
(Array.from(container.querySelectorAll('pds-form')).length > 1 ? `Form ${index + 1}` : 'Form');
|
|
176
175
|
|
|
177
176
|
const jsonSchema = serializeForDisplay(form.jsonSchema);
|
|
178
177
|
const uiSchema = serializeForDisplay(form.uiSchema);
|
|
@@ -190,7 +189,7 @@ export const withHTMLExtractor = (storyFn, context) => {
|
|
|
190
189
|
|
|
191
190
|
channel.emit(EVENTS.UPDATE_HTML, {
|
|
192
191
|
markup: html || '',
|
|
193
|
-
|
|
192
|
+
forms
|
|
194
193
|
});
|
|
195
194
|
}
|
|
196
195
|
};
|
package/.storybook/manager.js
CHANGED
|
@@ -25,6 +25,67 @@ let categoryIndex = new Map();
|
|
|
25
25
|
/** @type {Map<string, Set<string>>} Tag to items that have that tag */
|
|
26
26
|
let tagIndex = new Map();
|
|
27
27
|
|
|
28
|
+
/** @type {Set<string>} All searchable words from ontology (for prefix matching) */
|
|
29
|
+
let allWords = new Set();
|
|
30
|
+
|
|
31
|
+
/** @type {Map<string, string[]>} Story ID to its pds.tags (extracted from story parameters) */
|
|
32
|
+
let storyTagsIndex = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build story tags index from the storyIndex in pds-reference.json
|
|
36
|
+
* This is populated during loadOntology() from the pre-built reference data
|
|
37
|
+
*/
|
|
38
|
+
function buildStoryTagsIndex() {
|
|
39
|
+
if (!referenceData?.storyIndex) return;
|
|
40
|
+
|
|
41
|
+
let tagCount = 0;
|
|
42
|
+
|
|
43
|
+
// Helper to convert title to Storybook ID format
|
|
44
|
+
// "About PDS/What Is PDS" → "about-pds-what-is-pds"
|
|
45
|
+
const titleToId = (title) => {
|
|
46
|
+
return title
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9/\s-]/g, '')
|
|
49
|
+
.replace(/[\s/]+/g, '-')
|
|
50
|
+
.replace(/-+/g, '-')
|
|
51
|
+
.replace(/^-|-$/g, '');
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
Object.entries(referenceData.storyIndex).forEach(([slug, entry]) => {
|
|
55
|
+
if (!Array.isArray(entry.tags) || entry.tags.length === 0) return;
|
|
56
|
+
|
|
57
|
+
const normalizedTags = entry.tags.map(t => String(t).toLowerCase());
|
|
58
|
+
|
|
59
|
+
// Compute the Storybook component ID from the title
|
|
60
|
+
const componentId = entry.storyTitle ? titleToId(entry.storyTitle) : slug;
|
|
61
|
+
|
|
62
|
+
// Index by multiple possible ID formats
|
|
63
|
+
const idsToIndex = [
|
|
64
|
+
slug.toLowerCase(),
|
|
65
|
+
componentId,
|
|
66
|
+
// Also add story variant IDs if we have story names
|
|
67
|
+
...(entry.stories || []).map(s => s.id?.toLowerCase()).filter(Boolean)
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
idsToIndex.forEach(id => {
|
|
71
|
+
if (id) {
|
|
72
|
+
storyTagsIndex.set(id, normalizedTags);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Add tags to allWords for prefix matching
|
|
77
|
+
normalizedTags.forEach(tag => {
|
|
78
|
+
allWords.add(tag);
|
|
79
|
+
// Add individual words from multi-word tags
|
|
80
|
+
tag.split(/\s+/).forEach(word => {
|
|
81
|
+
if (word.length >= 2) allWords.add(word);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
tagCount += normalizedTags.length;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
28
89
|
/**
|
|
29
90
|
* Load ontology data from pds-reference.json
|
|
30
91
|
* This file is generated by build-pds-reference.mjs from the SSoT (pds-ontology.js)
|
|
@@ -38,16 +99,11 @@ async function loadOntology() {
|
|
|
38
99
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
39
100
|
referenceData = await response.json();
|
|
40
101
|
buildIndices();
|
|
41
|
-
|
|
42
|
-
searchRelations: Object.keys(referenceData.ontologyData?.searchRelations || {}).length,
|
|
43
|
-
primitives: referenceData.ontologyData?.primitives?.length || 0,
|
|
44
|
-
components: referenceData.ontologyData?.components?.length || 0,
|
|
45
|
-
enhancements: referenceData.ontologyData?.enhancements?.length || 0
|
|
46
|
-
});
|
|
102
|
+
buildStoryTagsIndex();
|
|
47
103
|
return referenceData;
|
|
48
104
|
} catch (err) {
|
|
49
105
|
console.warn('PDS Ontology: Failed to load pds-reference.json', err);
|
|
50
|
-
referenceData = { components: {}, ontologyData: {}, enhancements: [], tokens: {} };
|
|
106
|
+
referenceData = { components: {}, ontologyData: {}, enhancements: [], tokens: {}, storyIndex: {} };
|
|
51
107
|
return referenceData;
|
|
52
108
|
}
|
|
53
109
|
}
|
|
@@ -202,13 +258,122 @@ function buildIndices() {
|
|
|
202
258
|
});
|
|
203
259
|
});
|
|
204
260
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
262
|
+
// 6. Build allWords set for prefix matching
|
|
263
|
+
// Collect ALL words from all indices for comprehensive prefix search
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
relationIndex.forEach((related, key) => {
|
|
266
|
+
allWords.add(key);
|
|
267
|
+
related.forEach(r => allWords.add(r));
|
|
268
|
+
});
|
|
269
|
+
categoryIndex.forEach((members, cat) => {
|
|
270
|
+
allWords.add(cat);
|
|
271
|
+
members.forEach(m => allWords.add(m));
|
|
272
|
+
});
|
|
273
|
+
tagIndex.forEach((items, tag) => {
|
|
274
|
+
allWords.add(tag);
|
|
275
|
+
items.forEach(i => allWords.add(i));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Add words from components
|
|
279
|
+
Object.values(referenceData.components || {}).forEach(comp => {
|
|
280
|
+
if (comp.tag) allWords.add(comp.tag.toLowerCase());
|
|
281
|
+
if (comp.displayName) allWords.add(comp.displayName.toLowerCase());
|
|
282
|
+
(comp.pdsTags || comp.ontology?.tags || []).forEach(t => allWords.add(t.toLowerCase()));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Add words from primitives
|
|
286
|
+
(od.primitives || []).forEach(p => {
|
|
287
|
+
if (p.id) allWords.add(p.id.toLowerCase());
|
|
288
|
+
if (p.name) allWords.add(p.name.toLowerCase());
|
|
289
|
+
(p.tags || []).forEach(t => allWords.add(t.toLowerCase()));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Add words from enhancements
|
|
293
|
+
(od.enhancements || []).forEach(e => {
|
|
294
|
+
if (e.id) allWords.add(e.id.toLowerCase());
|
|
295
|
+
if (e.name) allWords.add(e.name.toLowerCase());
|
|
296
|
+
if (e.selector) allWords.add(e.selector.replace(/[[\]]/g, '').toLowerCase());
|
|
297
|
+
(e.tags || []).forEach(t => allWords.add(t.toLowerCase()));
|
|
209
298
|
});
|
|
210
299
|
}
|
|
211
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Normalize a word to its singular form (simple English rules)
|
|
303
|
+
* Also returns the plural form so both directions match
|
|
304
|
+
* @param {string} word - Word to normalize
|
|
305
|
+
* @returns {string[]} - Array of [singular, plural] forms
|
|
306
|
+
*/
|
|
307
|
+
function normalizePlural(word) {
|
|
308
|
+
const w = word.toLowerCase();
|
|
309
|
+
const forms = new Set([w]);
|
|
310
|
+
|
|
311
|
+
// Common irregular plurals
|
|
312
|
+
const irregulars = {
|
|
313
|
+
'children': 'child', 'child': 'children',
|
|
314
|
+
'people': 'person', 'person': 'people',
|
|
315
|
+
'men': 'man', 'man': 'men',
|
|
316
|
+
'women': 'woman', 'woman': 'women',
|
|
317
|
+
'mice': 'mouse', 'mouse': 'mice',
|
|
318
|
+
'feet': 'foot', 'foot': 'feet',
|
|
319
|
+
'teeth': 'tooth', 'tooth': 'teeth',
|
|
320
|
+
'geese': 'goose', 'goose': 'geese',
|
|
321
|
+
'indices': 'index', 'index': 'indices',
|
|
322
|
+
'matrices': 'matrix', 'matrix': 'matrices',
|
|
323
|
+
'vertices': 'vertex', 'vertex': 'vertices',
|
|
324
|
+
'data': 'datum', 'datum': 'data',
|
|
325
|
+
'media': 'medium', 'medium': 'media'
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (irregulars[w]) {
|
|
329
|
+
forms.add(irregulars[w]);
|
|
330
|
+
return [...forms];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// -ies → -y (e.g., utilities → utility)
|
|
334
|
+
if (w.endsWith('ies')) {
|
|
335
|
+
forms.add(w.slice(0, -3) + 'y');
|
|
336
|
+
}
|
|
337
|
+
// -y → -ies (e.g., utility → utilities)
|
|
338
|
+
if (w.endsWith('y') && !['ay', 'ey', 'oy', 'uy'].some(v => w.endsWith(v))) {
|
|
339
|
+
forms.add(w.slice(0, -1) + 'ies');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// -es → base (e.g., boxes → box, classes → class)
|
|
343
|
+
if (w.endsWith('ses') || w.endsWith('xes') || w.endsWith('zes') ||
|
|
344
|
+
w.endsWith('ches') || w.endsWith('shes')) {
|
|
345
|
+
forms.add(w.slice(0, -2));
|
|
346
|
+
}
|
|
347
|
+
// Add -es for words ending in s, x, z, ch, sh
|
|
348
|
+
if (w.endsWith('s') || w.endsWith('x') || w.endsWith('z') ||
|
|
349
|
+
w.endsWith('ch') || w.endsWith('sh')) {
|
|
350
|
+
forms.add(w + 'es');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// -ves → -f/-fe (e.g., leaves → leaf)
|
|
354
|
+
if (w.endsWith('ves')) {
|
|
355
|
+
forms.add(w.slice(0, -3) + 'f');
|
|
356
|
+
forms.add(w.slice(0, -3) + 'fe');
|
|
357
|
+
}
|
|
358
|
+
// -f/-fe → -ves
|
|
359
|
+
if (w.endsWith('f')) {
|
|
360
|
+
forms.add(w.slice(0, -1) + 'ves');
|
|
361
|
+
}
|
|
362
|
+
if (w.endsWith('fe')) {
|
|
363
|
+
forms.add(w.slice(0, -2) + 'ves');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Simple -s rule (most common)
|
|
367
|
+
if (w.endsWith('s') && w.length > 2 && !w.endsWith('ss')) {
|
|
368
|
+
forms.add(w.slice(0, -1)); // Remove trailing s
|
|
369
|
+
}
|
|
370
|
+
if (!w.endsWith('s')) {
|
|
371
|
+
forms.add(w + 's'); // Add trailing s
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return [...forms];
|
|
375
|
+
}
|
|
376
|
+
|
|
212
377
|
/**
|
|
213
378
|
* Expand a search query using ontology indices
|
|
214
379
|
* @param {string} query - User's search term
|
|
@@ -220,31 +385,69 @@ function expandQuery(query) {
|
|
|
220
385
|
|
|
221
386
|
const terms = new Set([q]);
|
|
222
387
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
388
|
+
// Add singular/plural forms of the query
|
|
389
|
+
const queryVariants = normalizePlural(q);
|
|
390
|
+
queryVariants.forEach(form => terms.add(form));
|
|
227
391
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
392
|
+
// Helper: check index with all variants of a term
|
|
393
|
+
const checkIndexWithVariants = (index, variants, addRelated = true) => {
|
|
394
|
+
variants.forEach(variant => {
|
|
395
|
+
if (index.has(variant)) {
|
|
396
|
+
terms.add(variant);
|
|
397
|
+
if (addRelated) {
|
|
398
|
+
index.get(variant).forEach(t => {
|
|
399
|
+
terms.add(t);
|
|
400
|
+
// Also add plural/singular of related terms
|
|
401
|
+
normalizePlural(t).forEach(form => terms.add(form));
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
};
|
|
232
407
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
408
|
+
// 1. Check searchRelations with all variants (primary SSoT for relationships)
|
|
409
|
+
checkIndexWithVariants(relationIndex, queryVariants);
|
|
410
|
+
|
|
411
|
+
// 2. Check categories with all variants
|
|
412
|
+
checkIndexWithVariants(categoryIndex, queryVariants);
|
|
413
|
+
|
|
414
|
+
// 3. Check tags with all variants
|
|
415
|
+
checkIndexWithVariants(tagIndex, queryVariants);
|
|
237
416
|
|
|
238
|
-
// 4. Partial matches
|
|
239
|
-
//
|
|
417
|
+
// 4. Partial/prefix matches for queries 3+ chars
|
|
418
|
+
// Find any indexed term that STARTS WITH the query (e.g., "notif" → "notification")
|
|
240
419
|
if (q.length >= 3) {
|
|
420
|
+
// Check relation keys for prefix matches
|
|
241
421
|
relationIndex.forEach((related, key) => {
|
|
242
|
-
|
|
243
|
-
const isPrefix = key.startsWith(q) || q.startsWith(key);
|
|
244
|
-
const isSuffix = key.endsWith(q) || q.endsWith(key);
|
|
245
|
-
if (isPrefix || isSuffix) {
|
|
422
|
+
if (key.startsWith(q)) {
|
|
246
423
|
terms.add(key);
|
|
247
|
-
//
|
|
424
|
+
// Also add plural/singular variants of matched key
|
|
425
|
+
normalizePlural(key).forEach(form => terms.add(form));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Check category keys for prefix matches
|
|
430
|
+
categoryIndex.forEach((members, cat) => {
|
|
431
|
+
if (cat.startsWith(q)) {
|
|
432
|
+
terms.add(cat);
|
|
433
|
+
normalizePlural(cat).forEach(form => terms.add(form));
|
|
434
|
+
}
|
|
435
|
+
// Also check category members for prefix matches
|
|
436
|
+
members.forEach(member => {
|
|
437
|
+
if (member.startsWith(q)) {
|
|
438
|
+
terms.add(member);
|
|
439
|
+
normalizePlural(member).forEach(form => terms.add(form));
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Check tag keys for prefix matches
|
|
445
|
+
tagIndex.forEach((items, tag) => {
|
|
446
|
+
if (tag.startsWith(q)) {
|
|
447
|
+
terms.add(tag);
|
|
448
|
+
normalizePlural(tag).forEach(form => terms.add(form));
|
|
449
|
+
// Also add the items that have this tag
|
|
450
|
+
items.forEach(item => terms.add(item));
|
|
248
451
|
}
|
|
249
452
|
});
|
|
250
453
|
}
|
|
@@ -257,27 +460,65 @@ function expandQuery(query) {
|
|
|
257
460
|
const name = comp.displayName?.toLowerCase();
|
|
258
461
|
const tagWithoutPrefix = tag?.replace('pds-', '');
|
|
259
462
|
|
|
260
|
-
// Match if query equals or closely matches the component
|
|
261
|
-
|
|
262
|
-
|
|
463
|
+
// Match if query equals, is prefix of, or closely matches the component
|
|
464
|
+
const matchesComponent =
|
|
465
|
+
tag === q || name === q || tagWithoutPrefix === q ||
|
|
466
|
+
tag?.startsWith(q) || name?.startsWith(q) || tagWithoutPrefix?.startsWith(q) ||
|
|
467
|
+
tag?.includes(q) || name?.includes(q);
|
|
468
|
+
|
|
469
|
+
if (matchesComponent) {
|
|
263
470
|
terms.add(tag);
|
|
264
471
|
terms.add(tagWithoutPrefix);
|
|
265
472
|
terms.add(name);
|
|
266
|
-
// Don't add pdsTags - they're too generic and cause over-matching
|
|
267
473
|
}
|
|
268
474
|
});
|
|
269
475
|
}
|
|
270
476
|
|
|
271
|
-
// 6. Enhancement selectors
|
|
477
|
+
// 6. Enhancement selectors - also check prefix matches
|
|
272
478
|
(referenceData?.enhancements || []).forEach(e => {
|
|
273
479
|
const sel = e.selector?.toLowerCase();
|
|
274
480
|
const id = e.id?.toLowerCase();
|
|
275
|
-
|
|
481
|
+
const name = e.name?.toLowerCase();
|
|
482
|
+
if (sel?.startsWith(q) || sel?.includes(q) ||
|
|
483
|
+
id?.startsWith(q) || id?.includes(q) ||
|
|
484
|
+
name?.startsWith(q) || name?.includes(q)) {
|
|
276
485
|
terms.add(sel);
|
|
277
486
|
terms.add(id);
|
|
487
|
+
terms.add(name);
|
|
278
488
|
}
|
|
279
489
|
});
|
|
280
490
|
|
|
491
|
+
// 7. Primitives - check for prefix matches in IDs and tags
|
|
492
|
+
(referenceData?.ontologyData?.primitives || []).forEach(p => {
|
|
493
|
+
const id = p.id?.toLowerCase();
|
|
494
|
+
const pTags = p.tags || [];
|
|
495
|
+
|
|
496
|
+
if (id?.startsWith(q) || id?.includes(q)) {
|
|
497
|
+
terms.add(id);
|
|
498
|
+
normalizePlural(id).forEach(form => terms.add(form));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
pTags.forEach(t => {
|
|
502
|
+
const tLower = t.toLowerCase();
|
|
503
|
+
if (tLower.startsWith(q) || tLower.includes(q)) {
|
|
504
|
+
terms.add(tLower);
|
|
505
|
+
normalizePlural(tLower).forEach(form => terms.add(form));
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// 8. Global prefix matching against ALL indexed words
|
|
511
|
+
// This catches anything we might have missed above
|
|
512
|
+
if (q.length >= 3) {
|
|
513
|
+
allWords.forEach(word => {
|
|
514
|
+
if (word.startsWith(q) || word.includes(q)) {
|
|
515
|
+
terms.add(word);
|
|
516
|
+
// Also add plural/singular variants
|
|
517
|
+
normalizePlural(word).forEach(form => terms.add(form));
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
281
522
|
return Array.from(terms).filter(Boolean);
|
|
282
523
|
}
|
|
283
524
|
|
|
@@ -291,15 +532,70 @@ function matchesOntology(item, expandedTerms) {
|
|
|
291
532
|
if (!expandedTerms.length) return true;
|
|
292
533
|
|
|
293
534
|
// Build searchable text from item properties
|
|
294
|
-
const
|
|
535
|
+
const searchableParts = [
|
|
295
536
|
item.name,
|
|
296
537
|
item.title,
|
|
297
538
|
item.id,
|
|
298
539
|
item.importPath
|
|
299
|
-
]
|
|
540
|
+
];
|
|
541
|
+
|
|
542
|
+
// Include pre-indexed pds.tags for this story (from storyTagsIndex)
|
|
543
|
+
const storyId = item.id?.toLowerCase();
|
|
544
|
+
let foundTags = false;
|
|
545
|
+
|
|
546
|
+
if (storyId) {
|
|
547
|
+
// Try exact match
|
|
548
|
+
if (storyTagsIndex.has(storyId)) {
|
|
549
|
+
searchableParts.push(...storyTagsIndex.get(storyId));
|
|
550
|
+
foundTags = true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Try parent ID (for story variants like "about-pds-what-is-pds--whatispds")
|
|
554
|
+
const parentId = storyId.replace(/--[^-]+$/, '');
|
|
555
|
+
if (parentId !== storyId && storyTagsIndex.has(parentId)) {
|
|
556
|
+
searchableParts.push(...storyTagsIndex.get(parentId));
|
|
557
|
+
foundTags = true;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Try partial match - check if any indexed ID is contained in this story ID
|
|
561
|
+
if (!foundTags) {
|
|
562
|
+
for (const [indexedId, tags] of storyTagsIndex.entries()) {
|
|
563
|
+
if (storyId.includes(indexedId) || indexedId.includes(storyId)) {
|
|
564
|
+
searchableParts.push(...tags);
|
|
565
|
+
foundTags = true;
|
|
566
|
+
break; // Found a match, no need to continue
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const searchableText = searchableParts.filter(Boolean).join(' ').toLowerCase();
|
|
573
|
+
|
|
574
|
+
// Split searchable text into words for prefix matching
|
|
575
|
+
// Include both space-separated and path-separated words (for IDs like "components-pds-icon")
|
|
576
|
+
const searchableWords = searchableText.split(/[\s\-_/]+/).filter(w => w.length > 0);
|
|
300
577
|
|
|
301
578
|
// Match if ANY expanded term is found in the searchable text
|
|
302
|
-
|
|
579
|
+
// Also check plural/singular variants of each term
|
|
580
|
+
return expandedTerms.some(term => {
|
|
581
|
+
// Direct substring match
|
|
582
|
+
if (searchableText.includes(term)) return true;
|
|
583
|
+
|
|
584
|
+
// Check plural/singular variants
|
|
585
|
+
const variants = normalizePlural(term);
|
|
586
|
+
if (variants.some(variant => searchableText.includes(variant))) return true;
|
|
587
|
+
|
|
588
|
+
// Prefix matching for terms 3+ chars (e.g., "notif" matches "notification")
|
|
589
|
+
if (term.length >= 3) {
|
|
590
|
+
if (searchableWords.some(word => word.startsWith(term))) return true;
|
|
591
|
+
// Also check variants as prefixes
|
|
592
|
+
if (variants.some(variant =>
|
|
593
|
+
variant.length >= 3 && searchableWords.some(word => word.startsWith(variant))
|
|
594
|
+
)) return true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return false;
|
|
598
|
+
});
|
|
303
599
|
}
|
|
304
600
|
|
|
305
601
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -457,9 +753,6 @@ addons.register(ADDON_ID, (api) => {
|
|
|
457
753
|
if (searchField.dataset.pdsHooked) return;
|
|
458
754
|
searchField.dataset.pdsHooked = 'true';
|
|
459
755
|
|
|
460
|
-
console.log('%c🎨 PDS Ontology Search Active', 'color: #029cfd; font-weight: bold; font-size: 14px');
|
|
461
|
-
console.log('%cSearch expands via ontology relations. Try: "text", "form", "modal"', 'color: #999');
|
|
462
|
-
|
|
463
756
|
let debounceTimer;
|
|
464
757
|
searchField.addEventListener('input', (e) => {
|
|
465
758
|
clearTimeout(debounceTimer);
|
|
@@ -524,7 +817,7 @@ addons.register(ADDON_ID, (api) => {
|
|
|
524
817
|
});
|
|
525
818
|
mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
526
819
|
|
|
527
|
-
//
|
|
820
|
+
// Public API for programmatic search
|
|
528
821
|
window.pdsOntology = {
|
|
529
822
|
search: (query) => {
|
|
530
823
|
const field = document.getElementById('storybook-explorer-searchfield');
|
|
@@ -542,12 +835,7 @@ addons.register(ADDON_ID, (api) => {
|
|
|
542
835
|
field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
543
836
|
}
|
|
544
837
|
applyFilter('');
|
|
545
|
-
}
|
|
546
|
-
data: () => referenceData,
|
|
547
|
-
relations: () => Object.fromEntries([...relationIndex].map(([k, v]) => [k, [...v]])),
|
|
548
|
-
categories: () => Object.fromEntries([...categoryIndex].map(([k, v]) => [k, [...v]])),
|
|
549
|
-
tags: () => Object.fromEntries([...tagIndex].map(([k, v]) => [k, [...v]])),
|
|
550
|
-
currentQuery: () => currentQuery
|
|
838
|
+
}
|
|
551
839
|
};
|
|
552
840
|
|
|
553
841
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
<!-- PDS runs in LIVE mode - styles are generated dynamically by Generator.applyStyles() -->
|
|
1
|
+
<!-- PDS runs in LIVE mode - styles are generated dynamically by Generator.applyStyles() -->
|
|
2
2
|
|
|
3
3
|
<!-- Source Code Pro font for code readability (like React.dev) -->
|
|
4
4
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
5
5
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
6
6
|
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600&display=swap" rel="stylesheet">
|
|
7
7
|
|
|
8
|
-
<!-- Import map for pds-
|
|
8
|
+
<!-- Import map for pds-form and other components that use #pds/lit -->
|
|
9
9
|
<script type="importmap">
|
|
10
10
|
{
|
|
11
11
|
"imports": {
|
package/.storybook/preview.js
CHANGED
|
@@ -1328,7 +1328,7 @@ const preview = {
|
|
|
1328
1328
|
'Enhancements',
|
|
1329
1329
|
['Mesh Gradients', 'Interactive States', 'Toggles', 'Dropdowns', 'Range Sliders', 'Required Fields'],
|
|
1330
1330
|
'Components',
|
|
1331
|
-
['
|
|
1331
|
+
['pds-form', 'Pds Icon', 'Pds Drawer', 'Pds Toaster', 'Pds Tabstrip', 'Pds Splitpanel', 'Pds Scrollrow', 'Pds Richtext', 'Pds Upload'],
|
|
1332
1332
|
'Reference'
|
|
1333
1333
|
],
|
|
1334
1334
|
'About PDS',
|
|
@@ -1346,7 +1346,7 @@ const preview = {
|
|
|
1346
1346
|
'Enhancements',
|
|
1347
1347
|
['Mesh Gradients', 'Interactive States', 'Toggles', 'Dropdowns', 'Range Sliders', 'Required Fields'],
|
|
1348
1348
|
'Components',
|
|
1349
|
-
['
|
|
1349
|
+
['pds-form', 'Pds Icon', 'Pds Drawer', 'Pds Toaster', 'Pds Tabstrip', 'Pds Splitpanel', 'Pds Scrollrow', 'Pds Richtext', 'Pds Upload'],
|
|
1350
1350
|
'Reference',
|
|
1351
1351
|
'*'
|
|
1352
1352
|
]
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PDS Storybook
|
|
1
|
+
# PDS Storybook
|
|
2
2
|
|
|
3
3
|
**Pure Design System** Storybook showcase with live configuration capabilities.
|
|
4
4
|
|
|
@@ -58,7 +58,7 @@ Web Components (`<pds-*>`):
|
|
|
58
58
|
- `pds-upload` - File upload with preview
|
|
59
59
|
- `pds-toaster` - Toast notifications
|
|
60
60
|
- `pds-richtext` - Rich text editor (prefers `#showdown` import map; set `format="markdown"` to submit Markdown)
|
|
61
|
-
- `pds-
|
|
61
|
+
- `pds-form` - JSON Schema forms
|
|
62
62
|
- `pds-splitpanel` - Resizable panes
|
|
63
63
|
- `pds-scrollrow` - Horizontal scroll
|
|
64
64
|
|