@pure-ds/core 0.6.10 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.github/copilot-instructions.md +6 -1
  2. package/custom-elements.json +803 -16
  3. package/dist/types/pds.d.ts +1 -0
  4. package/dist/types/public/assets/js/pds-ask.d.ts +2 -0
  5. package/dist/types/public/assets/js/pds-ask.d.ts.map +1 -0
  6. package/dist/types/public/assets/js/pds-auto-definer.d.ts +14 -0
  7. package/dist/types/public/assets/js/pds-auto-definer.d.ts.map +1 -0
  8. package/dist/types/public/assets/js/pds-autocomplete.d.ts +79 -0
  9. package/dist/types/public/assets/js/pds-autocomplete.d.ts.map +1 -0
  10. package/dist/types/public/assets/js/pds-enhancers.d.ts +7 -0
  11. package/dist/types/public/assets/js/pds-enhancers.d.ts.map +1 -0
  12. package/dist/types/public/assets/js/pds-manager.d.ts +98 -1
  13. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  14. package/dist/types/public/assets/js/pds-toast.d.ts +8 -0
  15. package/dist/types/public/assets/js/pds-toast.d.ts.map +1 -0
  16. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-drawer.d.ts +1 -143
  18. package/dist/types/public/assets/pds/components/pds-drawer.d.ts.map +1 -1
  19. package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
  20. package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -1
  21. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts +8 -0
  22. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts.map +1 -0
  23. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts +2 -0
  24. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts.map +1 -0
  25. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts +2 -0
  26. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts.map +1 -0
  27. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  28. package/dist/types/public/assets/pds/components/pds-richtext.d.ts.map +1 -1
  29. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts +1 -63
  30. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts.map +1 -1
  31. package/dist/types/public/assets/pds/components/pds-splitpanel.d.ts +1 -89
  32. package/dist/types/public/assets/pds/components/pds-splitpanel.d.ts.map +1 -1
  33. package/dist/types/public/assets/pds/components/pds-theme.d.ts +1 -22
  34. package/dist/types/public/assets/pds/components/pds-theme.d.ts.map +1 -1
  35. package/dist/types/public/assets/pds/components/pds-toaster.d.ts +1 -1
  36. package/dist/types/public/assets/pds/components/pds-toaster.d.ts.map +1 -1
  37. package/dist/types/public/assets/pds/components/pds-treeview.d.ts +37 -0
  38. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -0
  39. package/dist/types/public/assets/pds/components/pds-upload.d.ts.map +1 -1
  40. package/dist/types/src/js/common/ask.d.ts.map +1 -1
  41. package/dist/types/src/js/common/toast.d.ts +8 -0
  42. package/dist/types/src/js/common/toast.d.ts.map +1 -1
  43. package/dist/types/src/js/pds-ask.d.ts +2 -0
  44. package/dist/types/src/js/pds-ask.d.ts.map +1 -0
  45. package/dist/types/src/js/pds-auto-definer.d.ts +2 -0
  46. package/dist/types/src/js/pds-auto-definer.d.ts.map +1 -0
  47. package/dist/types/src/js/pds-autocomplete.d.ts +2 -0
  48. package/dist/types/src/js/pds-autocomplete.d.ts.map +1 -0
  49. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  50. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  51. package/dist/types/src/js/pds-core/pds-live.d.ts +2 -1
  52. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  53. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  54. package/dist/types/src/js/pds-enhancers.d.ts +2 -0
  55. package/dist/types/src/js/pds-enhancers.d.ts.map +1 -0
  56. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts +66 -0
  57. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts.map +1 -0
  58. package/dist/types/src/js/pds-live-manager/import-contract.d.ts +15 -0
  59. package/dist/types/src/js/pds-live-manager/import-contract.d.ts.map +1 -0
  60. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts +32 -0
  61. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts.map +1 -0
  62. package/dist/types/src/js/pds-live-manager/import-service.d.ts +21 -0
  63. package/dist/types/src/js/pds-live-manager/import-service.d.ts.map +1 -0
  64. package/dist/types/src/js/pds-live-manager/template-service.d.ts +17 -0
  65. package/dist/types/src/js/pds-live-manager/template-service.d.ts.map +1 -0
  66. package/dist/types/src/js/pds-manager.d.ts +4 -0
  67. package/dist/types/src/js/pds-toast.d.ts +2 -0
  68. package/dist/types/src/js/pds-toast.d.ts.map +1 -0
  69. package/dist/types/src/js/pds.d.ts.map +1 -1
  70. package/package.json +11 -5
  71. package/packages/pds-cli/README.md +60 -0
  72. package/packages/pds-cli/bin/pds-import.js +176 -0
  73. package/packages/pds-cli/bin/pds-static.js +27 -1
  74. package/packages/pds-cli/bin/postinstall.mjs +17 -8
  75. package/packages/pds-cli/bin/templates/bootstrap/pds.config.js +1 -5
  76. package/packages/pds-cli/bin/templates/bootstrap/public/index.html +2 -1
  77. package/packages/pds-cli/bin/templates/starter-templates.js +1 -3
  78. package/public/assets/js/app.js +9 -163
  79. package/public/assets/js/pds-ask.js +25 -0
  80. package/public/assets/js/pds-auto-definer.js +1 -0
  81. package/public/assets/js/pds-autocomplete.js +7 -0
  82. package/public/assets/js/pds-enhancers.js +1 -0
  83. package/public/assets/js/pds-manager.js +370 -267
  84. package/public/assets/js/pds-toast.js +1 -0
  85. package/public/assets/js/pds.js +2 -32
  86. package/public/assets/pds/components/pds-calendar.js +2 -2
  87. package/public/assets/pds/components/pds-drawer.js +1 -1
  88. package/public/assets/pds/components/pds-form.js +7 -6
  89. package/public/assets/pds/components/pds-icon.js +12 -9
  90. package/public/assets/pds/components/pds-live-converter.js +47 -0
  91. package/public/assets/pds/components/pds-live-edit.js +758 -44
  92. package/public/assets/pds/components/pds-live-importer.js +773 -0
  93. package/public/assets/pds/components/pds-live-template-canvas.js +172 -0
  94. package/public/assets/pds/components/pds-omnibox.js +147 -3
  95. package/public/assets/pds/components/pds-richtext.js +2 -0
  96. package/public/assets/pds/components/pds-scrollrow.js +61 -2
  97. package/public/assets/pds/components/pds-splitpanel.js +3 -1
  98. package/public/assets/pds/components/pds-theme.js +2 -0
  99. package/public/assets/pds/components/pds-toaster.js +52 -5
  100. package/public/assets/pds/components/pds-treeview.js +974 -0
  101. package/public/assets/pds/components/pds-upload.js +2 -0
  102. package/public/assets/pds/core/pds-ask.js +25 -0
  103. package/public/assets/pds/core/pds-auto-definer.js +1 -0
  104. package/public/assets/pds/core/pds-autocomplete.js +7 -0
  105. package/public/assets/pds/core/pds-enhancers.js +1 -0
  106. package/public/assets/pds/core/pds-manager.js +3646 -0
  107. package/public/assets/pds/core/pds-toast.js +1 -0
  108. package/public/assets/pds/core.js +2 -0
  109. package/public/assets/pds/custom-elements.json +803 -16
  110. package/public/assets/pds/pds-css-complete.json +7 -2
  111. package/public/assets/pds/templates/commerce-scroll-explorer.html +115 -0
  112. package/public/assets/pds/templates/content-brand-showcase.html +110 -0
  113. package/public/assets/pds/templates/feedback-ops-dashboard.html +91 -0
  114. package/public/assets/pds/templates/release-readiness-radar.html +69 -0
  115. package/public/assets/pds/templates/support-command-center.html +92 -0
  116. package/public/assets/pds/templates/templates.json +53 -0
  117. package/public/assets/pds/templates/workspace-settings-lab.html +131 -0
  118. package/public/assets/pds/vscode-custom-data.json +54 -4
  119. package/readme.md +38 -1
  120. package/src/js/pds-core/pds-config.js +9 -9
  121. package/src/js/pds-core/pds-enhancers.js +146 -0
  122. package/src/js/pds-core/pds-generator.js +170 -29
  123. package/src/js/pds-core/pds-live.js +456 -13
  124. package/src/js/pds-core/pds-start-helpers.js +5 -1
  125. package/src/js/pds-live-manager/conversion-service.js +3135 -0
  126. package/src/js/pds-live-manager/import-contract.js +57 -0
  127. package/src/js/pds-live-manager/import-history-service.js +145 -0
  128. package/src/js/pds-live-manager/import-service.js +255 -0
  129. package/src/js/pds-live-manager/tailwind-conversion-rules.json +383 -0
  130. package/src/js/pds-live-manager/template-service.js +170 -0
  131. package/src/js/pds.d.ts +1 -0
  132. package/src/js/pds.js +192 -12
@@ -1,4 +1,4 @@
1
- const PDS = globalThis.PDS;
1
+ import { PDS } from "#pds";
2
2
 
3
3
  const EDITOR_TAG = "pds-live-edit";
4
4
  const STYLE_ID = "pds-live-editor-styles";
@@ -96,9 +96,42 @@ const QUICK_STYLE_PROPERTIES = [
96
96
  "width",
97
97
  ];
98
98
 
99
+ const LIVE_EDIT_HIGHLIGHT_BLACKLIST = [
100
+ {
101
+ id: "toaster",
102
+ selector: "pds-toaster",
103
+ includeDescendants: true,
104
+ },
105
+ {
106
+ id: "live-edit-toggle",
107
+ selector: "#pds-live-edit-toggle",
108
+ includeDescendants: true,
109
+ },
110
+ {
111
+ id: "ask-dialog",
112
+ selector: "dialog",
113
+ includeDescendants: true,
114
+ },
115
+ ];
116
+
117
+ function isLiveEditHighlightBlacklisted(node) {
118
+ if (!(node instanceof Element)) return false;
119
+ return LIVE_EDIT_HIGHLIGHT_BLACKLIST.some((entry) => {
120
+ if (!entry?.selector) return false;
121
+ if (entry.includeDescendants) {
122
+ return Boolean(node.closest(entry.selector));
123
+ }
124
+ return node.matches(entry.selector);
125
+ });
126
+ }
127
+
99
128
  const INLINE_VAR_REGEX = /var\(\s*(--[^)\s,]+)\s*/g;
100
129
  const CUSTOM_PROP_REGEX = /--.+/;
101
130
  const COLOR_VALUE_REGEX = /#(?:[0-9a-f]{3,8})\b|rgba?\([^)]*\)|hsla?\([^)]*\)/gi;
131
+ const SHIKI_BUNDLE_URL = "https://esm.sh/shiki@1.29.2?bundle";
132
+ const SETTINGS_ONLY_ATTR = "data-pds-live-settings-only";
133
+
134
+ let shikiModulePromise = null;
102
135
 
103
136
  const ENUM_FIELD_OPTIONS = {
104
137
  "shape.borderWidth": ["hairline", "thin", "medium", "thick"],
@@ -108,6 +141,47 @@ let cachedTokenIndex = null;
108
141
  let cachedTokenIndexMeta = null;
109
142
  let colorNormalizer = null;
110
143
 
144
+ function escapeHtml(value) {
145
+ return String(value || "")
146
+ .replaceAll("&", "&")
147
+ .replaceAll("<", "&lt;")
148
+ .replaceAll(">", "&gt;")
149
+ .replaceAll('"', "&quot;")
150
+ .replaceAll("'", "&#39;");
151
+ }
152
+
153
+ async function loadShikiModule() {
154
+ if (shikiModulePromise) return shikiModulePromise;
155
+ shikiModulePromise = import(SHIKI_BUNDLE_URL).catch(() => null);
156
+ return shikiModulePromise;
157
+ }
158
+
159
+ function resolveShikiTheme() {
160
+ const isDark =
161
+ PDS?.theme === "dark" ||
162
+ document.documentElement.getAttribute("data-theme") === "dark" ||
163
+ document.documentElement.classList.contains("theme-dark");
164
+ return isDark ? "github-dark-default" : "github-light-default";
165
+ }
166
+
167
+ async function renderHtmlWithShiki(code = "") {
168
+ const fallback = `<pre><code>${escapeHtml(code)}</code></pre>`;
169
+ const shiki = await loadShikiModule();
170
+
171
+ if (!shiki || typeof shiki.codeToHtml !== "function") {
172
+ return fallback;
173
+ }
174
+
175
+ try {
176
+ return shiki.codeToHtml(String(code || ""), {
177
+ lang: "html",
178
+ theme: resolveShikiTheme(),
179
+ });
180
+ } catch (error) {
181
+ return fallback;
182
+ }
183
+ }
184
+
111
185
  const GLOBAL_LAYOUT_PATHS = new Set([
112
186
  "layout.maxWidth",
113
187
  "layout.maxWidths",
@@ -138,6 +212,8 @@ const DARK_MODE_PATH_MARKER = ".darkMode.";
138
212
  const QUICK_EDIT_LIMIT = 4;
139
213
  const DROPDOWN_VIEWPORT_PADDING = 8;
140
214
  const FONT_FAMILY_PATH_REGEX = /^typography\.fontFamily/i;
215
+ const FORM_THEME_CONTEXT_FIELD = "__pdsThemeContext";
216
+ const FORM_THEME_CONTEXT_POINTER = `/${FORM_THEME_CONTEXT_FIELD}`;
141
217
 
142
218
  function isHoverCapable() {
143
219
  if (typeof window === "undefined" || !window.matchMedia) return false;
@@ -191,7 +267,6 @@ ${EDITOR_TAG} {
191
267
  margin: 0;
192
268
  padding: 0;
193
269
  list-style: none;
194
- overflow: visible;
195
270
  }
196
271
  .${DROPDOWN_CLASS} .pds-live-editor-menu {
197
272
  display: block;
@@ -199,11 +274,9 @@ ${EDITOR_TAG} {
199
274
  padding: var(--spacing-1);
200
275
  max-width: 350px;
201
276
  padding-bottom: 0;
202
- overflow: visible;
203
277
  }
204
278
  .${DROPDOWN_CLASS} .pds-live-editor-form-container {
205
279
  padding-bottom: var(--spacing-2);
206
- overflow: visible;
207
280
  }
208
281
  .${DROPDOWN_CLASS} .pds-live-editor-title {
209
282
  display: block;
@@ -238,9 +311,42 @@ ${EDITOR_TAG} {
238
311
  background: var(--color-surface-base);
239
312
  position: sticky;
240
313
  justify-content: space-between;
314
+ align-items: center;
241
315
  bottom: 0;
242
316
  z-index: 1;
243
317
  }
318
+ .pds-live-editor-drawer-footer {
319
+ position: sticky;
320
+ bottom: 0;
321
+ z-index: 1;
322
+ padding-top: var(--spacing-3);
323
+ padding-bottom: env(safe-area-inset-bottom, 0);
324
+ border-top: var(--border-width-thin) solid var(--color-border);
325
+ background: var(--color-surface-base);
326
+ }
327
+ .pds-live-editor-drawer-footer > button {
328
+ width: 100%;
329
+ justify-content: center;
330
+ }
331
+ .pds-live-editor-drawer-footer .pds-live-editor-reset-btn {
332
+ color: var(--color-danger-700);
333
+ border-color: var(--color-danger-700);
334
+ }
335
+ .pds-live-editor-drawer-footer .pds-live-editor-reset-btn:hover,
336
+ .pds-live-editor-drawer-footer .pds-live-editor-reset-btn:focus-visible,
337
+ .pds-live-editor-drawer-footer .pds-live-editor-reset-btn:active {
338
+ color: var(--color-danger-700);
339
+ border-color: var(--color-danger-700);
340
+ }
341
+ .pds-live-editor-drawer-content {
342
+ min-height: 100%;
343
+ display: grid;
344
+ grid-template-rows: minmax(0, 1fr) auto;
345
+ gap: var(--spacing-4);
346
+ }
347
+ .pds-live-editor-drawer-content > .accordion {
348
+ min-height: 0;
349
+ }
244
350
  `;
245
351
  document.head.appendChild(style);
246
352
  }
@@ -319,6 +425,14 @@ function deepMerge(target = {}, source = {}) {
319
425
  return out;
320
426
  }
321
427
 
428
+ function markNodeTreeAsLiveEditIgnored(node) {
429
+ if (!(node instanceof Element)) return;
430
+ node.setAttribute("data-pds-live-edit-ignore", "true");
431
+ node.querySelectorAll("*").forEach((element) => {
432
+ element.setAttribute("data-pds-live-edit-ignore", "true");
433
+ });
434
+ }
435
+
322
436
  function titleize(value) {
323
437
  return String(value)
324
438
  .replace(/([a-z])([A-Z])/g, "$1 $2")
@@ -1010,6 +1124,7 @@ const GENERIC_FONT_FAMILIES = new Set([
1010
1124
  ]);
1011
1125
 
1012
1126
  let loadGoogleFontFnPromise = null;
1127
+ let managerModulePromise = null;
1013
1128
 
1014
1129
  function normalizeFontName(fontFamily) {
1015
1130
  return String(fontFamily || "")
@@ -1030,6 +1145,19 @@ async function getLoadGoogleFontFn() {
1030
1145
  }
1031
1146
  if (loadGoogleFontFnPromise) return loadGoogleFontFnPromise;
1032
1147
  loadGoogleFontFnPromise = (async () => {
1148
+ const manager = await getPdsManagerModule();
1149
+ if (typeof manager?.loadGoogleFont === "function") {
1150
+ return manager.loadGoogleFont;
1151
+ }
1152
+ return null;
1153
+ })();
1154
+ return loadGoogleFontFnPromise;
1155
+ }
1156
+
1157
+ async function getPdsManagerModule() {
1158
+ if (managerModulePromise) return managerModulePromise;
1159
+
1160
+ managerModulePromise = (async () => {
1033
1161
  const candidates = [
1034
1162
  PDS?.currentConfig?.managerURL,
1035
1163
  "../core/pds-manager.js",
@@ -1043,14 +1171,13 @@ async function getLoadGoogleFontFn() {
1043
1171
  if (attempted.has(resolved)) continue;
1044
1172
  attempted.add(resolved);
1045
1173
  const mod = await import(resolved);
1046
- if (typeof mod?.loadGoogleFont === "function") {
1047
- return mod.loadGoogleFont;
1048
- }
1174
+ if (mod && typeof mod === "object") return mod;
1049
1175
  } catch (e) {}
1050
1176
  }
1051
1177
  return null;
1052
1178
  })();
1053
- return loadGoogleFontFnPromise;
1179
+
1180
+ return managerModulePromise;
1054
1181
  }
1055
1182
 
1056
1183
  async function loadTypographyFontsForDesign(typography) {
@@ -1696,9 +1823,11 @@ async function exportFromLiveEdit(format) {
1696
1823
  }
1697
1824
 
1698
1825
  function setFormSchemas(form, schema, uiSchema, design) {
1699
- form.jsonSchema = schema;
1700
- form.uiSchema = uiSchema;
1701
- form.values = shallowClone(design);
1826
+ const themedSchema = withThemeConditionalSchema(schema, uiSchema);
1827
+ const formValues = withThemeContextValues(design);
1828
+ form.jsonSchema = themedSchema.schema;
1829
+ form.uiSchema = themedSchema.uiSchema;
1830
+ form.values = formValues;
1702
1831
  }
1703
1832
 
1704
1833
  async function waitForElementDefinition(tagName, timeoutMs = 4000) {
@@ -1970,6 +2099,106 @@ function deepClone(value) {
1970
2099
  return JSON.parse(JSON.stringify(value));
1971
2100
  }
1972
2101
 
2102
+ function withThemeContextValues(values) {
2103
+ const nextValues = shallowClone(values || {});
2104
+ nextValues[FORM_THEME_CONTEXT_FIELD] = getActiveTheme().value;
2105
+ return nextValues;
2106
+ }
2107
+
2108
+ function mergeVisibleWhenCondition(existingCondition, conditionToAdd) {
2109
+ if (!existingCondition) return deepClone(conditionToAdd);
2110
+ if (
2111
+ existingCondition &&
2112
+ typeof existingCondition === "object" &&
2113
+ Array.isArray(existingCondition.$and)
2114
+ ) {
2115
+ return {
2116
+ ...deepClone(existingCondition),
2117
+ $and: [...existingCondition.$and.map((entry) => deepClone(entry)), deepClone(conditionToAdd)],
2118
+ };
2119
+ }
2120
+ return {
2121
+ $and: [deepClone(existingCondition), deepClone(conditionToAdd)],
2122
+ };
2123
+ }
2124
+
2125
+ function collectSchemaNodeMap(schemaNode, pointer = "", out = new Map()) {
2126
+ if (!schemaNode || typeof schemaNode !== "object") return out;
2127
+ if (pointer) {
2128
+ out.set(pointer, schemaNode);
2129
+ }
2130
+ if (schemaNode.type === "object" && schemaNode.properties && typeof schemaNode.properties === "object") {
2131
+ Object.entries(schemaNode.properties).forEach(([key, childNode]) => {
2132
+ collectSchemaNodeMap(childNode, `${pointer}/${key}`, out);
2133
+ });
2134
+ }
2135
+ return out;
2136
+ }
2137
+
2138
+ function applyThemeVisibilityConditions(schema, uiSchema) {
2139
+ if (!schema || typeof schema !== "object") {
2140
+ return uiSchema && typeof uiSchema === "object" ? deepClone(uiSchema) : {};
2141
+ }
2142
+
2143
+ const conditionedUi = uiSchema && typeof uiSchema === "object" ? deepClone(uiSchema) : {};
2144
+ const schemaNodeMap = collectSchemaNodeMap(schema);
2145
+
2146
+ const darkCondition = { [FORM_THEME_CONTEXT_POINTER]: "dark" };
2147
+ const lightCondition = { [FORM_THEME_CONTEXT_POINTER]: { $ne: "dark" } };
2148
+
2149
+ const ensureCondition = (pointer, condition) => {
2150
+ if (!pointer || pointer === FORM_THEME_CONTEXT_POINTER) return;
2151
+ const current = conditionedUi[pointer] && typeof conditionedUi[pointer] === "object"
2152
+ ? conditionedUi[pointer]
2153
+ : {};
2154
+ conditionedUi[pointer] = {
2155
+ ...current,
2156
+ "ui:visibleWhen": mergeVisibleWhenCondition(current["ui:visibleWhen"], condition),
2157
+ };
2158
+ };
2159
+
2160
+ schemaNodeMap.forEach((node, pointer) => {
2161
+ if (!pointer.includes("/darkMode") || pointer === FORM_THEME_CONTEXT_POINTER) return;
2162
+
2163
+ ensureCondition(pointer, darkCondition);
2164
+
2165
+ const isDarkLeaf = !(node?.type === "object" && node?.properties);
2166
+ if (!isDarkLeaf) return;
2167
+
2168
+ const lightPointer = pointer.replace("/darkMode/", "/");
2169
+ const lightNode = schemaNodeMap.get(lightPointer);
2170
+ const isLightLeaf = !!lightNode && !(lightNode?.type === "object" && lightNode?.properties);
2171
+ if (isLightLeaf) {
2172
+ ensureCondition(lightPointer, lightCondition);
2173
+ }
2174
+ });
2175
+
2176
+ conditionedUi[FORM_THEME_CONTEXT_POINTER] = {
2177
+ ...(conditionedUi[FORM_THEME_CONTEXT_POINTER] || {}),
2178
+ "ui:hidden": true,
2179
+ };
2180
+
2181
+ return conditionedUi;
2182
+ }
2183
+
2184
+ function withThemeConditionalSchema(schema, uiSchema) {
2185
+ const baseSchema = deepClone(schema || { type: "object", properties: {} });
2186
+ if (!baseSchema.properties || typeof baseSchema.properties !== "object") {
2187
+ baseSchema.properties = {};
2188
+ }
2189
+
2190
+ baseSchema.properties[FORM_THEME_CONTEXT_FIELD] = {
2191
+ type: "string",
2192
+ oneOf: [
2193
+ { const: "light", title: "Light" },
2194
+ { const: "dark", title: "Dark" },
2195
+ ],
2196
+ };
2197
+
2198
+ const conditionedUi = applyThemeVisibilityConditions(baseSchema, uiSchema);
2199
+ return { schema: baseSchema, uiSchema: conditionedUi };
2200
+ }
2201
+
1973
2202
  function shouldKeepPathForSelection(selectedPaths, path) {
1974
2203
  if (!path) return true;
1975
2204
  return selectedPaths.some((selectedPath) => {
@@ -2233,6 +2462,12 @@ class PdsLiveEdit extends HTMLElement {
2233
2462
  this._undoStack = [];
2234
2463
  this._dropdownMenuOpen = false;
2235
2464
  this._dropdownObserver = null;
2465
+ this._boundThemeChanged = this._handleThemeChanged.bind(this);
2466
+ this._themeRefreshInFlight = null;
2467
+ this._drawerConfigFormContainer = null;
2468
+ this._drawerConfigFooter = null;
2469
+ this._interactiveEditingEnabled = true;
2470
+ this._interactionListenersAttached = false;
2236
2471
  }
2237
2472
 
2238
2473
  connectedCallback() {
@@ -2242,13 +2477,15 @@ class PdsLiveEdit extends HTMLElement {
2242
2477
  }
2243
2478
  PdsLiveEdit._activeInstance = this;
2244
2479
  this._connected = true;
2245
- if (!isHoverCapable()) return;
2246
-
2247
- ensureStyles();
2248
- this._selectors = collectSelectors();
2249
- document.addEventListener("mouseover", this._boundMouseOver, true);
2250
- document.addEventListener("mouseout", this._boundMouseOut, true);
2251
- document.addEventListener("mousemove", this._boundMouseMove, true);
2480
+ if (this.hasAttribute(SETTINGS_ONLY_ATTR)) {
2481
+ this._interactiveEditingEnabled = false;
2482
+ }
2483
+ if (PDS && typeof PDS.addEventListener === "function") {
2484
+ PDS.addEventListener("pds:theme:changed", this._boundThemeChanged);
2485
+ }
2486
+ if (this._interactiveEditingEnabled) {
2487
+ this._enableInteractiveEditing();
2488
+ }
2252
2489
  }
2253
2490
 
2254
2491
  disconnectedCallback() {
@@ -2256,21 +2493,141 @@ class PdsLiveEdit extends HTMLElement {
2256
2493
  }
2257
2494
 
2258
2495
  _teardown() {
2259
- if (this._connected) {
2260
- document.removeEventListener("mouseover", this._boundMouseOver, true);
2261
- document.removeEventListener("mouseout", this._boundMouseOut, true);
2262
- document.removeEventListener("mousemove", this._boundMouseMove, true);
2496
+ this._disableInteractiveEditing({ clearUI: true });
2497
+ this._connected = false;
2498
+ if (PDS && typeof PDS.removeEventListener === "function") {
2499
+ PDS.removeEventListener("pds:theme:changed", this._boundThemeChanged);
2263
2500
  }
2264
2501
  this._removeRepositionListeners();
2265
2502
  this._clearCloseTimer();
2266
2503
  this._removeActiveUI();
2267
- this._connected = false;
2504
+ this._drawerConfigFormContainer = null;
2505
+ this._drawerConfigFooter = null;
2268
2506
  if (PdsLiveEdit._activeInstance === this) {
2269
2507
  PdsLiveEdit._activeInstance = null;
2270
2508
  }
2271
2509
  }
2272
2510
 
2511
+ _enableInteractiveEditing() {
2512
+ if (!isHoverCapable()) return;
2513
+ ensureStyles();
2514
+ this._selectors = collectSelectors();
2515
+ if (this._interactionListenersAttached) return;
2516
+ document.addEventListener("mouseover", this._boundMouseOver, true);
2517
+ document.addEventListener("mouseout", this._boundMouseOut, true);
2518
+ document.addEventListener("mousemove", this._boundMouseMove, true);
2519
+ this._interactionListenersAttached = true;
2520
+ }
2521
+
2522
+ _disableInteractiveEditing(options = {}) {
2523
+ if (this._interactionListenersAttached) {
2524
+ document.removeEventListener("mouseover", this._boundMouseOver, true);
2525
+ document.removeEventListener("mouseout", this._boundMouseOut, true);
2526
+ document.removeEventListener("mousemove", this._boundMouseMove, true);
2527
+ this._interactionListenersAttached = false;
2528
+ }
2529
+
2530
+ if (options?.clearUI !== false) {
2531
+ this._removeActiveUI();
2532
+ }
2533
+ }
2534
+
2535
+ setInteractiveEditingEnabled(enabled = true) {
2536
+ const next = Boolean(enabled);
2537
+ this._interactiveEditingEnabled = next;
2538
+
2539
+ if (next) {
2540
+ this.removeAttribute(SETTINGS_ONLY_ATTR);
2541
+ } else {
2542
+ this.setAttribute(SETTINGS_ONLY_ATTR, "true");
2543
+ }
2544
+
2545
+ if (!this._connected) {
2546
+ return next;
2547
+ }
2548
+
2549
+ if (next) {
2550
+ this._enableInteractiveEditing();
2551
+ } else {
2552
+ this._disableInteractiveEditing({ clearUI: true });
2553
+ }
2554
+
2555
+ return next;
2556
+ }
2557
+
2558
+ isInteractiveEditingEnabled() {
2559
+ return Boolean(this._interactiveEditingEnabled);
2560
+ }
2561
+
2562
+ async _handleThemeChanged() {
2563
+ if (this._themeRefreshInFlight) return this._themeRefreshInFlight;
2564
+ this._themeRefreshInFlight = (async () => {
2565
+ try {
2566
+ await this._refreshQuickFormForTheme();
2567
+ await this._refreshDrawerConfigFormForTheme();
2568
+ } finally {
2569
+ this._themeRefreshInFlight = null;
2570
+ }
2571
+ })();
2572
+ return this._themeRefreshInFlight;
2573
+ }
2574
+
2575
+ async _refreshQuickFormForTheme() {
2576
+ if (!this._activeDropdown || !this._activeTarget) return;
2577
+ if (!document.contains(this._activeTarget)) return;
2578
+
2579
+ const formContainer = this._activeDropdown.querySelector('.pds-live-editor-form-container');
2580
+ const footer = this._activeDropdown.querySelector('.pds-live-editor-footer');
2581
+ if (!formContainer || !footer) return;
2582
+
2583
+ const currentDesign = shallowClone(PDS?.currentConfig?.design || {});
2584
+ const quickContext = collectQuickContext(this._activeTarget);
2585
+ const limitedPaths = quickContext.paths.slice(0, QUICK_EDIT_LIMIT);
2586
+ const quickPaths = quickContext.paths;
2587
+
2588
+ await this._renderQuickForm(
2589
+ formContainer,
2590
+ footer,
2591
+ limitedPaths,
2592
+ currentDesign,
2593
+ quickContext.hints,
2594
+ this._activeTarget,
2595
+ quickPaths
2596
+ );
2597
+ }
2598
+
2599
+ async _refreshDrawerConfigFormForTheme() {
2600
+ if (!this._drawer) return;
2601
+ if (!this._drawer.hasAttribute("open")) return;
2602
+ if (!this._drawerConfigFormContainer || !this._drawerConfigFooter) return;
2603
+
2604
+ const fullDesign = shallowClone(PDS?.currentConfig?.design || {});
2605
+ const fullConfigFormResult = await buildFullConfigForm(
2606
+ fullDesign,
2607
+ (event) => this._handleFormSubmit(event, fullConfigFormResult?.form),
2608
+ () => this._handleUndo()
2609
+ );
2610
+
2611
+ this._drawerConfigFormContainer.replaceChildren();
2612
+ this._drawerConfigFooter.replaceChildren();
2613
+
2614
+ if (fullConfigFormResult?.form) {
2615
+ fullConfigFormResult.form._undoBtn = fullConfigFormResult.undoBtn;
2616
+ fullConfigFormResult.undoBtn.disabled = this._undoStack.length === 0;
2617
+ this._drawerConfigFormContainer.appendChild(fullConfigFormResult.form);
2618
+ this._drawerConfigFooter.appendChild(fullConfigFormResult.applyBtn);
2619
+ this._drawerConfigFooter.appendChild(fullConfigFormResult.undoBtn);
2620
+ } else {
2621
+ const unavailable = document.createElement("p");
2622
+ unavailable.className = "text-muted";
2623
+ unavailable.textContent =
2624
+ "Full config metadata is unavailable in this runtime.";
2625
+ this._drawerConfigFormContainer.appendChild(unavailable);
2626
+ }
2627
+ }
2628
+
2273
2629
  _handleMouseOver(event) {
2630
+ if (!this._interactiveEditingEnabled) return;
2274
2631
  if (!event?.target || !(event.target instanceof Element)) return;
2275
2632
 
2276
2633
  // Check if we're hovering over the dropdown (including Shadow DOM elements)
@@ -2311,6 +2668,8 @@ class PdsLiveEdit extends HTMLElement {
2311
2668
  return null;
2312
2669
  }
2313
2670
  if (!this._selectors?.selector) return null;
2671
+ if (isLiveEditHighlightBlacklisted(node)) return null;
2672
+ if (node.closest("[data-pds-live-edit-ignore]")) return null;
2314
2673
  if (node.closest(EDITOR_TAG)) return null;
2315
2674
  if (node.closest(`.${DROPDOWN_CLASS}`)) return null;
2316
2675
  if (node.closest("pds-drawer")) return null;
@@ -2323,6 +2682,7 @@ class PdsLiveEdit extends HTMLElement {
2323
2682
  }
2324
2683
 
2325
2684
  _showForTarget(target) {
2685
+ if (!this._interactiveEditingEnabled) return;
2326
2686
  const quickContext = collectQuickContext(target);
2327
2687
  const quickPaths = quickContext.paths;
2328
2688
  if (!quickPaths.length) return;
@@ -2367,8 +2727,13 @@ class PdsLiveEdit extends HTMLElement {
2367
2727
  this._lastPointer = null;
2368
2728
  this._dropdownMenuOpen = false;
2369
2729
 
2370
- // Always re-enable mouseover when UI is removed
2371
- this._addMouseOverListener();
2730
+ if (
2731
+ this._connected &&
2732
+ this._interactiveEditingEnabled &&
2733
+ this._interactionListenersAttached
2734
+ ) {
2735
+ this._addMouseOverListener();
2736
+ }
2372
2737
  }
2373
2738
 
2374
2739
  _addDocumentListeners() {
@@ -2515,6 +2880,7 @@ class PdsLiveEdit extends HTMLElement {
2515
2880
 
2516
2881
  _addMouseOverListener() {
2517
2882
  if (typeof document === "undefined") return;
2883
+ if (!this._interactiveEditingEnabled) return;
2518
2884
  document.addEventListener("mouseover", this._boundMouseOver, true);
2519
2885
  }
2520
2886
 
@@ -2724,13 +3090,142 @@ class PdsLiveEdit extends HTMLElement {
2724
3090
  this._openDrawer(target, quickPaths);
2725
3091
  this._removeActiveUI();
2726
3092
  });
3093
+
3094
+ const quickModeNav = await this._buildQuickModeDropdown();
2727
3095
 
2728
3096
  // Add buttons to footer
2729
3097
  footer.appendChild(applyBtn);
2730
3098
  footer.appendChild(undoBtn);
3099
+ footer.appendChild(quickModeNav);
2731
3100
  footer.appendChild(gearBtn);
2732
3101
  }
2733
3102
 
3103
+ async _buildQuickModeDropdown() {
3104
+ const nav = document.createElement("nav");
3105
+ nav.setAttribute("data-dropdown", "");
3106
+ nav.setAttribute("data-mode", "auto");
3107
+ nav.setAttribute("data-direction", "auto");
3108
+
3109
+ const button = document.createElement("button");
3110
+ button.type = "button";
3111
+ button.className = "btn-outline btn-sm icon-only";
3112
+ button.setAttribute("aria-label", "Quick theme and preset");
3113
+
3114
+ const icon = document.createElement("pds-icon");
3115
+ icon.setAttribute("icon", "palette");
3116
+ icon.setAttribute("size", "sm");
3117
+ button.appendChild(icon);
3118
+
3119
+ const menu = document.createElement("menu");
3120
+ const content = await this._buildQuickModeContent();
3121
+ menu.appendChild(content);
3122
+ nav.append(button, menu);
3123
+ return nav;
3124
+ }
3125
+
3126
+ async _buildQuickModeContent() {
3127
+ const content = document.createElement("div");
3128
+ content.className = "pds-live-editor-menu stack-sm";
3129
+
3130
+ const themeLabel = document.createElement("label");
3131
+ themeLabel.className = "stack-xs";
3132
+ const themeText = document.createElement("span");
3133
+ themeText.textContent = "Theme";
3134
+ const themeToggle = document.createElement("pds-theme");
3135
+ themeLabel.append(themeText, themeToggle);
3136
+ content.appendChild(themeLabel);
3137
+
3138
+ const presetLabel = document.createElement("label");
3139
+ presetLabel.className = "stack-xs";
3140
+ const presetText = document.createElement("span");
3141
+ presetText.textContent = "Preset";
3142
+ presetLabel.appendChild(presetText);
3143
+
3144
+ let presetControlRendered = false;
3145
+ try {
3146
+ await waitForElementDefinition("pds-omnibox");
3147
+
3148
+ const presetOmnibox = document.createElement("pds-omnibox");
3149
+ presetOmnibox.setAttribute("item-grid", "0 1fr 0");
3150
+ presetOmnibox.setAttribute("placeholder", "Search presets...");
3151
+
3152
+ const activePresetId = getActivePresetId();
3153
+ const activePresetName = getPresetNameById(activePresetId);
3154
+ if (activePresetName) {
3155
+ presetOmnibox.value = activePresetName;
3156
+ }
3157
+
3158
+ const omniboxSettingsBuilder =
3159
+ typeof PDS?.buildPresetOmniboxSettings === "function"
3160
+ ? PDS.buildPresetOmniboxSettings.bind(PDS)
3161
+ : null;
3162
+
3163
+ if (omniboxSettingsBuilder) {
3164
+ presetOmnibox.settings = omniboxSettingsBuilder({
3165
+ onSelect: async ({ preset, selection }) => {
3166
+ if (selection?.disabled) return selection?.id;
3167
+ const presetId = preset?.id || selection?.id;
3168
+ await applyPresetSelection(presetId);
3169
+ return presetId;
3170
+ },
3171
+ });
3172
+ }
3173
+
3174
+ presetOmnibox.addEventListener("result-selected", (event) => {
3175
+ const selectedText = event?.detail?.text;
3176
+ if (typeof selectedText === "string" && selectedText.trim()) {
3177
+ presetOmnibox.value = selectedText;
3178
+ }
3179
+ });
3180
+
3181
+ presetLabel.appendChild(presetOmnibox);
3182
+ presetControlRendered = true;
3183
+ } catch (error) {
3184
+ console.warn("[PDS Live Edit] Quick preset omnibox unavailable, falling back to select.", error);
3185
+ }
3186
+
3187
+ if (!presetControlRendered) {
3188
+ const presetSelect = document.createElement("select");
3189
+ const presetOptions = getPresetOptions();
3190
+ const activePreset = getActivePresetId();
3191
+
3192
+ presetOptions.forEach((preset) => {
3193
+ const option = document.createElement("option");
3194
+ option.value = preset.id;
3195
+ option.textContent = preset.name;
3196
+ if (String(preset.id) === String(activePreset)) {
3197
+ option.selected = true;
3198
+ }
3199
+ presetSelect.appendChild(option);
3200
+ });
3201
+
3202
+ presetSelect.addEventListener("change", async (event) => {
3203
+ const nextPreset = event.target?.value;
3204
+ await applyPresetSelection(nextPreset);
3205
+ });
3206
+
3207
+ presetLabel.appendChild(presetSelect);
3208
+ }
3209
+
3210
+ content.appendChild(presetLabel);
3211
+ return content;
3212
+ }
3213
+
3214
+ async createSharedQuickModeMenuItem() {
3215
+ const item = document.createElement("li");
3216
+ item.className = "pds-live-shared-quick-mode-item";
3217
+ item.setAttribute("data-pds-live-edit-ignore", "true");
3218
+
3219
+ const quickModeContent = await this._buildQuickModeContent();
3220
+ if (!quickModeContent) {
3221
+ return item;
3222
+ }
3223
+
3224
+ markNodeTreeAsLiveEditIgnored(quickModeContent);
3225
+ item.appendChild(quickModeContent);
3226
+ return item;
3227
+ }
3228
+
2734
3229
  async _openDrawer(target, quickPaths) {
2735
3230
  if (!this._drawer) {
2736
3231
  this._drawer = document.createElement("pds-drawer");
@@ -2739,6 +3234,8 @@ class PdsLiveEdit extends HTMLElement {
2739
3234
  this.appendChild(this._drawer);
2740
3235
  }
2741
3236
 
3237
+ this._drawer.style.setProperty("--drawer-width", "min(96vw, 44rem)");
3238
+
2742
3239
  if (!customElements.get("pds-drawer")) {
2743
3240
  await customElements.whenDefined("pds-drawer");
2744
3241
  }
@@ -2750,7 +3247,7 @@ class PdsLiveEdit extends HTMLElement {
2750
3247
 
2751
3248
  const content = document.createElement("div");
2752
3249
  content.setAttribute("slot", "drawer-content");
2753
- content.className = "stack-md";
3250
+ content.className = "pds-live-editor-drawer-content";
2754
3251
 
2755
3252
  const presetCard = document.createElement("section");
2756
3253
  presetCard.className = "card surface-elevated stack-sm";
@@ -2864,6 +3361,8 @@ class PdsLiveEdit extends HTMLElement {
2864
3361
  const configFooter = document.createElement("div");
2865
3362
  configFooter.className = "flex gap-sm";
2866
3363
  configCard.appendChild(configFooter);
3364
+ this._drawerConfigFormContainer = configFormContainer;
3365
+ this._drawerConfigFooter = configFooter;
2867
3366
 
2868
3367
  const fullDesign = shallowClone(PDS?.currentConfig?.design || {});
2869
3368
  const fullConfigFormResult = await buildFullConfigForm(
@@ -2886,6 +3385,40 @@ class PdsLiveEdit extends HTMLElement {
2886
3385
  configFormContainer.appendChild(unavailable);
2887
3386
  }
2888
3387
 
3388
+ const templateCard = document.createElement("section");
3389
+ templateCard.className = "card surface-elevated stack-sm";
3390
+
3391
+ const templateTitle = document.createElement("h4");
3392
+ templateTitle.textContent = "Canvas Templates";
3393
+ templateCard.appendChild(templateTitle);
3394
+
3395
+ const templateCanvas = document.createElement("pds-live-template-canvas");
3396
+ templateCanvas.addEventListener("pds:live-template:inject", async (event) => {
3397
+ await this._applyImportResult(event?.detail?.result, { injectTemplate: true });
3398
+ });
3399
+ templateCard.appendChild(templateCanvas);
3400
+
3401
+ const importCard = document.createElement("section");
3402
+ importCard.className = "card surface-elevated stack-sm";
3403
+
3404
+ const importTitle = document.createElement("h4");
3405
+ importTitle.textContent = "Import & Convert";
3406
+ importCard.appendChild(importTitle);
3407
+
3408
+ const importer = document.createElement("pds-live-importer");
3409
+
3410
+ importer.addEventListener("pds:live-import:result", async (event) => {
3411
+ const result = event?.detail?.result;
3412
+ if (!result) return;
3413
+ await this._applyImportResult(result, {
3414
+ injectTemplate: true,
3415
+ importMode: event?.detail?.importMode,
3416
+ });
3417
+ this._endLiveEditSessionAfterImport();
3418
+ });
3419
+
3420
+ importCard.append(importer);
3421
+
2889
3422
  const exportCard = document.createElement("section");
2890
3423
  exportCard.className = "card surface-elevated stack-sm";
2891
3424
 
@@ -2945,28 +3478,49 @@ class PdsLiveEdit extends HTMLElement {
2945
3478
  exportNav.append(exportButton, exportMenu);
2946
3479
  exportCard.appendChild(exportNav);
2947
3480
 
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
3481
  const resetButton = document.createElement("button");
2956
3482
  resetButton.type = "button";
2957
- resetButton.className = "btn-outline";
3483
+ resetButton.className = "btn-outline pds-live-editor-reset-btn";
2958
3484
  resetButton.textContent = "Reset Config";
2959
- resetButton.addEventListener("click", () => {
2960
- window.localStorage.removeItem("pure-ds-config");
2961
- window.location.reload();
3485
+ resetButton.addEventListener("click", async () => {
3486
+ await this.resetConfig();
2962
3487
  });
2963
- resetCard.appendChild(resetButton);
2964
3488
 
2965
- content.appendChild(presetCard);
2966
- content.appendChild(themeCard);
2967
- content.appendChild(configCard);
2968
- content.appendChild(exportCard);
2969
- content.appendChild(resetCard);
3489
+ const drawerFooter = document.createElement("div");
3490
+ drawerFooter.className = "pds-live-editor-drawer-footer";
3491
+ drawerFooter.appendChild(resetButton);
3492
+
3493
+ const accordion = document.createElement("section");
3494
+ accordion.className = "accordion";
3495
+ accordion.setAttribute("aria-label", "Design settings groups");
3496
+
3497
+ const buildAccordionGroup = (title, nodes = [], open = false, sectionId = "") => {
3498
+ const details = document.createElement("details");
3499
+ if (open) details.open = true;
3500
+ if (sectionId) details.dataset.section = sectionId;
3501
+
3502
+ const summary = document.createElement("summary");
3503
+ summary.textContent = title;
3504
+
3505
+ const body = document.createElement("div");
3506
+ body.className = "stack-md";
3507
+ nodes.forEach((node) => {
3508
+ if (node) body.appendChild(node);
3509
+ });
3510
+
3511
+ details.append(summary, body);
3512
+ return details;
3513
+ };
3514
+
3515
+ accordion.appendChild(buildAccordionGroup("Preset & Theme", [presetCard, themeCard], true, "preset-theme"));
3516
+ accordion.appendChild(buildAccordionGroup("Configuration", [configCard], false, "configuration"));
3517
+ accordion.appendChild(buildAccordionGroup("Canvas", [templateCard], false, "canvas"));
3518
+ accordion.appendChild(
3519
+ buildAccordionGroup("Import & Export", [importCard, exportCard], false, "import-convert-export")
3520
+ );
3521
+
3522
+ content.appendChild(accordion);
3523
+ content.appendChild(drawerFooter);
2970
3524
 
2971
3525
  this._drawer.replaceChildren(header, content);
2972
3526
 
@@ -2981,6 +3535,158 @@ class PdsLiveEdit extends HTMLElement {
2981
3535
  await exportFromLiveEdit(format);
2982
3536
  }
2983
3537
 
3538
+ _ensureLiveCanvasContainer() {
3539
+ let container = document.getElementById("pds-live-edit-canvas");
3540
+ if (container) return container;
3541
+
3542
+ container = document.createElement("section");
3543
+ container.id = "pds-live-edit-canvas";
3544
+ container.className = "card stack-md";
3545
+
3546
+ const heading = document.createElement("hr");
3547
+ heading.setAttribute("data-content", "Injected HTML below");
3548
+
3549
+ const canvas = document.createElement("div");
3550
+ canvas.id = "pds-live-edit-canvas-content";
3551
+ canvas.className = "stack-md";
3552
+
3553
+ container.append(heading, canvas)
3554
+ document.body.appendChild(container);
3555
+ return container;
3556
+ }
3557
+
3558
+ _injectTemplateIntoCanvas(template) {
3559
+ if (!template || typeof template !== "object") return;
3560
+ const container = this._ensureLiveCanvasContainer();
3561
+ const canvas = container.querySelector("#pds-live-edit-canvas-content");
3562
+ if (!canvas) return;
3563
+ canvas.innerHTML = String(template.html || "");
3564
+ }
3565
+
3566
+ async openDesignSettings() {
3567
+ const target = this._activeTarget || document.body;
3568
+ const quickPaths = this._activeTarget ? collectQuickContext(this._activeTarget).paths : [];
3569
+
3570
+ await this._openDrawer(target, quickPaths);
3571
+
3572
+ const drawer = this._drawer;
3573
+ if (!drawer) return;
3574
+
3575
+ const presetGroup = drawer.querySelector('details[data-section="preset-theme"]');
3576
+ if (presetGroup) {
3577
+ presetGroup.setAttribute("open", "");
3578
+ }
3579
+
3580
+ const configGroup = drawer.querySelector('details[data-section="configuration"]');
3581
+ if (configGroup) {
3582
+ configGroup.setAttribute("open", "");
3583
+ }
3584
+ }
3585
+
3586
+ async resetConfig() {
3587
+ let confirmed = true;
3588
+ if (typeof PDS?.ask === "function") {
3589
+ confirmed = await PDS.ask(
3590
+ "This clears your saved local configuration and reloads the page.",
3591
+ {
3592
+ title: "Reset Config?",
3593
+ type: "confirm",
3594
+ buttons: {
3595
+ ok: { name: "Reset", variant: "danger" },
3596
+ cancel: { name: "Cancel", cancel: true },
3597
+ },
3598
+ }
3599
+ );
3600
+ }
3601
+
3602
+ if (!confirmed) return false;
3603
+
3604
+ try {
3605
+ window.localStorage.removeItem("pure-ds-config");
3606
+ } catch (e) {}
3607
+
3608
+ window.location.reload();
3609
+ return true;
3610
+ }
3611
+
3612
+ async openImportDetailsFromToast(options = {}) {
3613
+ const requestedFileName = String(options?.fileName || "");
3614
+ const requestedSourceType = String(options?.sourceType || "");
3615
+ const requestedImportMode = String(options?.importMode || "");
3616
+ const target = this._activeTarget || document.body;
3617
+ const quickPaths = this._activeTarget ? collectQuickContext(this._activeTarget).paths : [];
3618
+
3619
+ await this._openDrawer(target, quickPaths);
3620
+
3621
+ const drawer = this._drawer;
3622
+ if (!drawer) return;
3623
+
3624
+ const importGroup = drawer.querySelector('details[data-section="import-convert-export"]');
3625
+ if (importGroup) {
3626
+ importGroup.setAttribute("open", "");
3627
+ }
3628
+
3629
+ const tryOpenImporterDetails = (attempt = 0) => {
3630
+ const importer = drawer.querySelector("pds-live-importer");
3631
+ if (!importer) {
3632
+ if (attempt < 12) setTimeout(() => tryOpenImporterDetails(attempt + 1), 100);
3633
+ return;
3634
+ }
3635
+
3636
+ if (typeof importer.openHistoryDetailsByMeta === "function") {
3637
+ importer.openHistoryDetailsByMeta({
3638
+ fileName: requestedFileName,
3639
+ sourceType: requestedSourceType,
3640
+ importMode: requestedImportMode,
3641
+ });
3642
+ }
3643
+ };
3644
+
3645
+ tryOpenImporterDetails();
3646
+ }
3647
+
3648
+ _endLiveEditSessionAfterImport() {
3649
+ this._removeActiveUI();
3650
+
3651
+ if (this._drawer) {
3652
+ if (typeof this._drawer.closeDrawer === "function") {
3653
+ this._drawer.closeDrawer();
3654
+ } else {
3655
+ this._drawer.removeAttribute("open");
3656
+ }
3657
+ }
3658
+
3659
+ this.dispatchEvent(
3660
+ new CustomEvent("pds:live-edit:disable", {
3661
+ bubbles: true,
3662
+ composed: true,
3663
+ detail: { reason: "import-complete" },
3664
+ })
3665
+ );
3666
+ }
3667
+
3668
+ async _applyImportResult(result, options = {}) {
3669
+ if (!result || typeof result !== "object") return;
3670
+
3671
+ const importMode = String(options?.importMode || result?.meta?.importMode || "convert-only");
3672
+ const injectTemplate = options.injectTemplate !== false;
3673
+ const validationBlocked = Boolean(result?.meta?.validationBlocked || result?.meta?.validation?.ok === false);
3674
+ const shouldApplyPatch =
3675
+ options.applyDesignPatch === true ||
3676
+ (options.applyDesignPatch !== false && importMode !== "convert-only" && !validationBlocked);
3677
+ const patch = result.designPatch;
3678
+ const patchKeys =
3679
+ patch && typeof patch === "object" ? Object.keys(patch).filter(Boolean) : [];
3680
+
3681
+ if (shouldApplyPatch && patchKeys.length > 0) {
3682
+ await applyDesignPatch(patch);
3683
+ }
3684
+
3685
+ if (injectTemplate && result.template?.html) {
3686
+ this._injectTemplateIntoCanvas(result.template);
3687
+ }
3688
+ }
3689
+
2984
3690
  async _handleFormSubmit(event, form) {
2985
3691
  if (!form || typeof form.getValuesFlat !== "function") return;
2986
3692
 
@@ -3007,8 +3713,16 @@ class PdsLiveEdit extends HTMLElement {
3007
3713
  : typeof form._normalizeFlatValues === "function"
3008
3714
  ? form._normalizeFlatValues(form.getValuesFlat())
3009
3715
  : form.getValuesFlat();
3010
- const patch = {};
3716
+ const sanitizedFlatValues = {};
3011
3717
  Object.entries(flatValues || {}).forEach(([path, value]) => {
3718
+ if (path === FORM_THEME_CONTEXT_POINTER || path === FORM_THEME_CONTEXT_FIELD) {
3719
+ return;
3720
+ }
3721
+ sanitizedFlatValues[path] = value;
3722
+ });
3723
+
3724
+ const patch = {};
3725
+ Object.entries(sanitizedFlatValues).forEach(([path, value]) => {
3012
3726
  setValueAtJsonPath(patch, path, value);
3013
3727
  });
3014
3728
  await applyDesignPatch(patch);