@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
@@ -27,6 +27,15 @@ import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts
27
27
  * }} MetaField
28
28
  */
29
29
 
30
+ /**
31
+ * @typedef {{
32
+ * type?: string;
33
+ * enum?: string[];
34
+ * format?: string;
35
+ * properties?: Record<string, unknown>;
36
+ * }} FmSchemaEntry
37
+ */
38
+
30
39
  /** @type {MetaField[]} */
31
40
  const PAGE_FIELDS = [
32
41
  { label: "Description", attr: "name", key: "description" },
@@ -44,48 +53,53 @@ const OG_FIELDS = [
44
53
  /** Set of `name`/`property` values managed by the structured forms. */
45
54
  const MANAGED_META_KEYS = new Set([...PAGE_FIELDS, ...OG_FIELDS].map((f) => f.key));
46
55
 
56
+ /** Frontmatter keys managed by the PAGE dedicated controls (others live inside $head). */
57
+ const RESERVED_FM_KEYS = new Set(["title"]);
58
+
47
59
  // ─── Helpers ─────────────────────────────────────────────────────────────
48
60
 
49
61
  /**
50
62
  * Find a `$head` meta entry by attribute match.
51
63
  *
52
- * @param {any[]} head
64
+ * @param {JxHeadEntry[]} head
53
65
  * @param {"name" | "property"} attr
54
66
  * @param {string} key
55
- * @returns {any | undefined}
67
+ * @returns {JxHeadEntry | undefined}
56
68
  */
57
69
  function findMetaEntry(head, attr, key) {
58
70
  if (!head) return undefined;
59
71
  return head.find(
60
- (/** @type {any} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
72
+ (/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
61
73
  );
62
74
  }
63
75
 
64
76
  /**
65
77
  * Find a `$head` link entry by `rel` attribute.
66
78
  *
67
- * @param {any[]} head
79
+ * @param {JxHeadEntry[]} head
68
80
  * @param {string} rel
69
- * @returns {any | undefined}
81
+ * @returns {JxHeadEntry | undefined}
70
82
  */
71
83
  function findLinkEntry(head, rel) {
72
84
  if (!head) return undefined;
73
- return head.find((/** @type {any} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel);
85
+ return head.find(
86
+ (/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
87
+ );
74
88
  }
75
89
 
76
90
  /**
77
91
  * Check if a `$head` entry is managed by the structured forms.
78
92
  *
79
- * @param {any} entry
93
+ * @param {JxHeadEntry} entry
80
94
  * @returns {boolean}
81
95
  */
82
96
  function isManagedEntry(entry) {
83
97
  if (!entry?.tagName) return false;
84
98
  // Managed meta tags
85
99
  if (entry.tagName === "meta") {
86
- const name = entry?.attributes?.name;
87
- const prop = entry?.attributes?.property;
88
- return (name && MANAGED_META_KEYS.has(name)) || (prop && MANAGED_META_KEYS.has(prop));
100
+ const name = String(entry?.attributes?.name ?? "");
101
+ const prop = String(entry?.attributes?.property ?? "");
102
+ return !!(name && MANAGED_META_KEYS.has(name)) || !!(prop && MANAGED_META_KEYS.has(prop));
89
103
  }
90
104
  // Managed link: favicon
91
105
  if (entry.tagName === "link" && entry?.attributes?.rel === "icon") return true;
@@ -95,7 +109,7 @@ function isManagedEntry(entry) {
95
109
  /**
96
110
  * Upsert or remove a meta entry in `doc.$head`.
97
111
  *
98
- * @param {any} doc
112
+ * @param {JxMutableNode} doc
99
113
  * @param {"name" | "property"} attr
100
114
  * @param {string} key
101
115
  * @param {string} content
@@ -103,7 +117,7 @@ function isManagedEntry(entry) {
103
117
  function upsertMeta(doc, attr, key, content) {
104
118
  if (!doc.$head) doc.$head = [];
105
119
  const idx = doc.$head.findIndex(
106
- (/** @type {any} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
120
+ (/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
107
121
  );
108
122
  if (content) {
109
123
  const entry = { tagName: "meta", attributes: { [attr]: key, content } };
@@ -120,14 +134,14 @@ function upsertMeta(doc, attr, key, content) {
120
134
  /**
121
135
  * Upsert or remove a link entry in `doc.$head`.
122
136
  *
123
- * @param {any} doc
137
+ * @param {JxMutableNode} doc
124
138
  * @param {string} rel
125
139
  * @param {string} href
126
140
  */
127
141
  function upsertLink(doc, rel, href) {
128
142
  if (!doc.$head) doc.$head = [];
129
143
  const idx = doc.$head.findIndex(
130
- (/** @type {any} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
144
+ (/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
131
145
  );
132
146
  if (href) {
133
147
  const entry = { tagName: "link", attributes: { rel, href } };
@@ -144,29 +158,29 @@ function upsertLink(doc, rel, href) {
144
158
  /**
145
159
  * Get a display label for an arbitrary $head entry.
146
160
  *
147
- * @param {any} entry
161
+ * @param {JxHeadEntry} entry
148
162
  * @returns {string}
149
163
  */
150
164
  function entryLabel(entry) {
151
165
  if (!entry?.tagName) return "unknown";
152
166
  const a = entry.attributes ?? {};
153
- if (a.name) return `<meta name="${a.name}">`;
154
- if (a.property) return `<meta property="${a.property}">`;
155
- if (a.rel && a.href) return `<link rel="${a.rel}">`;
156
- if (a.src) return `<script src="${a.src}">`;
157
- if (a.charset) return `<meta charset="${a.charset}">`;
167
+ if (a.name) return `<meta name="${String(a.name)}">`;
168
+ if (a.property) return `<meta property="${String(a.property)}">`;
169
+ if (a.rel && a.href) return `<link rel="${String(a.rel)}">`;
170
+ if (a.src) return `<script src="${String(a.src)}">`;
171
+ if (a.charset) return `<meta charset="${String(a.charset)}">`;
158
172
  return `<${entry.tagName}>`;
159
173
  }
160
174
 
161
175
  /**
162
176
  * Get a display value for an arbitrary $head entry.
163
177
  *
164
- * @param {any} entry
178
+ * @param {JxHeadEntry} entry
165
179
  * @returns {string}
166
180
  */
167
181
  function entryValue(entry) {
168
182
  const a = entry?.attributes ?? {};
169
- return a.content ?? a.href ?? a.src ?? entry?.textContent ?? "";
183
+ return String(a.content ?? a.href ?? a.src ?? entry?.textContent ?? "");
170
184
  }
171
185
 
172
186
  // ─── Field renderers ─────────────────────────────────────────────────────
@@ -175,13 +189,13 @@ function entryValue(entry) {
175
189
  * Render a meta field row using renderFieldRow.
176
190
  *
177
191
  * @param {MetaField} field
178
- * @param {any[]} head
179
- * @param {(fn: (doc: any) => void) => void} applyMutation
180
- * @returns {any}
192
+ * @param {JxHeadEntry[]} head
193
+ * @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
194
+ * @returns {import("lit-html").TemplateResult}
181
195
  */
182
196
  function renderMetaFieldRow(field, head, applyMutation) {
183
197
  const entry = findMetaEntry(head, field.attr, field.key);
184
- const val = entry?.attributes?.content ?? "";
198
+ const val = String(entry?.attributes?.content ?? "");
185
199
 
186
200
  if (field.media) {
187
201
  return renderFieldRow({
@@ -189,9 +203,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
189
203
  label: field.label,
190
204
  hasValue: !!val,
191
205
  onClear: () =>
192
- applyMutation((/** @type {any} */ d) => upsertMeta(d, field.attr, field.key, "")),
193
- widget: renderMediaPicker(field.key, val, (/** @type {any} */ v) => {
194
- applyMutation((/** @type {any} */ d) => upsertMeta(d, field.attr, field.key, v || ""));
206
+ applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
207
+ widget: renderMediaPicker(field.key, val, (/** @type {string} */ v) => {
208
+ applyMutation((/** @type {JxMutableNode} */ d) =>
209
+ upsertMeta(d, field.attr, field.key, v || ""),
210
+ );
195
211
  }),
196
212
  });
197
213
  }
@@ -203,9 +219,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
203
219
  multiline
204
220
  .value=${live(val)}
205
221
  placeholder="${field.label}…"
206
- @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {any} */ e) => {
207
- const content = e.target.value?.trim() ?? "";
208
- applyMutation((/** @type {any} */ d) => upsertMeta(d, field.attr, field.key, content));
222
+ @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
223
+ const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
224
+ applyMutation((/** @type {JxMutableNode} */ d) =>
225
+ upsertMeta(d, field.attr, field.key, content),
226
+ );
209
227
  })}
210
228
  ></sp-textfield>
211
229
  `
@@ -216,9 +234,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
216
234
  placeholder=${field.key === "viewport"
217
235
  ? "width=device-width, initial-scale=1"
218
236
  : `${field.label}…`}
219
- @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {any} */ e) => {
220
- const content = e.target.value?.trim() ?? "";
221
- applyMutation((/** @type {any} */ d) => upsertMeta(d, field.attr, field.key, content));
237
+ @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
238
+ const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
239
+ applyMutation((/** @type {JxMutableNode} */ d) =>
240
+ upsertMeta(d, field.attr, field.key, content),
241
+ );
222
242
  })}
223
243
  ></sp-textfield>
224
244
  `;
@@ -228,7 +248,7 @@ function renderMetaFieldRow(field, head, applyMutation) {
228
248
  label: field.label,
229
249
  hasValue: !!val,
230
250
  onClear: () =>
231
- applyMutation((/** @type {any} */ d) => upsertMeta(d, field.attr, field.key, "")),
251
+ applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
232
252
  widget,
233
253
  });
234
254
  }
@@ -237,11 +257,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
237
257
 
238
258
  /**
239
259
  * @param {{
240
- * document: any;
241
- * applyMutation: (fn: (doc: any) => void) => void;
260
+ * document: JxMutableNode;
261
+ * applyMutation: (fn: (doc: JxMutableNode) => void) => void;
242
262
  * renderLeftPanel: () => void;
243
263
  * }} ctx
244
- * @returns {any}
264
+ * @returns {import("lit-html").TemplateResult}
245
265
  */
246
266
  export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPanel }) {
247
267
  const head = doc.$head ?? [];
@@ -249,11 +269,11 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
249
269
 
250
270
  // Icon (favicon) link
251
271
  const iconEntry = findLinkEntry(head, "icon");
252
- const iconHref = iconEntry?.attributes?.href ?? "";
272
+ const iconHref = String(iconEntry?.attributes?.href ?? "");
253
273
 
254
274
  // Custom entries not managed by structured forms, fonts, or preconnects
255
275
  const customEntries = head.filter(
256
- (/** @type {any} */ e) =>
276
+ (/** @type {JxHeadEntry} */ e) =>
257
277
  !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
258
278
  );
259
279
 
@@ -277,7 +297,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
277
297
  label: "Title",
278
298
  hasValue: !!title,
279
299
  onClear: () =>
280
- applyMutation((/** @type {any} */ d) => {
300
+ applyMutation((/** @type {JxMutableNode} */ d) => {
281
301
  delete d.title;
282
302
  }),
283
303
  widget: html`
@@ -285,9 +305,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
285
305
  size="s"
286
306
  .value=${live(title)}
287
307
  placeholder="Page title…"
288
- @input=${debouncedStyleCommit("head:title", 400, (/** @type {any} */ e) => {
289
- const val = e.target.value?.trim() ?? "";
290
- applyMutation((/** @type {any} */ d) => {
308
+ @input=${debouncedStyleCommit("head:title", 400, (/** @type {Event} */ e) => {
309
+ const val = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
310
+ applyMutation((/** @type {JxMutableNode} */ d) => {
291
311
  if (val) d.title = val;
292
312
  else delete d.title;
293
313
  });
@@ -300,9 +320,10 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
300
320
  prop: "icon",
301
321
  label: "Icon",
302
322
  hasValue: !!iconHref,
303
- onClear: () => applyMutation((/** @type {any} */ d) => upsertLink(d, "icon", "")),
304
- widget: renderMediaPicker("icon", iconHref, (/** @type {any} */ v) => {
305
- applyMutation((/** @type {any} */ d) => upsertLink(d, "icon", v || ""));
323
+ onClear: () =>
324
+ applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", "")),
325
+ widget: renderMediaPicker("icon", iconHref, (/** @type {string} */ v) => {
326
+ applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", v || ""));
306
327
  }),
307
328
  })}
308
329
  </div>
@@ -327,7 +348,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
327
348
  ${customEntries.length > 0
328
349
  ? html`
329
350
  <div class="imports-list">
330
- ${customEntries.map((/** @type {any} */ entry) => {
351
+ ${customEntries.map((/** @type {JxHeadEntry} */ entry) => {
331
352
  const label = entryLabel(entry);
332
353
  const value = entryValue(entry);
333
354
  return html`
@@ -339,7 +360,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
339
360
  size="xs"
340
361
  title="Remove"
341
362
  @click=${() => {
342
- applyMutation((/** @type {any} */ d) => {
363
+ applyMutation((/** @type {JxMutableNode} */ d) => {
343
364
  if (!d.$head) return;
344
365
  const idx = d.$head.indexOf(entry);
345
366
  if (idx >= 0) d.$head.splice(idx, 1);
@@ -373,19 +394,25 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
373
394
  quiet
374
395
  size="xs"
375
396
  title="Add tag"
376
- @click=${(/** @type {any} */ e) => {
377
- const form = e.target.closest(".head-add-form");
378
- const tagPicker = form?.querySelector(".head-add-tag");
379
- const attrField = form?.querySelector(".head-add-attr");
380
- const valField = form?.querySelector(".head-add-val");
397
+ @click=${(/** @type {Event} */ e) => {
398
+ const form = /** @type {HTMLElement} */ (e.target).closest(".head-add-form");
399
+ const tagPicker = /** @type {HTMLInputElement | null} */ (
400
+ form?.querySelector(".head-add-tag")
401
+ );
402
+ const attrField = /** @type {HTMLInputElement | null} */ (
403
+ form?.querySelector(".head-add-attr")
404
+ );
405
+ const valField = /** @type {HTMLInputElement | null} */ (
406
+ form?.querySelector(".head-add-val")
407
+ );
381
408
  const tagName = tagPicker?.value || "meta";
382
409
  const attrKey = attrField?.value?.trim();
383
410
  const attrVal = valField?.value?.trim();
384
411
  if (!attrKey || !attrVal) return;
385
- attrField.value = "";
386
- valField.value = "";
412
+ if (attrField) attrField.value = "";
413
+ if (valField) valField.value = "";
387
414
 
388
- /** @type {Record<string, any>} */
415
+ /** @type {JxHeadEntry} */
389
416
  const entry = { tagName, attributes: {} };
390
417
  if (tagName === "meta") {
391
418
  entry.attributes = { name: attrKey, content: attrVal };
@@ -395,7 +422,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
395
422
  entry.attributes = { [attrKey]: attrVal };
396
423
  }
397
424
 
398
- applyMutation((/** @type {any} */ d) => {
425
+ applyMutation((/** @type {JxMutableNode} */ d) => {
399
426
  if (!d.$head) d.$head = [];
400
427
  d.$head.push(entry);
401
428
  });
@@ -421,29 +448,30 @@ function renderFrontmatterSection() {
421
448
  const schemaProps = col?.schema?.properties;
422
449
  const requiredFields = new Set(col?.schema?.required || []);
423
450
 
424
- /** @type {{ field: string; entry: any; value: any }[]} */
451
+ /** @type {{ field: string; entry: FmSchemaEntry; value: JsonValue }[]} */
425
452
  const fields = [];
426
453
  if (schemaProps) {
427
454
  for (const [field, fieldSchema] of Object.entries(
428
- /** @type {Record<string, any>} */ (schemaProps),
455
+ /** @type {Record<string, FmSchemaEntry>} */ (schemaProps),
429
456
  )) {
430
- fields.push({ field, entry: fieldSchema, value: fm[field] });
457
+ if (RESERVED_FM_KEYS.has(field)) continue;
458
+ fields.push({ field, entry: fieldSchema, value: /** @type {JsonValue} */ (fm[field]) });
431
459
  }
432
460
  for (const [field, value] of Object.entries(fm)) {
433
- if (schemaProps[field] || field.startsWith("$")) continue;
461
+ if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
434
462
  fields.push({
435
463
  field,
436
464
  entry: { type: typeof value === "boolean" ? "boolean" : "string" },
437
- value,
465
+ value: /** @type {JsonValue} */ (value),
438
466
  });
439
467
  }
440
468
  } else {
441
469
  for (const [field, value] of Object.entries(fm)) {
442
- if (field.startsWith("$")) continue;
470
+ if (field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
443
471
  fields.push({
444
472
  field,
445
473
  entry: { type: typeof value === "boolean" ? "boolean" : "string" },
446
- value,
474
+ value: /** @type {JsonValue} */ (value),
447
475
  });
448
476
  }
449
477
  }
@@ -466,8 +494,8 @@ function renderFrontmatterSection() {
466
494
 
467
495
  function renderFmField(
468
496
  /** @type {string} */ field,
469
- /** @type {any} */ entry,
470
- /** @type {any} */ value,
497
+ /** @type {FmSchemaEntry} */ entry,
498
+ /** @type {JsonValue} */ value,
471
499
  /** @type {Set<string>} */ requiredFields,
472
500
  ) {
473
501
  const isRequired = requiredFields.has(field);
@@ -487,9 +515,13 @@ function renderFmField(
487
515
  <sp-checkbox
488
516
  size="s"
489
517
  .checked=${live(!!value)}
490
- @change=${(/** @type {any} */ e) =>
518
+ @change=${(/** @type {Event} */ e) =>
491
519
  transactDoc(activeTab.value, (t) =>
492
- mutateUpdateFrontmatter(t, field, e.target.checked || undefined),
520
+ mutateUpdateFrontmatter(
521
+ t,
522
+ field,
523
+ /** @type {HTMLInputElement} */ (e.target).checked || undefined,
524
+ ),
493
525
  )}
494
526
  ></sp-checkbox>
495
527
  `,
@@ -508,9 +540,9 @@ function renderFmField(
508
540
  size="s"
509
541
  placeholder="comma, separated"
510
542
  .value=${live(display)}
511
- @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) => {
512
- const arr = e.target.value
513
- ? e.target.value
543
+ @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
544
+ const arr = /** @type {HTMLInputElement} */ (e.target).value
545
+ ? /** @type {HTMLInputElement} */ (e.target).value
514
546
  .split(",")
515
547
  .map((/** @type {string} */ s) => s.trim())
516
548
  .filter(Boolean)
@@ -532,9 +564,13 @@ function renderFmField(
532
564
  <sp-picker
533
565
  size="s"
534
566
  .value=${live(value || "")}
535
- @change=${(/** @type {any} */ e) =>
567
+ @change=${(/** @type {Event} */ e) =>
536
568
  transactDoc(activeTab.value, (t) =>
537
- mutateUpdateFrontmatter(t, field, e.target.value || undefined),
569
+ mutateUpdateFrontmatter(
570
+ t,
571
+ field,
572
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
573
+ ),
538
574
  )}
539
575
  >
540
576
  ${entry.enum.map(
@@ -551,7 +587,7 @@ function renderFmField(
551
587
  label: displayLabel,
552
588
  hasValue: hasVal,
553
589
  onClear,
554
- widget: renderMediaPicker(field, value, (v) =>
590
+ widget: renderMediaPicker(field, /** @type {string} */ (value), (/** @type {string} */ v) =>
555
591
  transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
556
592
  ),
557
593
  });
@@ -568,10 +604,10 @@ function renderFmField(
568
604
  size="s"
569
605
  hide-stepper
570
606
  .value=${live(value !== undefined ? Number(value) : undefined)}
571
- @change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) => {
572
- const v = e.target.value;
607
+ @change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
608
+ const v = /** @type {HTMLInputElement} */ (e.target).value;
573
609
  transactDoc(activeTab.value, (t) =>
574
- mutateUpdateFrontmatter(t, field, isNaN(v) ? undefined : Number(v)),
610
+ mutateUpdateFrontmatter(t, field, isNaN(Number(v)) ? undefined : Number(v)),
575
611
  );
576
612
  })}
577
613
  ></sp-number-field>
@@ -589,9 +625,13 @@ function renderFmField(
589
625
  size="s"
590
626
  placeholder=${entry.format === "date" ? "YYYY-MM-DD" : ""}
591
627
  .value=${live(value || "")}
592
- @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) => {
628
+ @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
593
629
  transactDoc(activeTab.value, (t) =>
594
- mutateUpdateFrontmatter(t, field, e.target.value || undefined),
630
+ mutateUpdateFrontmatter(
631
+ t,
632
+ field,
633
+ /** @type {HTMLInputElement} */ (e.target).value || undefined,
634
+ ),
595
635
  );
596
636
  })}
597
637
  ></sp-textfield>