@jxsuite/studio 0.19.0 → 0.20.0

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 (93) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +12577 -12183
  3. package/dist/studio.js.map +146 -144
  4. package/dist/workers/editor.worker.js +79 -79
  5. package/dist/workers/json.worker.js +109 -109
  6. package/dist/workers/ts.worker.js +82 -82
  7. package/package.json +17 -6
  8. package/src/browse/browse.js +25 -19
  9. package/src/canvas/canvas-diff.js +24 -16
  10. package/src/canvas/canvas-helpers.js +24 -19
  11. package/src/canvas/canvas-live-render.js +94 -39
  12. package/src/canvas/canvas-render.js +119 -73
  13. package/src/canvas/canvas-utils.js +38 -15
  14. package/src/canvas/nested-site-style.js +50 -0
  15. package/src/editor/component-inline-edit.js +14 -13
  16. package/src/editor/content-inline-edit.js +22 -10
  17. package/src/editor/context-menu.js +1 -1
  18. package/src/editor/convert-to-component.js +12 -10
  19. package/src/editor/inline-edit.js +66 -28
  20. package/src/editor/insertion-helper.js +16 -12
  21. package/src/editor/shortcuts.js +4 -2
  22. package/src/editor/slash-menu.js +6 -4
  23. package/src/files/components.js +1 -1
  24. package/src/files/file-ops.js +37 -16
  25. package/src/files/files.js +78 -52
  26. package/src/github/github-auth.js +122 -0
  27. package/src/github/github-publish.js +115 -0
  28. package/src/markdown/md-convert.js +172 -107
  29. package/src/new-project/new-project-modal.js +204 -0
  30. package/src/panels/activity-bar.js +22 -20
  31. package/src/panels/ai-panel.js +399 -0
  32. package/src/panels/block-action-bar.js +5 -5
  33. package/src/panels/canvas-dnd.js +8 -2
  34. package/src/panels/data-explorer.js +19 -13
  35. package/src/panels/dnd.js +144 -92
  36. package/src/panels/editors.js +55 -35
  37. package/src/panels/elements-panel.js +33 -19
  38. package/src/panels/events-panel.js +15 -10
  39. package/src/panels/git-panel.js +469 -98
  40. package/src/panels/head-panel.js +121 -81
  41. package/src/panels/imports-panel.js +88 -64
  42. package/src/panels/layers-panel.js +64 -43
  43. package/src/panels/left-panel.js +85 -47
  44. package/src/panels/overlays.js +1 -0
  45. package/src/panels/panel-events.js +45 -30
  46. package/src/panels/preview-render.js +23 -18
  47. package/src/panels/properties-panel.js +365 -249
  48. package/src/panels/pseudo-preview.js +2 -2
  49. package/src/panels/quick-search.js +7 -5
  50. package/src/panels/right-panel.js +26 -2
  51. package/src/panels/shared.js +4 -4
  52. package/src/panels/signals-panel.js +295 -166
  53. package/src/panels/statusbar.js +4 -4
  54. package/src/panels/style-inputs.js +67 -35
  55. package/src/panels/style-panel.js +233 -132
  56. package/src/panels/style-utils.js +34 -16
  57. package/src/panels/stylebook-layers-panel.js +12 -10
  58. package/src/panels/stylebook-panel.js +134 -66
  59. package/src/panels/toolbar.js +333 -109
  60. package/src/panels/welcome-screen.js +121 -0
  61. package/src/platform.js +2 -4
  62. package/src/platforms/devserver.js +113 -7
  63. package/src/resize-edges.js +98 -0
  64. package/src/services/cem-export.js +12 -7
  65. package/src/services/code-services.js +30 -12
  66. package/src/settings/content-types-editor.js +7 -7
  67. package/src/settings/css-vars-editor.js +30 -24
  68. package/src/settings/defs-editor.js +12 -7
  69. package/src/settings/general-settings.js +85 -3
  70. package/src/settings/head-editor.js +30 -24
  71. package/src/settings/schema-field-ui.js +58 -39
  72. package/src/site-context.js +41 -31
  73. package/src/state.js +70 -29
  74. package/src/store.js +21 -26
  75. package/src/studio.js +103 -81
  76. package/src/tabs/tab.js +67 -43
  77. package/src/tabs/transact.js +23 -13
  78. package/src/ui/button-group.js +24 -24
  79. package/src/ui/color-selector.js +28 -23
  80. package/src/ui/field-row.js +3 -3
  81. package/src/ui/icons.js +2 -2
  82. package/src/ui/media-picker.js +7 -7
  83. package/src/ui/spectrum.js +5 -1
  84. package/src/ui/unit-selector.js +18 -16
  85. package/src/ui/value-selector.js +21 -15
  86. package/src/ui/widgets.js +22 -19
  87. package/src/utils/canvas-media.js +5 -4
  88. package/src/utils/edit-display.js +31 -20
  89. package/src/utils/google-fonts.js +11 -11
  90. package/src/utils/inherited-style.js +9 -8
  91. package/src/utils/studio-utils.js +7 -7
  92. package/src/view.js +58 -1
  93. package/src/workspace/workspace.js +10 -7
@@ -37,14 +37,14 @@ import htmlMeta from "../../data/html-meta.json";
37
37
  /**
38
38
  * Convert a human-friendly name like "Tablet" to a $media key "--tablet"
39
39
  *
40
- * @param {any} name
40
+ * @param {string} name
41
41
  */
42
42
  function friendlyNameToMedia(name) {
43
43
  return friendlyNameToVar(name, "--");
44
44
  }
45
45
 
46
46
  /** Check if a selection path is inside a $map template (contains [..., "children", "map", ...]). */
47
- function isInsideMapTemplate(/** @type {any} */ path) {
47
+ function isInsideMapTemplate(/** @type {JxPath | null} */ path) {
48
48
  if (!path) return false;
49
49
  for (let i = 0; i < path.length - 1; i++) {
50
50
  if (path[i] === "children" && path[i + 1] === "map") return true;
@@ -56,13 +56,28 @@ function isInsideMapTemplate(/** @type {any} */ path) {
56
56
  * Field row with binding toggle — allows switching between static value and signal binding.
57
57
  * rawValue can be a string/bool (static) or { $ref: "..." } (bound).
58
58
  */
59
+ /** @typedef {{ value: string; label: string }} SignalOption */
60
+
61
+ /**
62
+ * @typedef {{
63
+ * $section: string;
64
+ * $order: number;
65
+ * $elements?: string[];
66
+ * $label?: string;
67
+ * $input?: string;
68
+ * $shorthand?: boolean;
69
+ * type?: string;
70
+ * [key: string]: unknown;
71
+ * }} HtmlMetaEntry
72
+ */
73
+
59
74
  function bindableFieldRow(
60
- /** @type {any} */ label,
61
- /** @type {any} */ type,
62
- /** @type {any} */ rawValue,
63
- /** @type {any} */ onChange,
64
- /** @type {any} */ filterFn = null,
65
- /** @type {any} */ extraSignals = null,
75
+ /** @type {string} */ label,
76
+ /** @type {string} */ type,
77
+ /** @type {string | number | boolean | { $ref: string } | null | undefined} */ rawValue,
78
+ /** @type {(v: JsonValue) => void} */ onChange,
79
+ /** @type {((d: import("./signals-panel.js").SignalDef) => boolean) | null} */ filterFn = null,
80
+ /** @type {SignalOption[] | null} */ extraSignals = null,
66
81
  ) {
67
82
  const tab = activeTab.value;
68
83
  const defs = tab.doc.document.state || {};
@@ -72,11 +87,11 @@ function bindableFieldRow(
72
87
  filterFn ? filterFn(d) : !d.$handler && d.$prototype !== "Function",
73
88
  );
74
89
 
75
- /** @type {any} */
90
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
76
91
  let debounce;
77
- const onInput = (/** @type {any} */ e) => {
92
+ const onInput = (/** @type {Event} */ e) => {
78
93
  clearTimeout(debounce);
79
- debounce = setTimeout(() => onChange(e.target.value), 400);
94
+ debounce = setTimeout(() => onChange(/** @type {HTMLInputElement} */ (e.target).value), 400);
80
95
  };
81
96
 
82
97
  const staticVal = isBound ? "" : (rawValue ?? "");
@@ -86,7 +101,8 @@ function bindableFieldRow(
86
101
  : type === "checkbox"
87
102
  ? html`<sp-checkbox
88
103
  ?checked=${!!staticVal}
89
- @change=${(/** @type {any} */ e) => onChange(e.target.checked)}
104
+ @change=${(/** @type {Event} */ e) =>
105
+ onChange(/** @type {HTMLInputElement} */ (e.target).checked)}
90
106
  ></sp-checkbox>`
91
107
  : html`<sp-textfield size="s" value=${staticVal} @input=${onInput}></sp-textfield>`;
92
108
 
@@ -96,8 +112,9 @@ function bindableFieldRow(
96
112
  quiet
97
113
  placeholder="— select signal —"
98
114
  value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
99
- @change=${(/** @type {any} */ e) => {
100
- if (e.target.value) onChange({ $ref: e.target.value });
115
+ @change=${(/** @type {Event} */ e) => {
116
+ if (/** @type {HTMLInputElement} */ (e.target).value)
117
+ onChange({ $ref: /** @type {HTMLInputElement} */ (e.target).value });
101
118
  else onChange(undefined);
102
119
  }}
103
120
  >
@@ -108,7 +125,7 @@ function bindableFieldRow(
108
125
  ? html`
109
126
  <sp-menu-divider></sp-menu-divider>
110
127
  ${extraSignals.map(
111
- (/** @type {any} */ sig) =>
128
+ (/** @type {SignalOption} */ sig) =>
112
129
  html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
113
130
  )}
114
131
  `
@@ -129,7 +146,7 @@ function bindableFieldRow(
129
146
  } else {
130
147
  if (signalDefs.length > 0) {
131
148
  onChange({ $ref: `#/state/${signalDefs[0][0]}` });
132
- } else if (extraSignals?.length > 0) {
149
+ } else if (extraSignals && extraSignals.length > 0) {
133
150
  onChange({ $ref: extraSignals[0].value });
134
151
  }
135
152
  }
@@ -152,13 +169,13 @@ function bindableFieldRow(
152
169
 
153
170
  /** Key-value pair row for styles / attributes */
154
171
  function kvRow(
155
- /** @type {any} */ key,
156
- /** @type {any} */ value,
157
- /** @type {any} */ onChange,
158
- /** @type {any} */ onDelete,
159
- /** @type {any} */ datalistId = null,
172
+ /** @type {string} */ key,
173
+ /** @type {string} */ value,
174
+ /** @type {(newKey: string, newVal: string) => void} */ onChange,
175
+ /** @type {() => void} */ onDelete,
176
+ /** @type {string | null} */ datalistId = null,
160
177
  ) {
161
- /** @type {any} */
178
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
162
179
  let debounceTimer;
163
180
  let currentKey = key;
164
181
  let currentVal = value;
@@ -173,14 +190,20 @@ function kvRow(
173
190
  size="s"
174
191
  class="kv-key"
175
192
  value=${key}
176
- @input=${(/** @type {any} */ e) => {
177
- currentKey = e.target.value;
193
+ @input=${(/** @type {Event} */ e) => {
194
+ currentKey = /** @type {HTMLInputElement} */ (e.target).value;
178
195
  commit();
179
196
  }}
180
197
  @change=${datalistId === "css-props"
181
- ? (/** @type {any} */ e) => {
182
- const el = e.target.closest(".kv-row")?.querySelector(".kv-val");
183
- if (el) el.setAttribute("placeholder", getCssInitialMap().get(e.target.value) || "");
198
+ ? (/** @type {Event} */ e) => {
199
+ const el = /** @type {HTMLInputElement} */ (e.target)
200
+ .closest(".kv-row")
201
+ ?.querySelector(".kv-val");
202
+ if (el)
203
+ el.setAttribute(
204
+ "placeholder",
205
+ getCssInitialMap().get(/** @type {HTMLInputElement} */ (e.target).value) || "",
206
+ );
184
207
  }
185
208
  : nothing}
186
209
  ></sp-textfield>
@@ -189,8 +212,8 @@ function kvRow(
189
212
  class="kv-val"
190
213
  value=${value}
191
214
  placeholder=${placeholder}
192
- @input=${(/** @type {any} */ e) => {
193
- currentVal = e.target.value;
215
+ @input=${(/** @type {Event} */ e) => {
216
+ currentVal = /** @type {HTMLInputElement} */ (e.target).value;
194
217
  commit();
195
218
  }}
196
219
  ></sp-textfield>
@@ -205,23 +228,23 @@ function kvRow(
205
228
 
206
229
  /** Repeater fields template */
207
230
  function renderRepeaterFieldsTemplate(
208
- /** @type {any} */ node,
209
- /** @type {any} */ path,
210
- /** @type {any} */ _mapSignals,
231
+ /** @type {JxMutableNode} */ node,
232
+ /** @type {JxPath} */ path,
233
+ /** @type {SignalOption[] | null} */ _mapSignals,
211
234
  ) {
212
235
  return html`
213
- ${bindableFieldRow("Items", "text", node.items, (/** @type {any} */ v) =>
236
+ ${bindableFieldRow("Items", "text", node.items, (/** @type {JsonValue} */ v) =>
214
237
  transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
215
238
  )}
216
239
  ${node.filter
217
- ? bindableFieldRow("Filter", "text", node.filter, (/** @type {any} */ v) =>
240
+ ? bindableFieldRow("Filter", "text", node.filter, (/** @type {JsonValue} */ v) =>
218
241
  transactDoc(activeTab.value, (t) =>
219
242
  mutateUpdateProperty(t, path, "filter", v || undefined),
220
243
  ),
221
244
  )
222
245
  : nothing}
223
246
  ${node.sort
224
- ? bindableFieldRow("Sort", "text", node.sort, (/** @type {any} */ v) =>
247
+ ? bindableFieldRow("Sort", "text", node.sort, (/** @type {JsonValue} */ v) =>
225
248
  transactDoc(activeTab.value, (t) =>
226
249
  mutateUpdateProperty(t, path, "sort", v || undefined),
227
250
  ),
@@ -266,9 +289,9 @@ function renderRepeaterFieldsTemplate(
266
289
 
267
290
  /** Switch fields template */
268
291
  function renderSwitchFieldsTemplate(
269
- /** @type {any} */ node,
270
- /** @type {any} */ path,
271
- /** @type {any} */ mapSignals,
292
+ /** @type {JxMutableNode} */ node,
293
+ /** @type {JxPath} */ path,
294
+ /** @type {SignalOption[] | null} */ mapSignals,
272
295
  ) {
273
296
  const caseNames = Object.keys(node.cases || {});
274
297
  return html`
@@ -276,7 +299,7 @@ function renderSwitchFieldsTemplate(
276
299
  "Expression",
277
300
  "text",
278
301
  node.$switch,
279
- (/** @type {any} */ v) =>
302
+ (/** @type {JsonValue} */ v) =>
280
303
  transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
281
304
  null,
282
305
  mapSignals,
@@ -287,7 +310,7 @@ function renderSwitchFieldsTemplate(
287
310
  Cases
288
311
  </div>
289
312
  ${caseNames.map((caseName) => {
290
- /** @type {any} */
313
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
291
314
  let debounce;
292
315
  return html`
293
316
  <div class="field-row" style="display:flex;align-items:center;gap:4px;margin-bottom:3px">
@@ -295,12 +318,20 @@ function renderSwitchFieldsTemplate(
295
318
  class="field-input"
296
319
  value=${caseName}
297
320
  style="flex:1"
298
- @input=${(/** @type {any} */ e) => {
321
+ @input=${(/** @type {Event} */ e) => {
299
322
  clearTimeout(debounce);
300
323
  debounce = setTimeout(() => {
301
- if (e.target.value && e.target.value !== caseName)
324
+ if (
325
+ /** @type {HTMLInputElement} */ (e.target).value &&
326
+ /** @type {HTMLInputElement} */ (e.target).value !== caseName
327
+ )
302
328
  transactDoc(activeTab.value, (t) =>
303
- mutateRenameSwitchCase(t, path, caseName, e.target.value),
329
+ mutateRenameSwitchCase(
330
+ t,
331
+ path,
332
+ caseName,
333
+ /** @type {HTMLInputElement} */ (e.target).value,
334
+ ),
304
335
  );
305
336
  }, 500);
306
337
  }}
@@ -309,7 +340,7 @@ function renderSwitchFieldsTemplate(
309
340
  class="bind-toggle"
310
341
  title="Edit case"
311
342
  style="cursor:pointer"
312
- @click=${(/** @type {any} */ e) => {
343
+ @click=${(/** @type {Event} */ e) => {
313
344
  e.stopPropagation();
314
345
  activeTab.value.session.selection = [...path, "cases", caseName];
315
346
  }}
@@ -317,7 +348,7 @@ function renderSwitchFieldsTemplate(
317
348
  >
318
349
  <span
319
350
  style="cursor:pointer;color:var(--danger);font-size:11px"
320
- @click=${(/** @type {any} */ e) => {
351
+ @click=${(/** @type {Event} */ e) => {
321
352
  e.stopPropagation();
322
353
  transactDoc(activeTab.value, (t) => mutateRemoveSwitchCase(t, path, caseName));
323
354
  }}
@@ -340,9 +371,9 @@ function renderSwitchFieldsTemplate(
340
371
 
341
372
  /** Component props fields template */
342
373
  function renderComponentPropsFieldsTemplate(
343
- /** @type {any} */ node,
344
- /** @type {any} */ path,
345
- /** @type {any} */ mapSignals,
374
+ /** @type {JxMutableNode} */ node,
375
+ /** @type {JxPath} */ path,
376
+ /** @type {SignalOption[] | null} */ mapSignals,
346
377
  /** @type {(path: string) => void} */ navigateToComponent,
347
378
  ) {
348
379
  const tab = activeTab.value;
@@ -351,11 +382,16 @@ function renderComponentPropsFieldsTemplate(
351
382
  const isNpm = comp.source === "npm";
352
383
  const currentVals = isNpm ? node.attributes || {} : node.$props || {};
353
384
  const updateFn = isNpm
354
- ? (/** @type {string} */ name, /** @type {any} */ v) =>
385
+ ? (/** @type {string} */ name, /** @type {JsonValue} */ v) =>
355
386
  transactDoc(activeTab.value, (t) =>
356
- mutateUpdateAttribute(t, path, name, v === "" ? undefined : v),
387
+ mutateUpdateAttribute(
388
+ t,
389
+ path,
390
+ name,
391
+ v === "" ? undefined : /** @type {string | undefined} */ (v),
392
+ ),
357
393
  )
358
- : (/** @type {string} */ name, /** @type {any} */ v) =>
394
+ : (/** @type {string} */ name, /** @type {JsonValue} */ v) =>
359
395
  transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
360
396
 
361
397
  const defs = tab.doc.document.state || {};
@@ -365,139 +401,157 @@ function renderComponentPropsFieldsTemplate(
365
401
  const extraSignals = mapSignals;
366
402
 
367
403
  return html`
368
- ${comp.props.map((/** @type {any} */ prop) => {
369
- const rawValue = currentVals[prop.name];
370
- const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
371
- const hasVal = rawValue !== undefined && rawValue !== null;
372
- const parsed = parseCemType(prop.type);
373
- const onChange = (/** @type {any} */ v) => updateFn(prop.name, v);
374
-
375
- const clearProp = (/** @type {any} */ e) => {
376
- e.stopPropagation();
377
- updateFn(prop.name, undefined);
378
- };
379
-
380
- const onToggleBind = () => {
381
- if (isBound) {
382
- const ref = rawValue.$ref;
383
- const defName = ref.startsWith("#/state/") ? ref.slice(8) : ref;
384
- const def = defs[defName];
385
- let staticVal = "";
386
- if (def && def.default !== undefined)
387
- staticVal =
388
- typeof def.default === "object" ? JSON.stringify(def.default) : String(def.default);
389
- onChange(staticVal || undefined);
390
- } else {
391
- if (signalDefs.length > 0) {
392
- onChange({ $ref: `#/state/${signalDefs[0][0]}` });
393
- } else if (extraSignals?.length > 0) {
394
- onChange({ $ref: extraSignals[0].value });
404
+ ${comp.props.map(
405
+ (
406
+ /** @type {{ name: string; type?: string; format?: string; description?: string }} */ prop,
407
+ ) => {
408
+ const rawValue = currentVals[prop.name];
409
+ const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
410
+ const hasVal = rawValue !== undefined && rawValue !== null;
411
+ const parsed = parseCemType(prop.type);
412
+ const onChange = (/** @type {JsonValue} */ v) => updateFn(prop.name, v);
413
+
414
+ const clearProp = (/** @type {Event} */ e) => {
415
+ e.stopPropagation();
416
+ updateFn(prop.name, undefined);
417
+ };
418
+
419
+ const onToggleBind = () => {
420
+ if (isBound) {
421
+ const ref = rawValue.$ref;
422
+ const defName = ref.startsWith("#/state/") ? ref.slice(8) : ref;
423
+ const def = defs[defName];
424
+ let staticVal = "";
425
+ if (def && def.default !== undefined)
426
+ staticVal =
427
+ typeof def.default === "object" ? JSON.stringify(def.default) : String(def.default);
428
+ onChange(staticVal || undefined);
429
+ } else {
430
+ if (signalDefs.length > 0) {
431
+ onChange({ $ref: `#/state/${signalDefs[0][0]}` });
432
+ } else if (extraSignals && extraSignals.length > 0) {
433
+ onChange({ $ref: extraSignals[0].value });
434
+ }
395
435
  }
396
- }
397
- };
398
-
399
- const boundTpl = html`
400
- <sp-picker
401
- size="s"
402
- quiet
403
- placeholder="— select signal —"
404
- value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
405
- @change=${(/** @type {any} */ e) => {
406
- if (e.target.value) onChange({ $ref: e.target.value });
407
- else onChange(undefined);
408
- }}
409
- >
410
- ${signalDefs.map(
411
- ([defName]) =>
412
- html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
413
- )}
414
- ${extraSignals
415
- ? html`
416
- <sp-menu-divider></sp-menu-divider>
417
- ${extraSignals.map(
418
- (/** @type {any} */ sig) =>
419
- html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
420
- )}
421
- `
422
- : nothing}
423
- </sp-picker>
424
- `;
425
-
426
- /** @type {any} */
427
- let debounce;
428
- const staticVal = isBound ? "" : (rawValue ?? "");
429
- /** @type {any} */
430
- let widgetTpl;
431
- if (prop.format === "image") {
432
- widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
433
- } else if (prop.format === "color") {
434
- widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
435
- } else if (prop.format === "date") {
436
- widgetTpl = html`<sp-textfield
437
- size="s"
438
- placeholder="YYYY-MM-DD"
439
- value=${staticVal}
440
- @input=${(/** @type {any} */ e) => {
441
- clearTimeout(debounce);
442
- debounce = setTimeout(() => onChange(e.target.value), 400);
443
- }}
444
- ></sp-textfield>`;
445
- } else if (parsed.kind === "boolean") {
446
- widgetTpl = html`<sp-checkbox
447
- size="s"
448
- .checked=${live(!!staticVal)}
449
- @change=${(/** @type {any} */ e) => onChange(e.target.checked || undefined)}
450
- ></sp-checkbox>`;
451
- } else if (parsed.kind === "number") {
452
- widgetTpl = html`<sp-number-field
453
- size="s"
454
- value=${staticVal}
455
- @input=${(/** @type {any} */ e) => {
456
- clearTimeout(debounce);
457
- debounce = setTimeout(() => onChange(e.target.value), 400);
458
- }}
459
- ></sp-number-field>`;
460
- } else if (parsed.kind === "combobox") {
461
- const options = /** @type {string[]} */ (/** @type {any} */ (parsed).options);
462
- widgetTpl = html`<jx-value-selector
463
- .value=${String(staticVal)}
464
- size="s"
465
- placeholder="—"
466
- .options=${options.map((o) => ({ value: o, label: camelToLabel(o) }))}
467
- @change=${(/** @type {any} */ e) => onChange(e.detail?.value ?? e.target.value)}
468
- ></jx-value-selector>`;
469
- } else {
470
- widgetTpl = html`<sp-textfield
471
- size="s"
472
- value=${staticVal}
473
- @input=${(/** @type {any} */ e) => {
474
- clearTimeout(debounce);
475
- debounce = setTimeout(() => onChange(e.target.value), 400);
476
- }}
477
- ></sp-textfield>`;
478
- }
436
+ };
479
437
 
480
- return html`
481
- <div class="style-row" data-prop=${prop.name}>
482
- <div class="style-row-label">
483
- ${hasVal
484
- ? html`<span class="set-dot" title="Clear ${prop.name}" @click=${clearProp}></span>`
438
+ const boundTpl = html`
439
+ <sp-picker
440
+ size="s"
441
+ quiet
442
+ placeholder=" select signal —"
443
+ value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
444
+ @change=${(/** @type {Event} */ e) => {
445
+ if (/** @type {HTMLInputElement} */ (e.target).value)
446
+ onChange({ $ref: /** @type {HTMLInputElement} */ (e.target).value });
447
+ else onChange(undefined);
448
+ }}
449
+ >
450
+ ${signalDefs.map(
451
+ ([defName]) =>
452
+ html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
453
+ )}
454
+ ${extraSignals
455
+ ? html`
456
+ <sp-menu-divider></sp-menu-divider>
457
+ ${extraSignals.map(
458
+ (/** @type {SignalOption} */ sig) =>
459
+ html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
460
+ )}
461
+ `
485
462
  : nothing}
486
- <sp-field-label size="s" title=${prop.description || prop.name}
487
- >${camelToLabel(prop.name)}</sp-field-label
488
- >
489
- <sp-action-button
490
- size="xs"
491
- quiet
492
- title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
493
- @click=${onToggleBind}
494
- >${isBound ? "\u26A1" : "\u2194"}</sp-action-button
495
- >
463
+ </sp-picker>
464
+ `;
465
+
466
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
467
+ let debounce;
468
+ const staticVal = isBound ? "" : (rawValue ?? "");
469
+ /** @type {import("lit-html").TemplateResult | undefined} */
470
+ let widgetTpl;
471
+ if (prop.format === "image") {
472
+ widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
473
+ } else if (prop.format === "color") {
474
+ widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
475
+ } else if (prop.format === "date") {
476
+ widgetTpl = html`<sp-textfield
477
+ size="s"
478
+ placeholder="YYYY-MM-DD"
479
+ value=${staticVal}
480
+ @input=${(/** @type {Event} */ e) => {
481
+ clearTimeout(debounce);
482
+ debounce = setTimeout(
483
+ () => onChange(/** @type {HTMLInputElement} */ (e.target).value),
484
+ 400,
485
+ );
486
+ }}
487
+ ></sp-textfield>`;
488
+ } else if (parsed.kind === "boolean") {
489
+ widgetTpl = html`<sp-checkbox
490
+ size="s"
491
+ .checked=${live(!!staticVal)}
492
+ @change=${(/** @type {Event} */ e) =>
493
+ onChange(/** @type {HTMLInputElement} */ (e.target).checked || undefined)}
494
+ ></sp-checkbox>`;
495
+ } else if (parsed.kind === "number") {
496
+ widgetTpl = html`<sp-number-field
497
+ size="s"
498
+ value=${staticVal}
499
+ @input=${(/** @type {Event} */ e) => {
500
+ clearTimeout(debounce);
501
+ debounce = setTimeout(
502
+ () => onChange(/** @type {HTMLInputElement} */ (e.target).value),
503
+ 400,
504
+ );
505
+ }}
506
+ ></sp-number-field>`;
507
+ } else if (parsed.kind === "combobox") {
508
+ const options = /** @type {string[]} */ (
509
+ /** @type {{ options?: string[] }} */ (parsed).options
510
+ );
511
+ widgetTpl = html`<jx-value-selector
512
+ .value=${String(staticVal)}
513
+ size="s"
514
+ placeholder="—"
515
+ .options=${options.map((o) => ({ value: o, label: camelToLabel(o) }))}
516
+ @change=${(/** @type {Event & { detail?: { value?: string } }} */ e) =>
517
+ onChange(e.detail?.value ?? /** @type {HTMLInputElement} */ (e.target).value)}
518
+ ></jx-value-selector>`;
519
+ } else {
520
+ widgetTpl = html`<sp-textfield
521
+ size="s"
522
+ value=${staticVal}
523
+ @input=${(/** @type {Event} */ e) => {
524
+ clearTimeout(debounce);
525
+ debounce = setTimeout(
526
+ () => onChange(/** @type {HTMLInputElement} */ (e.target).value),
527
+ 400,
528
+ );
529
+ }}
530
+ ></sp-textfield>`;
531
+ }
532
+
533
+ return html`
534
+ <div class="style-row" data-prop=${prop.name}>
535
+ <div class="style-row-label">
536
+ ${hasVal
537
+ ? html`<span class="set-dot" title="Clear ${prop.name}" @click=${clearProp}></span>`
538
+ : nothing}
539
+ <sp-field-label size="s" title=${prop.description || prop.name}
540
+ >${camelToLabel(prop.name)}</sp-field-label
541
+ >
542
+ <sp-action-button
543
+ size="xs"
544
+ quiet
545
+ title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
546
+ @click=${onToggleBind}
547
+ >${isBound ? "\u26A1" : "\u2194"}</sp-action-button
548
+ >
549
+ </div>
550
+ ${isBound ? boundTpl : widgetTpl}
496
551
  </div>
497
- ${isBound ? boundTpl : widgetTpl}
498
- </div>
499
- `;
500
- })}
552
+ `;
553
+ },
554
+ )}
501
555
  ${comp.props.length === 0 ? html`<div class="empty-state">No props defined</div>` : nothing}
502
556
  ${comp.path
503
557
  ? html`<span class="kv-add" @click=${() => navigateToComponent(comp.path)}
@@ -509,10 +563,10 @@ function renderComponentPropsFieldsTemplate(
509
563
 
510
564
  /** Custom attrs fields template */
511
565
  function renderCustomAttrsFieldsTemplate(
512
- /** @type {any} */ node,
513
- /** @type {any} */ path,
514
- /** @type {any} */ attrs,
515
- /** @type {any} */ knownAttrNames,
566
+ /** @type {JxMutableNode} */ node,
567
+ /** @type {JxPath} */ path,
568
+ /** @type {Record<string, unknown>} */ attrs,
569
+ /** @type {Set<string>} */ knownAttrNames,
516
570
  ) {
517
571
  const customAttrs = Object.entries(attrs).filter(([k]) => !knownAttrNames.has(k));
518
572
  return html`
@@ -520,7 +574,7 @@ function renderCustomAttrsFieldsTemplate(
520
574
  kvRow(
521
575
  attr,
522
576
  String(val),
523
- (/** @type {any} */ newAttr, /** @type {any} */ newVal) => {
577
+ (/** @type {string} */ newAttr, /** @type {string} */ newVal) => {
524
578
  if (newAttr !== attr) {
525
579
  transactDoc(activeTab.value, (t) => {
526
580
  mutateUpdateAttribute(t, path, attr, undefined);
@@ -545,9 +599,9 @@ function renderCustomAttrsFieldsTemplate(
545
599
  // ─── Media breakpoints ──────────────────────────────────────────────────────
546
600
 
547
601
  /** Media breakpoint fields template */
548
- function renderMediaFieldsTemplate(/** @type {any} */ node) {
602
+ function renderMediaFieldsTemplate(/** @type {JxMutableNode} */ node) {
549
603
  const media = node.$media || {};
550
- /** @type {any} */
604
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
551
605
  let baseDebounce;
552
606
  const breakpoints = Object.entries(media).filter(([k]) => k !== "--");
553
607
 
@@ -559,10 +613,10 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
559
613
  style="width:70px;flex:none"
560
614
  placeholder="320px"
561
615
  value=${media["--"] || ""}
562
- @input=${(/** @type {any} */ e) => {
616
+ @input=${(/** @type {Event} */ e) => {
563
617
  clearTimeout(baseDebounce);
564
618
  baseDebounce = setTimeout(() => {
565
- const val = e.target.value.trim();
619
+ const val = /** @type {HTMLInputElement} */ (e.target).value.trim();
566
620
  transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--", val || undefined));
567
621
  }, 400);
568
622
  }}
@@ -583,7 +637,7 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
583
637
  <span
584
638
  class="kv-add"
585
639
  style=${view.showAddBreakpointForm ? "display:none" : ""}
586
- @click=${(/** @type {any} */ _e) => {
640
+ @click=${(/** @type {Event} */ _e) => {
587
641
  view.showAddBreakpointForm = true;
588
642
  renderOnly("rightPanel");
589
643
  }}
@@ -597,8 +651,9 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
597
651
  class="field-input"
598
652
  placeholder="Name (e.g. Tablet)"
599
653
  style="flex:1"
600
- @input=${(/** @type {any} */ e) => {
601
- view.addBreakpointPreview = friendlyNameToMedia(e.target.value) || "";
654
+ @input=${(/** @type {Event} */ e) => {
655
+ view.addBreakpointPreview =
656
+ friendlyNameToMedia(/** @type {HTMLInputElement} */ (e.target).value) || "";
602
657
  renderOnly("rightPanel");
603
658
  }}
604
659
  />
@@ -614,11 +669,15 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
614
669
  <button
615
670
  class="kv-add"
616
671
  style="padding:2px 10px;cursor:pointer"
617
- @click=${(/** @type {any} */ e) => {
618
- const wrap = e.target.closest("div").parentElement;
619
- const nameVal = wrap.querySelector("input")?.value;
620
- const queryVal = wrap.querySelector(".add-bp-query")?.value?.trim();
621
- const key = friendlyNameToMedia(nameVal);
672
+ @click=${(/** @type {Event} */ e) => {
673
+ const wrap = /** @type {HTMLElement} */ (e.target).closest(
674
+ "div",
675
+ )?.parentElement;
676
+ const nameVal = wrap?.querySelector("input")?.value;
677
+ const queryVal = /** @type {HTMLInputElement | null} */ (
678
+ wrap?.querySelector(".add-bp-query")
679
+ )?.value?.trim();
680
+ const key = friendlyNameToMedia(nameVal || "");
622
681
  if (key && queryVal) {
623
682
  view.showAddBreakpointForm = false;
624
683
  view.addBreakpointPreview = "";
@@ -648,8 +707,8 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
648
707
  }
649
708
 
650
709
  /** Single media breakpoint row template */
651
- function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */ query) {
652
- /** @type {any} */
710
+ function mediaBreakpointRowTemplate(/** @type {string} */ name, /** @type {string} */ query) {
711
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
653
712
  let debounceTimer;
654
713
  let currentRawLabel = name;
655
714
  return html`
@@ -659,17 +718,21 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
659
718
  class="field-input"
660
719
  value=${mediaDisplayName(name)}
661
720
  style="flex:1;font-weight:600;font-size:12px"
662
- @input=${(/** @type {any} */ e) => {
663
- const newKey = friendlyNameToMedia(e.target.value);
721
+ @input=${(/** @type {Event} */ e) => {
722
+ const newKey = friendlyNameToMedia(/** @type {HTMLInputElement} */ (e.target).value);
664
723
  currentRawLabel = newKey || "";
665
- const rawEl = e.target.parentElement?.querySelector(".bp-raw-label");
724
+ const rawEl = /** @type {HTMLElement} */ (e.target).parentElement?.querySelector(
725
+ ".bp-raw-label",
726
+ );
666
727
  if (rawEl) rawEl.textContent = currentRawLabel;
667
728
  clearTimeout(debounceTimer);
668
729
  debounceTimer = setTimeout(() => {
669
730
  if (newKey && newKey !== name) {
670
- const queryEl = e.target
671
- .closest("div[style]")
672
- ?.parentElement?.querySelector(".bp-query-input");
731
+ const queryEl = /** @type {HTMLInputElement | null} */ (
732
+ /** @type {HTMLElement} */ (e.target)
733
+ .closest("div[style]")
734
+ ?.parentElement?.querySelector(".bp-query-input")
735
+ );
673
736
  transactDoc(activeTab.value, (t) => {
674
737
  mutateUpdateMedia(t, name, undefined);
675
738
  mutateUpdateMedia(t, newKey, queryEl?.value || query);
@@ -694,10 +757,13 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
694
757
  class="field-input bp-query-input"
695
758
  value=${query}
696
759
  style="flex:1"
697
- @input=${(/** @type {any} */ e) => {
760
+ @input=${(/** @type {Event} */ e) => {
698
761
  clearTimeout(debounceTimer);
699
762
  debounceTimer = setTimeout(
700
- () => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, name, e.target.value)),
763
+ () =>
764
+ transactDoc(activeTab.value, (t) =>
765
+ mutateUpdateMedia(t, name, /** @type {HTMLInputElement} */ (e.target).value),
766
+ ),
701
767
  400,
702
768
  );
703
769
  }}
@@ -717,8 +783,11 @@ async function loadLayoutEntries() {
717
783
  const platform = getPlatform();
718
784
  const listing = await platform.listDirectory("layouts");
719
785
  layoutEntries = listing
720
- .filter((/** @type {any} */ f) => f.type === "file" && f.name.endsWith(".json"))
721
- .map((/** @type {any} */ f) => ({
786
+ .filter(
787
+ (/** @type {{ type: string; name: string }} */ f) =>
788
+ f.type === "file" && f.name.endsWith(".json"),
789
+ )
790
+ .map((/** @type {{ type: string; name: string }} */ f) => ({
722
791
  name: f.name.replace(/\.json$/, ""),
723
792
  path: `./layouts/${f.name}`,
724
793
  }));
@@ -732,12 +801,12 @@ export function invalidateLayoutPickerCache() {
732
801
  layoutEntries = null;
733
802
  }
734
803
 
735
- function isPageDocument(/** @type {any} */ documentPath) {
804
+ function isPageDocument(/** @type {string | undefined | null} */ documentPath) {
736
805
  if (!documentPath || !projectState?.isSiteProject) return false;
737
806
  return documentPath.startsWith("pages/") || documentPath.startsWith("./pages/");
738
807
  }
739
808
 
740
- function renderPageSection(/** @type {any} */ node) {
809
+ function renderPageSection(/** @type {JxMutableNode} */ node) {
741
810
  const tab = activeTab.value;
742
811
  if (!isPageDocument(tab.documentPath)) return nothing;
743
812
 
@@ -761,7 +830,7 @@ function renderPageSection(/** @type {any} */ node) {
761
830
  ? html`<span
762
831
  class="set-dot"
763
832
  title="Reset to default"
764
- @click=${(/** @type {any} */ e) => {
833
+ @click=${(/** @type {Event} */ e) => {
765
834
  e.stopPropagation();
766
835
  transactDoc(activeTab.value, (t) =>
767
836
  mutateUpdateProperty(t, [], "$layout", undefined),
@@ -774,8 +843,8 @@ function renderPageSection(/** @type {any} */ node) {
774
843
  <sp-picker
775
844
  size="s"
776
845
  value=${displayValue}
777
- @change=${(/** @type {any} */ e) => {
778
- const val = e.target.value;
846
+ @change=${(/** @type {Event} */ e) => {
847
+ const val = /** @type {HTMLInputElement} */ (e.target).value;
779
848
  if (val === "__default__") {
780
849
  transactDoc(activeTab.value, (t) =>
781
850
  mutateUpdateProperty(t, [], "$layout", undefined),
@@ -796,7 +865,7 @@ function renderPageSection(/** @type {any} */ node) {
796
865
  <sp-menu-item value="__none__">None</sp-menu-item>
797
866
  <sp-menu-divider></sp-menu-divider>
798
867
  ${layoutEntries.map(
799
- (/** @type {any} */ l) =>
868
+ (/** @type {{ name: string; path: string }} */ l) =>
800
869
  html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
801
870
  )}
802
871
  </sp-picker>
@@ -813,8 +882,12 @@ function renderPageSection(/** @type {any} */ node) {
813
882
 
814
883
  // ─── Layout selection panel ─────────────────────────────────────────────────
815
884
 
816
- function renderLayoutSelectionPanel(/** @type {any} */ ctx) {
817
- const { el, layoutPath } = view.layoutSelection;
885
+ function renderLayoutSelectionPanel(
886
+ /** @type {{ navigateToComponent: (path: string) => void }} */ ctx,
887
+ ) {
888
+ const { el, layoutPath } = /** @type {{ el: HTMLElement; layoutPath: string }} */ (
889
+ view.layoutSelection
890
+ );
818
891
  const tagName = el?.tagName?.toLowerCase() || "element";
819
892
  const className = el?.className || "";
820
893
  const displayPath = layoutPath || "layout";
@@ -879,7 +952,9 @@ export function renderPropertiesPanelTemplate(ctx) {
879
952
  const path = tab.session.selection;
880
953
  const isMapNode = node.$prototype === "Array";
881
954
  const isMapParent =
882
- node.children && typeof node.children === "object" && node.children.$prototype === "Array";
955
+ node.children &&
956
+ typeof node.children === "object" &&
957
+ /** @type {{ $prototype?: string }} */ (node.children).$prototype === "Array";
883
958
  const isSwitchNode = !!node.$switch;
884
959
  const isCustomInstance = (node.tagName || "").includes("-");
885
960
  const isRoot = path.length === 0;
@@ -894,9 +969,9 @@ export function renderPropertiesPanelTemplate(ctx) {
894
969
  : null;
895
970
 
896
971
  function renderAttrRow(
897
- /** @type {any} */ attr,
898
- /** @type {any} */ entry,
899
- /** @type {any} */ value,
972
+ /** @type {string} */ attr,
973
+ /** @type {HtmlMetaEntry} */ entry,
974
+ /** @type {unknown} */ value,
900
975
  ) {
901
976
  const type = inferInputType(entry);
902
977
  const hasVal = value !== undefined && value !== "";
@@ -912,9 +987,14 @@ export function renderPropertiesPanelTemplate(ctx) {
912
987
  <sp-checkbox
913
988
  size="s"
914
989
  .checked=${live(!!value)}
915
- @change=${(/** @type {any} */ e) =>
990
+ @change=${(/** @type {Event} */ e) =>
916
991
  transactDoc(activeTab.value, (t) =>
917
- mutateUpdateAttribute(t, path, attr, e.target.checked || undefined),
992
+ mutateUpdateAttribute(
993
+ t,
994
+ path,
995
+ attr,
996
+ /** @type {HTMLInputElement} */ (e.target).checked ? "" : undefined,
997
+ ),
918
998
  )}
919
999
  >
920
1000
  </sp-checkbox>
@@ -928,21 +1008,23 @@ export function renderPropertiesPanelTemplate(ctx) {
928
1008
  hasValue: hasVal,
929
1009
  onClear: () =>
930
1010
  transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
931
- widget: widgetForType(type, entry, attr, value || "", (/** @type {any} */ v) =>
1011
+ widget: widgetForType(type, entry, attr, String(value || ""), (/** @type {string} */ v) =>
932
1012
  transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
933
1013
  ),
934
1014
  });
935
1015
  }
936
1016
 
937
1017
  // ── Collect applicable attributes from html-meta ──
938
- const applicableAttrs = /** @type {Record<string, any>} */ ({});
939
- for (const [attr, entry] of /** @type {[string, any][]} */ (Object.entries(htmlMeta.$defs))) {
1018
+ const applicableAttrs = /** @type {Record<string, HtmlMetaEntry>} */ ({});
1019
+ for (const [attr, entry] of /** @type {[string, HtmlMetaEntry][]} */ (
1020
+ Object.entries(htmlMeta.$defs)
1021
+ )) {
940
1022
  if (!entry.$elements || entry.$elements.includes(tagName)) {
941
1023
  applicableAttrs[attr] = entry;
942
1024
  }
943
1025
  }
944
1026
 
945
- const attrSections = /** @type {Record<string, any[]>} */ ({});
1027
+ const attrSections = /** @type {Record<string, { name: string; entry: HtmlMetaEntry }[]>} */ ({});
946
1028
  for (const sec of htmlMeta.$sections) attrSections[sec.key] = [];
947
1029
  for (const [attr, entry] of Object.entries(applicableAttrs)) {
948
1030
  const secKey = entry.$section;
@@ -950,7 +1032,10 @@ export function renderPropertiesPanelTemplate(ctx) {
950
1032
  }
951
1033
  for (const sec of htmlMeta.$sections) {
952
1034
  attrSections[sec.key].sort(
953
- (/** @type {any} */ a, /** @type {any} */ b) => a.entry.$order - b.entry.$order,
1035
+ (
1036
+ /** @type {{ name: string; entry: HtmlMetaEntry }} */ a,
1037
+ /** @type {{ name: string; entry: HtmlMetaEntry }} */ b,
1038
+ ) => a.entry.$order - b.entry.$order,
954
1039
  );
955
1040
  }
956
1041
 
@@ -968,13 +1053,13 @@ export function renderPropertiesPanelTemplate(ctx) {
968
1053
  }
969
1054
  if (customAttrs.length > 0) autoOpen.add("__custom");
970
1055
 
971
- function isSectionOpen(/** @type {any} */ key) {
1056
+ function isSectionOpen(/** @type {string} */ key) {
972
1057
  if (tab.session.ui.inspectorSections[key] !== undefined)
973
1058
  return tab.session.ui.inspectorSections[key];
974
1059
  return autoOpen.has(key);
975
1060
  }
976
1061
 
977
- function toggleSection(/** @type {any} */ key) {
1062
+ function toggleSection(/** @type {string} */ key) {
978
1063
  const current = isSectionOpen(key);
979
1064
  activeTab.value.session.ui.inspectorSections = {
980
1065
  ...activeTab.value.session.ui.inspectorSections,
@@ -1000,9 +1085,14 @@ export function renderPropertiesPanelTemplate(ctx) {
1000
1085
  .value=${live(tagName)}
1001
1086
  autocomplete="off"
1002
1087
  list="tag-names"
1003
- @input=${debouncedStyleCommit("prop:tagName", 400, (/** @type {any} */ e) => {
1088
+ @input=${debouncedStyleCommit("prop:tagName", 400, (/** @type {Event} */ e) => {
1004
1089
  transactDoc(activeTab.value, (t) =>
1005
- mutateUpdateProperty(t, path, "tagName", e.target.value || undefined),
1090
+ mutateUpdateProperty(
1091
+ t,
1092
+ path,
1093
+ "tagName",
1094
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
1095
+ ),
1006
1096
  );
1007
1097
  })}
1008
1098
  ></sp-textfield>
@@ -1013,7 +1103,7 @@ export function renderPropertiesPanelTemplate(ctx) {
1013
1103
  ? html`<span
1014
1104
  class="set-dot"
1015
1105
  title="Clear $id"
1016
- @click=${(/** @type {any} */ e) => {
1106
+ @click=${(/** @type {Event} */ e) => {
1017
1107
  e.stopPropagation();
1018
1108
  transactDoc(activeTab.value, (t) =>
1019
1109
  mutateUpdateProperty(t, path, "$id", undefined),
@@ -1026,9 +1116,14 @@ export function renderPropertiesPanelTemplate(ctx) {
1026
1116
  <sp-textfield
1027
1117
  size="s"
1028
1118
  .value=${live(node.$id || "")}
1029
- @input=${debouncedStyleCommit("prop:$id", 400, (/** @type {any} */ e) => {
1119
+ @input=${debouncedStyleCommit("prop:$id", 400, (/** @type {Event} */ e) => {
1030
1120
  transactDoc(activeTab.value, (t) =>
1031
- mutateUpdateProperty(t, path, "$id", e.target.value || undefined),
1121
+ mutateUpdateProperty(
1122
+ t,
1123
+ path,
1124
+ "$id",
1125
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
1126
+ ),
1032
1127
  );
1033
1128
  })}
1034
1129
  ></sp-textfield>
@@ -1039,7 +1134,7 @@ export function renderPropertiesPanelTemplate(ctx) {
1039
1134
  ? html`<span
1040
1135
  class="set-dot"
1041
1136
  title="Clear class"
1042
- @click=${(/** @type {any} */ e) => {
1137
+ @click=${(/** @type {Event} */ e) => {
1043
1138
  e.stopPropagation();
1044
1139
  transactDoc(activeTab.value, (t) =>
1045
1140
  mutateUpdateProperty(t, path, "className", undefined),
@@ -1052,9 +1147,14 @@ export function renderPropertiesPanelTemplate(ctx) {
1052
1147
  <sp-textfield
1053
1148
  size="s"
1054
1149
  .value=${live(node.className || "")}
1055
- @input=${debouncedStyleCommit("prop:className", 400, (/** @type {any} */ e) => {
1150
+ @input=${debouncedStyleCommit("prop:className", 400, (/** @type {Event} */ e) => {
1056
1151
  transactDoc(activeTab.value, (t) =>
1057
- mutateUpdateProperty(t, path, "className", e.target.value || undefined),
1152
+ mutateUpdateProperty(
1153
+ t,
1154
+ path,
1155
+ "className",
1156
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
1157
+ ),
1058
1158
  );
1059
1159
  })}
1060
1160
  ></sp-textfield>
@@ -1067,7 +1167,7 @@ export function renderPropertiesPanelTemplate(ctx) {
1067
1167
  ? html`<span
1068
1168
  class="set-dot"
1069
1169
  title="Clear text"
1070
- @click=${(/** @type {any} */ e) => {
1170
+ @click=${(/** @type {Event} */ e) => {
1071
1171
  e.stopPropagation();
1072
1172
  transactDoc(activeTab.value, (t) =>
1073
1173
  mutateUpdateProperty(t, path, "textContent", undefined),
@@ -1085,11 +1185,20 @@ export function renderPropertiesPanelTemplate(ctx) {
1085
1185
  ? node.textContent
1086
1186
  : (node.textContent ?? ""),
1087
1187
  )}
1088
- @input=${debouncedStyleCommit("prop:textContent", 400, (/** @type {any} */ e) => {
1089
- transactDoc(activeTab.value, (t) =>
1090
- mutateUpdateProperty(t, path, "textContent", e.target.value || undefined),
1091
- );
1092
- })}
1188
+ @input=${debouncedStyleCommit(
1189
+ "prop:textContent",
1190
+ 400,
1191
+ (/** @type {Event} */ e) => {
1192
+ transactDoc(activeTab.value, (t) =>
1193
+ mutateUpdateProperty(
1194
+ t,
1195
+ path,
1196
+ "textContent",
1197
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
1198
+ ),
1199
+ );
1200
+ },
1201
+ )}
1093
1202
  ></sp-textfield>
1094
1203
  </div>
1095
1204
  `
@@ -1100,7 +1209,7 @@ export function renderPropertiesPanelTemplate(ctx) {
1100
1209
  ? html`<span
1101
1210
  class="set-dot"
1102
1211
  title="Clear hidden"
1103
- @click=${(/** @type {any} */ e) => {
1212
+ @click=${(/** @type {Event} */ e) => {
1104
1213
  e.stopPropagation();
1105
1214
  transactDoc(activeTab.value, (t) =>
1106
1215
  mutateUpdateProperty(t, path, "hidden", undefined),
@@ -1113,9 +1222,14 @@ export function renderPropertiesPanelTemplate(ctx) {
1113
1222
  <sp-checkbox
1114
1223
  size="s"
1115
1224
  .checked=${live(!!node.hidden)}
1116
- @change=${(/** @type {any} */ e) =>
1225
+ @change=${(/** @type {Event} */ e) =>
1117
1226
  transactDoc(activeTab.value, (t) =>
1118
- mutateUpdateProperty(t, path, "hidden", e.target.checked || undefined),
1227
+ mutateUpdateProperty(
1228
+ t,
1229
+ path,
1230
+ "hidden",
1231
+ /** @type {HTMLInputElement} */ (e.target).checked || undefined,
1232
+ ),
1119
1233
  )}
1120
1234
  >
1121
1235
  </sp-checkbox>
@@ -1207,7 +1321,9 @@ export function renderPropertiesPanelTemplate(ctx) {
1207
1321
  .filter((sec) => attrSections[sec.key].length > 0)
1208
1322
  .map((sec) => {
1209
1323
  const sectionAttrs = attrSections[sec.key];
1210
- const hasAnySet = sectionAttrs.some((/** @type {any} */ a) => attrs[a.name] !== undefined);
1324
+ const hasAnySet = sectionAttrs.some(
1325
+ (/** @type {{ name: string; entry: HtmlMetaEntry }} */ a) => attrs[a.name] !== undefined,
1326
+ );
1211
1327
  return html`
1212
1328
  <sp-accordion-item
1213
1329
  label=${sec.label}
@@ -1218,7 +1334,7 @@ export function renderPropertiesPanelTemplate(ctx) {
1218
1334
  ? html`<span slot="heading" class="set-dot set-dot--section"></span>`
1219
1335
  : nothing}
1220
1336
  <div class="style-section-body">
1221
- ${sectionAttrs.map((/** @type {any} */ a) =>
1337
+ ${sectionAttrs.map((/** @type {{ name: string; entry: HtmlMetaEntry }} */ a) =>
1222
1338
  renderAttrRow(a.name, a.entry, attrs[a.name]),
1223
1339
  )}
1224
1340
  </div>