@pure-ds/core 0.5.61 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/types/packages/pds-configurator/src/pds-home-content.d.ts +375 -0
  2. package/dist/types/packages/pds-configurator/src/pds-home-content.d.ts.map +1 -0
  3. package/dist/types/packages/pds-configurator/src/pds-home.d.ts +2 -0
  4. package/dist/types/packages/pds-configurator/src/pds-home.d.ts.map +1 -0
  5. package/dist/types/pds.config.d.ts +2 -2
  6. package/dist/types/pds.config.d.ts.map +1 -1
  7. package/dist/types/pds.d.ts +3 -0
  8. package/dist/types/public/assets/js/pds-manager.d.ts +144 -429
  9. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  10. package/dist/types/public/assets/js/pds.d.ts +3 -4
  11. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  12. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +150 -0
  13. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -0
  14. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +2 -0
  15. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  16. package/dist/types/public/assets/pds/components/pds-richtext.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-theme.d.ts +5 -0
  18. package/dist/types/public/assets/pds/components/pds-theme.d.ts.map +1 -1
  19. package/dist/types/src/js/pds-core/pds-config.d.ts +3 -0
  20. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  21. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  22. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  23. package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
  24. package/dist/types/src/js/pds-core/pds-theme-utils.d.ts +6 -0
  25. package/dist/types/src/js/pds-core/pds-theme-utils.d.ts.map +1 -0
  26. package/dist/types/src/js/pds.d.ts.map +1 -1
  27. package/package.json +1 -4
  28. package/packages/pds-cli/bin/templates/bootstrap/pds.config.js +1 -1
  29. package/public/assets/js/app.js +106 -5636
  30. package/public/assets/js/pds-manager.js +137 -137
  31. package/public/assets/js/pds.js +7 -7
  32. package/public/assets/pds/components/pds-live-edit.js +1555 -0
  33. package/public/assets/pds/components/pds-omnibox.js +558 -369
  34. package/public/assets/pds/components/pds-richtext.js +57 -7
  35. package/public/assets/pds/components/pds-theme.js +58 -0
  36. package/readme.md +2 -2
  37. package/src/js/pds-core/pds-config.js +21 -3
  38. package/src/js/pds-core/pds-enhancers.js +61 -4
  39. package/src/js/pds-core/pds-live.js +180 -1
  40. package/src/js/pds-core/pds-ontology.js +8 -0
  41. package/src/js/pds-core/pds-theme-utils.js +33 -0
  42. package/src/js/pds.d.ts +3 -0
  43. package/src/js/pds.js +22 -0
@@ -0,0 +1,1555 @@
1
+ const PDS = globalThis.PDS;
2
+
3
+ const EDITOR_TAG = "pds-live-edit";
4
+ const STYLE_ID = "pds-live-editor-styles";
5
+ const TARGET_ATTR = "data-pds-live-target";
6
+ const DROPDOWN_CLASS = "pds-live-editor-dropdown";
7
+ const MARKER_CLASS = "pds-live-editor-marker";
8
+
9
+ const CATEGORY_ICONS = {
10
+ colors: "palette",
11
+ typography: "text-aa",
12
+ spatialRhythm: "grid-four",
13
+ shape: "circle",
14
+ layout: "grid-four",
15
+ behavior: "sparkle",
16
+ layers: "squares-four",
17
+ icons: "sparkle",
18
+ };
19
+
20
+ const QUICK_RULES = [
21
+ {
22
+ selector: "button, .btn-primary, .btn-secondary, .btn-outline, .btn-sm, .btn-xs, .btn-lg",
23
+ paths: [
24
+ "colors.primary",
25
+ "shape.radiusSize",
26
+ "spatialRhythm.buttonPadding",
27
+ "layout.buttonMinHeight",
28
+ "behavior.transitionSpeed",
29
+ ],
30
+ },
31
+ {
32
+ selector: "input, textarea, select, label",
33
+ paths: [
34
+ "colors.secondary",
35
+ "shape.radiusSize",
36
+ "spatialRhythm.inputPadding",
37
+ "layout.inputMinHeight",
38
+ "typography.fontFamilyBody",
39
+ ],
40
+ },
41
+ {
42
+ selector: ".card, .surface-base, .surface-elevated, .surface-sunken, .surface-subtle",
43
+ paths: [
44
+ "colors.background",
45
+ "shape.radiusSize",
46
+ "layers.baseShadowOpacity",
47
+ ],
48
+ },
49
+ {
50
+ selector: "nav, menu",
51
+ paths: ["colors.background", "typography.fontFamilyBody", "spatialRhythm.baseUnit"],
52
+ },
53
+ {
54
+ selector: "pds-icon",
55
+ paths: ["icons.defaultSize", "icons.weight"],
56
+ },
57
+ ];
58
+
59
+ const DEFAULT_QUICK_PATHS = [
60
+ "colors.primary",
61
+ "typography.fontFamilyBody",
62
+ "shape.radiusSize",
63
+ ];
64
+
65
+ const QUICK_STYLE_PROPERTIES = [
66
+ "background-color",
67
+ "color",
68
+ "border-color",
69
+ "border-top-color",
70
+ "border-right-color",
71
+ "border-bottom-color",
72
+ "border-left-color",
73
+ "outline-color",
74
+ "box-shadow",
75
+ "text-shadow",
76
+ "fill",
77
+ "stroke",
78
+ "font-family",
79
+ "font-size",
80
+ "font-weight",
81
+ "letter-spacing",
82
+ "line-height",
83
+ "border-radius",
84
+ "padding",
85
+ "padding-top",
86
+ "padding-right",
87
+ "padding-bottom",
88
+ "padding-left",
89
+ "gap",
90
+ "row-gap",
91
+ "column-gap",
92
+ "min-height",
93
+ "min-width",
94
+ "height",
95
+ "width",
96
+ ];
97
+
98
+ const INLINE_VAR_REGEX = /var\(\s*(--[^)\s,]+)\s*/g;
99
+ const COLOR_VALUE_REGEX = /#(?:[0-9a-f]{3,8})\b|rgba?\([^)]*\)|hsla?\([^)]*\)/gi;
100
+
101
+ let cachedTokenIndex = null;
102
+ let cachedTokenIndexMeta = null;
103
+ let colorNormalizer = null;
104
+
105
+ const GLOBAL_LAYOUT_PATHS = new Set([
106
+ "layout.maxWidth",
107
+ "layout.maxWidths",
108
+ "layout.breakpoints",
109
+ "layout.containerMaxWidth",
110
+ "layout.containerPadding",
111
+ "layout.gridColumns",
112
+ "layout.gridGutter",
113
+ "layout.densityCompact",
114
+ "layout.densityNormal",
115
+ "layout.densityComfortable",
116
+ ]);
117
+
118
+ const FORM_CONTEXT_PATHS = new Set([
119
+ "spatialRhythm.inputPadding",
120
+ "layout.inputMinHeight",
121
+ "behavior.focusRingWidth",
122
+ "behavior.focusRingOpacity",
123
+ ]);
124
+
125
+ const SURFACE_CONTEXT_PATHS = new Set([
126
+ "colors.background",
127
+ "layers.baseShadowOpacity",
128
+ "layout.baseShadowOpacity",
129
+ ]);
130
+
131
+ const DARK_MODE_PATH_MARKER = ".darkMode.";
132
+ const QUICK_EDIT_LIMIT = 4;
133
+ const DROPDOWN_VIEWPORT_PADDING = 8;
134
+
135
+ function isHoverCapable() {
136
+ if (typeof window === "undefined" || !window.matchMedia) return false;
137
+ return window.matchMedia("(hover: hover) and (pointer: fine)").matches;
138
+ }
139
+
140
+ function ensureStyles() {
141
+ if (typeof document === "undefined") return;
142
+ if (document.getElementById(STYLE_ID)) return;
143
+ const style = document.createElement("style");
144
+ style.id = STYLE_ID;
145
+ style.textContent = `
146
+ ${EDITOR_TAG} {
147
+ display: contents;
148
+ }
149
+ [${TARGET_ATTR}] {
150
+ position: relative;
151
+ }
152
+ .${DROPDOWN_CLASS} {
153
+ position: fixed;
154
+ top: var(--pds-live-edit-top, auto);
155
+ right: var(--pds-live-edit-right, auto);
156
+ bottom: var(--pds-live-edit-bottom, auto);
157
+ left: var(--pds-live-edit-left, auto);
158
+ z-index: var(--z-popover);
159
+ }
160
+ .${MARKER_CLASS} {
161
+ pointer-events: auto;
162
+ }
163
+ .context-edit {
164
+ min-width: 0;
165
+ min-height: 0;
166
+ width: var(--spacing-6);
167
+ height: var(--spacing-6);
168
+ padding: 0;
169
+ }
170
+ .${DROPDOWN_CLASS} menu {
171
+ min-width: max-content;
172
+ max-width: 350px;
173
+ }
174
+ .${DROPDOWN_CLASS} .pds-live-editor-menu {
175
+ padding: var(--spacing-1);
176
+ max-width: 350px;
177
+ }
178
+ .${DROPDOWN_CLASS} .pds-live-editor-title {
179
+ display: block;
180
+ font-size: var(--font-size-sm);
181
+ font-weight: var(--font-weight-semibold);
182
+ margin-bottom: var(--spacing-2);
183
+ }
184
+ .${DROPDOWN_CLASS} .pds-live-editor-header {
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: space-between;
188
+ gap: var(--spacing-2);
189
+ }
190
+ .${DROPDOWN_CLASS} .pds-live-editor-debug {
191
+ font-size: var(--font-size-xs);
192
+ opacity: 0.7;
193
+ white-space: pre-wrap;
194
+ margin-top: var(--spacing-2);
195
+ }
196
+ .${DROPDOWN_CLASS} .pds-live-editor-menu input[type="color"] {
197
+ width: var(--spacing-9);
198
+ height: var(--spacing-6);
199
+ max-width: var(--spacing-9);
200
+ min-width: var(--spacing-9);
201
+ padding: 0;
202
+ border-radius: var(--radius-sm);
203
+ }
204
+ `;
205
+ document.head.appendChild(style);
206
+ }
207
+
208
+ function isSelectorSupported(selector) {
209
+ if (typeof selector !== "string" || !selector.trim()) return false;
210
+ if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
211
+ try {
212
+ return CSS.supports(`selector(${selector})`);
213
+ } catch (e) {
214
+ return false;
215
+ }
216
+ }
217
+ if (typeof document === "undefined") return false;
218
+ try {
219
+ document.querySelector(selector);
220
+ return true;
221
+ } catch (e) {
222
+ return false;
223
+ }
224
+ }
225
+
226
+ function collectSelectors() {
227
+ const ontology = PDS?.ontology;
228
+ const selectors = new Set();
229
+ if (!ontology) return { selector: "", list: [] };
230
+
231
+ const addSelector = (selector) => {
232
+ if (typeof selector !== "string" || !selector.trim()) return;
233
+ if (isSelectorSupported(selector)) selectors.add(selector);
234
+ };
235
+
236
+ const addSelectorList = (list) => {
237
+ (list || []).forEach((selector) => addSelector(selector));
238
+ };
239
+
240
+ const sections = [ontology.primitives, ontology.components, ontology.layoutPatterns];
241
+ sections.forEach((items) => {
242
+ if (!Array.isArray(items)) return;
243
+ items.forEach((item) => {
244
+ addSelectorList(item?.selectors || []);
245
+ });
246
+ });
247
+
248
+ Object.values(ontology.utilities || {}).forEach((group) => {
249
+ if (!group || typeof group !== "object") return;
250
+ Object.values(group).forEach((list) => addSelectorList(list));
251
+ });
252
+
253
+ (ontology.enhancements || []).forEach((enhancer) => {
254
+ addSelector(enhancer?.selector);
255
+ });
256
+
257
+ addSelector("body");
258
+ addSelector("[data-dropdown]");
259
+ addSelector("*");
260
+
261
+ return { selector: Array.from(selectors).join(", "), list: Array.from(selectors) };
262
+ }
263
+
264
+ function shallowClone(value) {
265
+ if (!value || typeof value !== "object") return value;
266
+ return Array.isArray(value) ? [...value] : { ...value };
267
+ }
268
+
269
+ function deepMerge(target = {}, source = {}) {
270
+ if (!source || typeof source !== "object") return target;
271
+ const out = Array.isArray(target) ? [...target] : { ...target };
272
+ for (const [key, value] of Object.entries(source)) {
273
+ if (value && typeof value === "object" && !Array.isArray(value)) {
274
+ out[key] = deepMerge(out[key] && typeof out[key] === "object" ? out[key] : {}, value);
275
+ } else {
276
+ out[key] = value;
277
+ }
278
+ }
279
+ return out;
280
+ }
281
+
282
+ function titleize(value) {
283
+ return String(value)
284
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
285
+ .replace(/[_-]+/g, " ")
286
+ .replace(/\s+/g, " ")
287
+ .trim()
288
+ .replace(/^./, (char) => char.toUpperCase());
289
+ }
290
+
291
+ function getValueAtPath(obj, pathSegments) {
292
+ let current = obj;
293
+ for (const segment of pathSegments) {
294
+ if (!current || typeof current !== "object") return undefined;
295
+ current = current[segment];
296
+ }
297
+ return current;
298
+ }
299
+
300
+ function setValueAtPath(target, pathSegments, value) {
301
+ let current = target;
302
+ for (let i = 0; i < pathSegments.length; i += 1) {
303
+ const segment = pathSegments[i];
304
+ if (i === pathSegments.length - 1) {
305
+ current[segment] = value;
306
+ return;
307
+ }
308
+ if (!current[segment] || typeof current[segment] !== "object") {
309
+ current[segment] = {};
310
+ }
311
+ current = current[segment];
312
+ }
313
+ }
314
+
315
+ function setValueAtJsonPath(target, jsonPath, value) {
316
+ if (!jsonPath || typeof jsonPath !== "string") return;
317
+ const parts = jsonPath.replace(/^\//, "").split("/").filter(Boolean);
318
+ if (!parts.length) return;
319
+ let current = target;
320
+ parts.forEach((segment, index) => {
321
+ if (index === parts.length - 1) {
322
+ current[segment] = value;
323
+ return;
324
+ }
325
+ if (!current[segment] || typeof current[segment] !== "object") {
326
+ current[segment] = {};
327
+ }
328
+ current = current[segment];
329
+ });
330
+ }
331
+
332
+ function normalizePaths(paths) {
333
+ const relations = PDS?.configRelations || {};
334
+ const seen = new Set();
335
+ const filtered = [];
336
+ (paths || []).forEach((path) => {
337
+ if (!relations[path]) return;
338
+ if (seen.has(path)) return;
339
+ seen.add(path);
340
+ filtered.push(path);
341
+ });
342
+ return filtered;
343
+ }
344
+
345
+ function collectRelationPathsByCategory() {
346
+ const relations = PDS?.configRelations || {};
347
+ const result = {};
348
+ Object.keys(relations).forEach((path) => {
349
+ const [category] = path.split(".");
350
+ if (!category) return;
351
+ if (!result[category]) result[category] = [];
352
+ result[category].push(path);
353
+ });
354
+ return result;
355
+ }
356
+
357
+ function collectPathsFromRelations(target) {
358
+ const relations = PDS?.configRelations || {};
359
+ const paths = [];
360
+ Object.entries(relations).forEach(([path, relation]) => {
361
+ const rules = relation?.rules || [];
362
+ if (!Array.isArray(rules) || !rules.length) return;
363
+ const matches = rules.some((rule) => {
364
+ const selectors = rule?.selectors || [];
365
+ return selectors.some((selector) => {
366
+ try {
367
+ return target.matches(selector) || Boolean(target.querySelector(selector));
368
+ } catch (e) {
369
+ return false;
370
+ }
371
+ });
372
+ });
373
+ if (matches) paths.push(path);
374
+ });
375
+ return paths;
376
+ }
377
+
378
+ function collectQuickRulePaths(target) {
379
+ return QUICK_RULES.filter((rule) => {
380
+ try {
381
+ return target.matches(rule.selector);
382
+ } catch (e) {
383
+ return false;
384
+ }
385
+ }).flatMap((rule) => rule.paths);
386
+ }
387
+
388
+ function filterPathsByContext(target, paths) {
389
+ if (!target || !paths.length) return paths;
390
+ const isGlobal = target.matches("body, main");
391
+ const isInForm = Boolean(target.closest("form, pds-form"));
392
+ const isOnSurface = Boolean(
393
+ target.closest(
394
+ ".card, .surface-base, .surface-elevated, .surface-sunken, .surface-subtle"
395
+ )
396
+ );
397
+ const theme = getActiveTheme();
398
+
399
+ return paths.filter((path) => {
400
+ if (!theme.isDark && path.includes(DARK_MODE_PATH_MARKER)) return false;
401
+ if (path.startsWith("typography.") && !isGlobal) return false;
402
+ if (GLOBAL_LAYOUT_PATHS.has(path) && !isGlobal) return false;
403
+ if (FORM_CONTEXT_PATHS.has(path) && !isInForm) return false;
404
+ if (SURFACE_CONTEXT_PATHS.has(path) && !(isOnSurface || isGlobal)) return false;
405
+ return true;
406
+ });
407
+ }
408
+
409
+ function getActiveTheme() {
410
+ if (typeof document === "undefined") return { value: "light", isDark: false };
411
+ const attr = document.documentElement?.getAttribute("data-theme");
412
+ const value = attr === "dark" ? "dark" : "light";
413
+ return { value, isDark: value === "dark" };
414
+ }
415
+
416
+ function getSpacingOffset() {
417
+ if (typeof window === "undefined" || typeof document === "undefined") return 8;
418
+ const value = window
419
+ .getComputedStyle(document.documentElement)
420
+ .getPropertyValue("--spacing-2")
421
+ .trim();
422
+ const parsed = Number.parseFloat(value);
423
+ if (Number.isNaN(parsed)) return 8;
424
+ return parsed;
425
+ }
426
+
427
+ function ensureColorNormalizer() {
428
+ if (typeof document === "undefined") return null;
429
+ if (colorNormalizer && document.contains(colorNormalizer)) return colorNormalizer;
430
+ if (!document.body) return null;
431
+ const probe = document.createElement("span");
432
+ probe.setAttribute("data-pds-color-normalizer", "");
433
+ probe.style.position = "fixed";
434
+ probe.style.left = "-9999px";
435
+ probe.style.top = "0";
436
+ probe.style.opacity = "0";
437
+ probe.style.pointerEvents = "none";
438
+ document.body.appendChild(probe);
439
+ colorNormalizer = probe;
440
+ return colorNormalizer;
441
+ }
442
+
443
+ function normalizeCssColor(value) {
444
+ if (!value || typeof window === "undefined") return null;
445
+ const probe = ensureColorNormalizer();
446
+ if (!probe) return null;
447
+ probe.style.color = "";
448
+ probe.style.color = value;
449
+ return window.getComputedStyle(probe).color || null;
450
+ }
451
+
452
+ function rgbToHex(value) {
453
+ if (!value) return null;
454
+ const match = value.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
455
+ if (!match) return null;
456
+ const [r, g, b] = match.slice(1, 4).map((num) => {
457
+ const parsed = Number.parseInt(num, 10);
458
+ if (Number.isNaN(parsed)) return 0;
459
+ return Math.max(0, Math.min(255, parsed));
460
+ });
461
+ const hex = (channel) => channel.toString(16).padStart(2, "0");
462
+ return `#${hex(r)}${hex(g)}${hex(b)}`;
463
+ }
464
+
465
+ function normalizeHexColor(value) {
466
+ if (!value) return null;
467
+ const trimmed = value.trim();
468
+ if (!trimmed.startsWith("#")) return null;
469
+ if (trimmed.length === 4) {
470
+ const [r, g, b] = trimmed.slice(1).split("");
471
+ return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
472
+ }
473
+ if (trimmed.length === 7) return trimmed.toLowerCase();
474
+ return null;
475
+ }
476
+
477
+ function toColorInputValue(value) {
478
+ if (!value) return value;
479
+ const normalizedHex = normalizeHexColor(value);
480
+ if (normalizedHex) return normalizedHex;
481
+ const normalized = normalizeCssColor(value) || value;
482
+ const hexValue = normalizeHexColor(normalized) || rgbToHex(normalized);
483
+ return hexValue || value;
484
+ }
485
+
486
+ function getCustomPropertyNames(style) {
487
+ const names = [];
488
+ if (!style) return names;
489
+ for (let i = 0; i < style.length; i += 1) {
490
+ const name = style[i];
491
+ if (name && name.startsWith("--")) names.push(name);
492
+ }
493
+ return names;
494
+ }
495
+
496
+ function makeTokenMatchers(relations) {
497
+ const matchers = [];
498
+ Object.entries(relations || {}).forEach(([path, relation]) => {
499
+ const tokens = relation?.tokens || [];
500
+ if (!Array.isArray(tokens)) return;
501
+ tokens.forEach((pattern) => {
502
+ if (!pattern || typeof pattern !== "string") return;
503
+ const escaped = pattern
504
+ .replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&")
505
+ .replace(/\*/g, ".*");
506
+ const regex = new RegExp(`^${escaped}$`);
507
+ matchers.push({ path, regex });
508
+ });
509
+ });
510
+ return matchers;
511
+ }
512
+
513
+ function addToValueMap(map, key, value) {
514
+ if (!key) return;
515
+ const entry = map.get(key);
516
+ if (entry) {
517
+ entry.add(value);
518
+ } else {
519
+ map.set(key, new Set([value]));
520
+ }
521
+ }
522
+
523
+ function getTokenIndex() {
524
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
525
+ const relations = PDS?.configRelations || {};
526
+ const relationCount = Object.keys(relations).length;
527
+ const root = document.documentElement;
528
+ if (!root) return null;
529
+ const rootStyle = window.getComputedStyle(root);
530
+ const bodyStyle = document.body ? window.getComputedStyle(document.body) : null;
531
+ const customProps = Array.from(
532
+ new Set([...
533
+ getCustomPropertyNames(rootStyle),
534
+ ...getCustomPropertyNames(bodyStyle),
535
+ ])
536
+ );
537
+ const meta = { relationCount, propCount: customProps.length };
538
+ if (cachedTokenIndex && cachedTokenIndexMeta) {
539
+ if (
540
+ cachedTokenIndexMeta.relationCount === meta.relationCount &&
541
+ cachedTokenIndexMeta.propCount === meta.propCount
542
+ ) {
543
+ return cachedTokenIndex;
544
+ }
545
+ }
546
+
547
+ const matchers = makeTokenMatchers(relations);
548
+ const varToPaths = new Map();
549
+ const valueToVars = new Map();
550
+ const colorToVars = new Map();
551
+
552
+ const getVarValue = (varName) => {
553
+ const rootValue = rootStyle.getPropertyValue(varName).trim();
554
+ if (rootValue) return rootValue;
555
+ if (!bodyStyle) return "";
556
+ return bodyStyle.getPropertyValue(varName).trim();
557
+ };
558
+
559
+ customProps.forEach((varName) => {
560
+ const varValue = getVarValue(varName);
561
+ if (varValue) {
562
+ addToValueMap(valueToVars, varValue, varName);
563
+ const normalizedColor = normalizeCssColor(varValue);
564
+ if (normalizedColor) addToValueMap(colorToVars, normalizedColor, varName);
565
+ }
566
+ matchers.forEach(({ path, regex }) => {
567
+ if (regex.test(varName)) {
568
+ addToValueMap(varToPaths, varName, path);
569
+ }
570
+ });
571
+ });
572
+
573
+ cachedTokenIndex = { varToPaths, valueToVars, colorToVars, matchers, getVarValue };
574
+ cachedTokenIndexMeta = meta;
575
+ return cachedTokenIndex;
576
+ }
577
+
578
+ function collectVarRefsFromInline(element) {
579
+ const vars = new Set();
580
+ if (!element || typeof element.getAttribute !== "function") return vars;
581
+ const styleAttr = element.getAttribute("style") || "";
582
+ if (!styleAttr) return vars;
583
+ INLINE_VAR_REGEX.lastIndex = 0;
584
+ let match = INLINE_VAR_REGEX.exec(styleAttr);
585
+ while (match) {
586
+ if (match[1]) vars.add(match[1]);
587
+ match = INLINE_VAR_REGEX.exec(styleAttr);
588
+ }
589
+ return vars;
590
+ }
591
+
592
+ function collectScanTargets(target, limit = 120) {
593
+ const nodes = [target];
594
+ if (!target || typeof target.querySelectorAll !== "function") return nodes;
595
+ const descendants = Array.from(target.querySelectorAll("*"));
596
+ if (descendants.length <= limit) return nodes.concat(descendants);
597
+ return nodes.concat(descendants.slice(0, limit));
598
+ }
599
+
600
+ function collectPathsFromComputedStyles(target) {
601
+ const index = getTokenIndex();
602
+ if (!index || !target || typeof window === "undefined") return [];
603
+ const { varToPaths, valueToVars, colorToVars, matchers, getVarValue } = index;
604
+ const varsSeen = new Set();
605
+ const varsOrdered = [];
606
+ const scanTargets = collectScanTargets(target);
607
+ const addVarName = (name) => {
608
+ if (!name || varsSeen.has(name)) return;
609
+ varsSeen.add(name);
610
+ varsOrdered.push(name);
611
+ };
612
+ const addVarSet = (set) => {
613
+ set.forEach((name) => addVarName(name));
614
+ };
615
+
616
+ scanTargets.forEach((node) => {
617
+ addVarSet(collectVarRefsFromInline(node));
618
+
619
+ let style = null;
620
+ try {
621
+ style = window.getComputedStyle(node);
622
+ } catch (e) {
623
+ style = null;
624
+ }
625
+ if (!style) return;
626
+
627
+ QUICK_STYLE_PROPERTIES.forEach((prop) => {
628
+ const value = style.getPropertyValue(prop);
629
+ if (!value) return;
630
+ const trimmed = value.trim();
631
+ if (trimmed && valueToVars.has(trimmed)) {
632
+ valueToVars.get(trimmed).forEach((varName) => addVarName(varName));
633
+ }
634
+
635
+ const colors = trimmed.match(COLOR_VALUE_REGEX) || [];
636
+ colors.forEach((color) => {
637
+ const normalized = normalizeCssColor(color) || color;
638
+ if (colorToVars.has(normalized)) {
639
+ colorToVars.get(normalized).forEach((varName) => addVarName(varName));
640
+ }
641
+ });
642
+ });
643
+ });
644
+
645
+ const paths = [];
646
+ const seenPaths = new Set();
647
+ const hints = {};
648
+ const addPath = (path) => {
649
+ if (!path || seenPaths.has(path)) return;
650
+ seenPaths.add(path);
651
+ paths.push(path);
652
+ };
653
+ const addHint = (path, varName) => {
654
+ if (!path || !varName) return;
655
+ if (!path.startsWith("colors.")) return;
656
+ if (path.includes(DARK_MODE_PATH_MARKER) && !getActiveTheme().isDark) return;
657
+ if (hints[path]) return;
658
+ const rawValue = getVarValue ? getVarValue(varName) : "";
659
+ if (!rawValue) return;
660
+ const normalized = toColorInputValue(rawValue);
661
+ hints[path] = normalized;
662
+ };
663
+ const matchVarName = (varName) => {
664
+ const direct = varToPaths.get(varName);
665
+ if (direct) {
666
+ direct.forEach((path) => {
667
+ addPath(path);
668
+ addHint(path, varName);
669
+ });
670
+ return;
671
+ }
672
+ (matchers || []).forEach(({ path, regex }) => {
673
+ if (regex.test(varName)) {
674
+ addPath(path);
675
+ addHint(path, varName);
676
+ }
677
+ });
678
+ };
679
+
680
+ varsOrdered.forEach((varName) => matchVarName(varName));
681
+
682
+ return { paths: normalizePaths(paths), hints, debug: { vars: varsOrdered, paths } };
683
+ }
684
+
685
+ function collectQuickContext(target) {
686
+ const computed = collectPathsFromComputedStyles(target);
687
+ const byComputed = computed?.paths || [];
688
+ const byRelations = collectPathsFromRelations(target);
689
+ const byQuickRules = collectQuickRulePaths(target);
690
+ const hints = computed?.hints || {};
691
+ const debug = computed?.debug || { vars: [], paths: [] };
692
+
693
+ const filtered = filterPathsByContext(target, [
694
+ ...byComputed,
695
+ ...byRelations,
696
+ ...byQuickRules,
697
+ ]);
698
+ if (!filtered.length) {
699
+ return {
700
+ paths: normalizePaths(DEFAULT_QUICK_PATHS),
701
+ hints: {},
702
+ debug: { vars: [], paths: [] },
703
+ };
704
+ }
705
+ return { paths: normalizePaths(filtered), hints, debug };
706
+ }
707
+
708
+ function collectDrawerPaths(quickPaths) {
709
+ const categories = new Set();
710
+ quickPaths.forEach((path) => categories.add(path.split(".")[0]));
711
+ const expanded = [];
712
+ const relationMap = collectRelationPathsByCategory();
713
+ categories.forEach((category) => {
714
+ const fields = relationMap[category] || [];
715
+ expanded.push(...fields);
716
+ });
717
+ return normalizePaths([...quickPaths, ...expanded]);
718
+ }
719
+
720
+ function buildSchemaFromPaths(paths, design, hints = {}) {
721
+ const schema = { type: "object", properties: {} };
722
+ const uiSchema = {};
723
+
724
+ const getStepFromValue = (value) => {
725
+ if (typeof value !== "number" || Number.isNaN(value)) return null;
726
+ const parts = String(value).split(".");
727
+ if (parts.length < 2) return 1;
728
+ const decimals = parts[1].length;
729
+ return Number(`0.${"1".padStart(decimals, "0")}`);
730
+ };
731
+
732
+ const inferRangeBounds = (path, value) => {
733
+ const hint = String(path || "").toLowerCase();
734
+ if (hint.includes("opacity")) return { min: 0, max: 1, step: 0.01 };
735
+ if (hint.includes("scale") || hint.includes("ratio")) {
736
+ return { min: 1, max: 2, step: 0.01 };
737
+ }
738
+ if (
739
+ hint.includes("size") ||
740
+ hint.includes("radius") ||
741
+ hint.includes("padding") ||
742
+ hint.includes("gap") ||
743
+ hint.includes("spacing") ||
744
+ hint.includes("width") ||
745
+ hint.includes("height") ||
746
+ hint.includes("shadow")
747
+ ) {
748
+ return { min: 0, max: 64, step: 1 };
749
+ }
750
+ if (typeof value === "number") {
751
+ if (value >= 0 && value <= 1) return { min: 0, max: 1, step: 0.01 };
752
+ const magnitude = Math.max(1, Math.abs(value));
753
+ const upper = Math.max(10, Math.ceil(magnitude * 4));
754
+ return { min: 0, max: upper, step: getStepFromValue(value) || 1 };
755
+ }
756
+ return { min: 0, max: 100, step: 1 };
757
+ };
758
+
759
+ const isColorValue = (value, path) => {
760
+ if (String(path || "").toLowerCase().startsWith("colors.")) return true;
761
+ if (typeof value !== "string") return false;
762
+ return /^#([0-9a-f]{3,8})$/i.test(value) || /^rgba?\(/i.test(value) || /^hsla?\(/i.test(value);
763
+ };
764
+
765
+ paths.forEach((path) => {
766
+ const segments = path.split(".");
767
+ const [category, ...rest] = segments;
768
+ if (!category || !rest.length) return;
769
+
770
+ let parent = schema.properties[category];
771
+ if (!parent) {
772
+ parent = { type: "object", title: titleize(category), properties: {} };
773
+ schema.properties[category] = parent;
774
+ }
775
+
776
+ let current = parent;
777
+ for (let i = 0; i < rest.length; i += 1) {
778
+ const segment = rest[i];
779
+ if (i === rest.length - 1) {
780
+ const value = getValueAtPath(design, [category, ...rest]);
781
+ const hintValue = hints[path];
782
+ const inferredType = Array.isArray(value)
783
+ ? "array"
784
+ : value === null
785
+ ? "string"
786
+ : typeof value;
787
+ const schemaType = inferredType === "number" || inferredType === "boolean"
788
+ ? inferredType
789
+ : "string";
790
+ current.properties[segment] = {
791
+ type: schemaType,
792
+ title: titleize(segment),
793
+ examples:
794
+ value !== undefined && value !== null
795
+ ? [value]
796
+ : hintValue !== undefined
797
+ ? [hintValue]
798
+ : undefined,
799
+ };
800
+
801
+ const pointer = `/${[category, ...rest].join("/")}`;
802
+ const uiEntry = {
803
+ "ui:icon": CATEGORY_ICONS[category] || "sparkle",
804
+ };
805
+
806
+ if (isColorValue(value, path)) {
807
+ uiEntry["ui:widget"] = "input-color";
808
+ } else if (schemaType === "number") {
809
+ const bounds = inferRangeBounds(path, value);
810
+ uiEntry["ui:widget"] = "input-range";
811
+ uiEntry["ui:min"] = bounds.min;
812
+ uiEntry["ui:max"] = bounds.max;
813
+ uiEntry["ui:step"] = bounds.step;
814
+ }
815
+
816
+ uiSchema[pointer] = uiEntry;
817
+ return;
818
+ }
819
+
820
+ if (!current.properties[segment]) {
821
+ current.properties[segment] = { type: "object", properties: {} };
822
+ }
823
+ current = current.properties[segment];
824
+ }
825
+ });
826
+
827
+ return { schema, uiSchema };
828
+ }
829
+
830
+ async function getGeneratorClass() {
831
+ if (!PDS?.getGenerator) return null;
832
+ try {
833
+ return await PDS.getGenerator();
834
+ } catch (e) {
835
+ return null;
836
+ }
837
+ }
838
+
839
+ async function applyDesignPatch(patch) {
840
+ if (!patch || typeof patch !== "object") return;
841
+ const Generator = await getGeneratorClass();
842
+ const generator = Generator?.instance;
843
+ if (!generator || !generator.options) return;
844
+
845
+ const presetKeyMatches = (key, compareTo) => {
846
+ if (!key || !compareTo) return false;
847
+ return slugifyPreset(key) === slugifyPreset(compareTo);
848
+ };
849
+
850
+ const slugifyPreset = (value) =>
851
+ String(value || "")
852
+ .toLowerCase()
853
+ .replace(/&/g, " and ")
854
+ .replace(/[^a-z0-9]+/g, "-")
855
+ .replace(/^-+|-+$/g, "");
856
+
857
+ const resolvePresetBase = (presetId) => {
858
+ const presets = PDS?.presets || {};
859
+ if (!presetId) return { id: null, preset: null };
860
+ if (presets[presetId]) {
861
+ return { id: presetId, preset: presets[presetId] };
862
+ }
863
+ const presetKeys = Object.keys(presets || {});
864
+ const matchedKey = presetKeys.find((key) => presetKeyMatches(key, presetId));
865
+ if (matchedKey) {
866
+ return { id: matchedKey, preset: presets[matchedKey] };
867
+ }
868
+ const found = Object.values(presets).find((preset) => {
869
+ const name = preset?.name || preset?.id || "";
870
+ return presetKeyMatches(name, presetId);
871
+ });
872
+ if (found) {
873
+ const foundId = found.id || found.name || presetId;
874
+ return { id: foundId, preset: found };
875
+ }
876
+ return { id: presetId, preset: null };
877
+ };
878
+
879
+ const currentOptions = generator.options;
880
+ let storedConfig = null;
881
+ if (typeof window !== "undefined" && window.localStorage) {
882
+ try {
883
+ const raw = window.localStorage.getItem("pure-ds-config");
884
+ if (raw) {
885
+ const parsed = JSON.parse(raw);
886
+ if (parsed && ("preset" in parsed || "design" in parsed)) {
887
+ storedConfig = parsed;
888
+ }
889
+ }
890
+ } catch (e) {
891
+ storedConfig = null;
892
+ }
893
+ }
894
+
895
+ const storedPreset = storedConfig?.preset;
896
+ const hasStoredPreset = Boolean(storedPreset);
897
+ const storedOverrides =
898
+ storedConfig && storedConfig.design && typeof storedConfig.design === "object"
899
+ ? storedConfig.design
900
+ : {};
901
+ let presetId = storedPreset || currentOptions.preset || PDS?.currentConfig?.preset || null;
902
+ const inferredPreset = currentOptions.design?.id || currentOptions.design?.name || null;
903
+ if (!presetId && inferredPreset && !hasStoredPreset) {
904
+ const inferredMatch = resolvePresetBase(inferredPreset);
905
+ if (inferredMatch?.preset) presetId = inferredMatch.id;
906
+ }
907
+ if (String(presetId || "").toLowerCase() === "default" && inferredPreset && !hasStoredPreset) {
908
+ const inferredMatch = resolvePresetBase(inferredPreset);
909
+ if (inferredMatch?.preset) presetId = inferredMatch.id;
910
+ }
911
+
912
+ const resolvedPreset = resolvePresetBase(presetId);
913
+ const resolvedPresetId = resolvedPreset.id || presetId || null;
914
+ const presetBase = resolvedPreset.preset || null;
915
+
916
+ const baseDesign = presetBase
917
+ ? deepMerge(shallowClone(presetBase), storedOverrides)
918
+ : shallowClone(currentOptions.design || {});
919
+ const nextDesign = deepMerge(shallowClone(baseDesign), patch);
920
+ const nextOptions = { ...currentOptions, design: nextDesign };
921
+ if (resolvedPresetId) nextOptions.preset = resolvedPresetId;
922
+
923
+ const nextGenerator = new Generator(nextOptions);
924
+ if (PDS?.applyStyles) {
925
+ await PDS.applyStyles(nextGenerator);
926
+ }
927
+
928
+ if (PDS) {
929
+ try {
930
+ PDS.currentConfig = Object.freeze({
931
+ ...(PDS.currentConfig || {}),
932
+ design: structuredClone(nextDesign),
933
+ preset: resolvedPresetId || PDS.currentConfig?.preset,
934
+ });
935
+ } catch (e) {
936
+ PDS.currentConfig = {
937
+ ...(PDS.currentConfig || {}),
938
+ design: nextDesign,
939
+ preset: resolvedPresetId || PDS.currentConfig?.preset,
940
+ };
941
+ }
942
+
943
+ try {
944
+ const event = new CustomEvent("design-updated", {
945
+ detail: { config: nextDesign },
946
+ });
947
+ PDS.dispatchEvent(event);
948
+ } catch (e) {}
949
+ }
950
+
951
+ if (typeof window !== "undefined" && window.localStorage) {
952
+ try {
953
+ const nextStored = {
954
+ preset: resolvedPresetId || null,
955
+ design: shallowClone(nextDesign),
956
+ };
957
+ window.localStorage.setItem("pure-ds-config", JSON.stringify(nextStored));
958
+ } catch (e) {}
959
+ }
960
+ }
961
+
962
+ function getStoredConfig() {
963
+ if (typeof window === "undefined" || !window.localStorage) return null;
964
+ try {
965
+ const raw = window.localStorage.getItem("pure-ds-config");
966
+ if (!raw) return null;
967
+ const parsed = JSON.parse(raw);
968
+ if (parsed && ("preset" in parsed || "design" in parsed)) return parsed;
969
+ } catch (e) {
970
+ return null;
971
+ }
972
+ return null;
973
+ }
974
+
975
+ function setStoredConfig(nextConfig) {
976
+ if (typeof window === "undefined" || !window.localStorage) return;
977
+ try {
978
+ window.localStorage.setItem("pure-ds-config", JSON.stringify(nextConfig));
979
+ } catch (e) {}
980
+ }
981
+
982
+ function getPresetOptions() {
983
+ const presets = PDS?.presets || {};
984
+ return Object.values(presets)
985
+ .map((preset) => ({
986
+ id: preset?.id || preset?.name,
987
+ name: preset?.name || preset?.id || "Unnamed",
988
+ }))
989
+ .filter((preset) => preset.id)
990
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)));
991
+ }
992
+
993
+ function getActivePresetId() {
994
+ const stored = getStoredConfig();
995
+ return stored?.preset || PDS?.currentConfig?.preset || PDS?.currentPreset || null;
996
+ }
997
+
998
+ async function applyPresetSelection(presetId) {
999
+ if (!presetId) return;
1000
+ setStoredConfig({
1001
+ preset: presetId,
1002
+ design: {},
1003
+ });
1004
+ await applyDesignPatch({});
1005
+ }
1006
+
1007
+ function setFormSchemas(form, schema, uiSchema, design) {
1008
+ form.jsonSchema = schema;
1009
+ form.uiSchema = uiSchema;
1010
+ form.values = shallowClone(design);
1011
+ }
1012
+
1013
+ async function buildForm(paths, design, onChange, hints = {}) {
1014
+ const { schema, uiSchema } = buildSchemaFromPaths(paths, design, hints);
1015
+ const form = document.createElement("pds-form");
1016
+ form.setAttribute("hide-actions", "");
1017
+ form.options = {
1018
+ layouts: {
1019
+ arrays: "compact",
1020
+ },
1021
+ enhancements: {
1022
+ rangeOutput: true,
1023
+ },
1024
+ };
1025
+ form.addEventListener("pw:value-change", onChange);
1026
+ const values = shallowClone(design || {});
1027
+ Object.entries(hints || {}).forEach(([path, hintValue]) => {
1028
+ const segments = path.split(".");
1029
+ const currentValue = getValueAtPath(values, segments);
1030
+ if (currentValue === undefined || currentValue === null) {
1031
+ setValueAtPath(values, segments, hintValue);
1032
+ }
1033
+ });
1034
+ setFormSchemas(form, schema, uiSchema, values);
1035
+
1036
+ if (!customElements.get("pds-form")) {
1037
+ customElements.whenDefined("pds-form").then(() => {
1038
+ setFormSchemas(form, schema, uiSchema, values);
1039
+ });
1040
+ }
1041
+
1042
+ return form;
1043
+ }
1044
+
1045
+ class PdsLiveEdit extends HTMLElement {
1046
+ constructor() {
1047
+ super();
1048
+ this._boundMouseOver = this._handleMouseOver.bind(this);
1049
+ this._boundMouseOut = this._handleMouseOut.bind(this);
1050
+ this._boundMouseMove = this._handleMouseMove.bind(this);
1051
+ this._boundReposition = this._repositionDropdown.bind(this);
1052
+ this._activeTarget = null;
1053
+ this._activeDropdown = null;
1054
+ this._holdOpen = false;
1055
+ this._closeTimer = null;
1056
+ this._drawer = null;
1057
+ this._pendingPatch = null;
1058
+ this._applyTimer = null;
1059
+ this._selectors = null;
1060
+ this._lastPointer = null;
1061
+ this._boundDocPointer = this._handleDocumentPointer.bind(this);
1062
+ this._boundDocKeydown = this._handleDocumentKeydown.bind(this);
1063
+ this._connected = false;
1064
+ }
1065
+
1066
+ connectedCallback() {
1067
+ if (this._connected) return;
1068
+ if (PdsLiveEdit._activeInstance && PdsLiveEdit._activeInstance !== this) {
1069
+ PdsLiveEdit._activeInstance._teardown();
1070
+ }
1071
+ PdsLiveEdit._activeInstance = this;
1072
+ this._connected = true;
1073
+ if (!isHoverCapable()) return;
1074
+
1075
+ ensureStyles();
1076
+ this._selectors = collectSelectors();
1077
+ document.addEventListener("mouseover", this._boundMouseOver, true);
1078
+ document.addEventListener("mouseout", this._boundMouseOut, true);
1079
+ document.addEventListener("mousemove", this._boundMouseMove, true);
1080
+ }
1081
+
1082
+ disconnectedCallback() {
1083
+ this._teardown();
1084
+ }
1085
+
1086
+ _teardown() {
1087
+ if (this._connected) {
1088
+ document.removeEventListener("mouseover", this._boundMouseOver, true);
1089
+ document.removeEventListener("mouseout", this._boundMouseOut, true);
1090
+ document.removeEventListener("mousemove", this._boundMouseMove, true);
1091
+ }
1092
+ this._removeRepositionListeners();
1093
+ this._clearCloseTimer();
1094
+ this._removeActiveUI();
1095
+ this._connected = false;
1096
+ if (PdsLiveEdit._activeInstance === this) {
1097
+ PdsLiveEdit._activeInstance = null;
1098
+ }
1099
+ }
1100
+
1101
+ _handleMouseOver(event) {
1102
+ if (!event?.target || !(event.target instanceof Element)) return;
1103
+ this._clearCloseTimer();
1104
+ if (this._activeDropdown && this._activeDropdown.contains(event.target)) return;
1105
+ const target = this._findEditableTarget(event.target);
1106
+ if (!target || target === this._activeTarget) return;
1107
+
1108
+ this._removeActiveUI();
1109
+ this._showForTarget(target);
1110
+ }
1111
+
1112
+ _handleMouseOut(event) {
1113
+ if (!this._activeTarget) return;
1114
+ }
1115
+
1116
+ _findEditableTarget(node) {
1117
+ const tag = node.tagName?.toLowerCase?.();
1118
+ if (tag && ["html", "head", "meta", "link", "style", "script", "title"].includes(tag)) {
1119
+ return null;
1120
+ }
1121
+ if (!this._selectors?.selector) return null;
1122
+ if (node.closest(EDITOR_TAG)) return null;
1123
+ if (node.closest(`.${DROPDOWN_CLASS}`)) return null;
1124
+ if (node.closest("pds-drawer")) return null;
1125
+
1126
+ try {
1127
+ return node.closest(this._selectors.selector);
1128
+ } catch (e) {
1129
+ return null;
1130
+ }
1131
+ }
1132
+
1133
+ _showForTarget(target) {
1134
+ const quickContext = collectQuickContext(target);
1135
+ const quickPaths = quickContext.paths;
1136
+ if (!quickPaths.length) return;
1137
+
1138
+ this._holdOpen = true;
1139
+
1140
+ target.setAttribute(TARGET_ATTR, "true");
1141
+ const dropdown = this._buildDropdown(
1142
+ target,
1143
+ quickPaths,
1144
+ quickContext.hints,
1145
+ quickContext.debug
1146
+ );
1147
+ document.body.appendChild(dropdown);
1148
+ this._positionDropdown(target, dropdown);
1149
+ this._addRepositionListeners();
1150
+ this._addDocumentListeners();
1151
+
1152
+ this._activeTarget = target;
1153
+ this._activeDropdown = dropdown;
1154
+ }
1155
+
1156
+ _removeActiveUI() {
1157
+ this._clearCloseTimer();
1158
+ this._removeRepositionListeners();
1159
+ this._removeDocumentListeners();
1160
+ if (this._activeDropdown && this._activeDropdown.parentNode) {
1161
+ this._activeDropdown.parentNode.removeChild(this._activeDropdown);
1162
+ }
1163
+ if (this._activeTarget) {
1164
+ this._activeTarget.removeAttribute(TARGET_ATTR);
1165
+ }
1166
+ this._activeTarget = null;
1167
+ this._activeDropdown = null;
1168
+ this._holdOpen = false;
1169
+ }
1170
+
1171
+ _addDocumentListeners() {
1172
+ if (typeof document === "undefined") return;
1173
+ document.addEventListener("pointerdown", this._boundDocPointer, true);
1174
+ document.addEventListener("keydown", this._boundDocKeydown, true);
1175
+ }
1176
+
1177
+ _removeDocumentListeners() {
1178
+ if (typeof document === "undefined") return;
1179
+ document.removeEventListener("pointerdown", this._boundDocPointer, true);
1180
+ document.removeEventListener("keydown", this._boundDocKeydown, true);
1181
+ }
1182
+
1183
+ _handleDocumentPointer(event) {
1184
+ if (!this._activeDropdown || !this._activeTarget) return;
1185
+ const target = event?.target;
1186
+ if (!(target instanceof Element)) return;
1187
+ if (this._activeDropdown.contains(target)) return;
1188
+ if (this._activeTarget.contains(target)) return;
1189
+ this._removeActiveUI();
1190
+ }
1191
+
1192
+ _handleDocumentKeydown(event) {
1193
+ if (!event) return;
1194
+ if (event.key !== "Escape") return;
1195
+ event.preventDefault();
1196
+ this._removeActiveUI();
1197
+ }
1198
+
1199
+ _scheduleClose() {
1200
+ if (typeof window === "undefined") return;
1201
+ this._clearCloseTimer();
1202
+ this._closeTimer = window.setTimeout(() => {
1203
+ if (this._holdOpen) return;
1204
+ if (this._activeDropdown && this._activeDropdown.matches(":hover")) return;
1205
+ if (this._activeTarget && this._activeTarget.matches(":hover")) return;
1206
+ if (this._isPointerWithinSafeZone()) {
1207
+ this._scheduleClose();
1208
+ return;
1209
+ }
1210
+ this._removeActiveUI();
1211
+ }, 500);
1212
+ }
1213
+
1214
+ _clearCloseTimer() {
1215
+ if (this._closeTimer) {
1216
+ clearTimeout(this._closeTimer);
1217
+ this._closeTimer = null;
1218
+ }
1219
+ }
1220
+
1221
+ _addRepositionListeners() {
1222
+ if (typeof window === "undefined") return;
1223
+ window.addEventListener("scroll", this._boundReposition, true);
1224
+ window.addEventListener("resize", this._boundReposition);
1225
+ }
1226
+
1227
+ _removeRepositionListeners() {
1228
+ if (typeof window === "undefined") return;
1229
+ window.removeEventListener("scroll", this._boundReposition, true);
1230
+ window.removeEventListener("resize", this._boundReposition);
1231
+ }
1232
+
1233
+ _repositionDropdown() {
1234
+ if (!this._activeTarget || !this._activeDropdown) return;
1235
+ if (!document.contains(this._activeTarget)) {
1236
+ this._removeActiveUI();
1237
+ return;
1238
+ }
1239
+ this._positionDropdown(this._activeTarget, this._activeDropdown);
1240
+ }
1241
+
1242
+ _positionDropdown(target, dropdown) {
1243
+ if (!target || !dropdown) return;
1244
+ const rect = target.getBoundingClientRect();
1245
+ const spacing = getSpacingOffset();
1246
+ const width = Math.max(dropdown.offsetWidth || 0, 160);
1247
+ const height = Math.max(dropdown.offsetHeight || 0, 120);
1248
+ const spaceRight = Math.max(0, window.innerWidth - rect.right);
1249
+ const spaceLeft = Math.max(0, rect.left);
1250
+ const spaceBelow = Math.max(0, window.innerHeight - rect.bottom);
1251
+ const spaceAbove = Math.max(0, rect.top);
1252
+
1253
+ const alignRight = spaceRight >= width || spaceRight >= spaceLeft;
1254
+ const alignBottom = spaceBelow >= height || spaceBelow >= spaceAbove;
1255
+
1256
+ const rightOffset = Math.max(0, window.innerWidth - rect.right);
1257
+ const bottomOffset = Math.max(0, window.innerHeight - rect.bottom);
1258
+
1259
+ dropdown.style.setProperty(
1260
+ "--pds-live-edit-left",
1261
+ alignRight ? `${rect.left + spacing}px` : "auto"
1262
+ );
1263
+ dropdown.style.setProperty(
1264
+ "--pds-live-edit-right",
1265
+ alignRight ? "auto" : `${rightOffset + spacing}px`
1266
+ );
1267
+ dropdown.style.setProperty(
1268
+ "--pds-live-edit-top",
1269
+ alignBottom ? `${rect.top + spacing}px` : "auto"
1270
+ );
1271
+ dropdown.style.setProperty(
1272
+ "--pds-live-edit-bottom",
1273
+ alignBottom ? "auto" : `${bottomOffset + spacing}px`
1274
+ );
1275
+
1276
+ const adjusted = dropdown.getBoundingClientRect();
1277
+ let shiftX = 0;
1278
+ let shiftY = 0;
1279
+ if (adjusted.left < DROPDOWN_VIEWPORT_PADDING) {
1280
+ shiftX = DROPDOWN_VIEWPORT_PADDING - adjusted.left;
1281
+ } else if (adjusted.right > window.innerWidth - DROPDOWN_VIEWPORT_PADDING) {
1282
+ shiftX = window.innerWidth - DROPDOWN_VIEWPORT_PADDING - adjusted.right;
1283
+ }
1284
+ if (adjusted.top < DROPDOWN_VIEWPORT_PADDING) {
1285
+ shiftY = DROPDOWN_VIEWPORT_PADDING - adjusted.top;
1286
+ } else if (adjusted.bottom > window.innerHeight - DROPDOWN_VIEWPORT_PADDING) {
1287
+ shiftY = window.innerHeight - DROPDOWN_VIEWPORT_PADDING - adjusted.bottom;
1288
+ }
1289
+ if (shiftX || shiftY) {
1290
+ const currentLeft = parseFloat(dropdown.style.getPropertyValue("--pds-live-edit-left"));
1291
+ const currentTop = parseFloat(dropdown.style.getPropertyValue("--pds-live-edit-top"));
1292
+ const currentRight = parseFloat(dropdown.style.getPropertyValue("--pds-live-edit-right"));
1293
+ const currentBottom = parseFloat(dropdown.style.getPropertyValue("--pds-live-edit-bottom"));
1294
+ if (!Number.isNaN(currentLeft)) {
1295
+ dropdown.style.setProperty("--pds-live-edit-left", `${currentLeft + shiftX}px`);
1296
+ } else if (!Number.isNaN(currentRight)) {
1297
+ dropdown.style.setProperty("--pds-live-edit-right", `${currentRight - shiftX}px`);
1298
+ }
1299
+ if (!Number.isNaN(currentTop)) {
1300
+ dropdown.style.setProperty("--pds-live-edit-top", `${currentTop + shiftY}px`);
1301
+ } else if (!Number.isNaN(currentBottom)) {
1302
+ dropdown.style.setProperty("--pds-live-edit-bottom", `${currentBottom - shiftY}px`);
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ _handleMouseMove(event) {
1308
+ if (!event) return;
1309
+ this._lastPointer = { x: event.clientX, y: event.clientY };
1310
+ }
1311
+
1312
+ _isPointerWithinSafeZone() {
1313
+ if (!this._lastPointer || !this._activeTarget || !this._activeDropdown) return false;
1314
+ const targetRect = this._activeTarget.getBoundingClientRect();
1315
+ const dropdownRect = this._activeDropdown.getBoundingClientRect();
1316
+ const padding = 12;
1317
+ const left = Math.min(targetRect.left, dropdownRect.left) - padding;
1318
+ const right = Math.max(targetRect.right, dropdownRect.right) + padding;
1319
+ const top = Math.min(targetRect.top, dropdownRect.top) - padding;
1320
+ const bottom = Math.max(targetRect.bottom, dropdownRect.bottom) + padding;
1321
+ const { x, y } = this._lastPointer;
1322
+ return x >= left && x <= right && y >= top && y <= bottom;
1323
+ }
1324
+
1325
+ _buildDropdown(target, quickPaths, hints, debug) {
1326
+ const nav = document.createElement("nav");
1327
+ nav.className = DROPDOWN_CLASS;
1328
+ nav.setAttribute("data-dropdown", "");
1329
+ nav.setAttribute("data-direction", "auto");
1330
+ nav.setAttribute("data-mode", "auto");
1331
+
1332
+ const button = document.createElement("button");
1333
+ button.className = `context-edit btn-primary btn-xs icon-only ${MARKER_CLASS}`;
1334
+ button.setAttribute("type", "button");
1335
+ button.setAttribute("data-direction", "auto");
1336
+ button.setAttribute("aria-label", "Edit design settings");
1337
+
1338
+ const icon = document.createElement("pds-icon");
1339
+ icon.setAttribute("icon", "pencil");
1340
+ icon.setAttribute("size", "sm");
1341
+ button.appendChild(icon);
1342
+
1343
+ const menu = document.createElement("menu");
1344
+ const quickItem = document.createElement("li");
1345
+ quickItem.className = "pds-live-editor-menu";
1346
+
1347
+ const header = document.createElement("div");
1348
+ header.className = "pds-live-editor-header";
1349
+
1350
+ const title = document.createElement("span");
1351
+ title.className = "pds-live-editor-title";
1352
+ title.textContent = "Quick edit";
1353
+ header.appendChild(title);
1354
+
1355
+ const openButton = document.createElement("button");
1356
+ openButton.className = "btn-outline btn-xs icon-only";
1357
+ openButton.setAttribute("type", "button");
1358
+ openButton.setAttribute("aria-label", "More settings");
1359
+ const openIcon = document.createElement("pds-icon");
1360
+ openIcon.setAttribute("icon", "gear");
1361
+ openIcon.setAttribute("size", "sm");
1362
+ openButton.appendChild(openIcon);
1363
+ openButton.addEventListener("click", (event) => {
1364
+ event.preventDefault();
1365
+ event.stopPropagation();
1366
+ this._openDrawer(target, quickPaths);
1367
+ });
1368
+ header.appendChild(openButton);
1369
+
1370
+ quickItem.appendChild(header);
1371
+
1372
+ const design = shallowClone(PDS?.currentConfig?.design || {});
1373
+ const formContainer = document.createElement("div");
1374
+ quickItem.appendChild(formContainer);
1375
+
1376
+ menu.appendChild(quickItem);
1377
+
1378
+ nav.appendChild(button);
1379
+ nav.appendChild(menu);
1380
+
1381
+ const limitedPaths = quickPaths.slice(0, QUICK_EDIT_LIMIT);
1382
+ this._renderQuickForm(formContainer, limitedPaths, design, hints);
1383
+
1384
+ if (debug && (debug.vars?.length || debug.paths?.length)) {
1385
+ const debugBlock = document.createElement("div");
1386
+ debugBlock.className = "pds-live-editor-debug";
1387
+ const debugVars = (debug.vars || []).slice(0, 8).join(", ");
1388
+ const debugPaths = (debug.paths || []).slice(0, 8).join(", ");
1389
+ debugBlock.textContent = `vars: ${debugVars}\npaths: ${debugPaths}`;
1390
+ quickItem.appendChild(debugBlock);
1391
+ }
1392
+
1393
+ return nav;
1394
+ }
1395
+
1396
+ async _renderQuickForm(container, paths, design, hints) {
1397
+ container.replaceChildren();
1398
+ const form = await buildForm(paths, design, (event) =>
1399
+ this._handleValueChange(event),
1400
+ hints
1401
+ );
1402
+ container.appendChild(form);
1403
+ }
1404
+
1405
+ async _openDrawer(target, quickPaths) {
1406
+ if (!this._drawer) {
1407
+ this._drawer = document.createElement("pds-drawer");
1408
+ this._drawer.setAttribute("position", "right");
1409
+ this._drawer.setAttribute("show-close", "");
1410
+ this.appendChild(this._drawer);
1411
+ }
1412
+
1413
+ if (!customElements.get("pds-drawer")) {
1414
+ await customElements.whenDefined("pds-drawer");
1415
+ }
1416
+
1417
+ const header = document.createElement("div");
1418
+ header.setAttribute("slot", "drawer-header");
1419
+ header.className = "flex items-center justify-between";
1420
+ header.textContent = "Design settings";
1421
+
1422
+ const content = document.createElement("div");
1423
+ content.setAttribute("slot", "drawer-content");
1424
+ content.className = "stack-md";
1425
+
1426
+ const presetCard = document.createElement("section");
1427
+ presetCard.className = "card surface-elevated stack-sm";
1428
+
1429
+ const presetTitle = document.createElement("h4");
1430
+ presetTitle.textContent = "Preset";
1431
+ presetCard.appendChild(presetTitle);
1432
+
1433
+ const presetLabel = document.createElement("label");
1434
+ presetLabel.className = "stack-xs";
1435
+
1436
+ const presetText = document.createElement("span");
1437
+ presetText.textContent = "Choose a base style";
1438
+ presetLabel.appendChild(presetText);
1439
+
1440
+ const presetSelect = document.createElement("select");
1441
+ const presetOptions = getPresetOptions();
1442
+ const activePreset = getActivePresetId();
1443
+
1444
+ presetOptions.forEach((preset) => {
1445
+ const option = document.createElement("option");
1446
+ option.value = preset.id;
1447
+ option.textContent = preset.name;
1448
+ if (String(preset.id) === String(activePreset)) {
1449
+ option.selected = true;
1450
+ }
1451
+ presetSelect.appendChild(option);
1452
+ });
1453
+
1454
+ presetSelect.addEventListener("change", async (event) => {
1455
+ const nextPreset = event.target?.value;
1456
+ await applyPresetSelection(nextPreset);
1457
+ });
1458
+
1459
+ presetLabel.appendChild(presetSelect);
1460
+ presetCard.appendChild(presetLabel);
1461
+
1462
+ const themeCard = document.createElement("section");
1463
+ themeCard.className = "card surface-elevated stack-sm";
1464
+
1465
+ const themeTitle = document.createElement("h4");
1466
+ themeTitle.textContent = "Theme";
1467
+ themeCard.appendChild(themeTitle);
1468
+
1469
+ const themeToggle = document.createElement("pds-theme");
1470
+ themeCard.appendChild(themeToggle);
1471
+
1472
+ const searchCard = document.createElement("section");
1473
+ searchCard.className = "card surface-elevated stack-sm";
1474
+
1475
+ const searchTitle = document.createElement("h4");
1476
+ searchTitle.textContent = "Search PDS";
1477
+ searchCard.appendChild(searchTitle);
1478
+
1479
+ const omnibox = document.createElement("pds-omnibox");
1480
+ omnibox.setAttribute("placeholder", "Search tokens, utilities, components...");
1481
+ omnibox.settings = {
1482
+ iconHandler: (item) => {
1483
+ return item.icon ? `<pds-icon icon="${item.icon}"></pds-icon>` : null;
1484
+ },
1485
+ categories: {
1486
+ Query: {
1487
+ trigger: (options) => options.search.length >= 2,
1488
+ getItems: async (options) => {
1489
+ const query = (options.search || "").trim();
1490
+ if (!query) return [];
1491
+ try {
1492
+ const results = await PDS.query(query);
1493
+ return (results || []).map((result) => ({
1494
+ text: result.text,
1495
+ id: result.value,
1496
+ icon: result.icon || "magnifying-glass",
1497
+ category: result.category,
1498
+ code: result.code,
1499
+ }));
1500
+ } catch (error) {
1501
+ console.warn("Omnibox query failed:", error);
1502
+ return [];
1503
+ }
1504
+ },
1505
+ action: async (options) => {
1506
+ if (options?.code && navigator.clipboard) {
1507
+ await navigator.clipboard.writeText(options.code);
1508
+ await PDS.toast("Copied token to clipboard", { type: "success" });
1509
+ }
1510
+ },
1511
+ },
1512
+ },
1513
+ };
1514
+ searchCard.appendChild(omnibox);
1515
+
1516
+ content.appendChild(presetCard);
1517
+ content.appendChild(themeCard);
1518
+ content.appendChild(searchCard);
1519
+
1520
+ this._drawer.replaceChildren(header, content);
1521
+
1522
+ if (typeof this._drawer.openDrawer === "function") {
1523
+ this._drawer.openDrawer();
1524
+ } else {
1525
+ this._drawer.setAttribute("open", "");
1526
+ }
1527
+ }
1528
+
1529
+ _handleValueChange(event) {
1530
+ const form = event?.currentTarget;
1531
+ if (!form || typeof form.getValuesFlat !== "function") return;
1532
+ const flatValues = form.getValuesFlat();
1533
+ const patch = {};
1534
+ Object.entries(flatValues || {}).forEach(([path, value]) => {
1535
+ setValueAtJsonPath(patch, path, value);
1536
+ });
1537
+ this._schedulePatch(patch);
1538
+ }
1539
+
1540
+ _schedulePatch(patch) {
1541
+ this._pendingPatch = this._pendingPatch
1542
+ ? deepMerge(this._pendingPatch, patch)
1543
+ : patch;
1544
+
1545
+ if (this._applyTimer) return;
1546
+ this._applyTimer = window.setTimeout(async () => {
1547
+ const nextPatch = this._pendingPatch;
1548
+ this._pendingPatch = null;
1549
+ this._applyTimer = null;
1550
+ await applyDesignPatch(nextPatch);
1551
+ }, 50);
1552
+ }
1553
+ }
1554
+
1555
+ customElements.define(EDITOR_TAG, PdsLiveEdit);