@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.
- package/dist/studio.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- 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 +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- 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 +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -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 +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- 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 +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- 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 +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- 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/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 +7 -7
- 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/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- 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/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 +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +10 -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,11 +87,11 @@ 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 ?? "");
|
|
@@ -86,7 +101,8 @@ function bindableFieldRow(
|
|
|
86
101
|
: type === "checkbox"
|
|
87
102
|
? html`<sp-checkbox
|
|
88
103
|
?checked=${!!staticVal}
|
|
89
|
-
@change=${(/** @type {
|
|
104
|
+
@change=${(/** @type {Event} */ e) =>
|
|
105
|
+
onChange(/** @type {HTMLInputElement} */ (e.target).checked)}
|
|
90
106
|
></sp-checkbox>`
|
|
91
107
|
: html`<sp-textfield size="s" value=${staticVal} @input=${onInput}></sp-textfield>`;
|
|
92
108
|
|
|
@@ -96,8 +112,9 @@ function bindableFieldRow(
|
|
|
96
112
|
quiet
|
|
97
113
|
placeholder="— select signal —"
|
|
98
114
|
value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
|
|
99
|
-
@change=${(/** @type {
|
|
100
|
-
if (
|
|
115
|
+
@change=${(/** @type {Event} */ e) => {
|
|
116
|
+
if (/** @type {HTMLInputElement} */ (e.target).value)
|
|
117
|
+
onChange({ $ref: /** @type {HTMLInputElement} */ (e.target).value });
|
|
101
118
|
else onChange(undefined);
|
|
102
119
|
}}
|
|
103
120
|
>
|
|
@@ -108,7 +125,7 @@ function bindableFieldRow(
|
|
|
108
125
|
? html`
|
|
109
126
|
<sp-menu-divider></sp-menu-divider>
|
|
110
127
|
${extraSignals.map(
|
|
111
|
-
(/** @type {
|
|
128
|
+
(/** @type {SignalOption} */ sig) =>
|
|
112
129
|
html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
|
|
113
130
|
)}
|
|
114
131
|
`
|
|
@@ -129,7 +146,7 @@ function bindableFieldRow(
|
|
|
129
146
|
} else {
|
|
130
147
|
if (signalDefs.length > 0) {
|
|
131
148
|
onChange({ $ref: `#/state/${signalDefs[0][0]}` });
|
|
132
|
-
} else if (extraSignals
|
|
149
|
+
} else if (extraSignals && extraSignals.length > 0) {
|
|
133
150
|
onChange({ $ref: extraSignals[0].value });
|
|
134
151
|
}
|
|
135
152
|
}
|
|
@@ -152,13 +169,13 @@ function bindableFieldRow(
|
|
|
152
169
|
|
|
153
170
|
/** Key-value pair row for styles / attributes */
|
|
154
171
|
function kvRow(
|
|
155
|
-
/** @type {
|
|
156
|
-
/** @type {
|
|
157
|
-
/** @type {
|
|
158
|
-
/** @type {
|
|
159
|
-
/** @type {
|
|
172
|
+
/** @type {string} */ key,
|
|
173
|
+
/** @type {string} */ value,
|
|
174
|
+
/** @type {(newKey: string, newVal: string) => void} */ onChange,
|
|
175
|
+
/** @type {() => void} */ onDelete,
|
|
176
|
+
/** @type {string | null} */ datalistId = null,
|
|
160
177
|
) {
|
|
161
|
-
/** @type {
|
|
178
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
162
179
|
let debounceTimer;
|
|
163
180
|
let currentKey = key;
|
|
164
181
|
let currentVal = value;
|
|
@@ -173,14 +190,20 @@ function kvRow(
|
|
|
173
190
|
size="s"
|
|
174
191
|
class="kv-key"
|
|
175
192
|
value=${key}
|
|
176
|
-
@input=${(/** @type {
|
|
177
|
-
currentKey = e.target.value;
|
|
193
|
+
@input=${(/** @type {Event} */ e) => {
|
|
194
|
+
currentKey = /** @type {HTMLInputElement} */ (e.target).value;
|
|
178
195
|
commit();
|
|
179
196
|
}}
|
|
180
197
|
@change=${datalistId === "css-props"
|
|
181
|
-
? (/** @type {
|
|
182
|
-
const el = e.target
|
|
183
|
-
|
|
198
|
+
? (/** @type {Event} */ e) => {
|
|
199
|
+
const el = /** @type {HTMLInputElement} */ (e.target)
|
|
200
|
+
.closest(".kv-row")
|
|
201
|
+
?.querySelector(".kv-val");
|
|
202
|
+
if (el)
|
|
203
|
+
el.setAttribute(
|
|
204
|
+
"placeholder",
|
|
205
|
+
getCssInitialMap().get(/** @type {HTMLInputElement} */ (e.target).value) || "",
|
|
206
|
+
);
|
|
184
207
|
}
|
|
185
208
|
: nothing}
|
|
186
209
|
></sp-textfield>
|
|
@@ -189,8 +212,8 @@ function kvRow(
|
|
|
189
212
|
class="kv-val"
|
|
190
213
|
value=${value}
|
|
191
214
|
placeholder=${placeholder}
|
|
192
|
-
@input=${(/** @type {
|
|
193
|
-
currentVal = e.target.value;
|
|
215
|
+
@input=${(/** @type {Event} */ e) => {
|
|
216
|
+
currentVal = /** @type {HTMLInputElement} */ (e.target).value;
|
|
194
217
|
commit();
|
|
195
218
|
}}
|
|
196
219
|
></sp-textfield>
|
|
@@ -205,23 +228,23 @@ function kvRow(
|
|
|
205
228
|
|
|
206
229
|
/** Repeater fields template */
|
|
207
230
|
function renderRepeaterFieldsTemplate(
|
|
208
|
-
/** @type {
|
|
209
|
-
/** @type {
|
|
210
|
-
/** @type {
|
|
231
|
+
/** @type {JxMutableNode} */ node,
|
|
232
|
+
/** @type {JxPath} */ path,
|
|
233
|
+
/** @type {SignalOption[] | null} */ _mapSignals,
|
|
211
234
|
) {
|
|
212
235
|
return html`
|
|
213
|
-
${bindableFieldRow("Items", "text", node.items, (/** @type {
|
|
236
|
+
${bindableFieldRow("Items", "text", node.items, (/** @type {JsonValue} */ v) =>
|
|
214
237
|
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
|
|
215
238
|
)}
|
|
216
239
|
${node.filter
|
|
217
|
-
? bindableFieldRow("Filter", "text", node.filter, (/** @type {
|
|
240
|
+
? bindableFieldRow("Filter", "text", node.filter, (/** @type {JsonValue} */ v) =>
|
|
218
241
|
transactDoc(activeTab.value, (t) =>
|
|
219
242
|
mutateUpdateProperty(t, path, "filter", v || undefined),
|
|
220
243
|
),
|
|
221
244
|
)
|
|
222
245
|
: nothing}
|
|
223
246
|
${node.sort
|
|
224
|
-
? bindableFieldRow("Sort", "text", node.sort, (/** @type {
|
|
247
|
+
? bindableFieldRow("Sort", "text", node.sort, (/** @type {JsonValue} */ v) =>
|
|
225
248
|
transactDoc(activeTab.value, (t) =>
|
|
226
249
|
mutateUpdateProperty(t, path, "sort", v || undefined),
|
|
227
250
|
),
|
|
@@ -266,9 +289,9 @@ function renderRepeaterFieldsTemplate(
|
|
|
266
289
|
|
|
267
290
|
/** Switch fields template */
|
|
268
291
|
function renderSwitchFieldsTemplate(
|
|
269
|
-
/** @type {
|
|
270
|
-
/** @type {
|
|
271
|
-
/** @type {
|
|
292
|
+
/** @type {JxMutableNode} */ node,
|
|
293
|
+
/** @type {JxPath} */ path,
|
|
294
|
+
/** @type {SignalOption[] | null} */ mapSignals,
|
|
272
295
|
) {
|
|
273
296
|
const caseNames = Object.keys(node.cases || {});
|
|
274
297
|
return html`
|
|
@@ -276,7 +299,7 @@ function renderSwitchFieldsTemplate(
|
|
|
276
299
|
"Expression",
|
|
277
300
|
"text",
|
|
278
301
|
node.$switch,
|
|
279
|
-
(/** @type {
|
|
302
|
+
(/** @type {JsonValue} */ v) =>
|
|
280
303
|
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
|
|
281
304
|
null,
|
|
282
305
|
mapSignals,
|
|
@@ -287,7 +310,7 @@ function renderSwitchFieldsTemplate(
|
|
|
287
310
|
Cases
|
|
288
311
|
</div>
|
|
289
312
|
${caseNames.map((caseName) => {
|
|
290
|
-
/** @type {
|
|
313
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
291
314
|
let debounce;
|
|
292
315
|
return html`
|
|
293
316
|
<div class="field-row" style="display:flex;align-items:center;gap:4px;margin-bottom:3px">
|
|
@@ -295,12 +318,20 @@ function renderSwitchFieldsTemplate(
|
|
|
295
318
|
class="field-input"
|
|
296
319
|
value=${caseName}
|
|
297
320
|
style="flex:1"
|
|
298
|
-
@input=${(/** @type {
|
|
321
|
+
@input=${(/** @type {Event} */ e) => {
|
|
299
322
|
clearTimeout(debounce);
|
|
300
323
|
debounce = setTimeout(() => {
|
|
301
|
-
if (
|
|
324
|
+
if (
|
|
325
|
+
/** @type {HTMLInputElement} */ (e.target).value &&
|
|
326
|
+
/** @type {HTMLInputElement} */ (e.target).value !== caseName
|
|
327
|
+
)
|
|
302
328
|
transactDoc(activeTab.value, (t) =>
|
|
303
|
-
mutateRenameSwitchCase(
|
|
329
|
+
mutateRenameSwitchCase(
|
|
330
|
+
t,
|
|
331
|
+
path,
|
|
332
|
+
caseName,
|
|
333
|
+
/** @type {HTMLInputElement} */ (e.target).value,
|
|
334
|
+
),
|
|
304
335
|
);
|
|
305
336
|
}, 500);
|
|
306
337
|
}}
|
|
@@ -309,7 +340,7 @@ function renderSwitchFieldsTemplate(
|
|
|
309
340
|
class="bind-toggle"
|
|
310
341
|
title="Edit case"
|
|
311
342
|
style="cursor:pointer"
|
|
312
|
-
@click=${(/** @type {
|
|
343
|
+
@click=${(/** @type {Event} */ e) => {
|
|
313
344
|
e.stopPropagation();
|
|
314
345
|
activeTab.value.session.selection = [...path, "cases", caseName];
|
|
315
346
|
}}
|
|
@@ -317,7 +348,7 @@ function renderSwitchFieldsTemplate(
|
|
|
317
348
|
>
|
|
318
349
|
<span
|
|
319
350
|
style="cursor:pointer;color:var(--danger);font-size:11px"
|
|
320
|
-
@click=${(/** @type {
|
|
351
|
+
@click=${(/** @type {Event} */ e) => {
|
|
321
352
|
e.stopPropagation();
|
|
322
353
|
transactDoc(activeTab.value, (t) => mutateRemoveSwitchCase(t, path, caseName));
|
|
323
354
|
}}
|
|
@@ -340,9 +371,9 @@ function renderSwitchFieldsTemplate(
|
|
|
340
371
|
|
|
341
372
|
/** Component props fields template */
|
|
342
373
|
function renderComponentPropsFieldsTemplate(
|
|
343
|
-
/** @type {
|
|
344
|
-
/** @type {
|
|
345
|
-
/** @type {
|
|
374
|
+
/** @type {JxMutableNode} */ node,
|
|
375
|
+
/** @type {JxPath} */ path,
|
|
376
|
+
/** @type {SignalOption[] | null} */ mapSignals,
|
|
346
377
|
/** @type {(path: string) => void} */ navigateToComponent,
|
|
347
378
|
) {
|
|
348
379
|
const tab = activeTab.value;
|
|
@@ -351,11 +382,16 @@ function renderComponentPropsFieldsTemplate(
|
|
|
351
382
|
const isNpm = comp.source === "npm";
|
|
352
383
|
const currentVals = isNpm ? node.attributes || {} : node.$props || {};
|
|
353
384
|
const updateFn = isNpm
|
|
354
|
-
? (/** @type {string} */ name, /** @type {
|
|
385
|
+
? (/** @type {string} */ name, /** @type {JsonValue} */ v) =>
|
|
355
386
|
transactDoc(activeTab.value, (t) =>
|
|
356
|
-
mutateUpdateAttribute(
|
|
387
|
+
mutateUpdateAttribute(
|
|
388
|
+
t,
|
|
389
|
+
path,
|
|
390
|
+
name,
|
|
391
|
+
v === "" ? undefined : /** @type {string | undefined} */ (v),
|
|
392
|
+
),
|
|
357
393
|
)
|
|
358
|
-
: (/** @type {string} */ name, /** @type {
|
|
394
|
+
: (/** @type {string} */ name, /** @type {JsonValue} */ v) =>
|
|
359
395
|
transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
|
|
360
396
|
|
|
361
397
|
const defs = tab.doc.document.state || {};
|
|
@@ -365,139 +401,157 @@ function renderComponentPropsFieldsTemplate(
|
|
|
365
401
|
const extraSignals = mapSignals;
|
|
366
402
|
|
|
367
403
|
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
|
-
|
|
404
|
+
${comp.props.map(
|
|
405
|
+
(
|
|
406
|
+
/** @type {{ name: string; type?: string; format?: string; description?: string }} */ prop,
|
|
407
|
+
) => {
|
|
408
|
+
const rawValue = currentVals[prop.name];
|
|
409
|
+
const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
|
|
410
|
+
const hasVal = rawValue !== undefined && rawValue !== null;
|
|
411
|
+
const parsed = parseCemType(prop.type);
|
|
412
|
+
const onChange = (/** @type {JsonValue} */ v) => updateFn(prop.name, v);
|
|
413
|
+
|
|
414
|
+
const clearProp = (/** @type {Event} */ e) => {
|
|
415
|
+
e.stopPropagation();
|
|
416
|
+
updateFn(prop.name, undefined);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const onToggleBind = () => {
|
|
420
|
+
if (isBound) {
|
|
421
|
+
const ref = rawValue.$ref;
|
|
422
|
+
const defName = ref.startsWith("#/state/") ? ref.slice(8) : ref;
|
|
423
|
+
const def = defs[defName];
|
|
424
|
+
let staticVal = "";
|
|
425
|
+
if (def && def.default !== undefined)
|
|
426
|
+
staticVal =
|
|
427
|
+
typeof def.default === "object" ? JSON.stringify(def.default) : String(def.default);
|
|
428
|
+
onChange(staticVal || undefined);
|
|
429
|
+
} else {
|
|
430
|
+
if (signalDefs.length > 0) {
|
|
431
|
+
onChange({ $ref: `#/state/${signalDefs[0][0]}` });
|
|
432
|
+
} else if (extraSignals && extraSignals.length > 0) {
|
|
433
|
+
onChange({ $ref: extraSignals[0].value });
|
|
434
|
+
}
|
|
395
435
|
}
|
|
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
|
-
`;
|
|
425
|
-
|
|
426
|
-
/** @type {any} */
|
|
427
|
-
let debounce;
|
|
428
|
-
const staticVal = isBound ? "" : (rawValue ?? "");
|
|
429
|
-
/** @type {any} */
|
|
430
|
-
let widgetTpl;
|
|
431
|
-
if (prop.format === "image") {
|
|
432
|
-
widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
|
|
433
|
-
} else if (prop.format === "color") {
|
|
434
|
-
widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
|
|
435
|
-
} else if (prop.format === "date") {
|
|
436
|
-
widgetTpl = html`<sp-textfield
|
|
437
|
-
size="s"
|
|
438
|
-
placeholder="YYYY-MM-DD"
|
|
439
|
-
value=${staticVal}
|
|
440
|
-
@input=${(/** @type {any} */ e) => {
|
|
441
|
-
clearTimeout(debounce);
|
|
442
|
-
debounce = setTimeout(() => onChange(e.target.value), 400);
|
|
443
|
-
}}
|
|
444
|
-
></sp-textfield>`;
|
|
445
|
-
} else if (parsed.kind === "boolean") {
|
|
446
|
-
widgetTpl = html`<sp-checkbox
|
|
447
|
-
size="s"
|
|
448
|
-
.checked=${live(!!staticVal)}
|
|
449
|
-
@change=${(/** @type {any} */ e) => onChange(e.target.checked || undefined)}
|
|
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
|
-
}
|
|
436
|
+
};
|
|
479
437
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
438
|
+
const boundTpl = html`
|
|
439
|
+
<sp-picker
|
|
440
|
+
size="s"
|
|
441
|
+
quiet
|
|
442
|
+
placeholder="— select signal —"
|
|
443
|
+
value=${isBound && rawValue.$ref ? rawValue.$ref : nothing}
|
|
444
|
+
@change=${(/** @type {Event} */ e) => {
|
|
445
|
+
if (/** @type {HTMLInputElement} */ (e.target).value)
|
|
446
|
+
onChange({ $ref: /** @type {HTMLInputElement} */ (e.target).value });
|
|
447
|
+
else onChange(undefined);
|
|
448
|
+
}}
|
|
449
|
+
>
|
|
450
|
+
${signalDefs.map(
|
|
451
|
+
([defName]) =>
|
|
452
|
+
html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
|
|
453
|
+
)}
|
|
454
|
+
${extraSignals
|
|
455
|
+
? html`
|
|
456
|
+
<sp-menu-divider></sp-menu-divider>
|
|
457
|
+
${extraSignals.map(
|
|
458
|
+
(/** @type {SignalOption} */ sig) =>
|
|
459
|
+
html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
|
|
460
|
+
)}
|
|
461
|
+
`
|
|
485
462
|
: nothing}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
463
|
+
</sp-picker>
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
467
|
+
let debounce;
|
|
468
|
+
const staticVal = isBound ? "" : (rawValue ?? "");
|
|
469
|
+
/** @type {import("lit-html").TemplateResult | undefined} */
|
|
470
|
+
let widgetTpl;
|
|
471
|
+
if (prop.format === "image") {
|
|
472
|
+
widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
|
|
473
|
+
} else if (prop.format === "color") {
|
|
474
|
+
widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
|
|
475
|
+
} else if (prop.format === "date") {
|
|
476
|
+
widgetTpl = html`<sp-textfield
|
|
477
|
+
size="s"
|
|
478
|
+
placeholder="YYYY-MM-DD"
|
|
479
|
+
value=${staticVal}
|
|
480
|
+
@input=${(/** @type {Event} */ e) => {
|
|
481
|
+
clearTimeout(debounce);
|
|
482
|
+
debounce = setTimeout(
|
|
483
|
+
() => onChange(/** @type {HTMLInputElement} */ (e.target).value),
|
|
484
|
+
400,
|
|
485
|
+
);
|
|
486
|
+
}}
|
|
487
|
+
></sp-textfield>`;
|
|
488
|
+
} else if (parsed.kind === "boolean") {
|
|
489
|
+
widgetTpl = html`<sp-checkbox
|
|
490
|
+
size="s"
|
|
491
|
+
.checked=${live(!!staticVal)}
|
|
492
|
+
@change=${(/** @type {Event} */ e) =>
|
|
493
|
+
onChange(/** @type {HTMLInputElement} */ (e.target).checked || undefined)}
|
|
494
|
+
></sp-checkbox>`;
|
|
495
|
+
} else if (parsed.kind === "number") {
|
|
496
|
+
widgetTpl = html`<sp-number-field
|
|
497
|
+
size="s"
|
|
498
|
+
value=${staticVal}
|
|
499
|
+
@input=${(/** @type {Event} */ e) => {
|
|
500
|
+
clearTimeout(debounce);
|
|
501
|
+
debounce = setTimeout(
|
|
502
|
+
() => onChange(/** @type {HTMLInputElement} */ (e.target).value),
|
|
503
|
+
400,
|
|
504
|
+
);
|
|
505
|
+
}}
|
|
506
|
+
></sp-number-field>`;
|
|
507
|
+
} else if (parsed.kind === "combobox") {
|
|
508
|
+
const options = /** @type {string[]} */ (
|
|
509
|
+
/** @type {{ options?: string[] }} */ (parsed).options
|
|
510
|
+
);
|
|
511
|
+
widgetTpl = html`<jx-value-selector
|
|
512
|
+
.value=${String(staticVal)}
|
|
513
|
+
size="s"
|
|
514
|
+
placeholder="—"
|
|
515
|
+
.options=${options.map((o) => ({ value: o, label: camelToLabel(o) }))}
|
|
516
|
+
@change=${(/** @type {Event & { detail?: { value?: string } }} */ e) =>
|
|
517
|
+
onChange(e.detail?.value ?? /** @type {HTMLInputElement} */ (e.target).value)}
|
|
518
|
+
></jx-value-selector>`;
|
|
519
|
+
} else {
|
|
520
|
+
widgetTpl = html`<sp-textfield
|
|
521
|
+
size="s"
|
|
522
|
+
value=${staticVal}
|
|
523
|
+
@input=${(/** @type {Event} */ e) => {
|
|
524
|
+
clearTimeout(debounce);
|
|
525
|
+
debounce = setTimeout(
|
|
526
|
+
() => onChange(/** @type {HTMLInputElement} */ (e.target).value),
|
|
527
|
+
400,
|
|
528
|
+
);
|
|
529
|
+
}}
|
|
530
|
+
></sp-textfield>`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return html`
|
|
534
|
+
<div class="style-row" data-prop=${prop.name}>
|
|
535
|
+
<div class="style-row-label">
|
|
536
|
+
${hasVal
|
|
537
|
+
? html`<span class="set-dot" title="Clear ${prop.name}" @click=${clearProp}></span>`
|
|
538
|
+
: nothing}
|
|
539
|
+
<sp-field-label size="s" title=${prop.description || prop.name}
|
|
540
|
+
>${camelToLabel(prop.name)}</sp-field-label
|
|
541
|
+
>
|
|
542
|
+
<sp-action-button
|
|
543
|
+
size="xs"
|
|
544
|
+
quiet
|
|
545
|
+
title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
|
|
546
|
+
@click=${onToggleBind}
|
|
547
|
+
>${isBound ? "\u26A1" : "\u2194"}</sp-action-button
|
|
548
|
+
>
|
|
549
|
+
</div>
|
|
550
|
+
${isBound ? boundTpl : widgetTpl}
|
|
496
551
|
</div>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
})}
|
|
552
|
+
`;
|
|
553
|
+
},
|
|
554
|
+
)}
|
|
501
555
|
${comp.props.length === 0 ? html`<div class="empty-state">No props defined</div>` : nothing}
|
|
502
556
|
${comp.path
|
|
503
557
|
? html`<span class="kv-add" @click=${() => navigateToComponent(comp.path)}
|
|
@@ -509,10 +563,10 @@ function renderComponentPropsFieldsTemplate(
|
|
|
509
563
|
|
|
510
564
|
/** Custom attrs fields template */
|
|
511
565
|
function renderCustomAttrsFieldsTemplate(
|
|
512
|
-
/** @type {
|
|
513
|
-
/** @type {
|
|
514
|
-
/** @type {
|
|
515
|
-
/** @type {
|
|
566
|
+
/** @type {JxMutableNode} */ node,
|
|
567
|
+
/** @type {JxPath} */ path,
|
|
568
|
+
/** @type {Record<string, unknown>} */ attrs,
|
|
569
|
+
/** @type {Set<string>} */ knownAttrNames,
|
|
516
570
|
) {
|
|
517
571
|
const customAttrs = Object.entries(attrs).filter(([k]) => !knownAttrNames.has(k));
|
|
518
572
|
return html`
|
|
@@ -520,7 +574,7 @@ function renderCustomAttrsFieldsTemplate(
|
|
|
520
574
|
kvRow(
|
|
521
575
|
attr,
|
|
522
576
|
String(val),
|
|
523
|
-
(/** @type {
|
|
577
|
+
(/** @type {string} */ newAttr, /** @type {string} */ newVal) => {
|
|
524
578
|
if (newAttr !== attr) {
|
|
525
579
|
transactDoc(activeTab.value, (t) => {
|
|
526
580
|
mutateUpdateAttribute(t, path, attr, undefined);
|
|
@@ -545,9 +599,9 @@ function renderCustomAttrsFieldsTemplate(
|
|
|
545
599
|
// ─── Media breakpoints ──────────────────────────────────────────────────────
|
|
546
600
|
|
|
547
601
|
/** Media breakpoint fields template */
|
|
548
|
-
function renderMediaFieldsTemplate(/** @type {
|
|
602
|
+
function renderMediaFieldsTemplate(/** @type {JxMutableNode} */ node) {
|
|
549
603
|
const media = node.$media || {};
|
|
550
|
-
/** @type {
|
|
604
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
551
605
|
let baseDebounce;
|
|
552
606
|
const breakpoints = Object.entries(media).filter(([k]) => k !== "--");
|
|
553
607
|
|
|
@@ -559,10 +613,10 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
559
613
|
style="width:70px;flex:none"
|
|
560
614
|
placeholder="320px"
|
|
561
615
|
value=${media["--"] || ""}
|
|
562
|
-
@input=${(/** @type {
|
|
616
|
+
@input=${(/** @type {Event} */ e) => {
|
|
563
617
|
clearTimeout(baseDebounce);
|
|
564
618
|
baseDebounce = setTimeout(() => {
|
|
565
|
-
const val = e.target.value.trim();
|
|
619
|
+
const val = /** @type {HTMLInputElement} */ (e.target).value.trim();
|
|
566
620
|
transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--", val || undefined));
|
|
567
621
|
}, 400);
|
|
568
622
|
}}
|
|
@@ -583,7 +637,7 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
583
637
|
<span
|
|
584
638
|
class="kv-add"
|
|
585
639
|
style=${view.showAddBreakpointForm ? "display:none" : ""}
|
|
586
|
-
@click=${(/** @type {
|
|
640
|
+
@click=${(/** @type {Event} */ _e) => {
|
|
587
641
|
view.showAddBreakpointForm = true;
|
|
588
642
|
renderOnly("rightPanel");
|
|
589
643
|
}}
|
|
@@ -597,8 +651,9 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
597
651
|
class="field-input"
|
|
598
652
|
placeholder="Name (e.g. Tablet)"
|
|
599
653
|
style="flex:1"
|
|
600
|
-
@input=${(/** @type {
|
|
601
|
-
view.addBreakpointPreview =
|
|
654
|
+
@input=${(/** @type {Event} */ e) => {
|
|
655
|
+
view.addBreakpointPreview =
|
|
656
|
+
friendlyNameToMedia(/** @type {HTMLInputElement} */ (e.target).value) || "";
|
|
602
657
|
renderOnly("rightPanel");
|
|
603
658
|
}}
|
|
604
659
|
/>
|
|
@@ -614,11 +669,15 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
614
669
|
<button
|
|
615
670
|
class="kv-add"
|
|
616
671
|
style="padding:2px 10px;cursor:pointer"
|
|
617
|
-
@click=${(/** @type {
|
|
618
|
-
const wrap = e.target.closest(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
672
|
+
@click=${(/** @type {Event} */ e) => {
|
|
673
|
+
const wrap = /** @type {HTMLElement} */ (e.target).closest(
|
|
674
|
+
"div",
|
|
675
|
+
)?.parentElement;
|
|
676
|
+
const nameVal = wrap?.querySelector("input")?.value;
|
|
677
|
+
const queryVal = /** @type {HTMLInputElement | null} */ (
|
|
678
|
+
wrap?.querySelector(".add-bp-query")
|
|
679
|
+
)?.value?.trim();
|
|
680
|
+
const key = friendlyNameToMedia(nameVal || "");
|
|
622
681
|
if (key && queryVal) {
|
|
623
682
|
view.showAddBreakpointForm = false;
|
|
624
683
|
view.addBreakpointPreview = "";
|
|
@@ -648,8 +707,8 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
648
707
|
}
|
|
649
708
|
|
|
650
709
|
/** Single media breakpoint row template */
|
|
651
|
-
function mediaBreakpointRowTemplate(/** @type {
|
|
652
|
-
/** @type {
|
|
710
|
+
function mediaBreakpointRowTemplate(/** @type {string} */ name, /** @type {string} */ query) {
|
|
711
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
653
712
|
let debounceTimer;
|
|
654
713
|
let currentRawLabel = name;
|
|
655
714
|
return html`
|
|
@@ -659,17 +718,21 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
659
718
|
class="field-input"
|
|
660
719
|
value=${mediaDisplayName(name)}
|
|
661
720
|
style="flex:1;font-weight:600;font-size:12px"
|
|
662
|
-
@input=${(/** @type {
|
|
663
|
-
const newKey = friendlyNameToMedia(e.target.value);
|
|
721
|
+
@input=${(/** @type {Event} */ e) => {
|
|
722
|
+
const newKey = friendlyNameToMedia(/** @type {HTMLInputElement} */ (e.target).value);
|
|
664
723
|
currentRawLabel = newKey || "";
|
|
665
|
-
const rawEl = e.target.parentElement?.querySelector(
|
|
724
|
+
const rawEl = /** @type {HTMLElement} */ (e.target).parentElement?.querySelector(
|
|
725
|
+
".bp-raw-label",
|
|
726
|
+
);
|
|
666
727
|
if (rawEl) rawEl.textContent = currentRawLabel;
|
|
667
728
|
clearTimeout(debounceTimer);
|
|
668
729
|
debounceTimer = setTimeout(() => {
|
|
669
730
|
if (newKey && newKey !== name) {
|
|
670
|
-
const queryEl =
|
|
671
|
-
.
|
|
672
|
-
|
|
731
|
+
const queryEl = /** @type {HTMLInputElement | null} */ (
|
|
732
|
+
/** @type {HTMLElement} */ (e.target)
|
|
733
|
+
.closest("div[style]")
|
|
734
|
+
?.parentElement?.querySelector(".bp-query-input")
|
|
735
|
+
);
|
|
673
736
|
transactDoc(activeTab.value, (t) => {
|
|
674
737
|
mutateUpdateMedia(t, name, undefined);
|
|
675
738
|
mutateUpdateMedia(t, newKey, queryEl?.value || query);
|
|
@@ -694,10 +757,13 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
694
757
|
class="field-input bp-query-input"
|
|
695
758
|
value=${query}
|
|
696
759
|
style="flex:1"
|
|
697
|
-
@input=${(/** @type {
|
|
760
|
+
@input=${(/** @type {Event} */ e) => {
|
|
698
761
|
clearTimeout(debounceTimer);
|
|
699
762
|
debounceTimer = setTimeout(
|
|
700
|
-
() =>
|
|
763
|
+
() =>
|
|
764
|
+
transactDoc(activeTab.value, (t) =>
|
|
765
|
+
mutateUpdateMedia(t, name, /** @type {HTMLInputElement} */ (e.target).value),
|
|
766
|
+
),
|
|
701
767
|
400,
|
|
702
768
|
);
|
|
703
769
|
}}
|
|
@@ -717,8 +783,11 @@ async function loadLayoutEntries() {
|
|
|
717
783
|
const platform = getPlatform();
|
|
718
784
|
const listing = await platform.listDirectory("layouts");
|
|
719
785
|
layoutEntries = listing
|
|
720
|
-
.filter(
|
|
721
|
-
|
|
786
|
+
.filter(
|
|
787
|
+
(/** @type {{ type: string; name: string }} */ f) =>
|
|
788
|
+
f.type === "file" && f.name.endsWith(".json"),
|
|
789
|
+
)
|
|
790
|
+
.map((/** @type {{ type: string; name: string }} */ f) => ({
|
|
722
791
|
name: f.name.replace(/\.json$/, ""),
|
|
723
792
|
path: `./layouts/${f.name}`,
|
|
724
793
|
}));
|
|
@@ -732,12 +801,12 @@ export function invalidateLayoutPickerCache() {
|
|
|
732
801
|
layoutEntries = null;
|
|
733
802
|
}
|
|
734
803
|
|
|
735
|
-
function isPageDocument(/** @type {
|
|
804
|
+
function isPageDocument(/** @type {string | undefined | null} */ documentPath) {
|
|
736
805
|
if (!documentPath || !projectState?.isSiteProject) return false;
|
|
737
806
|
return documentPath.startsWith("pages/") || documentPath.startsWith("./pages/");
|
|
738
807
|
}
|
|
739
808
|
|
|
740
|
-
function renderPageSection(/** @type {
|
|
809
|
+
function renderPageSection(/** @type {JxMutableNode} */ node) {
|
|
741
810
|
const tab = activeTab.value;
|
|
742
811
|
if (!isPageDocument(tab.documentPath)) return nothing;
|
|
743
812
|
|
|
@@ -761,7 +830,7 @@ function renderPageSection(/** @type {any} */ node) {
|
|
|
761
830
|
? html`<span
|
|
762
831
|
class="set-dot"
|
|
763
832
|
title="Reset to default"
|
|
764
|
-
@click=${(/** @type {
|
|
833
|
+
@click=${(/** @type {Event} */ e) => {
|
|
765
834
|
e.stopPropagation();
|
|
766
835
|
transactDoc(activeTab.value, (t) =>
|
|
767
836
|
mutateUpdateProperty(t, [], "$layout", undefined),
|
|
@@ -774,8 +843,8 @@ function renderPageSection(/** @type {any} */ node) {
|
|
|
774
843
|
<sp-picker
|
|
775
844
|
size="s"
|
|
776
845
|
value=${displayValue}
|
|
777
|
-
@change=${(/** @type {
|
|
778
|
-
const val = e.target.value;
|
|
846
|
+
@change=${(/** @type {Event} */ e) => {
|
|
847
|
+
const val = /** @type {HTMLInputElement} */ (e.target).value;
|
|
779
848
|
if (val === "__default__") {
|
|
780
849
|
transactDoc(activeTab.value, (t) =>
|
|
781
850
|
mutateUpdateProperty(t, [], "$layout", undefined),
|
|
@@ -796,7 +865,7 @@ function renderPageSection(/** @type {any} */ node) {
|
|
|
796
865
|
<sp-menu-item value="__none__">None</sp-menu-item>
|
|
797
866
|
<sp-menu-divider></sp-menu-divider>
|
|
798
867
|
${layoutEntries.map(
|
|
799
|
-
(/** @type {
|
|
868
|
+
(/** @type {{ name: string; path: string }} */ l) =>
|
|
800
869
|
html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
|
|
801
870
|
)}
|
|
802
871
|
</sp-picker>
|
|
@@ -813,8 +882,12 @@ function renderPageSection(/** @type {any} */ node) {
|
|
|
813
882
|
|
|
814
883
|
// ─── Layout selection panel ─────────────────────────────────────────────────
|
|
815
884
|
|
|
816
|
-
function renderLayoutSelectionPanel(
|
|
817
|
-
|
|
885
|
+
function renderLayoutSelectionPanel(
|
|
886
|
+
/** @type {{ navigateToComponent: (path: string) => void }} */ ctx,
|
|
887
|
+
) {
|
|
888
|
+
const { el, layoutPath } = /** @type {{ el: HTMLElement; layoutPath: string }} */ (
|
|
889
|
+
view.layoutSelection
|
|
890
|
+
);
|
|
818
891
|
const tagName = el?.tagName?.toLowerCase() || "element";
|
|
819
892
|
const className = el?.className || "";
|
|
820
893
|
const displayPath = layoutPath || "layout";
|
|
@@ -879,7 +952,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
879
952
|
const path = tab.session.selection;
|
|
880
953
|
const isMapNode = node.$prototype === "Array";
|
|
881
954
|
const isMapParent =
|
|
882
|
-
node.children &&
|
|
955
|
+
node.children &&
|
|
956
|
+
typeof node.children === "object" &&
|
|
957
|
+
/** @type {{ $prototype?: string }} */ (node.children).$prototype === "Array";
|
|
883
958
|
const isSwitchNode = !!node.$switch;
|
|
884
959
|
const isCustomInstance = (node.tagName || "").includes("-");
|
|
885
960
|
const isRoot = path.length === 0;
|
|
@@ -894,9 +969,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
894
969
|
: null;
|
|
895
970
|
|
|
896
971
|
function renderAttrRow(
|
|
897
|
-
/** @type {
|
|
898
|
-
/** @type {
|
|
899
|
-
/** @type {
|
|
972
|
+
/** @type {string} */ attr,
|
|
973
|
+
/** @type {HtmlMetaEntry} */ entry,
|
|
974
|
+
/** @type {unknown} */ value,
|
|
900
975
|
) {
|
|
901
976
|
const type = inferInputType(entry);
|
|
902
977
|
const hasVal = value !== undefined && value !== "";
|
|
@@ -912,9 +987,14 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
912
987
|
<sp-checkbox
|
|
913
988
|
size="s"
|
|
914
989
|
.checked=${live(!!value)}
|
|
915
|
-
@change=${(/** @type {
|
|
990
|
+
@change=${(/** @type {Event} */ e) =>
|
|
916
991
|
transactDoc(activeTab.value, (t) =>
|
|
917
|
-
mutateUpdateAttribute(
|
|
992
|
+
mutateUpdateAttribute(
|
|
993
|
+
t,
|
|
994
|
+
path,
|
|
995
|
+
attr,
|
|
996
|
+
/** @type {HTMLInputElement} */ (e.target).checked ? "" : undefined,
|
|
997
|
+
),
|
|
918
998
|
)}
|
|
919
999
|
>
|
|
920
1000
|
</sp-checkbox>
|
|
@@ -928,21 +1008,23 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
928
1008
|
hasValue: hasVal,
|
|
929
1009
|
onClear: () =>
|
|
930
1010
|
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
|
|
931
|
-
widget: widgetForType(type, entry, attr, value || "", (/** @type {
|
|
1011
|
+
widget: widgetForType(type, entry, attr, String(value || ""), (/** @type {string} */ v) =>
|
|
932
1012
|
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
|
|
933
1013
|
),
|
|
934
1014
|
});
|
|
935
1015
|
}
|
|
936
1016
|
|
|
937
1017
|
// ── Collect applicable attributes from html-meta ──
|
|
938
|
-
const applicableAttrs = /** @type {Record<string,
|
|
939
|
-
for (const [attr, entry] of /** @type {[string,
|
|
1018
|
+
const applicableAttrs = /** @type {Record<string, HtmlMetaEntry>} */ ({});
|
|
1019
|
+
for (const [attr, entry] of /** @type {[string, HtmlMetaEntry][]} */ (
|
|
1020
|
+
Object.entries(htmlMeta.$defs)
|
|
1021
|
+
)) {
|
|
940
1022
|
if (!entry.$elements || entry.$elements.includes(tagName)) {
|
|
941
1023
|
applicableAttrs[attr] = entry;
|
|
942
1024
|
}
|
|
943
1025
|
}
|
|
944
1026
|
|
|
945
|
-
const attrSections = /** @type {Record<string,
|
|
1027
|
+
const attrSections = /** @type {Record<string, { name: string; entry: HtmlMetaEntry }[]>} */ ({});
|
|
946
1028
|
for (const sec of htmlMeta.$sections) attrSections[sec.key] = [];
|
|
947
1029
|
for (const [attr, entry] of Object.entries(applicableAttrs)) {
|
|
948
1030
|
const secKey = entry.$section;
|
|
@@ -950,7 +1032,10 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
950
1032
|
}
|
|
951
1033
|
for (const sec of htmlMeta.$sections) {
|
|
952
1034
|
attrSections[sec.key].sort(
|
|
953
|
-
(
|
|
1035
|
+
(
|
|
1036
|
+
/** @type {{ name: string; entry: HtmlMetaEntry }} */ a,
|
|
1037
|
+
/** @type {{ name: string; entry: HtmlMetaEntry }} */ b,
|
|
1038
|
+
) => a.entry.$order - b.entry.$order,
|
|
954
1039
|
);
|
|
955
1040
|
}
|
|
956
1041
|
|
|
@@ -968,13 +1053,13 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
968
1053
|
}
|
|
969
1054
|
if (customAttrs.length > 0) autoOpen.add("__custom");
|
|
970
1055
|
|
|
971
|
-
function isSectionOpen(/** @type {
|
|
1056
|
+
function isSectionOpen(/** @type {string} */ key) {
|
|
972
1057
|
if (tab.session.ui.inspectorSections[key] !== undefined)
|
|
973
1058
|
return tab.session.ui.inspectorSections[key];
|
|
974
1059
|
return autoOpen.has(key);
|
|
975
1060
|
}
|
|
976
1061
|
|
|
977
|
-
function toggleSection(/** @type {
|
|
1062
|
+
function toggleSection(/** @type {string} */ key) {
|
|
978
1063
|
const current = isSectionOpen(key);
|
|
979
1064
|
activeTab.value.session.ui.inspectorSections = {
|
|
980
1065
|
...activeTab.value.session.ui.inspectorSections,
|
|
@@ -1000,9 +1085,14 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1000
1085
|
.value=${live(tagName)}
|
|
1001
1086
|
autocomplete="off"
|
|
1002
1087
|
list="tag-names"
|
|
1003
|
-
@input=${debouncedStyleCommit("prop:tagName", 400, (/** @type {
|
|
1088
|
+
@input=${debouncedStyleCommit("prop:tagName", 400, (/** @type {Event} */ e) => {
|
|
1004
1089
|
transactDoc(activeTab.value, (t) =>
|
|
1005
|
-
mutateUpdateProperty(
|
|
1090
|
+
mutateUpdateProperty(
|
|
1091
|
+
t,
|
|
1092
|
+
path,
|
|
1093
|
+
"tagName",
|
|
1094
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
1095
|
+
),
|
|
1006
1096
|
);
|
|
1007
1097
|
})}
|
|
1008
1098
|
></sp-textfield>
|
|
@@ -1013,7 +1103,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1013
1103
|
? html`<span
|
|
1014
1104
|
class="set-dot"
|
|
1015
1105
|
title="Clear $id"
|
|
1016
|
-
@click=${(/** @type {
|
|
1106
|
+
@click=${(/** @type {Event} */ e) => {
|
|
1017
1107
|
e.stopPropagation();
|
|
1018
1108
|
transactDoc(activeTab.value, (t) =>
|
|
1019
1109
|
mutateUpdateProperty(t, path, "$id", undefined),
|
|
@@ -1026,9 +1116,14 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1026
1116
|
<sp-textfield
|
|
1027
1117
|
size="s"
|
|
1028
1118
|
.value=${live(node.$id || "")}
|
|
1029
|
-
@input=${debouncedStyleCommit("prop:$id", 400, (/** @type {
|
|
1119
|
+
@input=${debouncedStyleCommit("prop:$id", 400, (/** @type {Event} */ e) => {
|
|
1030
1120
|
transactDoc(activeTab.value, (t) =>
|
|
1031
|
-
mutateUpdateProperty(
|
|
1121
|
+
mutateUpdateProperty(
|
|
1122
|
+
t,
|
|
1123
|
+
path,
|
|
1124
|
+
"$id",
|
|
1125
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
1126
|
+
),
|
|
1032
1127
|
);
|
|
1033
1128
|
})}
|
|
1034
1129
|
></sp-textfield>
|
|
@@ -1039,7 +1134,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1039
1134
|
? html`<span
|
|
1040
1135
|
class="set-dot"
|
|
1041
1136
|
title="Clear class"
|
|
1042
|
-
@click=${(/** @type {
|
|
1137
|
+
@click=${(/** @type {Event} */ e) => {
|
|
1043
1138
|
e.stopPropagation();
|
|
1044
1139
|
transactDoc(activeTab.value, (t) =>
|
|
1045
1140
|
mutateUpdateProperty(t, path, "className", undefined),
|
|
@@ -1052,9 +1147,14 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1052
1147
|
<sp-textfield
|
|
1053
1148
|
size="s"
|
|
1054
1149
|
.value=${live(node.className || "")}
|
|
1055
|
-
@input=${debouncedStyleCommit("prop:className", 400, (/** @type {
|
|
1150
|
+
@input=${debouncedStyleCommit("prop:className", 400, (/** @type {Event} */ e) => {
|
|
1056
1151
|
transactDoc(activeTab.value, (t) =>
|
|
1057
|
-
mutateUpdateProperty(
|
|
1152
|
+
mutateUpdateProperty(
|
|
1153
|
+
t,
|
|
1154
|
+
path,
|
|
1155
|
+
"className",
|
|
1156
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
1157
|
+
),
|
|
1058
1158
|
);
|
|
1059
1159
|
})}
|
|
1060
1160
|
></sp-textfield>
|
|
@@ -1067,7 +1167,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1067
1167
|
? html`<span
|
|
1068
1168
|
class="set-dot"
|
|
1069
1169
|
title="Clear text"
|
|
1070
|
-
@click=${(/** @type {
|
|
1170
|
+
@click=${(/** @type {Event} */ e) => {
|
|
1071
1171
|
e.stopPropagation();
|
|
1072
1172
|
transactDoc(activeTab.value, (t) =>
|
|
1073
1173
|
mutateUpdateProperty(t, path, "textContent", undefined),
|
|
@@ -1085,11 +1185,20 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1085
1185
|
? node.textContent
|
|
1086
1186
|
: (node.textContent ?? ""),
|
|
1087
1187
|
)}
|
|
1088
|
-
@input=${debouncedStyleCommit(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
)
|
|
1092
|
-
|
|
1188
|
+
@input=${debouncedStyleCommit(
|
|
1189
|
+
"prop:textContent",
|
|
1190
|
+
400,
|
|
1191
|
+
(/** @type {Event} */ e) => {
|
|
1192
|
+
transactDoc(activeTab.value, (t) =>
|
|
1193
|
+
mutateUpdateProperty(
|
|
1194
|
+
t,
|
|
1195
|
+
path,
|
|
1196
|
+
"textContent",
|
|
1197
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
1198
|
+
),
|
|
1199
|
+
);
|
|
1200
|
+
},
|
|
1201
|
+
)}
|
|
1093
1202
|
></sp-textfield>
|
|
1094
1203
|
</div>
|
|
1095
1204
|
`
|
|
@@ -1100,7 +1209,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1100
1209
|
? html`<span
|
|
1101
1210
|
class="set-dot"
|
|
1102
1211
|
title="Clear hidden"
|
|
1103
|
-
@click=${(/** @type {
|
|
1212
|
+
@click=${(/** @type {Event} */ e) => {
|
|
1104
1213
|
e.stopPropagation();
|
|
1105
1214
|
transactDoc(activeTab.value, (t) =>
|
|
1106
1215
|
mutateUpdateProperty(t, path, "hidden", undefined),
|
|
@@ -1113,9 +1222,14 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1113
1222
|
<sp-checkbox
|
|
1114
1223
|
size="s"
|
|
1115
1224
|
.checked=${live(!!node.hidden)}
|
|
1116
|
-
@change=${(/** @type {
|
|
1225
|
+
@change=${(/** @type {Event} */ e) =>
|
|
1117
1226
|
transactDoc(activeTab.value, (t) =>
|
|
1118
|
-
mutateUpdateProperty(
|
|
1227
|
+
mutateUpdateProperty(
|
|
1228
|
+
t,
|
|
1229
|
+
path,
|
|
1230
|
+
"hidden",
|
|
1231
|
+
/** @type {HTMLInputElement} */ (e.target).checked || undefined,
|
|
1232
|
+
),
|
|
1119
1233
|
)}
|
|
1120
1234
|
>
|
|
1121
1235
|
</sp-checkbox>
|
|
@@ -1207,7 +1321,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1207
1321
|
.filter((sec) => attrSections[sec.key].length > 0)
|
|
1208
1322
|
.map((sec) => {
|
|
1209
1323
|
const sectionAttrs = attrSections[sec.key];
|
|
1210
|
-
const hasAnySet = sectionAttrs.some(
|
|
1324
|
+
const hasAnySet = sectionAttrs.some(
|
|
1325
|
+
(/** @type {{ name: string; entry: HtmlMetaEntry }} */ a) => attrs[a.name] !== undefined,
|
|
1326
|
+
);
|
|
1211
1327
|
return html`
|
|
1212
1328
|
<sp-accordion-item
|
|
1213
1329
|
label=${sec.label}
|
|
@@ -1218,7 +1334,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1218
1334
|
? html`<span slot="heading" class="set-dot set-dot--section"></span>`
|
|
1219
1335
|
: nothing}
|
|
1220
1336
|
<div class="style-section-body">
|
|
1221
|
-
${sectionAttrs.map((/** @type {
|
|
1337
|
+
${sectionAttrs.map((/** @type {{ name: string; entry: HtmlMetaEntry }} */ a) =>
|
|
1222
1338
|
renderAttrRow(a.name, a.entry, attrs[a.name]),
|
|
1223
1339
|
)}
|
|
1224
1340
|
</div>
|