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