@pure-ds/core 0.6.9 → 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.
Files changed (40) hide show
  1. package/custom-elements.json +71 -28
  2. package/dist/types/pds.d.ts +30 -0
  3. package/dist/types/public/assets/js/pds-manager.d.ts +2 -1
  4. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  5. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  6. package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
  7. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +1 -195
  8. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -1
  9. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +0 -2
  10. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  11. package/dist/types/src/js/pds-core/pds-config.d.ts +1306 -13
  12. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  13. package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
  14. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  15. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  16. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  17. package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
  18. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts +1 -4
  19. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  20. package/dist/types/src/js/pds.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/packages/pds-cli/bin/pds-static.js +16 -1
  23. package/public/assets/js/app.js +21 -21
  24. package/public/assets/js/pds-manager.js +291 -161
  25. package/public/assets/js/pds.js +16 -16
  26. package/public/assets/pds/components/pds-form.js +124 -27
  27. package/public/assets/pds/components/pds-live-edit.js +820 -122
  28. package/public/assets/pds/components/pds-omnibox.js +10 -18
  29. package/public/assets/pds/custom-elements.json +71 -28
  30. package/public/assets/pds/pds-css-complete.json +1 -6
  31. package/public/assets/pds/pds.css-data.json +5 -35
  32. package/src/js/pds-core/pds-config.js +822 -31
  33. package/src/js/pds-core/pds-enhancers-meta.js +11 -0
  34. package/src/js/pds-core/pds-enhancers.js +113 -5
  35. package/src/js/pds-core/pds-generator.js +183 -23
  36. package/src/js/pds-core/pds-live.js +177 -2
  37. package/src/js/pds-core/pds-ontology.js +6 -0
  38. package/src/js/pds-core/pds-start-helpers.js +14 -6
  39. package/src/js/pds.d.ts +30 -0
  40. package/src/js/pds.js +36 -60
@@ -188,14 +188,22 @@ ${EDITOR_TAG} {
188
188
  .${DROPDOWN_CLASS} menu {
189
189
  min-width: max-content;
190
190
  max-width: 350px;
191
+ margin: 0;
192
+ padding: 0;
193
+ list-style: none;
194
+ overflow: visible;
191
195
  }
192
196
  .${DROPDOWN_CLASS} .pds-live-editor-menu {
197
+ display: block;
198
+ background-color: var(--color-surface-base);
193
199
  padding: var(--spacing-1);
194
200
  max-width: 350px;
195
201
  padding-bottom: 0;
202
+ overflow: visible;
196
203
  }
197
204
  .${DROPDOWN_CLASS} .pds-live-editor-form-container {
198
205
  padding-bottom: var(--spacing-2);
206
+ overflow: visible;
199
207
  }
200
208
  .${DROPDOWN_CLASS} .pds-live-editor-title {
201
209
  display: block;
@@ -589,6 +597,62 @@ function toColorInputValue(value) {
589
597
  return hexValue || value;
590
598
  }
591
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
+
592
656
  function getCustomPropertyNames(style) {
593
657
  const names = [];
594
658
  if (!style) return names;
@@ -929,7 +993,92 @@ function splitFontFamilyStack(value) {
929
993
  return parts;
930
994
  }
931
995
 
932
- function getPresetFontFamilyVariations() {
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()) {
933
1082
  const presets = Object.values(PDS?.presets || {});
934
1083
  const seen = new Set();
935
1084
  const items = [];
@@ -938,9 +1087,11 @@ function getPresetFontFamilyVariations() {
938
1087
  if (!normalized || seen.has(normalized)) return;
939
1088
  seen.add(normalized);
940
1089
  items.push({
1090
+ //index: items.length,
941
1091
  id: normalized,
1092
+ value: normalized,
942
1093
  text: normalized,
943
- style: `font-family: ${normalized}`,
1094
+ style: `font-family: ${normalized}; font-size: ${previewFontSize};`,
944
1095
  });
945
1096
  };
946
1097
 
@@ -959,35 +1110,95 @@ function getPresetFontFamilyVariations() {
959
1110
  });
960
1111
  });
961
1112
 
962
- return items;
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)));
963
1163
  }
964
1164
 
965
1165
  function buildFontFamilyOmniboxSettings() {
966
- const allItems = getPresetFontFamilyVariations();
967
- const filterItems = (search) => {
1166
+ const filterItems = (items, search) => {
968
1167
  const query = String(search || "").trim().toLowerCase();
969
- if (!query) return allItems;
970
- return allItems.filter((item) => item.text.toLowerCase().includes(query));
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
+ });
971
1175
  };
972
1176
 
973
- return {
1177
+
1178
+ return {
1179
+ //debug: true,
1180
+ itemGrid: "0 1fr 0",
974
1181
  hideCategory: true,
975
- iconHandler: (item) => {
976
-
1182
+ iconHandler: (item) => {
977
1183
  return "";
978
1184
  },
979
1185
  categories: {
980
1186
  FontFamilies: {
981
1187
  trigger: () => true,
982
- getItems: (options) => filterItems(options?.search),
983
- action: (options, ev) => {
984
- const input = document.querySelector("[name='/typography/fontFamilyHeadings']");
1188
+ getItems: async (options) => {
1189
+ const previewFontSize = resolveFontFamilyPreviewFontSize(options?.control);
1190
+ const allItems = getPresetFontFamilyVariations(previewFontSize);
1191
+ const items = filterItems(allItems, options?.search);
985
1192
 
986
- if (input && input.tagName === "PDS-OMNIBOX") {
987
- input.value = options?.text || "";
988
-
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;
989
1201
  }
990
- return options?.text || options?.id;
991
1202
  },
992
1203
  },
993
1204
  },
@@ -1034,7 +1245,7 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
1034
1245
  };
1035
1246
 
1036
1247
  const isColorValue = (value, path) => {
1037
- if (String(path || "").toLowerCase().startsWith("colors.")) return true;
1248
+ if (isColorPath(path)) return true;
1038
1249
  if (typeof value !== "string") return false;
1039
1250
  return /^#([0-9a-f]{3,8})$/i.test(value) || /^rgba?\(/i.test(value) || /^hsla?\(/i.test(value);
1040
1251
  };
@@ -1064,8 +1275,12 @@ function buildSchemaFromPaths(paths, design, hints = {}) {
1064
1275
  const value = getValueAtPath(design, [category, ...rest]);
1065
1276
  const hintValue = hints[path];
1066
1277
  const enumOptions = getEnumOptions(path);
1067
- const normalizedValue = normalizeEnumValue(path, value);
1068
- const normalizedHint = normalizeEnumValue(path, hintValue);
1278
+ const resolvedColorValue = resolveColorValueForPath(path, value, hintValue);
1279
+ const normalizedValue = normalizeEnumValue(path, resolvedColorValue ?? value);
1280
+ const normalizedHint = normalizeEnumValue(
1281
+ path,
1282
+ resolvedColorValue ?? hintValue,
1283
+ );
1069
1284
  const inferredType = Array.isArray(value)
1070
1285
  ? "array"
1071
1286
  : value === null
@@ -1232,6 +1447,10 @@ async function applyDesignPatch(patch) {
1232
1447
  const nextOptions = { ...currentOptions, design: nextDesign };
1233
1448
  if (resolvedPresetId) nextOptions.preset = resolvedPresetId;
1234
1449
 
1450
+ try {
1451
+ await loadTypographyFontsForDesign(nextDesign?.typography);
1452
+ } catch (e) {}
1453
+
1235
1454
  const nextGenerator = new Generator(nextOptions);
1236
1455
  if (PDS?.applyStyles) {
1237
1456
  await PDS.applyStyles(nextGenerator);
@@ -1307,6 +1526,17 @@ function getActivePresetId() {
1307
1526
  return stored?.preset || PDS?.currentConfig?.preset || PDS?.currentPreset || null;
1308
1527
  }
1309
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
+
1310
1540
  async function applyPresetSelection(presetId) {
1311
1541
  if (!presetId) return;
1312
1542
  setStoredConfig({
@@ -1471,13 +1701,51 @@ function setFormSchemas(form, schema, uiSchema, design) {
1471
1701
  form.values = shallowClone(design);
1472
1702
  }
1473
1703
 
1474
- async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
1475
- const { schema, uiSchema } = buildSchemaFromPaths(paths, design, hints);
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) {}
1476
1730
 
1477
- if (!customElements.get("pds-form")) {
1478
- await customElements.whenDefined("pds-form");
1731
+ if (!customElements.get(tagName)) {
1732
+ throw new Error(`<${tagName}> is not defined`);
1479
1733
  }
1480
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
+
1481
1749
  const form = document.createElement("pds-form");
1482
1750
  const fontFamilyOmniboxSettings = buildFontFamilyOmniboxSettings();
1483
1751
  form.setAttribute("hide-actions", "");
@@ -1488,16 +1756,38 @@ async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
1488
1756
  enhancements: {
1489
1757
  rangeOutput: true,
1490
1758
  },
1759
+ ...(formOptions && typeof formOptions === "object" ? formOptions : {}),
1491
1760
  };
1761
+
1492
1762
  form.defineRenderer(
1493
1763
  "font-family-omnibox",
1494
1764
  ({ id, path, value, attrs, set }) => {
1495
- const resolveSelectedValue = (options, actionResult) => {
1765
+ const resolveSelectedValue = (options, actionResult, selectionEvent) => {
1496
1766
  if (typeof actionResult === "string" && actionResult.trim()) {
1497
1767
  return actionResult;
1498
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
+
1499
1782
  const fromText = String(options?.text || "").trim();
1500
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
+
1501
1791
  return String(options?.id || "").trim();
1502
1792
  };
1503
1793
 
@@ -1509,15 +1799,36 @@ async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
1509
1799
  categoryName,
1510
1800
  {
1511
1801
  ...categoryConfig,
1512
- action: (options) => {
1802
+ action: (...args) => {
1803
+ const [options, selectionEvent] = args;
1513
1804
  const actionResult =
1514
1805
  typeof originalAction === "function"
1515
- ? originalAction(options)
1806
+ ? originalAction(...args)
1516
1807
  : undefined;
1517
- const selected = resolveSelectedValue(options, actionResult);
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
+ );
1518
1828
  if (selected) {
1519
1829
  set(selected);
1520
1830
  }
1831
+
1521
1832
  return actionResult;
1522
1833
  },
1523
1834
  },
@@ -1529,8 +1840,11 @@ async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
1529
1840
  const omnibox = document.createElement("pds-omnibox");
1530
1841
  omnibox.id = id;
1531
1842
  omnibox.setAttribute("name", path);
1532
- omnibox.setAttribute("item-grid", "0 1fr");
1533
- omnibox.setAttribute("placeholder", attrs?.placeholder || "Select a font family");
1843
+ omnibox.setAttribute("item-grid", "0 1fr 0");
1844
+ omnibox.setAttribute(
1845
+ "placeholder",
1846
+ attrs?.placeholder || "Select a font family"
1847
+ );
1534
1848
  omnibox.value = value ?? "";
1535
1849
  omnibox.settings = {
1536
1850
  ...fontFamilyOmniboxSettings,
@@ -1542,64 +1856,364 @@ async function buildForm(paths, design, onSubmit, onUndo, hints = {}) {
1542
1856
  omnibox.addEventListener("change", (event) => {
1543
1857
  set(event?.target?.value ?? omnibox.value ?? "");
1544
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
+ });
1545
1865
  return omnibox;
1546
1866
  }
1547
1867
  );
1868
+
1548
1869
  form.addEventListener("pw:submit", onSubmit);
1549
- const values = shallowClone(design || {});
1550
- Object.keys(ENUM_FIELD_OPTIONS).forEach((path) => {
1551
- const normalized = normalizeEnumValue(path, getValueAtPath(values, path.split(".")));
1552
- if (normalized !== undefined) {
1553
- setValueAtPath(values, path.split("."), normalized);
1554
- }
1555
- });
1556
- Object.entries(hints || {}).forEach(([path, hintValue]) => {
1557
- const segments = path.split(".");
1558
- const currentValue = getValueAtPath(values, segments);
1559
- if (currentValue === undefined || currentValue === null) {
1560
- setValueAtPath(values, segments, hintValue);
1561
- }
1562
- });
1563
- setFormSchemas(form, schema, uiSchema, values);
1870
+ if (typeof normalizeFlatValues === "function") {
1871
+ form._normalizeFlatValues = normalizeFlatValues;
1872
+ }
1873
+ setFormSchemas(form, schema, uiSchema, values || {});
1564
1874
 
1565
- // Apply button (will trigger form submit programmatically)
1566
1875
  const applyBtn = document.createElement("button");
1567
1876
  applyBtn.className = "btn-primary btn-sm";
1568
1877
  applyBtn.type = "button";
1569
1878
  applyBtn.textContent = "Apply";
1570
1879
  applyBtn.addEventListener("click", async () => {
1571
- // Manually trigger pw:submit event for pds-form
1572
1880
  if (typeof form.getValuesFlat === "function") {
1573
- // Wait for form to be ready if it's still loading
1574
1881
  if (!customElements.get("pds-form")) {
1575
1882
  await customElements.whenDefined("pds-form");
1576
1883
  }
1577
-
1578
- const flatValues = form.getValuesFlat();
1884
+
1885
+ const flatValues =
1886
+ typeof form._normalizeFlatValues === "function"
1887
+ ? form._normalizeFlatValues(form.getValuesFlat())
1888
+ : form.getValuesFlat();
1579
1889
  const event = new CustomEvent("pw:submit", {
1580
1890
  detail: {
1581
1891
  json: flatValues,
1582
1892
  formData: new FormData(),
1583
1893
  valid: true,
1584
- issues: []
1894
+ issues: [],
1585
1895
  },
1586
1896
  bubbles: true,
1587
- cancelable: true
1897
+ cancelable: true,
1588
1898
  });
1589
1899
  form.dispatchEvent(event);
1590
1900
  }
1591
1901
  });
1592
1902
 
1593
- // Undo button
1594
1903
  const undoBtn = document.createElement("button");
1595
- undoBtn.className = "btn-secondary btn-sm";
1904
+ undoBtn.className = "btn-secondary btn-sm icon-only";
1596
1905
  undoBtn.type = "button";
1597
- undoBtn.textContent = "Undo";
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);
1598
1912
  undoBtn.addEventListener("click", onUndo);
1599
1913
 
1600
1914
  return { form, applyBtn, undoBtn };
1601
1915
  }
1602
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
+
1603
2217
  class PdsLiveEdit extends HTMLElement {
1604
2218
  constructor() {
1605
2219
  super();
@@ -2017,7 +2631,7 @@ class PdsLiveEdit extends HTMLElement {
2017
2631
  button.appendChild(icon);
2018
2632
 
2019
2633
  const menu = document.createElement("menu");
2020
- const quickItem = document.createElement("li");
2634
+ const quickItem = document.createElement("div");
2021
2635
  quickItem.className = "pds-live-editor-menu";
2022
2636
 
2023
2637
  const header = document.createElement("div");
@@ -2063,13 +2677,28 @@ class PdsLiveEdit extends HTMLElement {
2063
2677
  container.replaceChildren();
2064
2678
  footer.replaceChildren();
2065
2679
 
2066
- const { form, applyBtn, undoBtn } = await buildForm(
2067
- paths,
2068
- design,
2069
- (event) => this._handleFormSubmit(event, form),
2070
- () => this._handleUndo(),
2071
- hints
2072
- );
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
+ }
2073
2702
 
2074
2703
  // Store reference to undo button for enabling/disabling
2075
2704
  form._undoBtn = undoBtn;
@@ -2137,26 +2766,72 @@ class PdsLiveEdit extends HTMLElement {
2137
2766
  presetText.textContent = "Choose a base style";
2138
2767
  presetLabel.appendChild(presetText);
2139
2768
 
2140
- const presetSelect = document.createElement("select");
2141
- const presetOptions = getPresetOptions();
2142
- const activePreset = getActivePresetId();
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...");
2143
2776
 
2144
- presetOptions.forEach((preset) => {
2145
- const option = document.createElement("option");
2146
- option.value = preset.id;
2147
- option.textContent = preset.name;
2148
- if (String(preset.id) === String(activePreset)) {
2149
- option.selected = true;
2777
+ const activePresetId = getActivePresetId();
2778
+ const activePresetName = getPresetNameById(activePresetId);
2779
+ if (activePresetName) {
2780
+ presetOmnibox.value = activePresetName;
2150
2781
  }
2151
- presetSelect.appendChild(option);
2152
- });
2153
2782
 
2154
- presetSelect.addEventListener("change", async (event) => {
2155
- const nextPreset = event.target?.value;
2156
- await applyPresetSelection(nextPreset);
2157
- });
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
+ }
2158
2834
 
2159
- presetLabel.appendChild(presetSelect);
2160
2835
  presetCard.appendChild(presetLabel);
2161
2836
 
2162
2837
  const themeCard = document.createElement("section");
@@ -2169,6 +2844,48 @@ class PdsLiveEdit extends HTMLElement {
2169
2844
  const themeToggle = document.createElement("pds-theme");
2170
2845
  themeCard.appendChild(themeToggle);
2171
2846
 
2847
+ const configCard = document.createElement("section");
2848
+ configCard.className = "card surface-elevated stack-sm";
2849
+
2850
+ const configTitle = document.createElement("h4");
2851
+ configTitle.textContent = "Configuration";
2852
+ configCard.appendChild(configTitle);
2853
+
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
+
2172
2889
  const exportCard = document.createElement("section");
2173
2890
  exportCard.className = "card surface-elevated stack-sm";
2174
2891
 
@@ -2228,54 +2945,28 @@ class PdsLiveEdit extends HTMLElement {
2228
2945
  exportNav.append(exportButton, exportMenu);
2229
2946
  exportCard.appendChild(exportNav);
2230
2947
 
2231
- const searchCard = document.createElement("section");
2232
- searchCard.className = "card surface-elevated stack-sm";
2948
+ const resetCard = document.createElement("section");
2949
+ resetCard.className = "card surface-elevated stack-sm";
2233
2950
 
2234
- const searchTitle = document.createElement("h4");
2235
- searchTitle.textContent = "Search PDS";
2236
- searchCard.appendChild(searchTitle);
2951
+ const resetTitle = document.createElement("h4");
2952
+ resetTitle.textContent = "Reset";
2953
+ resetCard.appendChild(resetTitle);
2237
2954
 
2238
- const omnibox = document.createElement("pds-omnibox");
2239
- omnibox.setAttribute("placeholder", "Search tokens, utilities, components...");
2240
- omnibox.settings = {
2241
- iconHandler: (item) => {
2242
- return item.icon ? `<pds-icon icon="${item.icon}"></pds-icon>` : null;
2243
- },
2244
- categories: {
2245
- Query: {
2246
- trigger: (options) => options.search.length >= 2,
2247
- getItems: async (options) => {
2248
- const query = (options.search || "").trim();
2249
- if (!query) return [];
2250
- try {
2251
- const results = await PDS.query(query);
2252
- return (results || []).map((result) => ({
2253
- text: result.text,
2254
- id: result.value,
2255
- icon: result.icon || "magnifying-glass",
2256
- category: result.category,
2257
- code: result.code,
2258
- }));
2259
- } catch (error) {
2260
- console.warn("Omnibox query failed:", error);
2261
- return [];
2262
- }
2263
- },
2264
- action: async (options) => {
2265
- if (options?.code && navigator.clipboard) {
2266
- await navigator.clipboard.writeText(options.code);
2267
- await PDS.toast("Copied token to clipboard", { type: "success" });
2268
- }
2269
- },
2270
- },
2271
- },
2272
- };
2273
- searchCard.appendChild(omnibox);
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);
2274
2964
 
2275
2965
  content.appendChild(presetCard);
2276
2966
  content.appendChild(themeCard);
2967
+ content.appendChild(configCard);
2277
2968
  content.appendChild(exportCard);
2278
- content.appendChild(searchCard);
2969
+ content.appendChild(resetCard);
2279
2970
 
2280
2971
  this._drawer.replaceChildren(header, content);
2281
2972
 
@@ -2308,7 +2999,14 @@ class PdsLiveEdit extends HTMLElement {
2308
2999
  }
2309
3000
 
2310
3001
  // Apply the changes
2311
- const flatValues = form.getValuesFlat();
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();
2312
3010
  const patch = {};
2313
3011
  Object.entries(flatValues || {}).forEach(([path, value]) => {
2314
3012
  setValueAtJsonPath(patch, path, value);