@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.
- package/dist/studio.css +98 -98
- package/dist/studio.js +13476 -12330
- package/dist/studio.js.map +152 -148
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +19 -7
- package/src/browse/browse-modal.js +16 -13
- package/src/browse/browse.js +44 -35
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +113 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +51 -21
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +28 -14
- package/src/editor/convert-targets.js +60 -0
- package/src/editor/convert-to-component.js +20 -19
- package/src/editor/convert-to-repeater.js +226 -0
- package/src/editor/inline-edit.js +69 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +14 -4
- package/src/editor/slash-menu.js +40 -19
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +322 -68
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +126 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +27 -25
- package/src/panels/ai-panel.js +406 -0
- package/src/panels/block-action-bar.js +77 -28
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +475 -99
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +94 -65
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +105 -48
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +378 -257
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +11 -9
- package/src/panels/right-panel.js +42 -16
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +415 -166
- package/src/panels/statusbar.js +10 -6
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +239 -134
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/tab-strip.js +5 -2
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +8 -8
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/settings/settings-modal.js +12 -9
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +36 -35
- package/src/studio.js +120 -90
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/layers.js +31 -1
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +32 -26
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +18 -18
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +29 -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
|
|
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,
|
|
22
|
-
* font: [string,
|
|
23
|
-
* size: [string,
|
|
24
|
-
* other: [string,
|
|
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,
|
|
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 {
|
|
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 {
|
|
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,
|
|
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 {
|
|
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,
|
|
159
|
+
* @param {[string, string | number][]} vars
|
|
157
160
|
* @param {Function} updateVar
|
|
158
161
|
* @param {Function} deleteVar
|
|
159
162
|
* @param {Function} addVar
|
|
160
|
-
* @param {
|
|
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 {
|
|
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,
|
|
199
|
+
* @param {[string, string | number][]} vars
|
|
196
200
|
* @param {Function} updateVar
|
|
197
201
|
* @param {Function} deleteVar
|
|
198
202
|
* @param {Function} addVar
|
|
199
|
-
* @param {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
259
|
+
@change=${(/** @type {Event} */ e) => {
|
|
255
260
|
if (!rootStyle[`@${o.mediaName}`]) rootStyle[`@${o.mediaName}`] = {};
|
|
256
|
-
rootStyle[`@${o.mediaName}`][varName] =
|
|
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 {
|
|
281
|
+
/** @type {HTMLInputElement | null} */
|
|
276
282
|
let nameEl = null;
|
|
277
|
-
/** @type {
|
|
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 {
|
|
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,
|
|
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,
|
|
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 {
|
|
363
|
-
newDefName = e.target.value;
|
|
362
|
+
@input=${(/** @type {Event} */ e) => {
|
|
363
|
+
newDefName = /** @type {HTMLInputElement} */ (e.target).value;
|
|
364
364
|
}}
|
|
365
|
-
@keydown=${(/** @type {
|
|
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(
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
28
|
-
const
|
|
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
|
-
|
|
31
|
-
|
|
32
|
+
attrs.rel = "stylesheet";
|
|
33
|
+
attrs.href = "";
|
|
32
34
|
} else if (tag === "meta") {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
attrs.name = "";
|
|
36
|
+
attrs.content = "";
|
|
35
37
|
} else if (tag === "script") {
|
|
36
|
-
|
|
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 {
|
|
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"><${entry.tagName}></span>
|
|
@@ -107,17 +109,17 @@ export function renderHeadEditor(container) {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
|
-
* @param {
|
|
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 {
|
|
117
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
116
118
|
let debounce;
|
|
117
|
-
const onFieldChange = (/** @type {string} */ key) => (/** @type {
|
|
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 {
|
|
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 {
|
|
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"
|
|
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 {
|
|
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 {
|
|
271
|
-
const input = e.target
|
|
272
|
-
|
|
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
|
>
|