@jxsuite/studio 0.23.1 → 0.24.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 (105) hide show
  1. package/dist/studio.js +2231 -715
  2. package/dist/studio.js.map +121 -104
  3. package/package.json +8 -7
  4. package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
  5. package/src/browse/{browse.js → browse.ts} +110 -85
  6. package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
  7. package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
  8. package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
  9. package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
  10. package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
  11. package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
  12. package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
  13. package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
  14. package/src/editor/{context-menu.js → context-menu.ts} +38 -36
  15. package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
  16. package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
  17. package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
  18. package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
  19. package/src/editor/{inline-format.js → inline-format.ts} +46 -51
  20. package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
  21. package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
  22. package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
  23. package/src/files/{components.js → components.ts} +12 -15
  24. package/src/files/{file-ops.js → file-ops.ts} +36 -29
  25. package/src/files/{files.js → files.ts} +102 -84
  26. package/src/github/{github-auth.js → github-auth.ts} +3 -3
  27. package/src/github/{github-publish.js → github-publish.ts} +20 -18
  28. package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
  29. package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
  30. package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
  31. package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
  32. package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
  33. package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
  34. package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
  35. package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
  36. package/src/panels/dnd.ts +417 -0
  37. package/src/panels/{editors.js → editors.ts} +43 -50
  38. package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
  39. package/src/panels/{events-panel.js → events-panel.ts} +33 -25
  40. package/src/panels/{git-panel.js → git-panel.ts} +97 -83
  41. package/src/panels/{head-panel.js → head-panel.ts} +117 -139
  42. package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
  43. package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
  44. package/src/panels/{left-panel.js → left-panel.ts} +56 -63
  45. package/src/panels/{overlays.js → overlays.ts} +28 -36
  46. package/src/panels/{panel-events.js → panel-events.ts} +53 -51
  47. package/src/panels/{preview-render.js → preview-render.ts} +20 -15
  48. package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
  49. package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
  50. package/src/panels/{quick-search.js → quick-search.ts} +16 -17
  51. package/src/panels/{right-panel.js → right-panel.ts} +41 -59
  52. package/src/panels/{shared.js → shared.ts} +7 -5
  53. package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
  54. package/src/panels/{statusbar.js → statusbar.ts} +14 -14
  55. package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
  56. package/src/panels/{style-panel.js → style-panel.ts} +235 -290
  57. package/src/panels/{style-utils.js → style-utils.ts} +32 -31
  58. package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
  59. package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
  60. package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
  61. package/src/panels/{toolbar.js → toolbar.ts} +68 -70
  62. package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
  63. package/src/{platform.js → platform.ts} +5 -3
  64. package/src/platforms/{devserver.js → devserver.ts} +53 -44
  65. package/src/{recent-projects.js → recent-projects.ts} +17 -12
  66. package/src/{resize-edges.js → resize-edges.ts} +18 -13
  67. package/src/services/{cem-export.js → cem-export.ts} +14 -7
  68. package/src/services/{code-services.js → code-services.ts} +29 -20
  69. package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
  70. package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
  71. package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
  72. package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
  73. package/src/settings/{general-settings.js → general-settings.ts} +29 -28
  74. package/src/settings/{head-editor.js → head-editor.ts} +37 -35
  75. package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
  76. package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
  77. package/src/{site-context.js → site-context.ts} +35 -32
  78. package/src/{state.js → state.ts} +95 -78
  79. package/src/{store.js → store.ts} +60 -63
  80. package/src/{studio.js → studio.ts} +117 -119
  81. package/src/tabs/tab.ts +196 -0
  82. package/src/tabs/{transact.js → transact.ts} +107 -60
  83. package/src/types.ts +185 -0
  84. package/src/ui/{button-group.js → button-group.ts} +21 -21
  85. package/src/ui/{color-selector.js → color-selector.ts} +44 -37
  86. package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
  87. package/src/ui/{field-row.js → field-row.ts} +19 -2
  88. package/src/ui/{icons.js → icons.ts} +2 -2
  89. package/src/ui/{layers.js → layers.ts} +33 -24
  90. package/src/ui/{media-picker.js → media-picker.ts} +13 -9
  91. package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
  92. package/src/ui/{spectrum.js → spectrum.ts} +4 -4
  93. package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
  94. package/src/ui/{value-selector.js → value-selector.ts} +28 -28
  95. package/src/ui/{widgets.js → widgets.ts} +62 -15
  96. package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
  97. package/src/utils/{edit-display.js → edit-display.ts} +23 -20
  98. package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
  99. package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
  100. package/src/utils/{studio-utils.js → studio-utils.ts} +38 -26
  101. package/src/{view.js → view.ts} +53 -55
  102. package/src/workspace/{workspace.js → workspace.ts} +44 -33
  103. package/src/panels/dnd.js +0 -426
  104. package/src/tabs/tab.js +0 -192
  105. /package/src/{reactivity.js → reactivity.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ /// <reference lib="dom" />
1
2
  /**
2
3
  * Head panel — Page meta, OpenGraph, Frontmatter, and custom `$head` entries.
3
4
  *
@@ -7,31 +8,46 @@
7
8
 
8
9
  import { html, nothing } from "lit-html";
9
10
  import { live } from "lit-html/directives/live.js";
10
- import { renderFieldRow } from "../ui/field-row.js";
11
- import { renderMediaPicker } from "../ui/media-picker.js";
12
- import { debouncedStyleCommit, renderOnly, projectState } from "../store.js";
13
- import { activeTab } from "../workspace/workspace.js";
14
- import { transactDoc, mutateUpdateFrontmatter } from "../tabs/transact.js";
15
- import { findContentTypeSchema } from "../utils/studio-utils.js";
16
- import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts.js";
17
- import { invalidateLayoutCache } from "../site-context.js";
18
- import { getPlatform } from "../platform.js";
11
+ import { renderFieldRow } from "../ui/field-row";
12
+ import { renderMediaPicker } from "../ui/media-picker";
13
+ import { debouncedStyleCommit, renderOnly, projectState } from "../store";
14
+ import type { JsonValue, DirEntry } from "../types";
15
+ import { activeTab } from "../workspace/workspace";
16
+ import { transactDoc, mutateUpdateFrontmatter } from "../tabs/transact";
17
+ import { findContentTypeSchema } from "../utils/studio-utils";
18
+ import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
19
+ import { invalidateLayoutCache } from "../site-context";
20
+ import { getPlatform } from "../platform";
21
+
22
+ import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
23
+
24
+ interface MetaField {
25
+ label: string;
26
+ attr: "name" | "property";
27
+ key: string;
28
+ multiline?: boolean;
29
+ media?: boolean;
30
+ }
31
+
32
+ interface FmSchemaEntry {
33
+ type?: string;
34
+ enum?: string[];
35
+ format?: string;
36
+ properties?: Record<string, unknown>;
37
+ }
19
38
 
20
39
  // ─── Layout picker ──────────────────────────────────────────────────────────
21
40
 
22
41
  /** @type {{ name: string; path: string }[] | null} */
23
- let layoutEntries = null;
42
+ let layoutEntries: { name: string; path: string }[] | null = null;
24
43
 
25
44
  async function loadLayoutEntries() {
26
45
  try {
27
46
  const platform = getPlatform();
28
47
  const listing = await platform.listDirectory("layouts");
29
48
  layoutEntries = listing
30
- .filter(
31
- (/** @type {{ type: string; name: string }} */ f) =>
32
- f.type === "file" && f.name.endsWith(".json"),
33
- )
34
- .map((/** @type {{ type: string; name: string }} */ f) => ({
49
+ .filter((f: DirEntry) => f.type === "file" && f.name.endsWith(".json"))
50
+ .map((f: DirEntry) => ({
35
51
  name: f.name
36
52
  .replace(/\.json$/, "")
37
53
  .replace(/[-_]+/g, " ")
@@ -50,33 +66,12 @@ export function invalidateLayoutPickerCache() {
50
66
 
51
67
  // ─── Field definitions ───────────────────────────────────────────────────
52
68
 
53
- /**
54
- * @typedef {{
55
- * label: string;
56
- * attr: "name" | "property";
57
- * key: string;
58
- * multiline?: boolean;
59
- * media?: boolean;
60
- * }} MetaField
61
- */
62
-
63
- /**
64
- * @typedef {{
65
- * type?: string;
66
- * enum?: string[];
67
- * format?: string;
68
- * properties?: Record<string, unknown>;
69
- * }} FmSchemaEntry
70
- */
71
-
72
- /** @type {MetaField[]} */
73
- const PAGE_FIELDS = [
69
+ const PAGE_FIELDS: MetaField[] = [
74
70
  { label: "Description", attr: "name", key: "description" },
75
71
  { label: "Viewport", attr: "name", key: "viewport" },
76
72
  ];
77
73
 
78
- /** @type {MetaField[]} */
79
- const OG_FIELDS = [
74
+ const OG_FIELDS: MetaField[] = [
80
75
  { label: "Title", attr: "property", key: "og:title" },
81
76
  { label: "Description", attr: "property", key: "og:description", multiline: true },
82
77
  { label: "Image", attr: "property", key: "og:image", media: true },
@@ -99,11 +94,9 @@ const RESERVED_FM_KEYS = new Set(["title"]);
99
94
  * @param {string} key
100
95
  * @returns {JxHeadEntry | undefined}
101
96
  */
102
- function findMetaEntry(head, attr, key) {
97
+ function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: string) {
103
98
  if (!head) return undefined;
104
- return head.find(
105
- (/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
106
- );
99
+ return head.find((e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key);
107
100
  }
108
101
 
109
102
  /**
@@ -113,11 +106,9 @@ function findMetaEntry(head, attr, key) {
113
106
  * @param {string} rel
114
107
  * @returns {JxHeadEntry | undefined}
115
108
  */
116
- function findLinkEntry(head, rel) {
109
+ function findLinkEntry(head: JxHeadEntry[], rel: string) {
117
110
  if (!head) return undefined;
118
- return head.find(
119
- (/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
120
- );
111
+ return head.find((e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel);
121
112
  }
122
113
 
123
114
  /**
@@ -126,7 +117,7 @@ function findLinkEntry(head, rel) {
126
117
  * @param {JxHeadEntry} entry
127
118
  * @returns {boolean}
128
119
  */
129
- function isManagedEntry(entry) {
120
+ function isManagedEntry(entry: JxHeadEntry) {
130
121
  if (!entry?.tagName) return false;
131
122
  // Managed meta tags
132
123
  if (entry.tagName === "meta") {
@@ -147,10 +138,10 @@ function isManagedEntry(entry) {
147
138
  * @param {string} key
148
139
  * @param {string} content
149
140
  */
150
- function upsertMeta(doc, attr, key, content) {
141
+ function upsertMeta(doc: JxMutableNode, attr: "name" | "property", key: string, content: string) {
151
142
  if (!doc.$head) doc.$head = [];
152
143
  const idx = doc.$head.findIndex(
153
- (/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
144
+ (e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
154
145
  );
155
146
  if (content) {
156
147
  const entry = { tagName: "meta", attributes: { [attr]: key, content } };
@@ -171,10 +162,10 @@ function upsertMeta(doc, attr, key, content) {
171
162
  * @param {string} rel
172
163
  * @param {string} href
173
164
  */
174
- function upsertLink(doc, rel, href) {
165
+ function upsertLink(doc: JxMutableNode, rel: string, href: string) {
175
166
  if (!doc.$head) doc.$head = [];
176
167
  const idx = doc.$head.findIndex(
177
- (/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
168
+ (e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel,
178
169
  );
179
170
  if (href) {
180
171
  const entry = { tagName: "link", attributes: { rel, href } };
@@ -194,7 +185,7 @@ function upsertLink(doc, rel, href) {
194
185
  * @param {JxHeadEntry} entry
195
186
  * @returns {string}
196
187
  */
197
- function entryLabel(entry) {
188
+ function entryLabel(entry: JxHeadEntry) {
198
189
  if (!entry?.tagName) return "unknown";
199
190
  const a = entry.attributes ?? {};
200
191
  if (a.name) return `<meta name="${String(a.name)}">`;
@@ -211,7 +202,7 @@ function entryLabel(entry) {
211
202
  * @param {JxHeadEntry} entry
212
203
  * @returns {string}
213
204
  */
214
- function entryValue(entry) {
205
+ function entryValue(entry: JxHeadEntry) {
215
206
  const a = entry?.attributes ?? {};
216
207
  return String(a.content ?? a.href ?? a.src ?? entry?.textContent ?? "");
217
208
  }
@@ -226,7 +217,11 @@ function entryValue(entry) {
226
217
  * @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
227
218
  * @returns {import("lit-html").TemplateResult}
228
219
  */
229
- function renderMetaFieldRow(field, head, applyMutation) {
220
+ function renderMetaFieldRow(
221
+ field: MetaField,
222
+ head: JxHeadEntry[],
223
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void,
224
+ ) {
230
225
  const entry = findMetaEntry(head, field.attr, field.key);
231
226
  const val = String(entry?.attributes?.content ?? "");
232
227
 
@@ -235,12 +230,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
235
230
  prop: field.key,
236
231
  label: field.label,
237
232
  hasValue: !!val,
238
- onClear: () =>
239
- applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
240
- widget: renderMediaPicker(field.key, val, (/** @type {string} */ v) => {
241
- applyMutation((/** @type {JxMutableNode} */ d) =>
242
- upsertMeta(d, field.attr, field.key, v || ""),
243
- );
233
+ onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
234
+ widget: renderMediaPicker(field.key, val, (v: string) => {
235
+ applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, v || ""));
244
236
  }),
245
237
  });
246
238
  }
@@ -252,11 +244,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
252
244
  multiline
253
245
  .value=${live(val)}
254
246
  placeholder="${field.label}…"
255
- @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
256
- const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
257
- applyMutation((/** @type {JxMutableNode} */ d) =>
258
- upsertMeta(d, field.attr, field.key, content),
259
- );
247
+ @input=${debouncedStyleCommit(`head:${field.key}`, 400, (e: Event) => {
248
+ const content = (e.target as HTMLInputElement).value?.trim() ?? "";
249
+ applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, content));
260
250
  })}
261
251
  ></sp-textfield>
262
252
  `
@@ -267,11 +257,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
267
257
  placeholder=${field.key === "viewport"
268
258
  ? "width=device-width, initial-scale=1"
269
259
  : `${field.label}…`}
270
- @input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
271
- const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
272
- applyMutation((/** @type {JxMutableNode} */ d) =>
273
- upsertMeta(d, field.attr, field.key, content),
274
- );
260
+ @input=${debouncedStyleCommit(`head:${field.key}`, 400, (e: Event) => {
261
+ const content = (e.target as HTMLInputElement).value?.trim() ?? "";
262
+ applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, content));
275
263
  })}
276
264
  ></sp-textfield>
277
265
  `;
@@ -280,8 +268,7 @@ function renderMetaFieldRow(field, head, applyMutation) {
280
268
  prop: field.key,
281
269
  label: field.label,
282
270
  hasValue: !!val,
283
- onClear: () =>
284
- applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
271
+ onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
285
272
  widget,
286
273
  });
287
274
  }
@@ -296,7 +283,15 @@ function renderMetaFieldRow(field, head, applyMutation) {
296
283
  * }} ctx
297
284
  * @returns {import("lit-html").TemplateResult}
298
285
  */
299
- export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPanel }) {
286
+ export function renderHeadTemplate({
287
+ document: doc,
288
+ applyMutation,
289
+ renderLeftPanel,
290
+ }: {
291
+ document: JxMutableNode;
292
+ applyMutation: (fn: (doc: JxMutableNode) => void) => void;
293
+ renderLeftPanel: () => void;
294
+ }) {
300
295
  const head = doc.$head ?? [];
301
296
  const title = doc.title ?? "";
302
297
 
@@ -306,8 +301,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
306
301
 
307
302
  // Custom entries not managed by structured forms, fonts, or preconnects
308
303
  const customEntries = head.filter(
309
- (/** @type {JxHeadEntry} */ e) =>
310
- !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
304
+ (e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
311
305
  );
312
306
 
313
307
  // Frontmatter section (content mode only)
@@ -321,8 +315,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
321
315
  projectState?.isSiteProject &&
322
316
  (tab.documentPath.startsWith("pages/") || tab.documentPath.startsWith("./pages/"));
323
317
 
324
- /** @type {import("lit-html").TemplateResult | symbol} */
325
- let layoutSection = nothing;
318
+ let layoutSection: import("lit-html").TemplateResult | symbol = nothing;
326
319
  if (isPage) {
327
320
  if (layoutEntries === null) {
328
321
  loadLayoutEntries();
@@ -336,7 +329,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
336
329
  .replace(/^\.\/layouts\//, "")
337
330
  .replace(/\.json$/, "")
338
331
  .replace(/[-_]+/g, " ")
339
- .replace(/\b\w/g, (/** @type {string} */ c) => c.toUpperCase())
332
+ .replace(/\b\w/g, (c: string) => c.toUpperCase())
340
333
  : "";
341
334
 
342
335
  layoutSection = html`
@@ -350,16 +343,16 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
350
343
  label: "Layout",
351
344
  hasValue: currentLayout !== undefined,
352
345
  onClear: () =>
353
- applyMutation((/** @type {JxMutableNode} */ d) => {
346
+ applyMutation((d: JxMutableNode) => {
354
347
  delete d.$layout;
355
348
  }),
356
349
  widget: html`
357
350
  <sp-picker
358
351
  size="s"
359
352
  value=${displayValue}
360
- @change=${(/** @type {Event} */ e) => {
361
- const val = /** @type {HTMLInputElement} */ (e.target).value;
362
- applyMutation((/** @type {JxMutableNode} */ d) => {
353
+ @change=${(e: Event) => {
354
+ const val = (e.target as HTMLInputElement).value;
355
+ applyMutation((d: JxMutableNode) => {
363
356
  if (val === "__default__") delete d.$layout;
364
357
  else if (val === "__none__") d.$layout = false;
365
358
  else d.$layout = val;
@@ -373,7 +366,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
373
366
  <sp-menu-item value="__none__">None</sp-menu-item>
374
367
  <sp-menu-divider></sp-menu-divider>
375
368
  ${layoutEntries.map(
376
- (/** @type {{ name: string; path: string }} */ l) =>
369
+ (l: { name: string; path: string }) =>
377
370
  html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
378
371
  )}
379
372
  </sp-picker>
@@ -400,7 +393,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
400
393
  label: "Title",
401
394
  hasValue: !!title,
402
395
  onClear: () =>
403
- applyMutation((/** @type {JxMutableNode} */ d) => {
396
+ applyMutation((d: JxMutableNode) => {
404
397
  delete d.title;
405
398
  }),
406
399
  widget: html`
@@ -408,9 +401,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
408
401
  size="s"
409
402
  .value=${live(title)}
410
403
  placeholder="Page title…"
411
- @input=${debouncedStyleCommit("head:title", 400, (/** @type {Event} */ e) => {
412
- const val = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
413
- applyMutation((/** @type {JxMutableNode} */ d) => {
404
+ @input=${debouncedStyleCommit("head:title", 400, (e: Event) => {
405
+ const val = (e.target as HTMLInputElement).value?.trim() ?? "";
406
+ applyMutation((d: JxMutableNode) => {
414
407
  if (val) d.title = val;
415
408
  else delete d.title;
416
409
  });
@@ -423,10 +416,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
423
416
  prop: "icon",
424
417
  label: "Icon",
425
418
  hasValue: !!iconHref,
426
- onClear: () =>
427
- applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", "")),
428
- widget: renderMediaPicker("icon", iconHref, (/** @type {string} */ v) => {
429
- applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", v || ""));
419
+ onClear: () => applyMutation((d: JxMutableNode) => upsertLink(d, "icon", "")),
420
+ widget: renderMediaPicker("icon", iconHref, (v: string) => {
421
+ applyMutation((d: JxMutableNode) => upsertLink(d, "icon", v || ""));
430
422
  }),
431
423
  })}
432
424
  </div>
@@ -451,7 +443,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
451
443
  ${customEntries.length > 0
452
444
  ? html`
453
445
  <div class="imports-list">
454
- ${customEntries.map((/** @type {JxHeadEntry} */ entry) => {
446
+ ${customEntries.map((entry: JxHeadEntry) => {
455
447
  const label = entryLabel(entry);
456
448
  const value = entryValue(entry);
457
449
  return html`
@@ -463,7 +455,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
463
455
  size="xs"
464
456
  title="Remove"
465
457
  @click=${() => {
466
- applyMutation((/** @type {JxMutableNode} */ d) => {
458
+ applyMutation((d: JxMutableNode) => {
467
459
  if (!d.$head) return;
468
460
  const idx = d.$head.indexOf(entry);
469
461
  if (idx >= 0) d.$head.splice(idx, 1);
@@ -497,17 +489,11 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
497
489
  quiet
498
490
  size="xs"
499
491
  title="Add tag"
500
- @click=${(/** @type {Event} */ e) => {
501
- const form = /** @type {HTMLElement} */ (e.target).closest(".head-add-form");
502
- const tagPicker = /** @type {HTMLInputElement | null} */ (
503
- form?.querySelector(".head-add-tag")
504
- );
505
- const attrField = /** @type {HTMLInputElement | null} */ (
506
- form?.querySelector(".head-add-attr")
507
- );
508
- const valField = /** @type {HTMLInputElement | null} */ (
509
- form?.querySelector(".head-add-val")
510
- );
492
+ @click=${(e: Event) => {
493
+ const form = (e.target as HTMLElement).closest(".head-add-form");
494
+ const tagPicker = form?.querySelector(".head-add-tag") as HTMLInputElement | null;
495
+ const attrField = form?.querySelector(".head-add-attr") as HTMLInputElement | null;
496
+ const valField = form?.querySelector(".head-add-val") as HTMLInputElement | null;
511
497
  const tagName = tagPicker?.value || "meta";
512
498
  const attrKey = attrField?.value?.trim();
513
499
  const attrVal = valField?.value?.trim();
@@ -515,8 +501,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
515
501
  if (attrField) attrField.value = "";
516
502
  if (valField) valField.value = "";
517
503
 
518
- /** @type {JxHeadEntry} */
519
- const entry = { tagName, attributes: {} };
504
+ const entry: JxHeadEntry = { tagName, attributes: {} };
520
505
  if (tagName === "meta") {
521
506
  entry.attributes = { name: attrKey, content: attrVal };
522
507
  } else if (tagName === "link") {
@@ -525,7 +510,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
525
510
  entry.attributes = { [attrKey]: attrVal };
526
511
  }
527
512
 
528
- applyMutation((/** @type {JxMutableNode} */ d) => {
513
+ applyMutation((d: JxMutableNode) => {
529
514
  if (!d.$head) d.$head = [];
530
515
  d.$head.push(entry);
531
516
  });
@@ -548,8 +533,11 @@ function renderFrontmatterSection() {
548
533
 
549
534
  const fm = tab.doc.content?.frontmatter || {};
550
535
  const col = findContentTypeSchema(tab.documentPath, projectState?.projectConfig);
551
- const schemaProps = col?.schema?.properties;
552
- const requiredFields = new Set(col?.schema?.required || []);
536
+ const schema = col?.schema as
537
+ | { properties?: Record<string, FmSchemaEntry>; required?: string[] }
538
+ | undefined;
539
+ const schemaProps = schema?.properties;
540
+ const requiredFields = new Set(schema?.required || []);
553
541
 
554
542
  /** @type {{ field: string; entry: FmSchemaEntry; value: JsonValue }[]} */
555
543
  const fields = [];
@@ -558,7 +546,7 @@ function renderFrontmatterSection() {
558
546
  /** @type {Record<string, FmSchemaEntry>} */ (schemaProps),
559
547
  )) {
560
548
  if (RESERVED_FM_KEYS.has(field)) continue;
561
- fields.push({ field, entry: fieldSchema, value: /** @type {JsonValue} */ (fm[field]) });
549
+ fields.push({ field, entry: fieldSchema, value: fm[field] as JsonValue });
562
550
  }
563
551
  for (const [field, value] of Object.entries(fm)) {
564
552
  if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
@@ -589,17 +577,17 @@ function renderFrontmatterSection() {
589
577
  >
590
578
  </div>
591
579
  <div class="head-section-body">
592
- ${fields.map((f) => renderFmField(f.field, f.entry, f.value, requiredFields))}
580
+ ${fields.map((f) => renderFmField(f.field, f.entry, f.value as JsonValue, requiredFields))}
593
581
  </div>
594
582
  </div>
595
583
  `;
596
584
  }
597
585
 
598
586
  function renderFmField(
599
- /** @type {string} */ field,
600
- /** @type {FmSchemaEntry} */ entry,
601
- /** @type {JsonValue} */ value,
602
- /** @type {Set<string>} */ requiredFields,
587
+ field: string,
588
+ entry: FmSchemaEntry,
589
+ value: JsonValue,
590
+ requiredFields: Set<string>,
603
591
  ) {
604
592
  const isRequired = requiredFields.has(field);
605
593
  const label = field.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
@@ -618,12 +606,12 @@ function renderFmField(
618
606
  <sp-checkbox
619
607
  size="s"
620
608
  .checked=${live(!!value)}
621
- @change=${(/** @type {Event} */ e) =>
609
+ @change=${(e: Event) =>
622
610
  transactDoc(activeTab.value, (t) =>
623
611
  mutateUpdateFrontmatter(
624
612
  t,
625
613
  field,
626
- /** @type {HTMLInputElement} */ (e.target).checked || undefined,
614
+ (e.target as HTMLInputElement).checked || undefined,
627
615
  ),
628
616
  )}
629
617
  ></sp-checkbox>
@@ -643,11 +631,11 @@ function renderFmField(
643
631
  size="s"
644
632
  placeholder="comma, separated"
645
633
  .value=${live(display)}
646
- @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
647
- const arr = /** @type {HTMLInputElement} */ (e.target).value
648
- ? /** @type {HTMLInputElement} */ (e.target).value
634
+ @input=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
635
+ const arr = (e.target as HTMLInputElement).value
636
+ ? (e.target as HTMLInputElement).value
649
637
  .split(",")
650
- .map((/** @type {string} */ s) => s.trim())
638
+ .map((s: string) => s.trim())
651
639
  .filter(Boolean)
652
640
  : undefined;
653
641
  transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, arr));
@@ -667,18 +655,12 @@ function renderFmField(
667
655
  <sp-picker
668
656
  size="s"
669
657
  .value=${live(value || "")}
670
- @change=${(/** @type {Event} */ e) =>
658
+ @change=${(e: Event) =>
671
659
  transactDoc(activeTab.value, (t) =>
672
- mutateUpdateFrontmatter(
673
- t,
674
- field,
675
- /** @type {HTMLInputElement} */ (e.target).value || undefined,
676
- ),
660
+ mutateUpdateFrontmatter(t, field, (e.target as HTMLInputElement).value || undefined),
677
661
  )}
678
662
  >
679
- ${entry.enum.map(
680
- (/** @type {string} */ opt) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`,
681
- )}
663
+ ${entry.enum.map((opt: string) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`)}
682
664
  </sp-picker>
683
665
  `,
684
666
  });
@@ -690,7 +672,7 @@ function renderFmField(
690
672
  label: displayLabel,
691
673
  hasValue: hasVal,
692
674
  onClear,
693
- widget: renderMediaPicker(field, /** @type {string} */ (value), (/** @type {string} */ v) =>
675
+ widget: renderMediaPicker(field, value as string, (v: string) =>
694
676
  transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
695
677
  ),
696
678
  });
@@ -707,8 +689,8 @@ function renderFmField(
707
689
  size="s"
708
690
  hide-stepper
709
691
  .value=${live(value !== undefined ? Number(value) : undefined)}
710
- @change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
711
- const v = /** @type {HTMLInputElement} */ (e.target).value;
692
+ @change=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
693
+ const v = (e.target as HTMLInputElement).value;
712
694
  transactDoc(activeTab.value, (t) =>
713
695
  mutateUpdateFrontmatter(t, field, isNaN(Number(v)) ? undefined : Number(v)),
714
696
  );
@@ -728,13 +710,9 @@ function renderFmField(
728
710
  size="s"
729
711
  placeholder=${entry.format === "date" ? "YYYY-MM-DD" : ""}
730
712
  .value=${live(value || "")}
731
- @input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
713
+ @input=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
732
714
  transactDoc(activeTab.value, (t) =>
733
- mutateUpdateFrontmatter(
734
- t,
735
- field,
736
- /** @type {HTMLInputElement} */ (e.target).value || undefined,
737
- ),
715
+ mutateUpdateFrontmatter(t, field, (e.target as HTMLInputElement).value || undefined),
738
716
  );
739
717
  })}
740
718
  ></sp-textfield>