@pure-ds/core 0.3.19 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +6 -6
- package/dist/types/pds.config.d.ts +4 -8
- package/dist/types/pds.config.d.ts.map +1 -1
- package/dist/types/pds.d.ts +408 -109
- package/dist/types/public/assets/js/pds.d.ts +3 -3
- package/dist/types/public/assets/js/pds.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-jsonform.d.ts +1 -0
- package/dist/types/public/assets/pds/components/pds-jsonform.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-tabstrip.d.ts +36 -5
- package/dist/types/public/assets/pds/components/pds-tabstrip.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-ontology.d.ts +186 -12
- package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-query.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/assets/js/app.js +315 -235
- package/public/assets/js/pds.js +186 -106
- package/public/assets/pds/components/pds-jsonform.js +2 -2
- package/public/assets/pds/components/pds-tabstrip.js +36 -5
- package/public/assets/pds/custom-elements.json +6 -6
- package/public/assets/pds/pds-css-complete.json +1 -1
- package/public/assets/pds/vscode-custom-data.json +4 -4
- package/src/js/pds-core/pds-enhancers.js +3 -5
- package/src/js/pds-core/pds-generator.js +152 -76
- package/src/js/pds-core/pds-ontology.js +803 -256
- package/src/js/pds-core/pds-query.js +582 -571
|
@@ -1,571 +1,582 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PDSQuery - Smart query engine for the Pure Design System
|
|
3
|
-
*
|
|
4
|
-
* Interprets natural language questions and maps them to PDS runtime data
|
|
5
|
-
* structures (tokens, components, utilities, patterns) using keyword matching,
|
|
6
|
-
* intent detection, and semantic scoring.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const query = new PDSQuery(PDS);
|
|
10
|
-
* const results = await query.search("what is the focus border color on inputs?");
|
|
11
|
-
* // Returns array of scored results with text, value, icon, category
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export class PDSQuery {
|
|
15
|
-
constructor(pds) {
|
|
16
|
-
this.pds = pds;
|
|
17
|
-
|
|
18
|
-
// Keyword dictionaries for intent detection
|
|
19
|
-
this.intents = {
|
|
20
|
-
color: ['color', 'colours', 'shade', 'tint', 'hue', 'foreground', 'background', 'text', 'fill', 'bg', 'fg'],
|
|
21
|
-
spacing: ['spacing', 'space', 'gap', 'padding', 'margin', 'distance', 'rhythm'],
|
|
22
|
-
typography: ['font', 'text', 'type', 'typography', 'heading', 'body', 'size', 'weight', 'family'],
|
|
23
|
-
border: ['border', 'outline', 'stroke', 'edge', 'frame'],
|
|
24
|
-
radius: ['radius', 'rounded', 'corner', 'curve', 'round'],
|
|
25
|
-
shadow: ['shadow', 'elevation', 'depth', 'glow', 'drop-shadow'],
|
|
26
|
-
component: ['component', 'element', 'widget'],
|
|
27
|
-
utility: ['utility', 'class', 'helper', 'css'],
|
|
28
|
-
layout: ['layout', 'container', 'grid', 'flex', 'group', 'arrange', 'organize'],
|
|
29
|
-
pattern: ['pattern', 'example', 'template', 'structure'],
|
|
30
|
-
interaction: ['hover', 'focus', 'active', 'disabled', 'pressed', 'selected', 'checked'],
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Entity/element keywords
|
|
34
|
-
this.entities = {
|
|
35
|
-
button: ['button', 'btn', 'cta'],
|
|
36
|
-
input: ['input', 'field', 'textbox', 'text-field', 'form-control'],
|
|
37
|
-
card: ['card', 'panel'],
|
|
38
|
-
badge: ['badge', 'pill', 'tag', 'chip'],
|
|
39
|
-
surface: ['surface', 'background', 'layer', 'container'],
|
|
40
|
-
icon: ['icon', 'svg', 'glyph', 'symbol'],
|
|
41
|
-
link: ['link', 'anchor', 'hyperlink'],
|
|
42
|
-
nav: ['nav', 'navigation', 'menu'],
|
|
43
|
-
modal: ['modal', 'dialog', 'popup', 'overlay'],
|
|
44
|
-
drawer: ['drawer', 'sidebar', 'panel'],
|
|
45
|
-
tab: ['tab', 'tabstrip'],
|
|
46
|
-
toast: ['toast', 'notification', 'alert', 'message'],
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Question patterns
|
|
50
|
-
this.questionWords = ['what', 'which', 'how', 'where', 'when', 'show', 'find', 'get', 'give', 'tell'];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Main search entry point
|
|
55
|
-
* @param {string} query - Natural language question
|
|
56
|
-
* @returns {Promise<Array>} Array of results with text, value, icon, category, score
|
|
57
|
-
*/
|
|
58
|
-
async search(query) {
|
|
59
|
-
if (!query || query.length < 2) return [];
|
|
60
|
-
|
|
61
|
-
const normalized = query.toLowerCase().trim();
|
|
62
|
-
const tokens = this.tokenize(normalized);
|
|
63
|
-
|
|
64
|
-
// Detect intent and entities from query
|
|
65
|
-
const context = this.analyzeQuery(tokens, normalized);
|
|
66
|
-
|
|
67
|
-
// Generate results from multiple strategies
|
|
68
|
-
const results = [];
|
|
69
|
-
|
|
70
|
-
// Strategy 1: Direct token/color queries
|
|
71
|
-
if (context.intents.has('color')) {
|
|
72
|
-
results.push(...this.queryColors(context, normalized));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Strategy 2: Utility class queries
|
|
76
|
-
if (context.intents.has('utility') || context.intents.has('border') ||
|
|
77
|
-
context.intents.has('layout') || normalized.includes('class')) {
|
|
78
|
-
results.push(...this.queryUtilities(context, normalized));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Strategy 3: Component queries
|
|
82
|
-
if (context.intents.has('component') || context.entities.size > 0) {
|
|
83
|
-
results.push(...this.queryComponents(context, normalized));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Strategy 4: Pattern/layout queries
|
|
87
|
-
if (context.intents.has('layout') || context.intents.has('pattern')) {
|
|
88
|
-
results.push(...this.queryPatterns(context, normalized));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Strategy 5: Typography queries
|
|
92
|
-
if (context.intents.has('typography')) {
|
|
93
|
-
results.push(...this.queryTypography(context, normalized));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Strategy 6: Spacing queries
|
|
97
|
-
if (context.intents.has('spacing')) {
|
|
98
|
-
results.push(...this.querySpacing(context, normalized));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Deduplicate by value, keeping highest score
|
|
102
|
-
const seen = new Map();
|
|
103
|
-
for (const result of results) {
|
|
104
|
-
const key = result.value;
|
|
105
|
-
if (!seen.has(key) || seen.get(key).score < result.score) {
|
|
106
|
-
seen.set(key, result);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Sort by score descending, limit to top 10
|
|
111
|
-
return Array.from(seen.values())
|
|
112
|
-
.sort((a, b) => b.score - a.score)
|
|
113
|
-
.slice(0, 10);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Tokenize and normalize query string
|
|
118
|
-
*/
|
|
119
|
-
tokenize(text) {
|
|
120
|
-
return text.toLowerCase()
|
|
121
|
-
.replace(/[?!.]/g, '')
|
|
122
|
-
.split(/\s+/)
|
|
123
|
-
.filter(t => t.length > 0);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Analyze query to extract intents and entities
|
|
128
|
-
*/
|
|
129
|
-
analyzeQuery(tokens, fullText) {
|
|
130
|
-
const context = {
|
|
131
|
-
intents: new Set(),
|
|
132
|
-
entities: new Set(),
|
|
133
|
-
modifiers: new Set(),
|
|
134
|
-
isQuestion: false,
|
|
135
|
-
tokens,
|
|
136
|
-
fullText
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Check if it's a question
|
|
140
|
-
context.isQuestion = this.questionWords.some(qw => tokens.includes(qw));
|
|
141
|
-
|
|
142
|
-
// Match intents
|
|
143
|
-
for (const [intent, keywords] of Object.entries(this.intents)) {
|
|
144
|
-
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
145
|
-
context.intents.add(intent);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Match entities
|
|
150
|
-
for (const [entity, keywords] of Object.entries(this.entities)) {
|
|
151
|
-
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
152
|
-
context.entities.add(entity);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Extract interaction modifiers
|
|
157
|
-
if (tokens.includes('hover') || fullText.includes('hover')) context.modifiers.add('hover');
|
|
158
|
-
if (tokens.includes('focus') || fullText.includes('focus')) context.modifiers.add('focus');
|
|
159
|
-
if (tokens.includes('active') || fullText.includes('active')) context.modifiers.add('active');
|
|
160
|
-
if (tokens.includes('disabled') || fullText.includes('disabled')) context.modifiers.add('disabled');
|
|
161
|
-
|
|
162
|
-
return context;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Query color tokens and surfaces
|
|
167
|
-
*/
|
|
168
|
-
queryColors(context, query) {
|
|
169
|
-
const results = [];
|
|
170
|
-
const compiled = this.pds.compiled;
|
|
171
|
-
|
|
172
|
-
if (!compiled?.tokens?.colors) return results;
|
|
173
|
-
|
|
174
|
-
const colors = compiled.tokens.colors;
|
|
175
|
-
const entities = Array.from(context.entities);
|
|
176
|
-
const modifiers = Array.from(context.modifiers);
|
|
177
|
-
|
|
178
|
-
// Specific color + element queries: "focus border color on inputs"
|
|
179
|
-
if (modifiers.includes('focus') && context.intents.has('border') && entities.includes('input')) {
|
|
180
|
-
results.push({
|
|
181
|
-
text: 'Focus border color: var(--color-primary-500)',
|
|
182
|
-
value: '--color-primary-500',
|
|
183
|
-
icon: 'palette',
|
|
184
|
-
category: 'Color Token',
|
|
185
|
-
score: 100,
|
|
186
|
-
cssVar: 'var(--color-primary-500)',
|
|
187
|
-
description: 'Primary color used for focus states on form inputs'
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Foreground on surface queries
|
|
192
|
-
if ((query.includes('foreground') || query.includes('text')) &&
|
|
193
|
-
(query.includes('surface') || context.entities.has('surface'))) {
|
|
194
|
-
results.push({
|
|
195
|
-
text: 'Text on surface: var(--surface-text)',
|
|
196
|
-
value: '--surface-text',
|
|
197
|
-
icon: 'palette',
|
|
198
|
-
category: 'Surface Token',
|
|
199
|
-
score: 95,
|
|
200
|
-
cssVar: 'var(--surface-text)',
|
|
201
|
-
description: 'Default text color for current surface'
|
|
202
|
-
});
|
|
203
|
-
results.push({
|
|
204
|
-
text: 'Secondary text: var(--surface-text-secondary)',
|
|
205
|
-
value: '--surface-text-secondary',
|
|
206
|
-
icon: 'palette',
|
|
207
|
-
category: 'Surface Token',
|
|
208
|
-
score: 90,
|
|
209
|
-
cssVar: 'var(--surface-text-secondary)',
|
|
210
|
-
description: 'Secondary/muted text on surface'
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Generic color scale queries
|
|
215
|
-
if (query.includes('primary') || query.includes('accent') || query.includes('secondary')) {
|
|
216
|
-
const scale = query.includes('primary') ? 'primary' :
|
|
217
|
-
query.includes('accent') ? 'accent' : 'secondary';
|
|
218
|
-
|
|
219
|
-
for (const shade of [500, 600, 700]) {
|
|
220
|
-
const varName = `--color-${scale}-${shade}`;
|
|
221
|
-
results.push({
|
|
222
|
-
text: `${scale.charAt(0).toUpperCase() + scale.slice(1)} ${shade}: var(${varName})`,
|
|
223
|
-
value: varName,
|
|
224
|
-
icon: 'palette',
|
|
225
|
-
category: 'Color Scale',
|
|
226
|
-
score: 80 - (shade - 500) / 100,
|
|
227
|
-
cssVar: `var(${varName})`,
|
|
228
|
-
description: `${scale} color scale shade ${shade}`
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Button color queries
|
|
234
|
-
if (entities.includes('button') && context.intents.has('color')) {
|
|
235
|
-
const modifier = modifiers[0];
|
|
236
|
-
if (modifier) {
|
|
237
|
-
results.push({
|
|
238
|
-
text: `Button ${modifier} fill: var(--${modifier === 'hover' ? 'primary' : 'primary'}-fill-${modifier})`,
|
|
239
|
-
value: `--primary-fill-${modifier}`,
|
|
240
|
-
icon: 'palette',
|
|
241
|
-
category: 'Interactive Token',
|
|
242
|
-
score: 92,
|
|
243
|
-
description: `Button background color in ${modifier} state`
|
|
244
|
-
});
|
|
245
|
-
} else {
|
|
246
|
-
results.push({
|
|
247
|
-
text: 'Button fill: var(--primary-fill)',
|
|
248
|
-
value: '--primary-fill',
|
|
249
|
-
icon: 'palette',
|
|
250
|
-
category: 'Interactive Token',
|
|
251
|
-
score: 88,
|
|
252
|
-
description: 'Default button background color'
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return results;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Query utility classes
|
|
262
|
-
*/
|
|
263
|
-
queryUtilities(context, query) {
|
|
264
|
-
const results = [];
|
|
265
|
-
const ontology = this.pds.ontology;
|
|
266
|
-
|
|
267
|
-
if (!ontology?.utilities) return results;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1
|
+
/**
|
|
2
|
+
* PDSQuery - Smart query engine for the Pure Design System
|
|
3
|
+
*
|
|
4
|
+
* Interprets natural language questions and maps them to PDS runtime data
|
|
5
|
+
* structures (tokens, components, utilities, patterns) using keyword matching,
|
|
6
|
+
* intent detection, and semantic scoring.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const query = new PDSQuery(PDS);
|
|
10
|
+
* const results = await query.search("what is the focus border color on inputs?");
|
|
11
|
+
* // Returns array of scored results with text, value, icon, category
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export class PDSQuery {
|
|
15
|
+
constructor(pds) {
|
|
16
|
+
this.pds = pds;
|
|
17
|
+
|
|
18
|
+
// Keyword dictionaries for intent detection
|
|
19
|
+
this.intents = {
|
|
20
|
+
color: ['color', 'colours', 'shade', 'tint', 'hue', 'foreground', 'background', 'text', 'fill', 'bg', 'fg'],
|
|
21
|
+
spacing: ['spacing', 'space', 'gap', 'padding', 'margin', 'distance', 'rhythm'],
|
|
22
|
+
typography: ['font', 'text', 'type', 'typography', 'heading', 'body', 'size', 'weight', 'family'],
|
|
23
|
+
border: ['border', 'outline', 'stroke', 'edge', 'frame'],
|
|
24
|
+
radius: ['radius', 'rounded', 'corner', 'curve', 'round'],
|
|
25
|
+
shadow: ['shadow', 'elevation', 'depth', 'glow', 'drop-shadow'],
|
|
26
|
+
component: ['component', 'element', 'widget'],
|
|
27
|
+
utility: ['utility', 'class', 'helper', 'css'],
|
|
28
|
+
layout: ['layout', 'container', 'grid', 'flex', 'group', 'arrange', 'organize'],
|
|
29
|
+
pattern: ['pattern', 'example', 'template', 'structure'],
|
|
30
|
+
interaction: ['hover', 'focus', 'active', 'disabled', 'pressed', 'selected', 'checked'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Entity/element keywords
|
|
34
|
+
this.entities = {
|
|
35
|
+
button: ['button', 'btn', 'cta'],
|
|
36
|
+
input: ['input', 'field', 'textbox', 'text-field', 'form-control'],
|
|
37
|
+
card: ['card', 'panel'],
|
|
38
|
+
badge: ['badge', 'pill', 'tag', 'chip'],
|
|
39
|
+
surface: ['surface', 'background', 'layer', 'container'],
|
|
40
|
+
icon: ['icon', 'svg', 'glyph', 'symbol'],
|
|
41
|
+
link: ['link', 'anchor', 'hyperlink'],
|
|
42
|
+
nav: ['nav', 'navigation', 'menu'],
|
|
43
|
+
modal: ['modal', 'dialog', 'popup', 'overlay'],
|
|
44
|
+
drawer: ['drawer', 'sidebar', 'panel'],
|
|
45
|
+
tab: ['tab', 'tabstrip'],
|
|
46
|
+
toast: ['toast', 'notification', 'alert', 'message'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Question patterns
|
|
50
|
+
this.questionWords = ['what', 'which', 'how', 'where', 'when', 'show', 'find', 'get', 'give', 'tell'];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Main search entry point
|
|
55
|
+
* @param {string} query - Natural language question
|
|
56
|
+
* @returns {Promise<Array>} Array of results with text, value, icon, category, score
|
|
57
|
+
*/
|
|
58
|
+
async search(query) {
|
|
59
|
+
if (!query || query.length < 2) return [];
|
|
60
|
+
|
|
61
|
+
const normalized = query.toLowerCase().trim();
|
|
62
|
+
const tokens = this.tokenize(normalized);
|
|
63
|
+
|
|
64
|
+
// Detect intent and entities from query
|
|
65
|
+
const context = this.analyzeQuery(tokens, normalized);
|
|
66
|
+
|
|
67
|
+
// Generate results from multiple strategies
|
|
68
|
+
const results = [];
|
|
69
|
+
|
|
70
|
+
// Strategy 1: Direct token/color queries
|
|
71
|
+
if (context.intents.has('color')) {
|
|
72
|
+
results.push(...this.queryColors(context, normalized));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Strategy 2: Utility class queries
|
|
76
|
+
if (context.intents.has('utility') || context.intents.has('border') ||
|
|
77
|
+
context.intents.has('layout') || normalized.includes('class')) {
|
|
78
|
+
results.push(...this.queryUtilities(context, normalized));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Strategy 3: Component queries
|
|
82
|
+
if (context.intents.has('component') || context.entities.size > 0) {
|
|
83
|
+
results.push(...this.queryComponents(context, normalized));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Strategy 4: Pattern/layout queries
|
|
87
|
+
if (context.intents.has('layout') || context.intents.has('pattern')) {
|
|
88
|
+
results.push(...this.queryPatterns(context, normalized));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Strategy 5: Typography queries
|
|
92
|
+
if (context.intents.has('typography')) {
|
|
93
|
+
results.push(...this.queryTypography(context, normalized));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Strategy 6: Spacing queries
|
|
97
|
+
if (context.intents.has('spacing')) {
|
|
98
|
+
results.push(...this.querySpacing(context, normalized));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Deduplicate by value, keeping highest score
|
|
102
|
+
const seen = new Map();
|
|
103
|
+
for (const result of results) {
|
|
104
|
+
const key = result.value;
|
|
105
|
+
if (!seen.has(key) || seen.get(key).score < result.score) {
|
|
106
|
+
seen.set(key, result);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sort by score descending, limit to top 10
|
|
111
|
+
return Array.from(seen.values())
|
|
112
|
+
.sort((a, b) => b.score - a.score)
|
|
113
|
+
.slice(0, 10);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tokenize and normalize query string
|
|
118
|
+
*/
|
|
119
|
+
tokenize(text) {
|
|
120
|
+
return text.toLowerCase()
|
|
121
|
+
.replace(/[?!.]/g, '')
|
|
122
|
+
.split(/\s+/)
|
|
123
|
+
.filter(t => t.length > 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Analyze query to extract intents and entities
|
|
128
|
+
*/
|
|
129
|
+
analyzeQuery(tokens, fullText) {
|
|
130
|
+
const context = {
|
|
131
|
+
intents: new Set(),
|
|
132
|
+
entities: new Set(),
|
|
133
|
+
modifiers: new Set(),
|
|
134
|
+
isQuestion: false,
|
|
135
|
+
tokens,
|
|
136
|
+
fullText
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Check if it's a question
|
|
140
|
+
context.isQuestion = this.questionWords.some(qw => tokens.includes(qw));
|
|
141
|
+
|
|
142
|
+
// Match intents
|
|
143
|
+
for (const [intent, keywords] of Object.entries(this.intents)) {
|
|
144
|
+
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
145
|
+
context.intents.add(intent);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Match entities
|
|
150
|
+
for (const [entity, keywords] of Object.entries(this.entities)) {
|
|
151
|
+
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
152
|
+
context.entities.add(entity);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extract interaction modifiers
|
|
157
|
+
if (tokens.includes('hover') || fullText.includes('hover')) context.modifiers.add('hover');
|
|
158
|
+
if (tokens.includes('focus') || fullText.includes('focus')) context.modifiers.add('focus');
|
|
159
|
+
if (tokens.includes('active') || fullText.includes('active')) context.modifiers.add('active');
|
|
160
|
+
if (tokens.includes('disabled') || fullText.includes('disabled')) context.modifiers.add('disabled');
|
|
161
|
+
|
|
162
|
+
return context;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Query color tokens and surfaces
|
|
167
|
+
*/
|
|
168
|
+
queryColors(context, query) {
|
|
169
|
+
const results = [];
|
|
170
|
+
const compiled = this.pds.compiled;
|
|
171
|
+
|
|
172
|
+
if (!compiled?.tokens?.colors) return results;
|
|
173
|
+
|
|
174
|
+
const colors = compiled.tokens.colors;
|
|
175
|
+
const entities = Array.from(context.entities);
|
|
176
|
+
const modifiers = Array.from(context.modifiers);
|
|
177
|
+
|
|
178
|
+
// Specific color + element queries: "focus border color on inputs"
|
|
179
|
+
if (modifiers.includes('focus') && context.intents.has('border') && entities.includes('input')) {
|
|
180
|
+
results.push({
|
|
181
|
+
text: 'Focus border color: var(--color-primary-500)',
|
|
182
|
+
value: '--color-primary-500',
|
|
183
|
+
icon: 'palette',
|
|
184
|
+
category: 'Color Token',
|
|
185
|
+
score: 100,
|
|
186
|
+
cssVar: 'var(--color-primary-500)',
|
|
187
|
+
description: 'Primary color used for focus states on form inputs'
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Foreground on surface queries
|
|
192
|
+
if ((query.includes('foreground') || query.includes('text')) &&
|
|
193
|
+
(query.includes('surface') || context.entities.has('surface'))) {
|
|
194
|
+
results.push({
|
|
195
|
+
text: 'Text on surface: var(--surface-text)',
|
|
196
|
+
value: '--surface-text',
|
|
197
|
+
icon: 'palette',
|
|
198
|
+
category: 'Surface Token',
|
|
199
|
+
score: 95,
|
|
200
|
+
cssVar: 'var(--surface-text)',
|
|
201
|
+
description: 'Default text color for current surface'
|
|
202
|
+
});
|
|
203
|
+
results.push({
|
|
204
|
+
text: 'Secondary text: var(--surface-text-secondary)',
|
|
205
|
+
value: '--surface-text-secondary',
|
|
206
|
+
icon: 'palette',
|
|
207
|
+
category: 'Surface Token',
|
|
208
|
+
score: 90,
|
|
209
|
+
cssVar: 'var(--surface-text-secondary)',
|
|
210
|
+
description: 'Secondary/muted text on surface'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generic color scale queries
|
|
215
|
+
if (query.includes('primary') || query.includes('accent') || query.includes('secondary')) {
|
|
216
|
+
const scale = query.includes('primary') ? 'primary' :
|
|
217
|
+
query.includes('accent') ? 'accent' : 'secondary';
|
|
218
|
+
|
|
219
|
+
for (const shade of [500, 600, 700]) {
|
|
220
|
+
const varName = `--color-${scale}-${shade}`;
|
|
221
|
+
results.push({
|
|
222
|
+
text: `${scale.charAt(0).toUpperCase() + scale.slice(1)} ${shade}: var(${varName})`,
|
|
223
|
+
value: varName,
|
|
224
|
+
icon: 'palette',
|
|
225
|
+
category: 'Color Scale',
|
|
226
|
+
score: 80 - (shade - 500) / 100,
|
|
227
|
+
cssVar: `var(${varName})`,
|
|
228
|
+
description: `${scale} color scale shade ${shade}`
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Button color queries
|
|
234
|
+
if (entities.includes('button') && context.intents.has('color')) {
|
|
235
|
+
const modifier = modifiers[0];
|
|
236
|
+
if (modifier) {
|
|
237
|
+
results.push({
|
|
238
|
+
text: `Button ${modifier} fill: var(--${modifier === 'hover' ? 'primary' : 'primary'}-fill-${modifier})`,
|
|
239
|
+
value: `--primary-fill-${modifier}`,
|
|
240
|
+
icon: 'palette',
|
|
241
|
+
category: 'Interactive Token',
|
|
242
|
+
score: 92,
|
|
243
|
+
description: `Button background color in ${modifier} state`
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
results.push({
|
|
247
|
+
text: 'Button fill: var(--primary-fill)',
|
|
248
|
+
value: '--primary-fill',
|
|
249
|
+
icon: 'palette',
|
|
250
|
+
category: 'Interactive Token',
|
|
251
|
+
score: 88,
|
|
252
|
+
description: 'Default button background color'
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Query utility classes
|
|
262
|
+
*/
|
|
263
|
+
queryUtilities(context, query) {
|
|
264
|
+
const results = [];
|
|
265
|
+
const ontology = this.pds.ontology;
|
|
266
|
+
|
|
267
|
+
if (!ontology?.utilities) return results;
|
|
268
|
+
|
|
269
|
+
// Flatten utilities object into array of class names
|
|
270
|
+
const utilitiesObj = ontology.utilities;
|
|
271
|
+
const utilities = [];
|
|
272
|
+
for (const category of Object.values(utilitiesObj)) {
|
|
273
|
+
if (typeof category === 'object') {
|
|
274
|
+
for (const value of Object.values(category)) {
|
|
275
|
+
if (Array.isArray(value)) {
|
|
276
|
+
utilities.push(...value);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Border utilities
|
|
283
|
+
if (context.intents.has('border')) {
|
|
284
|
+
const borderUtils = utilities.filter(u =>
|
|
285
|
+
u.includes('border') || u.includes('outline')
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
borderUtils.forEach(util => {
|
|
289
|
+
let score = 80;
|
|
290
|
+
if (query.includes('gradient') && util.includes('gradient')) score = 95;
|
|
291
|
+
if (query.includes('glow') && util.includes('glow')) score = 95;
|
|
292
|
+
|
|
293
|
+
results.push({
|
|
294
|
+
text: `${util} - Border utility class`,
|
|
295
|
+
value: util,
|
|
296
|
+
icon: 'code',
|
|
297
|
+
category: 'Utility Class',
|
|
298
|
+
score,
|
|
299
|
+
code: `<div class="${util}">...</div>`,
|
|
300
|
+
description: this.describeUtility(util)
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Layout utilities
|
|
306
|
+
if (context.intents.has('layout')) {
|
|
307
|
+
const layoutUtils = utilities.filter(u =>
|
|
308
|
+
u.includes('flex') || u.includes('grid') || u.includes('items-') ||
|
|
309
|
+
u.includes('justify-') || u.includes('gap-')
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
layoutUtils.forEach(util => {
|
|
313
|
+
results.push({
|
|
314
|
+
text: `${util} - Layout utility`,
|
|
315
|
+
value: util,
|
|
316
|
+
icon: 'layout',
|
|
317
|
+
category: 'Utility Class',
|
|
318
|
+
score: 85,
|
|
319
|
+
code: `<div class="${util}">...</div>`,
|
|
320
|
+
description: this.describeUtility(util)
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Button group utilities
|
|
326
|
+
if (query.includes('group') && context.entities.has('button')) {
|
|
327
|
+
results.push({
|
|
328
|
+
text: '.btn-group - Group buttons together',
|
|
329
|
+
value: '.btn-group',
|
|
330
|
+
icon: 'code',
|
|
331
|
+
category: 'Utility Class',
|
|
332
|
+
score: 90,
|
|
333
|
+
code: `<div class="btn-group">\n <button class="btn-primary">One</button>\n <button class="btn-primary">Two</button>\n</div>`,
|
|
334
|
+
description: 'Container for grouped buttons with connected styling'
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return results;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Query components
|
|
343
|
+
*/
|
|
344
|
+
queryComponents(context, query) {
|
|
345
|
+
const results = [];
|
|
346
|
+
const ontology = this.pds.ontology;
|
|
347
|
+
|
|
348
|
+
if (!ontology?.components && !ontology?.primitives) return results;
|
|
349
|
+
|
|
350
|
+
// Search custom components
|
|
351
|
+
if (ontology.components) {
|
|
352
|
+
ontology.components.forEach(comp => {
|
|
353
|
+
const matchScore = this.scoreMatch(query, comp.name + ' ' + comp.id);
|
|
354
|
+
if (matchScore > 50) {
|
|
355
|
+
results.push({
|
|
356
|
+
text: `<${comp.id}> - ${comp.name}`,
|
|
357
|
+
value: comp.id,
|
|
358
|
+
icon: 'brackets-curly',
|
|
359
|
+
category: 'Web Component',
|
|
360
|
+
score: matchScore,
|
|
361
|
+
code: `<${comp.id}></${comp.id}>`,
|
|
362
|
+
description: comp.description || `${comp.name} web component`
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Search primitives (native HTML elements with PDS styling)
|
|
369
|
+
if (ontology.primitives) {
|
|
370
|
+
ontology.primitives.forEach(prim => {
|
|
371
|
+
const matchScore = this.scoreMatch(query, prim.name + ' ' + prim.id);
|
|
372
|
+
if (matchScore > 50) {
|
|
373
|
+
const selector = prim.selectors?.[0] || prim.id;
|
|
374
|
+
results.push({
|
|
375
|
+
text: `${selector} - ${prim.name}`,
|
|
376
|
+
value: prim.id,
|
|
377
|
+
icon: 'tag',
|
|
378
|
+
category: 'Primitive',
|
|
379
|
+
score: matchScore - 5,
|
|
380
|
+
code: this.generatePrimitiveExample(prim),
|
|
381
|
+
description: prim.description || `${prim.name} primitive element`
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Icon-specific queries
|
|
388
|
+
if (query.includes('icon') && (query.includes('only') || query.includes('button'))) {
|
|
389
|
+
results.push({
|
|
390
|
+
text: 'Icon-only button: <button class="btn-icon">',
|
|
391
|
+
value: 'btn-icon',
|
|
392
|
+
icon: 'star',
|
|
393
|
+
category: 'Pattern',
|
|
394
|
+
score: 95,
|
|
395
|
+
code: `<button class="btn-icon btn-primary">\n <pds-icon icon="heart"></pds-icon>\n</button>`,
|
|
396
|
+
description: 'Button with only an icon, no text label'
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return results;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Query layout patterns
|
|
405
|
+
*/
|
|
406
|
+
queryPatterns(context, query) {
|
|
407
|
+
const results = [];
|
|
408
|
+
const ontology = this.pds.ontology;
|
|
409
|
+
|
|
410
|
+
if (!ontology?.layoutPatterns) return results;
|
|
411
|
+
|
|
412
|
+
ontology.layoutPatterns.forEach(pattern => {
|
|
413
|
+
const matchScore = this.scoreMatch(query, pattern.name + ' ' + pattern.id + ' ' + (pattern.description || ''));
|
|
414
|
+
if (matchScore > 50) {
|
|
415
|
+
const selector = pattern.selectors?.[0] || `.${pattern.id}`;
|
|
416
|
+
results.push({
|
|
417
|
+
text: `${pattern.name} - ${pattern.description || 'Layout pattern'}`,
|
|
418
|
+
value: pattern.id,
|
|
419
|
+
icon: 'layout',
|
|
420
|
+
category: 'Layout Pattern',
|
|
421
|
+
score: matchScore,
|
|
422
|
+
code: `<div class="${selector.replace('.', '')}">\n <!-- content -->\n</div>`,
|
|
423
|
+
description: pattern.description || pattern.name
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Container queries
|
|
429
|
+
if (query.includes('container') || query.includes('group')) {
|
|
430
|
+
results.push({
|
|
431
|
+
text: 'Card - Container for grouping content',
|
|
432
|
+
value: 'card',
|
|
433
|
+
icon: 'layout',
|
|
434
|
+
category: 'Primitive',
|
|
435
|
+
score: 88,
|
|
436
|
+
code: `<article class="card">\n <header>\n <h3>Title</h3>\n </header>\n <p>Content...</p>\n</article>`,
|
|
437
|
+
description: 'Card container with optional header, body, and footer'
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
results.push({
|
|
441
|
+
text: 'Section - Semantic container for grouping',
|
|
442
|
+
value: 'section',
|
|
443
|
+
icon: 'layout',
|
|
444
|
+
category: 'Pattern',
|
|
445
|
+
score: 85,
|
|
446
|
+
code: `<section>\n <h2>Section Title</h2>\n <!-- content -->\n</section>`,
|
|
447
|
+
description: 'Semantic section element for content grouping'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return results;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Query typography tokens
|
|
456
|
+
*/
|
|
457
|
+
queryTypography(context, query) {
|
|
458
|
+
const results = [];
|
|
459
|
+
const compiled = this.pds.compiled;
|
|
460
|
+
|
|
461
|
+
if (!compiled?.tokens?.typography) return results;
|
|
462
|
+
|
|
463
|
+
const typo = compiled.tokens.typography;
|
|
464
|
+
|
|
465
|
+
if (query.includes('heading') || query.includes('title')) {
|
|
466
|
+
results.push({
|
|
467
|
+
text: 'Heading font: var(--font-family-heading)',
|
|
468
|
+
value: '--font-family-heading',
|
|
469
|
+
icon: 'text-aa',
|
|
470
|
+
category: 'Typography Token',
|
|
471
|
+
score: 85,
|
|
472
|
+
cssVar: 'var(--font-family-heading)',
|
|
473
|
+
description: 'Font family for headings'
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (query.includes('body') || query.includes('text')) {
|
|
478
|
+
results.push({
|
|
479
|
+
text: 'Body font: var(--font-family-body)',
|
|
480
|
+
value: '--font-family-body',
|
|
481
|
+
icon: 'text-aa',
|
|
482
|
+
category: 'Typography Token',
|
|
483
|
+
score: 85,
|
|
484
|
+
cssVar: 'var(--font-family-body)',
|
|
485
|
+
description: 'Font family for body text'
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return results;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Query spacing tokens
|
|
494
|
+
*/
|
|
495
|
+
querySpacing(context, query) {
|
|
496
|
+
const results = [];
|
|
497
|
+
const compiled = this.pds.compiled;
|
|
498
|
+
|
|
499
|
+
if (!compiled?.tokens?.spacing) return results;
|
|
500
|
+
|
|
501
|
+
const spacing = compiled.tokens.spacing;
|
|
502
|
+
|
|
503
|
+
// Show common spacing values
|
|
504
|
+
for (const [key, value] of Object.entries(spacing)) {
|
|
505
|
+
if (['2', '4', '6', '8'].includes(key)) {
|
|
506
|
+
results.push({
|
|
507
|
+
text: `Spacing ${key}: var(--spacing-${key})`,
|
|
508
|
+
value: `--spacing-${key}`,
|
|
509
|
+
icon: 'ruler',
|
|
510
|
+
category: 'Spacing Token',
|
|
511
|
+
score: 75,
|
|
512
|
+
cssVar: `var(--spacing-${key})`,
|
|
513
|
+
description: `Spacing value: ${value}`
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return results;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Calculate match score between query and target text
|
|
523
|
+
*/
|
|
524
|
+
scoreMatch(query, target) {
|
|
525
|
+
const queryLower = query.toLowerCase();
|
|
526
|
+
const targetLower = target.toLowerCase();
|
|
527
|
+
|
|
528
|
+
let score = 0;
|
|
529
|
+
|
|
530
|
+
// Exact match
|
|
531
|
+
if (queryLower === targetLower) return 100;
|
|
532
|
+
|
|
533
|
+
// Contains full query
|
|
534
|
+
if (targetLower.includes(queryLower)) score += 80;
|
|
535
|
+
|
|
536
|
+
// Word overlap
|
|
537
|
+
const queryWords = this.tokenize(queryLower);
|
|
538
|
+
const targetWords = this.tokenize(targetLower);
|
|
539
|
+
const overlap = queryWords.filter(w => targetWords.includes(w)).length;
|
|
540
|
+
score += (overlap / queryWords.length) * 40;
|
|
541
|
+
|
|
542
|
+
// Starts with
|
|
543
|
+
if (targetLower.startsWith(queryLower)) score += 20;
|
|
544
|
+
|
|
545
|
+
return Math.min(100, score);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Generate example code for a primitive
|
|
550
|
+
*/
|
|
551
|
+
generatePrimitiveExample(primitive) {
|
|
552
|
+
const selector = primitive.selectors?.[0] || primitive.id;
|
|
553
|
+
|
|
554
|
+
if (selector.includes('button') || primitive.id === 'button') {
|
|
555
|
+
return '<button class="btn-primary">Click me</button>';
|
|
556
|
+
}
|
|
557
|
+
if (selector.includes('card') || primitive.id === 'card') {
|
|
558
|
+
return '<article class="card">\n <h3>Title</h3>\n <p>Content</p>\n</article>';
|
|
559
|
+
}
|
|
560
|
+
if (selector.includes('badge') || primitive.id === 'badge') {
|
|
561
|
+
return '<span class="badge">New</span>';
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return `<${selector}>Content</${selector}>`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Describe utility class purpose
|
|
569
|
+
*/
|
|
570
|
+
describeUtility(utilClass) {
|
|
571
|
+
if (utilClass.includes('border-gradient')) return 'Apply animated gradient border effect';
|
|
572
|
+
if (utilClass.includes('border-glow')) return 'Apply glowing border effect';
|
|
573
|
+
if (utilClass.includes('flex')) return 'Flexbox container utility';
|
|
574
|
+
if (utilClass.includes('grid')) return 'Grid container utility';
|
|
575
|
+
if (utilClass.includes('gap-')) return 'Set gap between flex/grid children';
|
|
576
|
+
if (utilClass.includes('items-')) return 'Align items in flex container';
|
|
577
|
+
if (utilClass.includes('justify-')) return 'Justify content in flex container';
|
|
578
|
+
if (utilClass === '.btn-group') return 'Group buttons with connected styling';
|
|
579
|
+
|
|
580
|
+
return 'Utility class for styling';
|
|
581
|
+
}
|
|
582
|
+
}
|