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