@pure-ds/core 0.6.8 → 0.6.10
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 +71 -28
- package/dist/types/pds.d.ts +30 -0
- package/dist/types/public/assets/js/pds-manager.d.ts +146 -429
- package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/js/pds.d.ts +3 -4
- package/dist/types/public/assets/js/pds.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +1 -169
- package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +0 -2
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-config.d.ts +1306 -13
- package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers-meta.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-live.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-start-helpers.d.ts +1 -4
- package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
- package/dist/types/src/js/pds-manager.d.ts +1 -0
- package/dist/types/src/js/pds.d.ts.map +1 -1
- package/package.json +2 -2
- package/packages/pds-cli/bin/pds-static.js +16 -1
- package/public/assets/js/app.js +21 -21
- package/public/assets/js/pds-manager.js +291 -161
- package/public/assets/js/pds.js +16 -16
- package/public/assets/pds/components/pds-form.js +124 -27
- package/public/assets/pds/components/pds-live-edit.js +1214 -104
- package/public/assets/pds/components/pds-omnibox.js +10 -18
- package/public/assets/pds/custom-elements.json +71 -28
- package/public/assets/pds/pds-css-complete.json +1 -6
- package/public/assets/pds/pds.css-data.json +5 -35
- package/src/js/pds-core/pds-config.js +822 -31
- package/src/js/pds-core/pds-enhancers-meta.js +11 -0
- package/src/js/pds-core/pds-enhancers.js +113 -5
- package/src/js/pds-core/pds-generator.js +183 -23
- package/src/js/pds-core/pds-live.js +177 -2
- package/src/js/pds-core/pds-ontology.js +6 -0
- package/src/js/pds-core/pds-start-helpers.js +14 -6
- package/src/js/pds.d.ts +30 -0
- package/src/js/pds.js +36 -60
|
@@ -137,6 +137,7 @@ const SURFACE_CONTEXT_PATHS = new Set([
|
|
|
137
137
|
const DARK_MODE_PATH_MARKER = ".darkMode.";
|
|
138
138
|
const QUICK_EDIT_LIMIT = 4;
|
|
139
139
|
const DROPDOWN_VIEWPORT_PADDING = 8;
|
|
140
|
+
const FONT_FAMILY_PATH_REGEX = /^typography\.fontFamily/i;
|
|
140
141
|
|
|
141
142
|
function isHoverCapable() {
|
|
142
143
|
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
@@ -187,14 +188,22 @@ ${EDITOR_TAG} {
|
|
|
187
188
|
.${DROPDOWN_CLASS} menu {
|
|
188
189
|
min-width: max-content;
|
|
189
190
|
max-width: 350px;
|
|
191
|
+
margin: 0;
|
|
192
|
+
padding: 0;
|
|
193
|
+
list-style: none;
|
|
194
|
+
overflow: visible;
|
|
190
195
|
}
|
|
191
196
|
.${DROPDOWN_CLASS} .pds-live-editor-menu {
|
|
197
|
+
display: block;
|
|
198
|
+
background-color: var(--color-surface-base);
|
|
192
199
|
padding: var(--spacing-1);
|
|
193
200
|
max-width: 350px;
|
|
194
201
|
padding-bottom: 0;
|
|
202
|
+
overflow: visible;
|
|
195
203
|
}
|
|
196
204
|
.${DROPDOWN_CLASS} .pds-live-editor-form-container {
|
|
197
205
|
padding-bottom: var(--spacing-2);
|
|
206
|
+
overflow: visible;
|
|
198
207
|
}
|
|
199
208
|
.${DROPDOWN_CLASS} .pds-live-editor-title {
|
|
200
209
|
display: block;
|
|
@@ -443,6 +452,33 @@ function collectQuickRulePaths(target) {
|
|
|
443
452
|
}).flatMap((rule) => rule.paths);
|
|
444
453
|
}
|
|
445
454
|
|
|
455
|
+
function isHeadingElement(target) {
|
|
456
|
+
if (!target || !(target instanceof Element)) return false;
|
|
457
|
+
const tag = target.tagName?.toLowerCase?.() || "";
|
|
458
|
+
if (/^h[1-6]$/.test(tag)) return true;
|
|
459
|
+
if (target.getAttribute("role") === "heading") return true;
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function hasMeaningfulText(target) {
|
|
464
|
+
if (!target || !(target instanceof Element)) return false;
|
|
465
|
+
const tag = target.tagName?.toLowerCase?.() || "";
|
|
466
|
+
if (["script", "style", "svg", "path", "defs", "symbol"].includes(tag)) return false;
|
|
467
|
+
const text = target.textContent || "";
|
|
468
|
+
return text.trim().length > 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function collectTypographyPathsForTarget(target) {
|
|
472
|
+
if (!target || !(target instanceof Element)) return [];
|
|
473
|
+
if (isHeadingElement(target)) {
|
|
474
|
+
return ["typography.fontFamilyHeadings"];
|
|
475
|
+
}
|
|
476
|
+
if (hasMeaningfulText(target)) {
|
|
477
|
+
return ["typography.fontFamilyBody"];
|
|
478
|
+
}
|
|
479
|
+
return [];
|
|
480
|
+
}
|
|
481
|
+
|
|
446
482
|
function pathExistsInDesign(path, design) {
|
|
447
483
|
if (!path || !design) return false;
|
|
448
484
|
const segments = path.split(".");
|
|
@@ -455,7 +491,7 @@ function pathExistsInDesign(path, design) {
|
|
|
455
491
|
return true;
|
|
456
492
|
}
|
|
457
493
|
|
|
458
|
-
function filterPathsByContext(target, paths) {
|
|
494
|
+
function filterPathsByContext(target, paths, alwaysAllow = new Set()) {
|
|
459
495
|
if (!target || !paths.length) return paths;
|
|
460
496
|
const isGlobal = target.matches("body, main");
|
|
461
497
|
const isInForm = Boolean(target.closest("form, pds-form"));
|
|
@@ -468,6 +504,7 @@ function filterPathsByContext(target, paths) {
|
|
|
468
504
|
const design = PDS?.currentConfig?.design || {};
|
|
469
505
|
|
|
470
506
|
return paths.filter((path) => {
|
|
507
|
+
if (alwaysAllow.has(path)) return true;
|
|
471
508
|
if (!theme.isDark && path.includes(DARK_MODE_PATH_MARKER)) return false;
|
|
472
509
|
if (path.startsWith("typography.") && !isGlobal) return false;
|
|
473
510
|
if (GLOBAL_LAYOUT_PATHS.has(path) && !isGlobal) return false;
|
|
@@ -560,6 +597,62 @@ function toColorInputValue(value) {
|
|
|
560
597
|
return hexValue || value;
|
|
561
598
|
}
|
|
562
599
|
|
|
600
|
+
function isColorPath(path) {
|
|
601
|
+
return String(path || "").toLowerCase().startsWith("colors.");
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function inferColorVariableCandidates(path) {
|
|
605
|
+
const normalizedPath = String(path || "").toLowerCase();
|
|
606
|
+
const key = normalizedPath.replace(/^colors\./, "").replace(/^darkmode\./, "");
|
|
607
|
+
const tail = key.split(".").pop();
|
|
608
|
+
|
|
609
|
+
const directMap = {
|
|
610
|
+
primary: ["--color-primary-500"],
|
|
611
|
+
secondary: ["--color-secondary-500", "--color-gray-500"],
|
|
612
|
+
accent: ["--color-accent-500"],
|
|
613
|
+
background: ["--color-surface-base"],
|
|
614
|
+
success: ["--color-success-500"],
|
|
615
|
+
warning: ["--color-warning-500"],
|
|
616
|
+
danger: ["--color-danger-500"],
|
|
617
|
+
info: ["--color-info-500"],
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const candidates = new Set();
|
|
621
|
+
if (tail && directMap[tail]) {
|
|
622
|
+
directMap[tail].forEach((item) => candidates.add(item));
|
|
623
|
+
}
|
|
624
|
+
if (tail) {
|
|
625
|
+
candidates.add(`--color-${tail}-500`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return Array.from(candidates);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function resolveColorValueForPath(path, value, hintValue) {
|
|
632
|
+
if (!isColorPath(path)) return null;
|
|
633
|
+
|
|
634
|
+
const fromValue = toColorInputValue(value);
|
|
635
|
+
if (normalizeHexColor(fromValue)) return fromValue;
|
|
636
|
+
|
|
637
|
+
const fromHint = toColorInputValue(hintValue);
|
|
638
|
+
if (normalizeHexColor(fromHint)) return fromHint;
|
|
639
|
+
|
|
640
|
+
if (typeof window === "undefined" || typeof document === "undefined") return null;
|
|
641
|
+
const root = document.documentElement;
|
|
642
|
+
if (!root) return null;
|
|
643
|
+
|
|
644
|
+
const style = window.getComputedStyle(root);
|
|
645
|
+
const candidates = inferColorVariableCandidates(path);
|
|
646
|
+
for (const varName of candidates) {
|
|
647
|
+
const raw = style.getPropertyValue(varName).trim();
|
|
648
|
+
if (!raw) continue;
|
|
649
|
+
const resolved = toColorInputValue(raw);
|
|
650
|
+
if (normalizeHexColor(resolved)) return resolved;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
|
|
563
656
|
function getCustomPropertyNames(style) {
|
|
564
657
|
const names = [];
|
|
565
658
|
if (!style) return names;
|
|
@@ -768,7 +861,10 @@ function collectPathsFromComputedStyles(target) {
|
|
|
768
861
|
const trimmed = value.trim();
|
|
769
862
|
|
|
770
863
|
// Extract var refs from the value (handles var() with fallbacks)
|
|
771
|
-
|
|
864
|
+
const directVarRefs = extractAllVarRefs(trimmed);
|
|
865
|
+
addVarSet(directVarRefs);
|
|
866
|
+
const hasDirectVarRefs = directVarRefs.size > 0;
|
|
867
|
+
if (hasDirectVarRefs) return;
|
|
772
868
|
|
|
773
869
|
if (trimmed && valueToVars.has(trimmed)) {
|
|
774
870
|
valueToVars.get(trimmed).forEach((varName) => addVarName(varName));
|
|
@@ -829,15 +925,18 @@ function collectQuickContext(target) {
|
|
|
829
925
|
const byComputed = computed?.paths || [];
|
|
830
926
|
const byRelations = collectPathsFromRelations(target);
|
|
831
927
|
const byQuickRules = collectQuickRulePaths(target);
|
|
928
|
+
const byTypographyContext = collectTypographyPathsForTarget(target);
|
|
832
929
|
const hints = computed?.hints || {};
|
|
833
930
|
const debug = computed?.debug || { vars: [], paths: [] };
|
|
931
|
+
const alwaysAllow = new Set(byTypographyContext);
|
|
834
932
|
|
|
835
933
|
// Prioritize quick rule paths first (selector-based), then computed/relations
|
|
836
934
|
const filtered = filterPathsByContext(target, [
|
|
935
|
+
...byTypographyContext,
|
|
837
936
|
...byQuickRules,
|
|
838
937
|
...byComputed,
|
|
839
938
|
...byRelations,
|
|
840
|
-
]);
|
|
939
|
+
], alwaysAllow);
|
|
841
940
|
if (!filtered.length) {
|
|
842
941
|
return {
|
|
843
942
|
paths: normalizePaths(DEFAULT_QUICK_PATHS),
|
|
@@ -860,6 +959,252 @@ function collectDrawerPaths(quickPaths) {
|
|
|
860
959
|
return normalizePaths([...quickPaths, ...expanded]);
|
|
861
960
|
}
|
|
862
961
|
|
|
962
|
+
function splitFontFamilyStack(value) {
|
|
963
|
+
if (typeof value !== "string") return [];
|
|
964
|
+
const input = value.trim();
|
|
965
|
+
if (!input) return [];
|
|
966
|
+
const parts = [];
|
|
967
|
+
let buffer = "";
|
|
968
|
+
let quote = null;
|
|
969
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
970
|
+
const char = input[i];
|
|
971
|
+
if (quote) {
|
|
972
|
+
buffer += char;
|
|
973
|
+
if (char === quote && input[i - 1] !== "\\") {
|
|
974
|
+
quote = null;
|
|
975
|
+
}
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (char === '"' || char === "'") {
|
|
979
|
+
quote = char;
|
|
980
|
+
buffer += char;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (char === ",") {
|
|
984
|
+
const token = buffer.trim();
|
|
985
|
+
if (token) parts.push(token);
|
|
986
|
+
buffer = "";
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
buffer += char;
|
|
990
|
+
}
|
|
991
|
+
const last = buffer.trim();
|
|
992
|
+
if (last) parts.push(last);
|
|
993
|
+
return parts;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const GENERIC_FONT_FAMILIES = new Set([
|
|
997
|
+
"serif",
|
|
998
|
+
"sans-serif",
|
|
999
|
+
"monospace",
|
|
1000
|
+
"cursive",
|
|
1001
|
+
"fantasy",
|
|
1002
|
+
"system-ui",
|
|
1003
|
+
"ui-serif",
|
|
1004
|
+
"ui-sans-serif",
|
|
1005
|
+
"ui-monospace",
|
|
1006
|
+
"ui-rounded",
|
|
1007
|
+
"emoji",
|
|
1008
|
+
"math",
|
|
1009
|
+
"fangsong",
|
|
1010
|
+
]);
|
|
1011
|
+
|
|
1012
|
+
let loadGoogleFontFnPromise = null;
|
|
1013
|
+
|
|
1014
|
+
function normalizeFontName(fontFamily) {
|
|
1015
|
+
return String(fontFamily || "")
|
|
1016
|
+
.trim()
|
|
1017
|
+
.replace(/^['"]+|['"]+$/g, "")
|
|
1018
|
+
.trim();
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function isLikelyLoadableFont(fontFamily) {
|
|
1022
|
+
const normalized = normalizeFontName(fontFamily).toLowerCase();
|
|
1023
|
+
if (!normalized) return false;
|
|
1024
|
+
return !GENERIC_FONT_FAMILIES.has(normalized);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
async function getLoadGoogleFontFn() {
|
|
1028
|
+
if (typeof PDS?.loadGoogleFont === "function") {
|
|
1029
|
+
return PDS.loadGoogleFont;
|
|
1030
|
+
}
|
|
1031
|
+
if (loadGoogleFontFnPromise) return loadGoogleFontFnPromise;
|
|
1032
|
+
loadGoogleFontFnPromise = (async () => {
|
|
1033
|
+
const candidates = [
|
|
1034
|
+
PDS?.currentConfig?.managerURL,
|
|
1035
|
+
"../core/pds-manager.js",
|
|
1036
|
+
"/assets/pds/core/pds-manager.js",
|
|
1037
|
+
].filter(Boolean);
|
|
1038
|
+
|
|
1039
|
+
const attempted = new Set();
|
|
1040
|
+
for (const candidate of candidates) {
|
|
1041
|
+
try {
|
|
1042
|
+
const resolved = new URL(candidate, import.meta.url).href;
|
|
1043
|
+
if (attempted.has(resolved)) continue;
|
|
1044
|
+
attempted.add(resolved);
|
|
1045
|
+
const mod = await import(resolved);
|
|
1046
|
+
if (typeof mod?.loadGoogleFont === "function") {
|
|
1047
|
+
return mod.loadGoogleFont;
|
|
1048
|
+
}
|
|
1049
|
+
} catch (e) {}
|
|
1050
|
+
}
|
|
1051
|
+
return null;
|
|
1052
|
+
})();
|
|
1053
|
+
return loadGoogleFontFnPromise;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function loadTypographyFontsForDesign(typography) {
|
|
1057
|
+
if (!typography || typeof typography !== "object") return;
|
|
1058
|
+
|
|
1059
|
+
const loadGoogleFont = await getLoadGoogleFontFn();
|
|
1060
|
+
if (typeof loadGoogleFont !== "function") return;
|
|
1061
|
+
|
|
1062
|
+
const families = [
|
|
1063
|
+
typography.fontFamilyHeadings,
|
|
1064
|
+
typography.fontFamilyBody,
|
|
1065
|
+
typography.fontFamilyMono,
|
|
1066
|
+
];
|
|
1067
|
+
|
|
1068
|
+
const fontNames = new Set();
|
|
1069
|
+
families.forEach((stack) => {
|
|
1070
|
+
splitFontFamilyStack(stack).forEach((item) => {
|
|
1071
|
+
const fontName = normalizeFontName(item);
|
|
1072
|
+
if (isLikelyLoadableFont(fontName)) {
|
|
1073
|
+
fontNames.add(fontName);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
await Promise.allSettled(Array.from(fontNames).map((name) => loadGoogleFont(name)));
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function getPresetFontFamilyVariations(previewFontSize = resolveFontFamilyPreviewFontSize()) {
|
|
1082
|
+
const presets = Object.values(PDS?.presets || {});
|
|
1083
|
+
const seen = new Set();
|
|
1084
|
+
const items = [];
|
|
1085
|
+
const addItem = (fontFamily) => {
|
|
1086
|
+
const normalized = String(fontFamily || "").trim().replace(/\s+/g, " ");
|
|
1087
|
+
if (!normalized || seen.has(normalized)) return;
|
|
1088
|
+
seen.add(normalized);
|
|
1089
|
+
items.push({
|
|
1090
|
+
//index: items.length,
|
|
1091
|
+
id: normalized,
|
|
1092
|
+
value: normalized,
|
|
1093
|
+
text: normalized,
|
|
1094
|
+
style: `font-family: ${normalized}; font-size: ${previewFontSize};`,
|
|
1095
|
+
});
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
presets.forEach((preset) => {
|
|
1099
|
+
const typography = preset?.typography || {};
|
|
1100
|
+
["fontFamilyHeadings", "fontFamilyBody"].forEach((key) => {
|
|
1101
|
+
const stack = typography[key];
|
|
1102
|
+
if (typeof stack !== "string" || !stack.trim()) return;
|
|
1103
|
+
|
|
1104
|
+
addItem(stack);
|
|
1105
|
+
const parts = splitFontFamilyStack(stack);
|
|
1106
|
+
parts.forEach((part) => addItem(part));
|
|
1107
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
1108
|
+
addItem(parts.slice(i).join(", "));
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
return items.sort((a, b) =>
|
|
1114
|
+
String(b?.text || "").localeCompare(String(a?.text || ""), undefined, {
|
|
1115
|
+
sensitivity: "base",
|
|
1116
|
+
})
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function resolveFontFamilyPreviewFontSize(control) {
|
|
1121
|
+
const controlInput = control?.querySelector?.(".ac-input");
|
|
1122
|
+
if (controlInput) {
|
|
1123
|
+
const fontSize = getComputedStyle(controlInput).fontSize;
|
|
1124
|
+
if (fontSize) return fontSize;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const selectors = [
|
|
1128
|
+
"[name='/typography/fontFamilyBody']",
|
|
1129
|
+
"[name='/typography/fontFamilyHeadings']",
|
|
1130
|
+
"[name='/typography/fontFamilyMono']",
|
|
1131
|
+
];
|
|
1132
|
+
|
|
1133
|
+
for (const selector of selectors) {
|
|
1134
|
+
const omnibox = document.querySelector(selector);
|
|
1135
|
+
const input = omnibox?.shadowRoot?.querySelector?.(".ac-input");
|
|
1136
|
+
if (!input) continue;
|
|
1137
|
+
const fontSize = getComputedStyle(input).fontSize;
|
|
1138
|
+
if (fontSize) return fontSize;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return "var(--font-size-md)";
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async function loadGoogleFontsForFontFamilyItems(items) {
|
|
1145
|
+
if (!Array.isArray(items) || !items.length) return;
|
|
1146
|
+
|
|
1147
|
+
const loadGoogleFont = await getLoadGoogleFontFn();
|
|
1148
|
+
if (typeof loadGoogleFont !== "function") return;
|
|
1149
|
+
|
|
1150
|
+
const fontNames = new Set();
|
|
1151
|
+
items.forEach((item) => {
|
|
1152
|
+
const stack = item?.value || item?.text;
|
|
1153
|
+
splitFontFamilyStack(stack).forEach((entry) => {
|
|
1154
|
+
const fontName = normalizeFontName(entry);
|
|
1155
|
+
if (isLikelyLoadableFont(fontName)) {
|
|
1156
|
+
fontNames.add(fontName);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
if (!fontNames.size) return;
|
|
1162
|
+
await Promise.allSettled(Array.from(fontNames).map((name) => loadGoogleFont(name)));
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function buildFontFamilyOmniboxSettings() {
|
|
1166
|
+
const filterItems = (items, search) => {
|
|
1167
|
+
const query = String(search || "").trim().toLowerCase();
|
|
1168
|
+
if (!query) return items;
|
|
1169
|
+
return items.filter((item) => {
|
|
1170
|
+
const text = String(
|
|
1171
|
+
item?.text || item?.id || item?.element?.textContent || ""
|
|
1172
|
+
).toLowerCase();
|
|
1173
|
+
return text.includes(query);
|
|
1174
|
+
});
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
return {
|
|
1179
|
+
//debug: true,
|
|
1180
|
+
itemGrid: "0 1fr 0",
|
|
1181
|
+
hideCategory: true,
|
|
1182
|
+
iconHandler: (item) => {
|
|
1183
|
+
return "";
|
|
1184
|
+
},
|
|
1185
|
+
categories: {
|
|
1186
|
+
FontFamilies: {
|
|
1187
|
+
trigger: () => true,
|
|
1188
|
+
getItems: async (options) => {
|
|
1189
|
+
const previewFontSize = resolveFontFamilyPreviewFontSize(options?.control);
|
|
1190
|
+
const allItems = getPresetFontFamilyVariations(previewFontSize);
|
|
1191
|
+
const items = filterItems(allItems, options?.search);
|
|
1192
|
+
|
|
1193
|
+
await loadGoogleFontsForFontFamilyItems(items);
|
|
1194
|
+
|
|
1195
|
+
return items;
|
|
1196
|
+
},
|
|
1197
|
+
action: (options) => {
|
|
1198
|
+
const input = document.querySelector("pds-omnibox");
|
|
1199
|
+
if (input) {
|
|
1200
|
+
input.value = options.text;
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
863
1208
|
function buildSchemaFromPaths(paths, design, hints = {}) {
|
|
864
1209
|
const schema = { type: "object", properties: {} };
|
|
865
1210
|
const uiSchema = {};
|
|
@@ -900,7 +1245,7 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
|
|
|
900
1245
|
};
|
|
901
1246
|
|
|
902
1247
|
const isColorValue = (value, path) => {
|
|
903
|
-
if (
|
|
1248
|
+
if (isColorPath(path)) return true;
|
|
904
1249
|
if (typeof value !== "string") return false;
|
|
905
1250
|
return /^#([0-9a-f]{3,8})$/i.test(value) || /^rgba?\(/i.test(value) || /^hsla?\(/i.test(value);
|
|
906
1251
|
};
|
|
@@ -914,6 +1259,13 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
|
|
|
914
1259
|
if (!parent) {
|
|
915
1260
|
parent = { type: "object", title: titleize(category), properties: {} };
|
|
916
1261
|
schema.properties[category] = parent;
|
|
1262
|
+
|
|
1263
|
+
if (category === "colors") {
|
|
1264
|
+
uiSchema[`/${category}`] = {
|
|
1265
|
+
"ui:layout": "flex",
|
|
1266
|
+
"ui:layoutOptions": { wrap: true, gap: "sm" },
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
917
1269
|
}
|
|
918
1270
|
|
|
919
1271
|
let current = parent;
|
|
@@ -923,8 +1275,12 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
|
|
|
923
1275
|
const value = getValueAtPath(design, [category, ...rest]);
|
|
924
1276
|
const hintValue = hints[path];
|
|
925
1277
|
const enumOptions = getEnumOptions(path);
|
|
926
|
-
const
|
|
927
|
-
const
|
|
1278
|
+
const resolvedColorValue = resolveColorValueForPath(path, value, hintValue);
|
|
1279
|
+
const normalizedValue = normalizeEnumValue(path, resolvedColorValue ?? value);
|
|
1280
|
+
const normalizedHint = normalizeEnumValue(
|
|
1281
|
+
path,
|
|
1282
|
+
resolvedColorValue ?? hintValue,
|
|
1283
|
+
);
|
|
928
1284
|
const inferredType = Array.isArray(value)
|
|
929
1285
|
? "array"
|
|
930
1286
|
: value === null
|
|
@@ -973,6 +1329,8 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
|
|
|
973
1329
|
uiEntry["ui:step"] = bounds.step;
|
|
974
1330
|
} else if (isColorValue(value, path)) {
|
|
975
1331
|
uiEntry["ui:widget"] = "input-color";
|
|
1332
|
+
} else if (FONT_FAMILY_PATH_REGEX.test(path) && schemaType === "string") {
|
|
1333
|
+
uiEntry["ui:widget"] = "font-family-omnibox";
|
|
976
1334
|
}
|
|
977
1335
|
|
|
978
1336
|
const isTextOrNumberInput =
|
|
@@ -1089,6 +1447,10 @@ async function applyDesignPatch(patch) {
|
|
|
1089
1447
|
const nextOptions = { ...currentOptions, design: nextDesign };
|
|
1090
1448
|
if (resolvedPresetId) nextOptions.preset = resolvedPresetId;
|
|
1091
1449
|
|
|
1450
|
+
try {
|
|
1451
|
+
await loadTypographyFontsForDesign(nextDesign?.typography);
|
|
1452
|
+
} catch (e) {}
|
|
1453
|
+
|
|
1092
1454
|
const nextGenerator = new Generator(nextOptions);
|
|
1093
1455
|
if (PDS?.applyStyles) {
|
|
1094
1456
|
await PDS.applyStyles(nextGenerator);
|
|
@@ -1164,6 +1526,17 @@ function getActivePresetId() {
|
|
|
1164
1526
|
return stored?.preset || PDS?.currentConfig?.preset || PDS?.currentPreset || null;
|
|
1165
1527
|
}
|
|
1166
1528
|
|
|
1529
|
+
function getPresetNameById(presetId) {
|
|
1530
|
+
if (!presetId) return "";
|
|
1531
|
+
const presets = PDS?.presets || {};
|
|
1532
|
+
const preset =
|
|
1533
|
+
presets?.[presetId] ||
|
|
1534
|
+
Object.values(presets || {}).find(
|
|
1535
|
+
(candidate) => String(candidate?.id || candidate?.name) === String(presetId)
|
|
1536
|
+
);
|
|
1537
|
+
return preset?.name || String(presetId);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1167
1540
|
async function applyPresetSelection(presetId) {
|
|
1168
1541
|
if (!presetId) return;
|
|
1169
1542
|
setStoredConfig({
|
|
@@ -1173,15 +1546,208 @@ async function applyPresetSelection(presetId) {
|
|
|
1173
1546
|
await applyDesignPatch({});
|
|
1174
1547
|
}
|
|
1175
1548
|
|
|
1549
|
+
function figmafyTokens(rawTokens) {
|
|
1550
|
+
const isPlainObject = (value) =>
|
|
1551
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1552
|
+
|
|
1553
|
+
const detectType = (path, key, value) => {
|
|
1554
|
+
const root = path[0];
|
|
1555
|
+
|
|
1556
|
+
if (root === "colors") {
|
|
1557
|
+
if (key === "scheme") return "string";
|
|
1558
|
+
return "color";
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
if (root === "spacing" || root === "radius" || root === "borderWidths") {
|
|
1562
|
+
return "dimension";
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (root === "typography") {
|
|
1566
|
+
const group = path[1];
|
|
1567
|
+
if (group === "fontFamily") return "fontFamily";
|
|
1568
|
+
if (group === "fontSize") return "fontSize";
|
|
1569
|
+
if (group === "fontWeight") return "fontWeight";
|
|
1570
|
+
if (group === "lineHeight") return "lineHeight";
|
|
1571
|
+
return "string";
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if (root === "shadows") return "shadow";
|
|
1575
|
+
if (root === "layout") return "dimension";
|
|
1576
|
+
if (root === "transitions") return "duration";
|
|
1577
|
+
if (root === "zIndex") return "number";
|
|
1578
|
+
|
|
1579
|
+
if (root === "icons") {
|
|
1580
|
+
if (key === "defaultSize" || path.includes("sizes")) {
|
|
1581
|
+
return "dimension";
|
|
1582
|
+
}
|
|
1583
|
+
return "string";
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
if (typeof value === "number") {
|
|
1587
|
+
return "number";
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (typeof value === "string") {
|
|
1591
|
+
if (/^\d+(\.\d+)?ms$/.test(value)) return "duration";
|
|
1592
|
+
if (/^\d+(\.\d+)?(px|rem|em|vh|vw|%)$/.test(value)) return "dimension";
|
|
1593
|
+
|
|
1594
|
+
if (
|
|
1595
|
+
/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value) ||
|
|
1596
|
+
/^(rgb|rgba|hsl|hsla|oklab|lab)\(/.test(value)
|
|
1597
|
+
) {
|
|
1598
|
+
return "color";
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
return undefined;
|
|
1603
|
+
};
|
|
1604
|
+
|
|
1605
|
+
const walk = (node, path = []) => {
|
|
1606
|
+
if (node == null) return node;
|
|
1607
|
+
|
|
1608
|
+
if (Array.isArray(node)) {
|
|
1609
|
+
return node.map((item, index) => walk(item, path.concat(String(index))));
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (isPlainObject(node)) {
|
|
1613
|
+
if (
|
|
1614
|
+
Object.prototype.hasOwnProperty.call(node, "value") &&
|
|
1615
|
+
(Object.prototype.hasOwnProperty.call(node, "type") ||
|
|
1616
|
+
Object.keys(node).length === 1)
|
|
1617
|
+
) {
|
|
1618
|
+
return node;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const result = {};
|
|
1622
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1623
|
+
result[key] = walk(value, path.concat(key));
|
|
1624
|
+
}
|
|
1625
|
+
return result;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const key = path[path.length - 1] ?? "";
|
|
1629
|
+
const type = detectType(path, key, node);
|
|
1630
|
+
let value = node;
|
|
1631
|
+
|
|
1632
|
+
if (type === "number" && typeof value === "string") {
|
|
1633
|
+
const num = Number(value);
|
|
1634
|
+
if (!Number.isNaN(num)) value = num;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
return type ? { value, type } : { value };
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
return walk(rawTokens, []);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function downloadTextFile(content, filename, mimeType) {
|
|
1644
|
+
if (typeof document === "undefined") return;
|
|
1645
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1646
|
+
const url = URL.createObjectURL(blob);
|
|
1647
|
+
const anchor = document.createElement("a");
|
|
1648
|
+
anchor.href = url;
|
|
1649
|
+
anchor.download = filename;
|
|
1650
|
+
anchor.click();
|
|
1651
|
+
URL.revokeObjectURL(url);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function getLiveEditExportConfig() {
|
|
1655
|
+
const stored = getStoredConfig();
|
|
1656
|
+
const design = shallowClone(PDS?.currentConfig?.design || stored?.design || {});
|
|
1657
|
+
const preset = stored?.preset || PDS?.currentConfig?.preset || PDS?.currentPreset || null;
|
|
1658
|
+
return {
|
|
1659
|
+
preset,
|
|
1660
|
+
design,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function buildConfigModuleContent(config) {
|
|
1665
|
+
return `export const pdsConfig = ${JSON.stringify(config, null, 2)};\n\nexport default pdsConfig;\n`;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
async function exportFromLiveEdit(format) {
|
|
1669
|
+
try {
|
|
1670
|
+
if (format === "config") {
|
|
1671
|
+
const config = getLiveEditExportConfig();
|
|
1672
|
+
const content = buildConfigModuleContent(config);
|
|
1673
|
+
downloadTextFile(content, "pds.config.js", "text/javascript");
|
|
1674
|
+
await PDS?.toast?.("Exported config file", { type: "success" });
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (format === "figma") {
|
|
1679
|
+
const Generator = await getGeneratorClass();
|
|
1680
|
+
const generator = Generator?.instance;
|
|
1681
|
+
if (!generator || typeof generator.generateTokens !== "function") {
|
|
1682
|
+
throw new Error("Token generator unavailable");
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const rawTokens = generator.generateTokens();
|
|
1686
|
+
const figmaTokens = figmafyTokens(rawTokens);
|
|
1687
|
+
const content = JSON.stringify(figmaTokens, null, 2);
|
|
1688
|
+
downloadTextFile(content, "design-tokens.figma.json", "application/json");
|
|
1689
|
+
await PDS?.toast?.("Exported Figma tokens", { type: "success" });
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
console.warn("[pds-live-edit] Export failed", error);
|
|
1694
|
+
await PDS?.toast?.("Export failed", { type: "error" });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1176
1698
|
function setFormSchemas(form, schema, uiSchema, design) {
|
|
1177
1699
|
form.jsonSchema = schema;
|
|
1178
1700
|
form.uiSchema = uiSchema;
|
|
1179
1701
|
form.values = shallowClone(design);
|
|
1180
1702
|
}
|
|
1181
1703
|
|
|
1182
|
-
async function
|
|
1183
|
-
|
|
1704
|
+
async function waitForElementDefinition(tagName, timeoutMs = 4000) {
|
|
1705
|
+
if (customElements.get(tagName)) return true;
|
|
1706
|
+
|
|
1707
|
+
let probe = null;
|
|
1708
|
+
try {
|
|
1709
|
+
if (typeof document !== "undefined" && document.body) {
|
|
1710
|
+
probe = document.createElement(tagName);
|
|
1711
|
+
probe.setAttribute("hidden", "");
|
|
1712
|
+
probe.setAttribute("aria-hidden", "true");
|
|
1713
|
+
probe.style.display = "none";
|
|
1714
|
+
document.body.appendChild(probe);
|
|
1715
|
+
}
|
|
1716
|
+
} catch (e) {}
|
|
1717
|
+
|
|
1718
|
+
await Promise.race([
|
|
1719
|
+
customElements.whenDefined(tagName),
|
|
1720
|
+
new Promise((_, reject) => {
|
|
1721
|
+
setTimeout(() => reject(new Error(`Timed out waiting for <${tagName}> definition`)), timeoutMs);
|
|
1722
|
+
}),
|
|
1723
|
+
]);
|
|
1724
|
+
|
|
1725
|
+
try {
|
|
1726
|
+
if (probe && probe.parentNode) {
|
|
1727
|
+
probe.parentNode.removeChild(probe);
|
|
1728
|
+
}
|
|
1729
|
+
} catch (e) {}
|
|
1730
|
+
|
|
1731
|
+
if (!customElements.get(tagName)) {
|
|
1732
|
+
throw new Error(`<${tagName}> is not defined`);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
return true;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
async function createConfiguredForm({
|
|
1739
|
+
schema,
|
|
1740
|
+
uiSchema,
|
|
1741
|
+
values,
|
|
1742
|
+
onSubmit,
|
|
1743
|
+
onUndo,
|
|
1744
|
+
normalizeFlatValues,
|
|
1745
|
+
formOptions,
|
|
1746
|
+
}) {
|
|
1747
|
+
await waitForElementDefinition("pds-form");
|
|
1748
|
+
|
|
1184
1749
|
const form = document.createElement("pds-form");
|
|
1750
|
+
const fontFamilyOmniboxSettings = buildFontFamilyOmniboxSettings();
|
|
1185
1751
|
form.setAttribute("hide-actions", "");
|
|
1186
1752
|
form.options = {
|
|
1187
1753
|
layouts: {
|
|
@@ -1190,68 +1756,464 @@ async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
|
|
|
1190
1756
|
enhancements: {
|
|
1191
1757
|
rangeOutput: true,
|
|
1192
1758
|
},
|
|
1759
|
+
...(formOptions && typeof formOptions === "object" ? formOptions : {}),
|
|
1193
1760
|
};
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1761
|
+
|
|
1762
|
+
form.defineRenderer(
|
|
1763
|
+
"font-family-omnibox",
|
|
1764
|
+
({ id, path, value, attrs, set }) => {
|
|
1765
|
+
const resolveSelectedValue = (options, actionResult, selectionEvent) => {
|
|
1766
|
+
if (typeof actionResult === "string" && actionResult.trim()) {
|
|
1767
|
+
return actionResult;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
const eventDetail = selectionEvent?.detail;
|
|
1771
|
+
const fromEventValue = String(eventDetail?.value || "").trim();
|
|
1772
|
+
if (fromEventValue) return fromEventValue;
|
|
1773
|
+
|
|
1774
|
+
const fromEventText = String(eventDetail?.text || "").trim();
|
|
1775
|
+
if (fromEventText) return fromEventText;
|
|
1776
|
+
|
|
1777
|
+
const fromEventElementText = String(
|
|
1778
|
+
eventDetail?.element?.textContent || ""
|
|
1779
|
+
).trim();
|
|
1780
|
+
if (fromEventElementText) return fromEventElementText;
|
|
1781
|
+
|
|
1782
|
+
const fromText = String(options?.text || "").trim();
|
|
1783
|
+
if (fromText) return fromText;
|
|
1784
|
+
|
|
1785
|
+
const fromValue = String(options?.value || "").trim();
|
|
1786
|
+
if (fromValue) return fromValue;
|
|
1787
|
+
|
|
1788
|
+
const fromElementText = String(options?.element?.textContent || "").trim();
|
|
1789
|
+
if (fromElementText) return fromElementText;
|
|
1790
|
+
|
|
1791
|
+
return String(options?.id || "").trim();
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
const categories = Object.fromEntries(
|
|
1795
|
+
Object.entries(fontFamilyOmniboxSettings.categories || {}).map(
|
|
1796
|
+
([categoryName, categoryConfig]) => {
|
|
1797
|
+
const originalAction = categoryConfig?.action;
|
|
1798
|
+
return [
|
|
1799
|
+
categoryName,
|
|
1800
|
+
{
|
|
1801
|
+
...categoryConfig,
|
|
1802
|
+
action: (...args) => {
|
|
1803
|
+
const [options, selectionEvent] = args;
|
|
1804
|
+
const actionResult =
|
|
1805
|
+
typeof originalAction === "function"
|
|
1806
|
+
? originalAction(...args)
|
|
1807
|
+
: undefined;
|
|
1808
|
+
|
|
1809
|
+
if (actionResult && typeof actionResult.then === "function") {
|
|
1810
|
+
return actionResult.then((resolved) => {
|
|
1811
|
+
const selected = resolveSelectedValue(
|
|
1812
|
+
options,
|
|
1813
|
+
resolved,
|
|
1814
|
+
selectionEvent
|
|
1815
|
+
);
|
|
1816
|
+
if (selected) {
|
|
1817
|
+
set(selected);
|
|
1818
|
+
}
|
|
1819
|
+
return resolved;
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
const selected = resolveSelectedValue(
|
|
1824
|
+
options,
|
|
1825
|
+
actionResult,
|
|
1826
|
+
selectionEvent
|
|
1827
|
+
);
|
|
1828
|
+
if (selected) {
|
|
1829
|
+
set(selected);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
return actionResult;
|
|
1833
|
+
},
|
|
1834
|
+
},
|
|
1835
|
+
];
|
|
1836
|
+
}
|
|
1837
|
+
)
|
|
1838
|
+
);
|
|
1839
|
+
|
|
1840
|
+
const omnibox = document.createElement("pds-omnibox");
|
|
1841
|
+
omnibox.id = id;
|
|
1842
|
+
omnibox.setAttribute("name", path);
|
|
1843
|
+
omnibox.setAttribute("item-grid", "0 1fr 0");
|
|
1844
|
+
omnibox.setAttribute(
|
|
1845
|
+
"placeholder",
|
|
1846
|
+
attrs?.placeholder || "Select a font family"
|
|
1847
|
+
);
|
|
1848
|
+
omnibox.value = value ?? "";
|
|
1849
|
+
omnibox.settings = {
|
|
1850
|
+
...fontFamilyOmniboxSettings,
|
|
1851
|
+
categories,
|
|
1852
|
+
};
|
|
1853
|
+
omnibox.addEventListener("input", (event) => {
|
|
1854
|
+
set(event?.target?.value ?? omnibox.value ?? "");
|
|
1855
|
+
});
|
|
1856
|
+
omnibox.addEventListener("change", (event) => {
|
|
1857
|
+
set(event?.target?.value ?? omnibox.value ?? "");
|
|
1858
|
+
});
|
|
1859
|
+
omnibox.addEventListener("result-selected", (event) => {
|
|
1860
|
+
const selected = resolveSelectedValue(event?.detail, undefined, event);
|
|
1861
|
+
if (!selected) return;
|
|
1862
|
+
omnibox.value = selected;
|
|
1863
|
+
set(selected);
|
|
1864
|
+
});
|
|
1865
|
+
return omnibox;
|
|
1207
1866
|
}
|
|
1208
|
-
|
|
1209
|
-
setFormSchemas(form, schema, uiSchema, values);
|
|
1867
|
+
);
|
|
1210
1868
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
});
|
|
1869
|
+
form.addEventListener("pw:submit", onSubmit);
|
|
1870
|
+
if (typeof normalizeFlatValues === "function") {
|
|
1871
|
+
form._normalizeFlatValues = normalizeFlatValues;
|
|
1215
1872
|
}
|
|
1873
|
+
setFormSchemas(form, schema, uiSchema, values || {});
|
|
1216
1874
|
|
|
1217
|
-
// Apply button (will trigger form submit programmatically)
|
|
1218
1875
|
const applyBtn = document.createElement("button");
|
|
1219
1876
|
applyBtn.className = "btn-primary btn-sm";
|
|
1220
1877
|
applyBtn.type = "button";
|
|
1221
1878
|
applyBtn.textContent = "Apply";
|
|
1222
1879
|
applyBtn.addEventListener("click", async () => {
|
|
1223
|
-
// Manually trigger pw:submit event for pds-form
|
|
1224
1880
|
if (typeof form.getValuesFlat === "function") {
|
|
1225
|
-
// Wait for form to be ready if it's still loading
|
|
1226
1881
|
if (!customElements.get("pds-form")) {
|
|
1227
1882
|
await customElements.whenDefined("pds-form");
|
|
1228
1883
|
}
|
|
1229
|
-
|
|
1230
|
-
const flatValues =
|
|
1884
|
+
|
|
1885
|
+
const flatValues =
|
|
1886
|
+
typeof form._normalizeFlatValues === "function"
|
|
1887
|
+
? form._normalizeFlatValues(form.getValuesFlat())
|
|
1888
|
+
: form.getValuesFlat();
|
|
1231
1889
|
const event = new CustomEvent("pw:submit", {
|
|
1232
1890
|
detail: {
|
|
1233
1891
|
json: flatValues,
|
|
1234
1892
|
formData: new FormData(),
|
|
1235
1893
|
valid: true,
|
|
1236
|
-
issues: []
|
|
1894
|
+
issues: [],
|
|
1237
1895
|
},
|
|
1238
1896
|
bubbles: true,
|
|
1239
|
-
cancelable: true
|
|
1897
|
+
cancelable: true,
|
|
1240
1898
|
});
|
|
1241
1899
|
form.dispatchEvent(event);
|
|
1242
1900
|
}
|
|
1243
1901
|
});
|
|
1244
1902
|
|
|
1245
|
-
// Undo button
|
|
1246
1903
|
const undoBtn = document.createElement("button");
|
|
1247
|
-
undoBtn.className = "btn-secondary btn-sm";
|
|
1904
|
+
undoBtn.className = "btn-secondary btn-sm icon-only";
|
|
1248
1905
|
undoBtn.type = "button";
|
|
1249
|
-
undoBtn.
|
|
1906
|
+
undoBtn.setAttribute("aria-label", "Undo");
|
|
1907
|
+
undoBtn.setAttribute("title", "Undo");
|
|
1908
|
+
const undoIcon = document.createElement("pds-icon");
|
|
1909
|
+
undoIcon.setAttribute("icon", "arrow-counter-clockwise");
|
|
1910
|
+
undoIcon.setAttribute("size", "sm");
|
|
1911
|
+
undoBtn.appendChild(undoIcon);
|
|
1250
1912
|
undoBtn.addEventListener("click", onUndo);
|
|
1251
1913
|
|
|
1252
1914
|
return { form, applyBtn, undoBtn };
|
|
1253
1915
|
}
|
|
1254
1916
|
|
|
1917
|
+
async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
|
|
1918
|
+
const quickPayload = buildQuickConfigPayload(paths, design, hints);
|
|
1919
|
+
const schema = quickPayload?.schema;
|
|
1920
|
+
const uiSchema = quickPayload?.uiSchema;
|
|
1921
|
+
const values = quickPayload?.values;
|
|
1922
|
+
|
|
1923
|
+
if (!schema || !uiSchema) {
|
|
1924
|
+
throw new Error("Central config form metadata is unavailable for quick edit");
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return createConfiguredForm({
|
|
1928
|
+
schema,
|
|
1929
|
+
uiSchema,
|
|
1930
|
+
values: values || {},
|
|
1931
|
+
onSubmit,
|
|
1932
|
+
onUndo,
|
|
1933
|
+
formOptions: {
|
|
1934
|
+
layouts: {
|
|
1935
|
+
arrays: "compact",
|
|
1936
|
+
},
|
|
1937
|
+
enhancements: {
|
|
1938
|
+
rangeOutput: true,
|
|
1939
|
+
},
|
|
1940
|
+
},
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function getConfigFormPayloadFromMetadata(design) {
|
|
1945
|
+
if (typeof PDS?.buildConfigFormSchema === "function") {
|
|
1946
|
+
return PDS.buildConfigFormSchema(design);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const payload = PDS?.configFormSchema;
|
|
1950
|
+
if (payload && payload.schema && payload.uiSchema) {
|
|
1951
|
+
return {
|
|
1952
|
+
schema: payload.schema,
|
|
1953
|
+
uiSchema: payload.uiSchema,
|
|
1954
|
+
values: shallowClone(design || payload.values || {}),
|
|
1955
|
+
metadata: payload.metadata || {},
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
return null;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function deepClone(value) {
|
|
1963
|
+
if (typeof structuredClone === "function") {
|
|
1964
|
+
try {
|
|
1965
|
+
return structuredClone(value);
|
|
1966
|
+
} catch (e) {
|
|
1967
|
+
// Fall through to JSON clone
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return JSON.parse(JSON.stringify(value));
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function shouldKeepPathForSelection(selectedPaths, path) {
|
|
1974
|
+
if (!path) return true;
|
|
1975
|
+
return selectedPaths.some((selectedPath) => {
|
|
1976
|
+
if (selectedPath === path) return true;
|
|
1977
|
+
return selectedPath.startsWith(`${path}.`);
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
function pruneSchemaForPaths(node, selectedPaths, path = "") {
|
|
1982
|
+
if (!node || typeof node !== "object") return node;
|
|
1983
|
+
if (!isObjectSchemaNode(node)) return deepClone(node);
|
|
1984
|
+
|
|
1985
|
+
if (path && !shouldKeepPathForSelection(selectedPaths, path)) {
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const properties = {};
|
|
1990
|
+
Object.entries(node.properties || {}).forEach(([key, childNode]) => {
|
|
1991
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
1992
|
+
if (!shouldKeepPathForSelection(selectedPaths, childPath)) return;
|
|
1993
|
+
const prunedChild = pruneSchemaForPaths(childNode, selectedPaths, childPath);
|
|
1994
|
+
if (prunedChild) {
|
|
1995
|
+
properties[key] = prunedChild;
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
if (!Object.keys(properties).length) return null;
|
|
2000
|
+
|
|
2001
|
+
const clonedNode = deepClone(node);
|
|
2002
|
+
clonedNode.properties = properties;
|
|
2003
|
+
if (Array.isArray(clonedNode.required)) {
|
|
2004
|
+
clonedNode.required = clonedNode.required.filter((key) =>
|
|
2005
|
+
Object.prototype.hasOwnProperty.call(properties, key)
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
return clonedNode;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function uiPointerToPath(pointer) {
|
|
2012
|
+
if (!pointer || pointer === "/") return "";
|
|
2013
|
+
return pointer
|
|
2014
|
+
.replace(/^\//, "")
|
|
2015
|
+
.split("/")
|
|
2016
|
+
.filter(Boolean)
|
|
2017
|
+
.join(".");
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
function filterUiSchemaForPaths(uiSchema, selectedPaths) {
|
|
2021
|
+
if (!uiSchema || typeof uiSchema !== "object") return {};
|
|
2022
|
+
const filtered = {};
|
|
2023
|
+
Object.entries(uiSchema).forEach(([pointer, value]) => {
|
|
2024
|
+
const path = uiPointerToPath(pointer);
|
|
2025
|
+
if (!path || shouldKeepPathForSelection(selectedPaths, path)) {
|
|
2026
|
+
filtered[pointer] = deepClone(value);
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
return filtered;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function buildValuesForPaths(valuesSource, selectedPaths, hints = {}) {
|
|
2033
|
+
const values = {};
|
|
2034
|
+
selectedPaths.forEach((path) => {
|
|
2035
|
+
const segments = path.split(".");
|
|
2036
|
+
let value = getValueAtPath(valuesSource, segments);
|
|
2037
|
+
if ((value === undefined || value === null) && hints[path] !== undefined) {
|
|
2038
|
+
value = hints[path];
|
|
2039
|
+
}
|
|
2040
|
+
if (isColorPath(path)) {
|
|
2041
|
+
const resolvedColorValue = resolveColorValueForPath(path, value, hints[path]);
|
|
2042
|
+
if (resolvedColorValue) {
|
|
2043
|
+
value = resolvedColorValue;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (value !== undefined) {
|
|
2047
|
+
setValueAtPath(values, segments, deepClone(value));
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
return values;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
function buildQuickConfigPayload(paths, design, hints = {}) {
|
|
2054
|
+
const payload = getConfigFormPayloadFromMetadata(design);
|
|
2055
|
+
if (!payload?.schema || !payload?.uiSchema) return null;
|
|
2056
|
+
|
|
2057
|
+
const selectedPaths = normalizePaths(paths);
|
|
2058
|
+
if (!selectedPaths.length) return null;
|
|
2059
|
+
|
|
2060
|
+
const schema = pruneSchemaForPaths(payload.schema, selectedPaths, "");
|
|
2061
|
+
if (!schema) return null;
|
|
2062
|
+
|
|
2063
|
+
const uiSchema = filterUiSchemaForPaths(payload.uiSchema, selectedPaths);
|
|
2064
|
+
const valuesSource =
|
|
2065
|
+
payload?.values && typeof payload.values === "object"
|
|
2066
|
+
? payload.values
|
|
2067
|
+
: shallowClone(design || {});
|
|
2068
|
+
const values = buildValuesForPaths(valuesSource || {}, selectedPaths, hints);
|
|
2069
|
+
|
|
2070
|
+
return { schema, uiSchema, values };
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const FULL_CONFIG_GROUPS_KEY = "__groups";
|
|
2074
|
+
|
|
2075
|
+
function isObjectSchemaNode(node) {
|
|
2076
|
+
return !!(node && typeof node === "object" && node.type === "object" && node.properties);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function buildGroupedFullConfigPayload(payload, design) {
|
|
2080
|
+
const values =
|
|
2081
|
+
payload?.values && typeof payload.values === "object"
|
|
2082
|
+
? payload.values
|
|
2083
|
+
: shallowClone(design || {});
|
|
2084
|
+
|
|
2085
|
+
if (!payload?.schema || !payload?.uiSchema || !isObjectSchemaNode(payload.schema)) {
|
|
2086
|
+
return {
|
|
2087
|
+
schema: payload?.schema,
|
|
2088
|
+
uiSchema: payload?.uiSchema,
|
|
2089
|
+
values,
|
|
2090
|
+
normalizeFlatValues: null,
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
const rootProperties = payload.schema.properties || {};
|
|
2095
|
+
const groupedKeys = [];
|
|
2096
|
+
const scalarKeys = [];
|
|
2097
|
+
|
|
2098
|
+
Object.entries(rootProperties).forEach(([key, schemaNode]) => {
|
|
2099
|
+
if (isObjectSchemaNode(schemaNode)) {
|
|
2100
|
+
groupedKeys.push(key);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
scalarKeys.push(key);
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
if (!groupedKeys.length || !scalarKeys.length) {
|
|
2107
|
+
return {
|
|
2108
|
+
schema: payload.schema,
|
|
2109
|
+
uiSchema: payload.uiSchema,
|
|
2110
|
+
values,
|
|
2111
|
+
normalizeFlatValues: null,
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
const transformedSchema = {
|
|
2116
|
+
...payload.schema,
|
|
2117
|
+
properties: {
|
|
2118
|
+
...Object.fromEntries(scalarKeys.map((key) => [key, rootProperties[key]])),
|
|
2119
|
+
[FULL_CONFIG_GROUPS_KEY]: {
|
|
2120
|
+
type: "object",
|
|
2121
|
+
title: "Design Groups",
|
|
2122
|
+
properties: Object.fromEntries(
|
|
2123
|
+
groupedKeys.map((key) => [key, rootProperties[key]])
|
|
2124
|
+
),
|
|
2125
|
+
},
|
|
2126
|
+
},
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
const transformedValues = {
|
|
2130
|
+
...Object.fromEntries(scalarKeys.map((key) => [key, values?.[key]])),
|
|
2131
|
+
[FULL_CONFIG_GROUPS_KEY]: Object.fromEntries(
|
|
2132
|
+
groupedKeys.map((key) => [key, values?.[key]])
|
|
2133
|
+
),
|
|
2134
|
+
};
|
|
2135
|
+
|
|
2136
|
+
const transformedUiSchema = { ...(payload.uiSchema || {}) };
|
|
2137
|
+
const addGroupPrefix = (path = "") => `/${FULL_CONFIG_GROUPS_KEY}${path}`;
|
|
2138
|
+
|
|
2139
|
+
groupedKeys.forEach((key) => {
|
|
2140
|
+
const originalPath = `/${key}`;
|
|
2141
|
+
|
|
2142
|
+
if (Object.prototype.hasOwnProperty.call(transformedUiSchema, originalPath)) {
|
|
2143
|
+
transformedUiSchema[addGroupPrefix(originalPath)] = transformedUiSchema[originalPath];
|
|
2144
|
+
delete transformedUiSchema[originalPath];
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
Object.keys(transformedUiSchema).forEach((path) => {
|
|
2148
|
+
if (!path.startsWith(`${originalPath}/`)) return;
|
|
2149
|
+
transformedUiSchema[addGroupPrefix(path)] = transformedUiSchema[path];
|
|
2150
|
+
delete transformedUiSchema[path];
|
|
2151
|
+
});
|
|
2152
|
+
});
|
|
2153
|
+
|
|
2154
|
+
transformedUiSchema[`/${FULL_CONFIG_GROUPS_KEY}`] = {
|
|
2155
|
+
"ui:layout": "accordion",
|
|
2156
|
+
"ui:layoutOptions": { openFirst: false },
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
const normalizeFlatValues = (flatValues = {}) => {
|
|
2160
|
+
const normalized = {};
|
|
2161
|
+
const groupPointerPrefix = `/${FULL_CONFIG_GROUPS_KEY}/`;
|
|
2162
|
+
const groupDotPrefix = `${FULL_CONFIG_GROUPS_KEY}.`;
|
|
2163
|
+
Object.entries(flatValues || {}).forEach(([path, value]) => {
|
|
2164
|
+
const inputPath = String(path || "");
|
|
2165
|
+
if (!inputPath) return;
|
|
2166
|
+
if (inputPath === FULL_CONFIG_GROUPS_KEY || inputPath === `/${FULL_CONFIG_GROUPS_KEY}`) {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
if (inputPath.startsWith(groupPointerPrefix)) {
|
|
2171
|
+
normalized[`/${inputPath.slice(groupPointerPrefix.length)}`] = value;
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
if (inputPath.startsWith(groupDotPrefix)) {
|
|
2176
|
+
normalized[inputPath.slice(groupDotPrefix.length)] = value;
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
normalized[inputPath] = value;
|
|
2181
|
+
});
|
|
2182
|
+
return normalized;
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
return {
|
|
2186
|
+
schema: transformedSchema,
|
|
2187
|
+
uiSchema: transformedUiSchema,
|
|
2188
|
+
values: transformedValues,
|
|
2189
|
+
normalizeFlatValues,
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
async function buildFullConfigForm(design, onSubmit, onUndo) {
|
|
2194
|
+
const payload = getConfigFormPayloadFromMetadata(design);
|
|
2195
|
+
if (!payload?.schema || !payload?.uiSchema) return null;
|
|
2196
|
+
|
|
2197
|
+
const groupedPayload = buildGroupedFullConfigPayload(payload, design);
|
|
2198
|
+
|
|
2199
|
+
return createConfiguredForm({
|
|
2200
|
+
schema: groupedPayload.schema,
|
|
2201
|
+
uiSchema: groupedPayload.uiSchema,
|
|
2202
|
+
values: groupedPayload.values,
|
|
2203
|
+
onSubmit,
|
|
2204
|
+
onUndo,
|
|
2205
|
+
normalizeFlatValues: groupedPayload.normalizeFlatValues,
|
|
2206
|
+
formOptions: {
|
|
2207
|
+
layouts: {
|
|
2208
|
+
arrays: "compact",
|
|
2209
|
+
},
|
|
2210
|
+
enhancements: {
|
|
2211
|
+
rangeOutput: true,
|
|
2212
|
+
},
|
|
2213
|
+
},
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
|
|
1255
2217
|
class PdsLiveEdit extends HTMLElement {
|
|
1256
2218
|
constructor() {
|
|
1257
2219
|
super();
|
|
@@ -1669,7 +2631,7 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1669
2631
|
button.appendChild(icon);
|
|
1670
2632
|
|
|
1671
2633
|
const menu = document.createElement("menu");
|
|
1672
|
-
|
|
2634
|
+
const quickItem = document.createElement("div");
|
|
1673
2635
|
quickItem.className = "pds-live-editor-menu";
|
|
1674
2636
|
|
|
1675
2637
|
const header = document.createElement("div");
|
|
@@ -1715,13 +2677,28 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1715
2677
|
container.replaceChildren();
|
|
1716
2678
|
footer.replaceChildren();
|
|
1717
2679
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2680
|
+
let form;
|
|
2681
|
+
let applyBtn;
|
|
2682
|
+
let undoBtn;
|
|
2683
|
+
try {
|
|
2684
|
+
const result = await buildForm(
|
|
2685
|
+
paths,
|
|
2686
|
+
design,
|
|
2687
|
+
(event) => this._handleFormSubmit(event, form),
|
|
2688
|
+
() => this._handleUndo(),
|
|
2689
|
+
hints
|
|
2690
|
+
);
|
|
2691
|
+
form = result.form;
|
|
2692
|
+
applyBtn = result.applyBtn;
|
|
2693
|
+
undoBtn = result.undoBtn;
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
const fallback = document.createElement("p");
|
|
2696
|
+
fallback.className = "text-muted";
|
|
2697
|
+
fallback.textContent = "Editor form unavailable. Lazy component definition did not complete in time.";
|
|
2698
|
+
container.appendChild(fallback);
|
|
2699
|
+
console.warn("[PDS Live Edit] Failed to render quick form:", error);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
1725
2702
|
|
|
1726
2703
|
// Store reference to undo button for enabling/disabling
|
|
1727
2704
|
form._undoBtn = undoBtn;
|
|
@@ -1789,26 +2766,72 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1789
2766
|
presetText.textContent = "Choose a base style";
|
|
1790
2767
|
presetLabel.appendChild(presetText);
|
|
1791
2768
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2769
|
+
let presetControlRendered = false;
|
|
2770
|
+
try {
|
|
2771
|
+
await waitForElementDefinition("pds-omnibox");
|
|
2772
|
+
|
|
2773
|
+
const presetOmnibox = document.createElement("pds-omnibox");
|
|
2774
|
+
presetOmnibox.setAttribute("item-grid", "0 1fr 0");
|
|
2775
|
+
presetOmnibox.setAttribute("placeholder", "Search presets...");
|
|
1795
2776
|
|
|
1796
|
-
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
if (String(preset.id) === String(activePreset)) {
|
|
1801
|
-
option.selected = true;
|
|
2777
|
+
const activePresetId = getActivePresetId();
|
|
2778
|
+
const activePresetName = getPresetNameById(activePresetId);
|
|
2779
|
+
if (activePresetName) {
|
|
2780
|
+
presetOmnibox.value = activePresetName;
|
|
1802
2781
|
}
|
|
1803
|
-
presetSelect.appendChild(option);
|
|
1804
|
-
});
|
|
1805
2782
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2783
|
+
const omniboxSettingsBuilder =
|
|
2784
|
+
typeof PDS?.buildPresetOmniboxSettings === "function"
|
|
2785
|
+
? PDS.buildPresetOmniboxSettings.bind(PDS)
|
|
2786
|
+
: null;
|
|
2787
|
+
|
|
2788
|
+
if (omniboxSettingsBuilder) {
|
|
2789
|
+
presetOmnibox.settings = omniboxSettingsBuilder({
|
|
2790
|
+
onSelect: async ({ preset, selection }) => {
|
|
2791
|
+
if (selection?.disabled) return selection?.id;
|
|
2792
|
+
const presetId = preset?.id || selection?.id;
|
|
2793
|
+
await applyPresetSelection(presetId);
|
|
2794
|
+
return presetId;
|
|
2795
|
+
},
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
presetOmnibox.addEventListener("result-selected", (event) => {
|
|
2800
|
+
const selectedText = event?.detail?.text;
|
|
2801
|
+
if (typeof selectedText === "string" && selectedText.trim()) {
|
|
2802
|
+
presetOmnibox.value = selectedText;
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
|
|
2806
|
+
presetLabel.appendChild(presetOmnibox);
|
|
2807
|
+
presetControlRendered = true;
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
console.warn("[PDS Live Edit] Preset omnibox unavailable, falling back to select.", error);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
if (!presetControlRendered) {
|
|
2813
|
+
const presetSelect = document.createElement("select");
|
|
2814
|
+
const presetOptions = getPresetOptions();
|
|
2815
|
+
const activePreset = getActivePresetId();
|
|
2816
|
+
|
|
2817
|
+
presetOptions.forEach((preset) => {
|
|
2818
|
+
const option = document.createElement("option");
|
|
2819
|
+
option.value = preset.id;
|
|
2820
|
+
option.textContent = preset.name;
|
|
2821
|
+
if (String(preset.id) === String(activePreset)) {
|
|
2822
|
+
option.selected = true;
|
|
2823
|
+
}
|
|
2824
|
+
presetSelect.appendChild(option);
|
|
2825
|
+
});
|
|
2826
|
+
|
|
2827
|
+
presetSelect.addEventListener("change", async (event) => {
|
|
2828
|
+
const nextPreset = event.target?.value;
|
|
2829
|
+
await applyPresetSelection(nextPreset);
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
presetLabel.appendChild(presetSelect);
|
|
2833
|
+
}
|
|
1810
2834
|
|
|
1811
|
-
presetLabel.appendChild(presetSelect);
|
|
1812
2835
|
presetCard.appendChild(presetLabel);
|
|
1813
2836
|
|
|
1814
2837
|
const themeCard = document.createElement("section");
|
|
@@ -1821,53 +2844,129 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1821
2844
|
const themeToggle = document.createElement("pds-theme");
|
|
1822
2845
|
themeCard.appendChild(themeToggle);
|
|
1823
2846
|
|
|
1824
|
-
const
|
|
1825
|
-
|
|
2847
|
+
const configCard = document.createElement("section");
|
|
2848
|
+
configCard.className = "card surface-elevated stack-sm";
|
|
1826
2849
|
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
2850
|
+
const configTitle = document.createElement("h4");
|
|
2851
|
+
configTitle.textContent = "Configuration";
|
|
2852
|
+
configCard.appendChild(configTitle);
|
|
1830
2853
|
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
2854
|
+
const configDescription = document.createElement("p");
|
|
2855
|
+
configDescription.className = "text-muted";
|
|
2856
|
+
configDescription.textContent =
|
|
2857
|
+
"Edit the full design config generated from PDS metadata.";
|
|
2858
|
+
configCard.appendChild(configDescription);
|
|
2859
|
+
|
|
2860
|
+
const configFormContainer = document.createElement("div");
|
|
2861
|
+
configFormContainer.className = "stack-sm";
|
|
2862
|
+
configCard.appendChild(configFormContainer);
|
|
2863
|
+
|
|
2864
|
+
const configFooter = document.createElement("div");
|
|
2865
|
+
configFooter.className = "flex gap-sm";
|
|
2866
|
+
configCard.appendChild(configFooter);
|
|
2867
|
+
|
|
2868
|
+
const fullDesign = shallowClone(PDS?.currentConfig?.design || {});
|
|
2869
|
+
const fullConfigFormResult = await buildFullConfigForm(
|
|
2870
|
+
fullDesign,
|
|
2871
|
+
(event) => this._handleFormSubmit(event, fullConfigFormResult?.form),
|
|
2872
|
+
() => this._handleUndo()
|
|
2873
|
+
);
|
|
2874
|
+
|
|
2875
|
+
if (fullConfigFormResult?.form) {
|
|
2876
|
+
fullConfigFormResult.form._undoBtn = fullConfigFormResult.undoBtn;
|
|
2877
|
+
fullConfigFormResult.undoBtn.disabled = this._undoStack.length === 0;
|
|
2878
|
+
configFormContainer.appendChild(fullConfigFormResult.form);
|
|
2879
|
+
configFooter.appendChild(fullConfigFormResult.applyBtn);
|
|
2880
|
+
configFooter.appendChild(fullConfigFormResult.undoBtn);
|
|
2881
|
+
} else {
|
|
2882
|
+
const unavailable = document.createElement("p");
|
|
2883
|
+
unavailable.className = "text-muted";
|
|
2884
|
+
unavailable.textContent =
|
|
2885
|
+
"Full config metadata is unavailable in this runtime.";
|
|
2886
|
+
configFormContainer.appendChild(unavailable);
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
const exportCard = document.createElement("section");
|
|
2890
|
+
exportCard.className = "card surface-elevated stack-sm";
|
|
2891
|
+
|
|
2892
|
+
const exportTitle = document.createElement("h4");
|
|
2893
|
+
exportTitle.textContent = "Export";
|
|
2894
|
+
exportCard.appendChild(exportTitle);
|
|
2895
|
+
|
|
2896
|
+
const exportNav = document.createElement("nav");
|
|
2897
|
+
exportNav.setAttribute("data-dropdown", "");
|
|
2898
|
+
exportNav.setAttribute("data-mode", "auto");
|
|
2899
|
+
|
|
2900
|
+
const exportButton = document.createElement("button");
|
|
2901
|
+
exportButton.className = "btn-primary";
|
|
2902
|
+
const exportIcon = document.createElement("pds-icon");
|
|
2903
|
+
exportIcon.setAttribute("icon", "download");
|
|
2904
|
+
exportIcon.setAttribute("size", "sm");
|
|
2905
|
+
const exportLabel = document.createElement("span");
|
|
2906
|
+
exportLabel.textContent = "Download";
|
|
2907
|
+
const exportCaret = document.createElement("pds-icon");
|
|
2908
|
+
exportCaret.setAttribute("icon", "caret-down");
|
|
2909
|
+
exportCaret.setAttribute("size", "sm");
|
|
2910
|
+
exportButton.append(exportIcon, exportLabel, exportCaret);
|
|
2911
|
+
|
|
2912
|
+
const exportMenu = document.createElement("menu");
|
|
2913
|
+
|
|
2914
|
+
const configItem = document.createElement("li");
|
|
2915
|
+
const configLink = document.createElement("a");
|
|
2916
|
+
configLink.href = "#";
|
|
2917
|
+
configLink.addEventListener("click", async (event) => {
|
|
2918
|
+
event.preventDefault();
|
|
2919
|
+
await this._handleExport("config");
|
|
2920
|
+
});
|
|
2921
|
+
const configIcon = document.createElement("pds-icon");
|
|
2922
|
+
configIcon.setAttribute("icon", "file-js");
|
|
2923
|
+
configIcon.setAttribute("size", "sm");
|
|
2924
|
+
const configLabel = document.createElement("span");
|
|
2925
|
+
configLabel.textContent = "Config File";
|
|
2926
|
+
configLink.append(configIcon, configLabel);
|
|
2927
|
+
configItem.appendChild(configLink);
|
|
2928
|
+
|
|
2929
|
+
const figmaItem = document.createElement("li");
|
|
2930
|
+
const figmaLink = document.createElement("a");
|
|
2931
|
+
figmaLink.href = "#";
|
|
2932
|
+
figmaLink.addEventListener("click", async (event) => {
|
|
2933
|
+
event.preventDefault();
|
|
2934
|
+
await this._handleExport("figma");
|
|
2935
|
+
});
|
|
2936
|
+
const figmaIcon = document.createElement("pds-icon");
|
|
2937
|
+
figmaIcon.setAttribute("icon", "brackets-curly");
|
|
2938
|
+
figmaIcon.setAttribute("size", "sm");
|
|
2939
|
+
const figmaLabel = document.createElement("span");
|
|
2940
|
+
figmaLabel.textContent = "Figma Tokens (JSON)";
|
|
2941
|
+
figmaLink.append(figmaIcon, figmaLabel);
|
|
2942
|
+
figmaItem.appendChild(figmaLink);
|
|
2943
|
+
|
|
2944
|
+
exportMenu.append(configItem, figmaItem);
|
|
2945
|
+
exportNav.append(exportButton, exportMenu);
|
|
2946
|
+
exportCard.appendChild(exportNav);
|
|
2947
|
+
|
|
2948
|
+
const resetCard = document.createElement("section");
|
|
2949
|
+
resetCard.className = "card surface-elevated stack-sm";
|
|
2950
|
+
|
|
2951
|
+
const resetTitle = document.createElement("h4");
|
|
2952
|
+
resetTitle.textContent = "Reset";
|
|
2953
|
+
resetCard.appendChild(resetTitle);
|
|
2954
|
+
|
|
2955
|
+
const resetButton = document.createElement("button");
|
|
2956
|
+
resetButton.type = "button";
|
|
2957
|
+
resetButton.className = "btn-outline";
|
|
2958
|
+
resetButton.textContent = "Reset Config";
|
|
2959
|
+
resetButton.addEventListener("click", () => {
|
|
2960
|
+
window.localStorage.removeItem("pure-ds-config");
|
|
2961
|
+
window.location.reload();
|
|
2962
|
+
});
|
|
2963
|
+
resetCard.appendChild(resetButton);
|
|
1867
2964
|
|
|
1868
2965
|
content.appendChild(presetCard);
|
|
1869
2966
|
content.appendChild(themeCard);
|
|
1870
|
-
content.appendChild(
|
|
2967
|
+
content.appendChild(configCard);
|
|
2968
|
+
content.appendChild(exportCard);
|
|
2969
|
+
content.appendChild(resetCard);
|
|
1871
2970
|
|
|
1872
2971
|
this._drawer.replaceChildren(header, content);
|
|
1873
2972
|
|
|
@@ -1878,6 +2977,10 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1878
2977
|
}
|
|
1879
2978
|
}
|
|
1880
2979
|
|
|
2980
|
+
async _handleExport(format) {
|
|
2981
|
+
await exportFromLiveEdit(format);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
1881
2984
|
async _handleFormSubmit(event, form) {
|
|
1882
2985
|
if (!form || typeof form.getValuesFlat !== "function") return;
|
|
1883
2986
|
|
|
@@ -1896,7 +2999,14 @@ class PdsLiveEdit extends HTMLElement {
|
|
|
1896
2999
|
}
|
|
1897
3000
|
|
|
1898
3001
|
// Apply the changes
|
|
1899
|
-
const
|
|
3002
|
+
const eventJson = event?.detail?.json;
|
|
3003
|
+
const hasEventPayload =
|
|
3004
|
+
eventJson && typeof eventJson === "object" && Object.keys(eventJson).length > 0;
|
|
3005
|
+
const flatValues = hasEventPayload
|
|
3006
|
+
? eventJson
|
|
3007
|
+
: typeof form._normalizeFlatValues === "function"
|
|
3008
|
+
? form._normalizeFlatValues(form.getValuesFlat())
|
|
3009
|
+
: form.getValuesFlat();
|
|
1900
3010
|
const patch = {};
|
|
1901
3011
|
Object.entries(flatValues || {}).forEach(([path, value]) => {
|
|
1902
3012
|
setValueAtJsonPath(patch, path, value);
|