@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
@@ -12,16 +12,16 @@ import { friendlyNameToVar, varDisplayName } from "../utils/studio-utils.js";
12
12
 
13
13
  /** @param {HTMLElement} container */
14
14
  export function renderCssVarsEditor(container) {
15
- const config = projectState.projectConfig || {};
15
+ const config = projectState?.projectConfig || {};
16
16
  const rootStyle = config.style || {};
17
17
  const media = getEffectiveMedia(config.$media);
18
18
 
19
19
  /**
20
20
  * @type {{
21
- * color: [string, any][];
22
- * font: [string, any][];
23
- * size: [string, any][];
24
- * other: [string, any][];
21
+ * color: [string, string | number][];
22
+ * font: [string, string | number][];
23
+ * size: [string, string | number][];
24
+ * other: [string, string | number][];
25
25
  * }}
26
26
  */
27
27
  const groups = { color: [], font: [], size: [], other: [] };
@@ -81,7 +81,7 @@ export function renderCssVarsEditor(container) {
81
81
  }
82
82
 
83
83
  /**
84
- * @param {[string, any][]} vars
84
+ * @param {[string, string | number][]} vars
85
85
  * @param {Function} updateVar
86
86
  * @param {Function} deleteVar
87
87
  * @param {Function} addVar
@@ -96,15 +96,17 @@ function renderColorSection(vars, updateVar, deleteVar, addVar) {
96
96
  <div class="css-var-swatch" style="background:${val}">
97
97
  <input
98
98
  type="color"
99
- .value=${val && val.startsWith("#") ? val : "#007acc"}
100
- @input=${(/** @type {any} */ e) => updateVar(name, e.target.value)}
99
+ .value=${val && String(val).startsWith("#") ? val : "#007acc"}
100
+ @input=${(/** @type {Event} */ e) =>
101
+ updateVar(name, /** @type {HTMLInputElement} */ (e.target).value)}
101
102
  />
102
103
  </div>
103
104
  <span class="css-var-name">${varDisplayName(name, "--color-")}</span>
104
105
  <sp-textfield
105
106
  size="s"
106
107
  .value=${String(val)}
107
- @change=${(/** @type {any} */ e) => updateVar(name, e.target.value)}
108
+ @change=${(/** @type {Event} */ e) =>
109
+ updateVar(name, /** @type {HTMLInputElement} */ (e.target).value)}
108
110
  style="flex:1;max-width:160px"
109
111
  ></sp-textfield>
110
112
  <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
@@ -119,7 +121,7 @@ function renderColorSection(vars, updateVar, deleteVar, addVar) {
119
121
  }
120
122
 
121
123
  /**
122
- * @param {[string, any][]} vars
124
+ * @param {[string, string | number][]} vars
123
125
  * @param {Function} updateVar
124
126
  * @param {Function} deleteVar
125
127
  * @param {Function} addVar
@@ -135,7 +137,8 @@ function renderFontSection(vars, updateVar, deleteVar, addVar) {
135
137
  <sp-textfield
136
138
  size="s"
137
139
  .value=${String(val)}
138
- @change=${(/** @type {any} */ e) => updateVar(name, e.target.value)}
140
+ @change=${(/** @type {Event} */ e) =>
141
+ updateVar(name, /** @type {HTMLInputElement} */ (e.target).value)}
139
142
  style="flex:1"
140
143
  ></sp-textfield>
141
144
  <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
@@ -153,11 +156,11 @@ function renderFontSection(vars, updateVar, deleteVar, addVar) {
153
156
  }
154
157
 
155
158
  /**
156
- * @param {[string, any][]} vars
159
+ * @param {[string, string | number][]} vars
157
160
  * @param {Function} updateVar
158
161
  * @param {Function} deleteVar
159
162
  * @param {Function} addVar
160
- * @param {any} rootStyle
163
+ * @param {JxStyle} rootStyle
161
164
  * @param {string[]} mediaNames
162
165
  */
163
166
  function renderSizeSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
@@ -176,7 +179,8 @@ function renderSizeSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaN
176
179
  <sp-textfield
177
180
  size="s"
178
181
  .value=${String(val)}
179
- @change=${(/** @type {any} */ e) => updateVar(name, e.target.value)}
182
+ @change=${(/** @type {Event} */ e) =>
183
+ updateVar(name, /** @type {HTMLInputElement} */ (e.target).value)}
180
184
  style="max-width:120px"
181
185
  ></sp-textfield>
182
186
  <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
@@ -192,11 +196,11 @@ function renderSizeSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaN
192
196
  }
193
197
 
194
198
  /**
195
- * @param {[string, any][]} vars
199
+ * @param {[string, string | number][]} vars
196
200
  * @param {Function} updateVar
197
201
  * @param {Function} deleteVar
198
202
  * @param {Function} addVar
199
- * @param {any} rootStyle
203
+ * @param {JxStyle} rootStyle
200
204
  * @param {string[]} mediaNames
201
205
  */
202
206
  function renderOtherSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
@@ -210,7 +214,8 @@ function renderOtherSection(vars, updateVar, deleteVar, addVar, rootStyle, media
210
214
  <sp-textfield
211
215
  size="s"
212
216
  .value=${String(val)}
213
- @change=${(/** @type {any} */ e) => updateVar(name, e.target.value)}
217
+ @change=${(/** @type {Event} */ e) =>
218
+ updateVar(name, /** @type {HTMLInputElement} */ (e.target).value)}
214
219
  style="flex:1"
215
220
  ></sp-textfield>
216
221
  <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
@@ -227,7 +232,7 @@ function renderOtherSection(vars, updateVar, deleteVar, addVar, rootStyle, media
227
232
 
228
233
  /**
229
234
  * @param {string} varName
230
- * @param {any} rootStyle
235
+ * @param {JxStyle} rootStyle
231
236
  * @param {string[]} mediaNames
232
237
  */
233
238
  function renderMediaOverrides(varName, rootStyle, mediaNames) {
@@ -251,9 +256,10 @@ function renderMediaOverrides(varName, rootStyle, mediaNames) {
251
256
  <sp-textfield
252
257
  size="s"
253
258
  .value=${String(o.value)}
254
- @change=${(/** @type {any} */ e) => {
259
+ @change=${(/** @type {Event} */ e) => {
255
260
  if (!rootStyle[`@${o.mediaName}`]) rootStyle[`@${o.mediaName}`] = {};
256
- rootStyle[`@${o.mediaName}`][varName] = e.target.value;
261
+ /** @type {Record<string, unknown>} */ (rootStyle[`@${o.mediaName}`])[varName] =
262
+ /** @type {HTMLInputElement} */ (e.target).value;
257
263
  updateSiteConfig({ style: { ...rootStyle } });
258
264
  }}
259
265
  style="max-width:120px"
@@ -272,9 +278,9 @@ function renderMediaOverrides(varName, rootStyle, mediaNames) {
272
278
  * @param {Function} addVar
273
279
  */
274
280
  function renderAddRow(prefix, placeholder, valuePlaceholder, addVar) {
275
- /** @type {any} */
281
+ /** @type {HTMLInputElement | null} */
276
282
  let nameEl = null;
277
- /** @type {any} */
283
+ /** @type {HTMLInputElement | null} */
278
284
  let valEl = null;
279
285
 
280
286
  return html`
@@ -283,14 +289,14 @@ function renderAddRow(prefix, placeholder, valuePlaceholder, addVar) {
283
289
  size="s"
284
290
  placeholder=${placeholder}
285
291
  ${ref((el) => {
286
- if (el) nameEl = el;
292
+ if (el) nameEl = /** @type {HTMLInputElement} */ (el);
287
293
  })}
288
294
  ></sp-textfield>
289
295
  <sp-textfield
290
296
  size="s"
291
297
  placeholder=${valuePlaceholder}
292
298
  ${ref((el) => {
293
- if (el) valEl = el;
299
+ if (el) valEl = /** @type {HTMLInputElement} */ (el);
294
300
  })}
295
301
  ></sp-textfield>
296
302
  <sp-action-button
@@ -29,7 +29,7 @@ let newDefName = "";
29
29
 
30
30
  async function saveProjectConfig() {
31
31
  const platform = getPlatform();
32
- const config = /** @type {any} */ (projectState).projectConfig;
32
+ const config = /** @type {{ projectConfig: ProjectConfig }} */ (projectState).projectConfig;
33
33
  await platform.writeFile("project.json", JSON.stringify(config, null, "\t"));
34
34
  }
35
35
 
@@ -131,7 +131,7 @@ function handleRenameField(oldName, newName, rerender) {
131
131
  const def = getSelectedDef();
132
132
  if (!def?.properties || !newName || def.properties[newName]) return;
133
133
 
134
- /** @type {Record<string, any>} */
134
+ /** @type {Record<string, unknown>} */
135
135
  const newProps = {};
136
136
  for (const [key, val] of Object.entries(def.properties)) {
137
137
  newProps[key === oldName ? newName : key] = val;
@@ -253,7 +253,7 @@ function handleRenameNested(parentName, oldChild, newChild, rerender) {
253
253
  const parent = def?.properties?.[parentName];
254
254
  if (!parent?.properties || !newChild || parent.properties[newChild]) return;
255
255
 
256
- /** @type {Record<string, any>} */
256
+ /** @type {Record<string, unknown>} */
257
257
  const newProps = {};
258
258
  for (const [key, val] of Object.entries(parent.properties)) {
259
259
  newProps[key === oldChild ? newChild : key] = val;
@@ -359,10 +359,10 @@ export function renderDefsEditor(container) {
359
359
  size="s"
360
360
  placeholder="TypeName"
361
361
  .value=${newDefName}
362
- @input=${(/** @type {any} */ e) => {
363
- newDefName = e.target.value;
362
+ @input=${(/** @type {Event} */ e) => {
363
+ newDefName = /** @type {HTMLInputElement} */ (e.target).value;
364
364
  }}
365
- @keydown=${(/** @type {any} */ e) => {
365
+ @keydown=${(/** @type {KeyboardEvent} */ e) => {
366
366
  if (e.key === "Enter") handleNewDef(rerender);
367
367
  if (e.key === "Escape") {
368
368
  showNewDef = false;
@@ -415,7 +415,12 @@ export function renderDefsEditor(container) {
415
415
  };
416
416
 
417
417
  const fieldCards = Object.entries(properties).map(([name, fieldDef]) =>
418
- fieldCardTpl(name, /** @type {any} */ (fieldDef), required.includes(name), handlers),
418
+ fieldCardTpl(
419
+ name,
420
+ /** @type {import("./schema-field-ui.js").SchemaProperty} */ (fieldDef),
421
+ required.includes(name),
422
+ handlers,
423
+ ),
419
424
  );
420
425
 
421
426
  editorTpl = html`
@@ -1,4 +1,4 @@
1
- /** General settings section — favicon, platform adapter, and other site-wide config. */
1
+ /** General settings section — favicon, platform adapter, breakpoints, and other site-wide config. */
2
2
 
3
3
  import { html, render as litRender, nothing } from "lit-html";
4
4
  import { projectState } from "../store.js";
@@ -9,7 +9,7 @@ import { closeSettingsModal } from "./settings-modal.js";
9
9
 
10
10
  /** @param {HTMLElement} container */
11
11
  export function renderGeneralSettings(container) {
12
- const config = projectState.projectConfig || {};
12
+ const config = /** @type {ProjectConfig} */ (projectState?.projectConfig || {});
13
13
 
14
14
  const onFaviconUpload = async () => {
15
15
  const input = document.createElement("input");
@@ -26,7 +26,7 @@ export function renderGeneralSettings(container) {
26
26
  input.click();
27
27
  };
28
28
 
29
- const onAdapterChange = (/** @type {any} */ e) => {
29
+ const onAdapterChange = (/** @type {Event & { target: { value: string } }} */ e) => {
30
30
  updateSiteConfig({ build: { ...config.build, adapter: e.target.value } });
31
31
  };
32
32
 
@@ -35,6 +35,43 @@ export function renderGeneralSettings(container) {
35
35
  openFileInTab("project.json");
36
36
  };
37
37
 
38
+ // ─── Breakpoints ($media) ───────────────────────────────────────────────────
39
+
40
+ const media = /** @type {Record<string, string>} */ (config.$media || {});
41
+ const mediaEntries = Object.entries(media);
42
+
43
+ const onMediaValueChange =
44
+ (/** @type {string} */ key) => (/** @type {Event & { target: { value: string } }} */ e) => {
45
+ const updated = { ...media, [key]: e.target.value };
46
+ updateSiteConfig({ $media: updated });
47
+ };
48
+
49
+ const onMediaNameChange =
50
+ (/** @type {string} */ oldKey) => (/** @type {Event & { target: { value: string } }} */ e) => {
51
+ const rawName = e.target.value.trim();
52
+ const newKey = rawName.startsWith("--") ? rawName : `--${rawName}`;
53
+ if (newKey === oldKey) return;
54
+ const updated = /** @type {Record<string, string>} */ ({});
55
+ for (const [k, v] of Object.entries(media)) {
56
+ updated[k === oldKey ? newKey : k] = v;
57
+ }
58
+ updateSiteConfig({ $media: updated });
59
+ renderGeneralSettings(container);
60
+ };
61
+
62
+ const onRemoveBreakpoint = (/** @type {string} */ key) => () => {
63
+ const updated = { ...media };
64
+ delete updated[key];
65
+ updateSiteConfig({ $media: updated });
66
+ renderGeneralSettings(container);
67
+ };
68
+
69
+ const onAddBreakpoint = () => {
70
+ const updated = { ...media, "--new": "(max-width: 480px)" };
71
+ updateSiteConfig({ $media: updated });
72
+ renderGeneralSettings(container);
73
+ };
74
+
38
75
  const currentFavicon = config.favicon;
39
76
 
40
77
  const tpl = html`
@@ -80,6 +117,51 @@ export function renderGeneralSettings(container) {
80
117
  </sp-picker>
81
118
  </div>
82
119
 
120
+ <div class="settings-field">
121
+ <label class="settings-field-label">Breakpoints</label>
122
+ <p class="settings-field-desc">
123
+ Responsive breakpoints for canvas panels and media query styles.
124
+ </p>
125
+ <div class="settings-media-list">
126
+ ${mediaEntries.map(([key, value]) => {
127
+ const isBase = key === "--";
128
+ return html`
129
+ <div class="settings-media-row">
130
+ ${isBase
131
+ ? html`<span class="settings-media-name-fixed">Base</span>`
132
+ : html`<sp-textfield
133
+ size="s"
134
+ class="settings-media-name"
135
+ .value=${key.replace(/^--/, "")}
136
+ placeholder="name"
137
+ @change=${onMediaNameChange(key)}
138
+ ></sp-textfield>`}
139
+ <sp-textfield
140
+ size="s"
141
+ class="settings-media-value"
142
+ .value=${value}
143
+ placeholder=${isBase ? "1280px" : "(max-width: 768px)"}
144
+ @change=${onMediaValueChange(key)}
145
+ ></sp-textfield>
146
+ ${isBase
147
+ ? nothing
148
+ : html`<sp-action-button
149
+ size="s"
150
+ quiet
151
+ title="Remove breakpoint"
152
+ @click=${onRemoveBreakpoint(key)}
153
+ >
154
+ ×
155
+ </sp-action-button>`}
156
+ </div>
157
+ `;
158
+ })}
159
+ </div>
160
+ <sp-action-button size="s" style="margin-top:8px" @click=${onAddBreakpoint}>
161
+ + Add Breakpoint
162
+ </sp-action-button>
163
+ </div>
164
+
83
165
  <div class="settings-field">
84
166
  <label class="settings-field-label">Global Styles</label>
85
167
  <p class="settings-field-desc">Edit default element styles that apply across all pages.</p>
@@ -16,24 +16,26 @@ import {
16
16
 
17
17
  /** @param {HTMLElement} container */
18
18
  export function renderHeadEditor(container) {
19
- const config = projectState.projectConfig || {};
20
- const headEntries = config.$head || [];
19
+ const config = /** @type {ProjectConfig} */ (projectState?.projectConfig || {});
20
+ const headEntries = /** @type {JxHeadEntry[]} */ (config.$head || []);
21
21
 
22
22
  const save = () => {
23
23
  updateSiteConfig({ $head: headEntries });
24
24
  };
25
25
 
26
26
  const addEntry = (/** @type {string} */ tag) => {
27
- /** @type {any} */
28
- const entry = { tagName: tag, attributes: {} };
27
+ /** @type {Record<string, string | boolean>} */
28
+ const attrs = {};
29
+ /** @type {JxHeadEntry} */
30
+ const entry = { tagName: tag, attributes: attrs };
29
31
  if (tag === "link") {
30
- entry.attributes.rel = "stylesheet";
31
- entry.attributes.href = "";
32
+ attrs.rel = "stylesheet";
33
+ attrs.href = "";
32
34
  } else if (tag === "meta") {
33
- entry.attributes.name = "";
34
- entry.attributes.content = "";
35
+ attrs.name = "";
36
+ attrs.content = "";
35
37
  } else if (tag === "script") {
36
- entry.attributes.src = "";
38
+ attrs.src = "";
37
39
  } else if (tag === "style") {
38
40
  entry.content = "";
39
41
  }
@@ -80,7 +82,7 @@ export function renderHeadEditor(container) {
80
82
 
81
83
  <div class="head-entries">
82
84
  ${headEntries.map(
83
- (/** @type {any} */ entry, /** @type {number} */ idx) => html`
85
+ (/** @type {JxHeadEntry} */ entry, /** @type {number} */ idx) => html`
84
86
  <div class="head-entry">
85
87
  <div class="head-entry-header">
86
88
  <span class="head-entry-tag">&lt;${entry.tagName}&gt;</span>
@@ -107,17 +109,17 @@ export function renderHeadEditor(container) {
107
109
  }
108
110
 
109
111
  /**
110
- * @param {any} entry
112
+ * @param {JxHeadEntry} entry
111
113
  * @param {number} idx
112
114
  * @param {(idx: number, key: string, val: string) => void} updateEntry
113
115
  */
114
116
  function renderEntryFields(entry, idx, updateEntry) {
115
- /** @type {any} */
117
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
116
118
  let debounce;
117
- const onFieldChange = (/** @type {string} */ key) => (/** @type {any} */ e) => {
119
+ const onFieldChange = (/** @type {string} */ key) => (/** @type {Event} */ e) => {
118
120
  clearTimeout(debounce);
119
121
  debounce = setTimeout(() => {
120
- updateEntry(idx, key, e.target.value);
122
+ updateEntry(idx, key, /** @type {HTMLInputElement} */ (e.target).value);
121
123
  }, 300);
122
124
  };
123
125
 
@@ -208,7 +210,7 @@ function renderEntryFields(entry, idx, updateEntry) {
208
210
  /**
209
211
  * Render the Google Fonts management section.
210
212
  *
211
- * @param {any[]} headEntries
213
+ * @param {JxHeadEntry[]} headEntries
212
214
  * @param {() => void} save
213
215
  * @param {() => void} rerender
214
216
  */
@@ -225,7 +227,7 @@ function renderGoogleFontsSection(headEntries, save, rerender) {
225
227
  rerender();
226
228
  };
227
229
 
228
- const removeFont = (/** @type {any} */ entry) => {
230
+ const removeFont = (/** @type {JxHeadEntry} */ entry) => {
229
231
  const idx = headEntries.indexOf(entry);
230
232
  if (idx >= 0) headEntries.splice(idx, 1);
231
233
  const cleaned = cleanupGoogleFontPreconnects(headEntries);
@@ -243,7 +245,9 @@ function renderGoogleFontsSection(headEntries, save, rerender) {
243
245
  ? fontEntries.map(
244
246
  (entry) => html`
245
247
  <div class="head-entry" style="flex-direction:row;align-items:center;gap:8px">
246
- <span style="flex:1">${extractFontFamily(entry.attributes.href)}</span>
248
+ <span style="flex:1"
249
+ >${extractFontFamily(String(entry.attributes?.href || ""))}</span
250
+ >
247
251
  <sp-action-button quiet size="s" @click=${() => removeFont(entry)}>
248
252
  <sp-icon-delete slot="icon"></sp-icon-delete>
249
253
  </sp-action-button>
@@ -257,21 +261,23 @@ function renderGoogleFontsSection(headEntries, save, rerender) {
257
261
  size="s"
258
262
  placeholder="Font family name…"
259
263
  style="flex:1"
260
- @keydown=${(/** @type {any} */ e) => {
264
+ @keydown=${(/** @type {KeyboardEvent} */ e) => {
261
265
  if (e.key !== "Enter") return;
262
- const family = e.target.value?.trim();
266
+ const family = /** @type {HTMLInputElement} */ (e.target).value?.trim();
263
267
  if (!family) return;
264
- e.target.value = "";
268
+ /** @type {HTMLInputElement} */ (e.target).value = "";
265
269
  addFont(family);
266
270
  }}
267
271
  ></sp-textfield>
268
272
  <sp-action-button
269
273
  size="s"
270
- @click=${(/** @type {any} */ e) => {
271
- const input = e.target.closest("div")?.querySelector("sp-textfield");
272
- const family = input?.value?.trim();
274
+ @click=${(/** @type {MouseEvent} */ e) => {
275
+ const input = /** @type {HTMLElement} */ (e.target)
276
+ .closest("div")
277
+ ?.querySelector("sp-textfield");
278
+ const family = /** @type {HTMLInputElement | null} */ (input)?.value?.trim();
273
279
  if (!family) return;
274
- input.value = "";
280
+ /** @type {HTMLInputElement} */ (input).value = "";
275
281
  addFont(family);
276
282
  }}
277
283
  >