@jxsuite/studio 0.10.2 → 0.13.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/README.md +82 -0
- package/dist/studio.js +5114 -3424
- package/dist/studio.js.map +65 -49
- package/package.json +6 -3
- package/src/browse/browse.js +27 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +136 -17
- package/src/canvas/canvas-render.js +154 -21
- package/src/canvas/canvas-utils.js +21 -23
- package/src/editor/component-inline-edit.js +54 -41
- package/src/editor/content-inline-edit.js +46 -47
- package/src/editor/context-menu.js +63 -39
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -10
- package/src/editor/shortcuts.js +69 -39
- package/src/files/components.js +15 -4
- package/src/files/files.js +72 -24
- package/src/panels/activity-bar.js +29 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +132 -50
- package/src/panels/dnd.js +32 -28
- package/src/panels/editors.js +7 -14
- package/src/panels/elements-panel.js +9 -3
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +45 -3
- package/src/panels/layers-panel.js +16 -11
- package/src/panels/left-panel.js +108 -43
- package/src/panels/overlays.js +97 -35
- package/src/panels/panel-events.js +18 -11
- package/src/panels/properties-panel.js +467 -98
- package/src/panels/right-panel.js +85 -37
- package/src/panels/shared.js +0 -22
- package/src/panels/signals-panel.js +125 -54
- package/src/panels/statusbar.js +23 -8
- package/src/panels/style-inputs.js +4 -2
- package/src/panels/style-panel.js +128 -105
- package/src/panels/stylebook-panel.js +42 -17
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +39 -9
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +78 -8
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +99 -11
- package/src/site-context.js +105 -0
- package/src/state.js +0 -456
- package/src/store.js +105 -124
- package/src/studio.js +112 -121
- package/src/tabs/tab.js +153 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/spectrum.js +2 -0
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +89 -0
|
@@ -2,24 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { html, nothing } from "lit-html";
|
|
4
4
|
import { live } from "lit-html/directives/live.js";
|
|
5
|
+
import { getNodeAtPath, debouncedStyleCommit, renderOnly, projectState } from "../store.js";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
renameSwitchCase,
|
|
18
|
-
debouncedStyleCommit,
|
|
19
|
-
renderOnly,
|
|
20
|
-
updateUi,
|
|
21
|
-
projectState,
|
|
22
|
-
} from "../store.js";
|
|
7
|
+
transactDoc,
|
|
8
|
+
mutateUpdateProperty,
|
|
9
|
+
mutateUpdateAttribute,
|
|
10
|
+
mutateUpdateProp,
|
|
11
|
+
mutateUpdateFrontmatter,
|
|
12
|
+
mutateUpdateMedia,
|
|
13
|
+
mutateAddSwitchCase,
|
|
14
|
+
mutateRemoveSwitchCase,
|
|
15
|
+
mutateRenameSwitchCase,
|
|
16
|
+
} from "../tabs/transact.js";
|
|
17
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
23
18
|
import { view } from "../view.js";
|
|
24
19
|
import { componentRegistry } from "../files/components.js";
|
|
25
20
|
import { widgetForType } from "./style-inputs.js";
|
|
@@ -35,6 +30,10 @@ import {
|
|
|
35
30
|
import { isCustomElementDoc, collectCssParts } from "./signals-panel.js";
|
|
36
31
|
import { mediaDisplayName } from "./shared.js";
|
|
37
32
|
import { getCssInitialMap } from "./style-utils.js";
|
|
33
|
+
import { renderMediaPicker } from "../ui/media-picker.js";
|
|
34
|
+
import { renderColorSelector } from "../ui/color-selector.js";
|
|
35
|
+
import { getEffectiveLayoutPath, invalidateLayoutCache } from "../site-context.js";
|
|
36
|
+
import { getPlatform } from "../platform.js";
|
|
38
37
|
import htmlMeta from "../../data/html-meta.json";
|
|
39
38
|
|
|
40
39
|
/**
|
|
@@ -67,8 +66,8 @@ function bindableFieldRow(
|
|
|
67
66
|
/** @type {any} */ filterFn = null,
|
|
68
67
|
/** @type {any} */ extraSignals = null,
|
|
69
68
|
) {
|
|
70
|
-
const
|
|
71
|
-
const defs =
|
|
69
|
+
const tab = activeTab.value;
|
|
70
|
+
const defs = tab.doc.document.state || {};
|
|
72
71
|
const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
|
|
73
72
|
|
|
74
73
|
const signalDefs = Object.entries(defs).filter(([, d]) =>
|
|
@@ -208,9 +207,9 @@ function kvRow(
|
|
|
208
207
|
|
|
209
208
|
/** Frontmatter-only panel shown in content mode when no element is selected */
|
|
210
209
|
function renderFrontmatterOnlyPanel() {
|
|
211
|
-
const
|
|
212
|
-
const fm =
|
|
213
|
-
const col = findContentTypeSchema(
|
|
210
|
+
const tab = activeTab.value;
|
|
211
|
+
const fm = tab.doc.content?.frontmatter || {};
|
|
212
|
+
const col = findContentTypeSchema(tab.documentPath, projectState?.projectConfig);
|
|
214
213
|
const schemaProps = col?.schema?.properties;
|
|
215
214
|
const requiredFields = new Set(col?.schema?.required || []);
|
|
216
215
|
|
|
@@ -245,9 +244,12 @@ function renderFrontmatterOnlyPanel() {
|
|
|
245
244
|
return html`<div class="empty-state">No frontmatter. Select an element to inspect.</div>`;
|
|
246
245
|
}
|
|
247
246
|
|
|
247
|
+
const pageT = renderPageSection(tab.doc.document || {});
|
|
248
|
+
|
|
248
249
|
return html`
|
|
249
250
|
<div class="style-sidebar">
|
|
250
251
|
<sp-accordion allow-multiple size="s">
|
|
252
|
+
${pageT}
|
|
251
253
|
<sp-accordion-item label=${col ? `Frontmatter (${col.name})` : "Frontmatter"} open>
|
|
252
254
|
<div class="style-section-body">
|
|
253
255
|
${fields.map((f) => renderFmFieldRow(f.field, f.entry, f.value, requiredFields))}
|
|
@@ -265,12 +267,12 @@ function renderFmFieldRow(
|
|
|
265
267
|
/** @type {any} */ value,
|
|
266
268
|
/** @type {Set<string>} */ requiredFields,
|
|
267
269
|
) {
|
|
268
|
-
const S = getState();
|
|
269
270
|
const isRequired = requiredFields.has(field);
|
|
270
271
|
const label = field.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
|
|
271
272
|
const displayLabel = label + (isRequired ? " *" : "");
|
|
272
273
|
const hasVal = value !== undefined && value !== "" && value !== false;
|
|
273
|
-
const onClear = () =>
|
|
274
|
+
const onClear = () =>
|
|
275
|
+
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, undefined));
|
|
274
276
|
|
|
275
277
|
if (entry.type === "boolean") {
|
|
276
278
|
return renderFieldRow({
|
|
@@ -283,7 +285,9 @@ function renderFmFieldRow(
|
|
|
283
285
|
size="s"
|
|
284
286
|
.checked=${live(!!value)}
|
|
285
287
|
@change=${(/** @type {any} */ e) =>
|
|
286
|
-
|
|
288
|
+
transactDoc(activeTab.value, (t) =>
|
|
289
|
+
mutateUpdateFrontmatter(t, field, e.target.checked || undefined),
|
|
290
|
+
)}
|
|
287
291
|
></sp-checkbox>
|
|
288
292
|
`,
|
|
289
293
|
});
|
|
@@ -308,7 +312,7 @@ function renderFmFieldRow(
|
|
|
308
312
|
.map((/** @type {string} */ s) => s.trim())
|
|
309
313
|
.filter(Boolean)
|
|
310
314
|
: undefined;
|
|
311
|
-
|
|
315
|
+
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, arr));
|
|
312
316
|
})}
|
|
313
317
|
></sp-textfield>
|
|
314
318
|
`,
|
|
@@ -326,7 +330,9 @@ function renderFmFieldRow(
|
|
|
326
330
|
size="s"
|
|
327
331
|
.value=${live(value || "")}
|
|
328
332
|
@change=${(/** @type {any} */ e) =>
|
|
329
|
-
|
|
333
|
+
transactDoc(activeTab.value, (t) =>
|
|
334
|
+
mutateUpdateFrontmatter(t, field, e.target.value || undefined),
|
|
335
|
+
)}
|
|
330
336
|
>
|
|
331
337
|
${entry.enum.map(
|
|
332
338
|
(/** @type {string} */ opt) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`,
|
|
@@ -336,6 +342,74 @@ function renderFmFieldRow(
|
|
|
336
342
|
});
|
|
337
343
|
}
|
|
338
344
|
|
|
345
|
+
if (entry.format === "image") {
|
|
346
|
+
return renderFieldRow({
|
|
347
|
+
prop: field,
|
|
348
|
+
label: displayLabel,
|
|
349
|
+
hasValue: hasVal,
|
|
350
|
+
onClear,
|
|
351
|
+
widget: renderMediaPicker(field, value, (v) =>
|
|
352
|
+
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
|
|
353
|
+
),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (entry.type === "array" && entry.items?.format === "image") {
|
|
358
|
+
const images = Array.isArray(value) ? value : [];
|
|
359
|
+
return renderFieldRow({
|
|
360
|
+
prop: field,
|
|
361
|
+
label: displayLabel,
|
|
362
|
+
hasValue: hasVal,
|
|
363
|
+
onClear,
|
|
364
|
+
widget: html`
|
|
365
|
+
<div class="gallery-picker">
|
|
366
|
+
<div class="gallery-picker-strip">
|
|
367
|
+
${images.map(
|
|
368
|
+
(img, i) => html`
|
|
369
|
+
<div class="gallery-picker-item">
|
|
370
|
+
<img src=${img} alt="" class="gallery-picker-thumb" />
|
|
371
|
+
<sp-action-button
|
|
372
|
+
size="xs"
|
|
373
|
+
quiet
|
|
374
|
+
title="Remove"
|
|
375
|
+
@click=${() => {
|
|
376
|
+
const next = images.filter((_, idx) => idx !== i);
|
|
377
|
+
transactDoc(activeTab.value, (t) =>
|
|
378
|
+
mutateUpdateFrontmatter(t, field, next.length ? next : undefined),
|
|
379
|
+
);
|
|
380
|
+
}}
|
|
381
|
+
>
|
|
382
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
383
|
+
</sp-action-button>
|
|
384
|
+
</div>
|
|
385
|
+
`,
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
${renderMediaPicker(`${field}:add`, "", (v) => {
|
|
389
|
+
if (!v) return;
|
|
390
|
+
const next = [...images, v];
|
|
391
|
+
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, next));
|
|
392
|
+
})}
|
|
393
|
+
</div>
|
|
394
|
+
`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (entry.$ref) {
|
|
399
|
+
const targetName = entry.$ref.replace("#/contentTypes/", "");
|
|
400
|
+
const targetDef = projectState?.projectConfig?.contentTypes?.[targetName];
|
|
401
|
+
const picker = renderReferencePicker(field, value, targetName, targetDef, (v) =>
|
|
402
|
+
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
|
|
403
|
+
);
|
|
404
|
+
return renderFieldRow({
|
|
405
|
+
prop: field,
|
|
406
|
+
label: displayLabel,
|
|
407
|
+
hasValue: hasVal,
|
|
408
|
+
onClear,
|
|
409
|
+
widget: picker,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
339
413
|
if (entry.type === "number") {
|
|
340
414
|
return renderFieldRow({
|
|
341
415
|
prop: field,
|
|
@@ -349,7 +423,9 @@ function renderFmFieldRow(
|
|
|
349
423
|
.value=${live(value !== undefined ? Number(value) : undefined)}
|
|
350
424
|
@change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) => {
|
|
351
425
|
const v = e.target.value;
|
|
352
|
-
|
|
426
|
+
transactDoc(activeTab.value, (t) =>
|
|
427
|
+
mutateUpdateFrontmatter(t, field, isNaN(v) ? undefined : Number(v)),
|
|
428
|
+
);
|
|
353
429
|
})}
|
|
354
430
|
></sp-number-field>
|
|
355
431
|
`,
|
|
@@ -367,13 +443,87 @@ function renderFmFieldRow(
|
|
|
367
443
|
placeholder=${entry.format === "date" ? "YYYY-MM-DD" : ""}
|
|
368
444
|
.value=${live(value || "")}
|
|
369
445
|
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) => {
|
|
370
|
-
|
|
446
|
+
transactDoc(activeTab.value, (t) =>
|
|
447
|
+
mutateUpdateFrontmatter(t, field, e.target.value || undefined),
|
|
448
|
+
);
|
|
371
449
|
})}
|
|
372
450
|
></sp-textfield>
|
|
373
451
|
`,
|
|
374
452
|
});
|
|
375
453
|
}
|
|
376
454
|
|
|
455
|
+
// ─── Reference picker ────────────────────────────────────────────────────────
|
|
456
|
+
|
|
457
|
+
/** @type {Map<string, { slug: string; title: string }[]>} */
|
|
458
|
+
const refEntriesCache = new Map();
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Render a reference field as a picker of entries from the target content type.
|
|
462
|
+
*
|
|
463
|
+
* @param {string} field
|
|
464
|
+
* @param {any} value
|
|
465
|
+
* @param {string} targetName
|
|
466
|
+
* @param {any} targetDef
|
|
467
|
+
* @param {(val: any) => void} onCommit
|
|
468
|
+
*/
|
|
469
|
+
function renderReferencePicker(field, value, targetName, targetDef, onCommit) {
|
|
470
|
+
if (!targetDef?.source) {
|
|
471
|
+
return html`<sp-textfield
|
|
472
|
+
size="s"
|
|
473
|
+
placeholder="slug"
|
|
474
|
+
.value=${live(value || "")}
|
|
475
|
+
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {any} */ e) =>
|
|
476
|
+
onCommit(e.target.value),
|
|
477
|
+
)}
|
|
478
|
+
></sp-textfield>`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const cacheKey = targetName;
|
|
482
|
+
if (!refEntriesCache.has(cacheKey)) {
|
|
483
|
+
loadRefEntries(targetName, targetDef);
|
|
484
|
+
}
|
|
485
|
+
const entries = refEntriesCache.get(cacheKey) || [];
|
|
486
|
+
|
|
487
|
+
return html`
|
|
488
|
+
<sp-picker
|
|
489
|
+
size="s"
|
|
490
|
+
label=${targetName}
|
|
491
|
+
.value=${live(value || "")}
|
|
492
|
+
@change=${(/** @type {any} */ e) => onCommit(e.target.value || undefined)}
|
|
493
|
+
>
|
|
494
|
+
<sp-menu-item value="">— none —</sp-menu-item>
|
|
495
|
+
${entries.map(
|
|
496
|
+
(ent) => html`<sp-menu-item value=${ent.slug}>${ent.title || ent.slug}</sp-menu-item>`,
|
|
497
|
+
)}
|
|
498
|
+
</sp-picker>
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function loadRefEntries(/** @type {string} */ targetName, /** @type {any} */ targetDef) {
|
|
503
|
+
const platform = getPlatform();
|
|
504
|
+
const sourceDir = targetDef.source.replace(/^\.\//, "").split("/**")[0].split("/*")[0];
|
|
505
|
+
try {
|
|
506
|
+
const listing = await platform.listDirectory(sourceDir);
|
|
507
|
+
const entries = [];
|
|
508
|
+
for (const item of listing) {
|
|
509
|
+
if (item.type === "directory") continue;
|
|
510
|
+
const slug = item.name.replace(/\.[^.]+$/, "");
|
|
511
|
+
let title = slug;
|
|
512
|
+
try {
|
|
513
|
+
const content = await platform.readFile(item.path);
|
|
514
|
+
const match = content.match(/^---[\s\S]*?title:\s*(.+?)[\r\n]/m);
|
|
515
|
+
if (match) title = match[1].trim().replace(/^["']|["']$/g, "");
|
|
516
|
+
} catch {}
|
|
517
|
+
entries.push({ slug, title });
|
|
518
|
+
}
|
|
519
|
+
refEntriesCache.set(targetName, entries);
|
|
520
|
+
} catch {}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function invalidateRefCache() {
|
|
524
|
+
refEntriesCache.clear();
|
|
525
|
+
}
|
|
526
|
+
|
|
377
527
|
// ─── Sub-templates ──────────────────────────────────────────────────────────
|
|
378
528
|
|
|
379
529
|
/** Repeater fields template */
|
|
@@ -382,33 +532,42 @@ function renderRepeaterFieldsTemplate(
|
|
|
382
532
|
/** @type {any} */ path,
|
|
383
533
|
/** @type {any} */ _mapSignals,
|
|
384
534
|
) {
|
|
385
|
-
const S = getState();
|
|
386
535
|
return html`
|
|
387
536
|
${bindableFieldRow("Items", "text", node.items, (/** @type {any} */ v) =>
|
|
388
|
-
|
|
537
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
|
|
389
538
|
)}
|
|
390
539
|
${node.filter
|
|
391
540
|
? bindableFieldRow("Filter", "text", node.filter, (/** @type {any} */ v) =>
|
|
392
|
-
|
|
541
|
+
transactDoc(activeTab.value, (t) =>
|
|
542
|
+
mutateUpdateProperty(t, path, "filter", v || undefined),
|
|
543
|
+
),
|
|
393
544
|
)
|
|
394
545
|
: nothing}
|
|
395
546
|
${node.sort
|
|
396
547
|
? bindableFieldRow("Sort", "text", node.sort, (/** @type {any} */ v) =>
|
|
397
|
-
|
|
548
|
+
transactDoc(activeTab.value, (t) =>
|
|
549
|
+
mutateUpdateProperty(t, path, "sort", v || undefined),
|
|
550
|
+
),
|
|
398
551
|
)
|
|
399
552
|
: nothing}
|
|
400
553
|
<div style="display:flex;gap:8px;margin-top:4px">
|
|
401
554
|
${!node.filter
|
|
402
555
|
? html`<span
|
|
403
556
|
class="kv-add"
|
|
404
|
-
@click=${() =>
|
|
557
|
+
@click=${() =>
|
|
558
|
+
transactDoc(activeTab.value, (t) =>
|
|
559
|
+
mutateUpdateProperty(t, path, "filter", { $ref: "#/state/" }),
|
|
560
|
+
)}
|
|
405
561
|
>+ Add filter</span
|
|
406
562
|
>`
|
|
407
563
|
: nothing}
|
|
408
564
|
${!node.sort
|
|
409
565
|
? html`<span
|
|
410
566
|
class="kv-add"
|
|
411
|
-
@click=${() =>
|
|
567
|
+
@click=${() =>
|
|
568
|
+
transactDoc(activeTab.value, (t) =>
|
|
569
|
+
mutateUpdateProperty(t, path, "sort", { $ref: "#/state/" }),
|
|
570
|
+
)}
|
|
412
571
|
>+ Add sort</span
|
|
413
572
|
>`
|
|
414
573
|
: nothing}
|
|
@@ -418,7 +577,9 @@ function renderRepeaterFieldsTemplate(
|
|
|
418
577
|
<sp-action-button
|
|
419
578
|
size="s"
|
|
420
579
|
style="margin-top:8px;width:100%"
|
|
421
|
-
@click=${() =>
|
|
580
|
+
@click=${() => {
|
|
581
|
+
activeTab.value.session.selection = [...path, "map"];
|
|
582
|
+
}}
|
|
422
583
|
>Edit template →</sp-action-button
|
|
423
584
|
>
|
|
424
585
|
`
|
|
@@ -438,7 +599,8 @@ function renderSwitchFieldsTemplate(
|
|
|
438
599
|
"Expression",
|
|
439
600
|
"text",
|
|
440
601
|
node.$switch,
|
|
441
|
-
(/** @type {any} */ v) =>
|
|
602
|
+
(/** @type {any} */ v) =>
|
|
603
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
|
|
442
604
|
null,
|
|
443
605
|
mapSignals,
|
|
444
606
|
)}
|
|
@@ -460,7 +622,9 @@ function renderSwitchFieldsTemplate(
|
|
|
460
622
|
clearTimeout(debounce);
|
|
461
623
|
debounce = setTimeout(() => {
|
|
462
624
|
if (e.target.value && e.target.value !== caseName)
|
|
463
|
-
|
|
625
|
+
transactDoc(activeTab.value, (t) =>
|
|
626
|
+
mutateRenameSwitchCase(t, path, caseName, e.target.value),
|
|
627
|
+
);
|
|
464
628
|
}, 500);
|
|
465
629
|
}}
|
|
466
630
|
/>
|
|
@@ -470,7 +634,7 @@ function renderSwitchFieldsTemplate(
|
|
|
470
634
|
style="cursor:pointer"
|
|
471
635
|
@click=${(/** @type {any} */ e) => {
|
|
472
636
|
e.stopPropagation();
|
|
473
|
-
|
|
637
|
+
activeTab.value.session.selection = [...path, "cases", caseName];
|
|
474
638
|
}}
|
|
475
639
|
>→</span
|
|
476
640
|
>
|
|
@@ -478,7 +642,7 @@ function renderSwitchFieldsTemplate(
|
|
|
478
642
|
style="cursor:pointer;color:var(--danger);font-size:11px"
|
|
479
643
|
@click=${(/** @type {any} */ e) => {
|
|
480
644
|
e.stopPropagation();
|
|
481
|
-
|
|
645
|
+
transactDoc(activeTab.value, (t) => mutateRemoveSwitchCase(t, path, caseName));
|
|
482
646
|
}}
|
|
483
647
|
>✕</span
|
|
484
648
|
>
|
|
@@ -488,7 +652,9 @@ function renderSwitchFieldsTemplate(
|
|
|
488
652
|
<span
|
|
489
653
|
class="kv-add"
|
|
490
654
|
@click=${() => {
|
|
491
|
-
|
|
655
|
+
transactDoc(activeTab.value, (t) =>
|
|
656
|
+
mutateAddSwitchCase(t, path, `case${caseNames.length + 1}`),
|
|
657
|
+
);
|
|
492
658
|
}}
|
|
493
659
|
>+ Add case</span
|
|
494
660
|
>
|
|
@@ -502,18 +668,20 @@ function renderComponentPropsFieldsTemplate(
|
|
|
502
668
|
/** @type {any} */ mapSignals,
|
|
503
669
|
/** @type {(path: string) => void} */ navigateToComponent,
|
|
504
670
|
) {
|
|
505
|
-
const
|
|
671
|
+
const tab = activeTab.value;
|
|
506
672
|
const comp = componentRegistry.find((c) => c.tagName === node.tagName);
|
|
507
|
-
if (!comp) return html`<div class="empty-state">Component not found</div>`;
|
|
673
|
+
if (!comp || !comp.props) return html`<div class="empty-state">Component not found</div>`;
|
|
508
674
|
const isNpm = comp.source === "npm";
|
|
509
675
|
const currentVals = isNpm ? node.attributes || {} : node.$props || {};
|
|
510
676
|
const updateFn = isNpm
|
|
511
677
|
? (/** @type {string} */ name, /** @type {any} */ v) =>
|
|
512
|
-
|
|
678
|
+
transactDoc(activeTab.value, (t) =>
|
|
679
|
+
mutateUpdateAttribute(t, path, name, v === "" ? undefined : v),
|
|
680
|
+
)
|
|
513
681
|
: (/** @type {string} */ name, /** @type {any} */ v) =>
|
|
514
|
-
|
|
682
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
|
|
515
683
|
|
|
516
|
-
const defs =
|
|
684
|
+
const defs = tab.doc.document.state || {};
|
|
517
685
|
const signalDefs = Object.entries(defs).filter(
|
|
518
686
|
([, d]) => !d.$handler && d.$prototype !== "Function",
|
|
519
687
|
);
|
|
@@ -583,7 +751,21 @@ function renderComponentPropsFieldsTemplate(
|
|
|
583
751
|
const staticVal = isBound ? "" : (rawValue ?? "");
|
|
584
752
|
/** @type {any} */
|
|
585
753
|
let widgetTpl;
|
|
586
|
-
if (
|
|
754
|
+
if (prop.format === "image") {
|
|
755
|
+
widgetTpl = renderMediaPicker(prop.name, staticVal, onChange);
|
|
756
|
+
} else if (prop.format === "color") {
|
|
757
|
+
widgetTpl = renderColorSelector(prop.name, staticVal, onChange);
|
|
758
|
+
} else if (prop.format === "date") {
|
|
759
|
+
widgetTpl = html`<sp-textfield
|
|
760
|
+
size="s"
|
|
761
|
+
placeholder="YYYY-MM-DD"
|
|
762
|
+
value=${staticVal}
|
|
763
|
+
@input=${(/** @type {any} */ e) => {
|
|
764
|
+
clearTimeout(debounce);
|
|
765
|
+
debounce = setTimeout(() => onChange(e.target.value), 400);
|
|
766
|
+
}}
|
|
767
|
+
></sp-textfield>`;
|
|
768
|
+
} else if (parsed.kind === "boolean") {
|
|
587
769
|
widgetTpl = html`<sp-checkbox
|
|
588
770
|
size="s"
|
|
589
771
|
.checked=${live(!!staticVal)}
|
|
@@ -662,19 +844,22 @@ function renderCustomAttrsFieldsTemplate(
|
|
|
662
844
|
attr,
|
|
663
845
|
String(val),
|
|
664
846
|
(/** @type {any} */ newAttr, /** @type {any} */ newVal) => {
|
|
665
|
-
const S = getState();
|
|
666
847
|
if (newAttr !== attr) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
848
|
+
transactDoc(activeTab.value, (t) => {
|
|
849
|
+
mutateUpdateAttribute(t, path, attr, undefined);
|
|
850
|
+
mutateUpdateAttribute(t, path, newAttr, newVal);
|
|
851
|
+
});
|
|
670
852
|
} else {
|
|
671
|
-
|
|
853
|
+
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, newVal));
|
|
672
854
|
}
|
|
673
855
|
},
|
|
674
|
-
() =>
|
|
856
|
+
() => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
|
|
675
857
|
),
|
|
676
858
|
)}
|
|
677
|
-
<span
|
|
859
|
+
<span
|
|
860
|
+
class="kv-add"
|
|
861
|
+
@click=${() =>
|
|
862
|
+
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, "data-", ""))}
|
|
678
863
|
>+ Add attribute</span
|
|
679
864
|
>
|
|
680
865
|
`;
|
|
@@ -701,12 +886,15 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
701
886
|
clearTimeout(baseDebounce);
|
|
702
887
|
baseDebounce = setTimeout(() => {
|
|
703
888
|
const val = e.target.value.trim();
|
|
704
|
-
|
|
889
|
+
transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--", val || undefined));
|
|
705
890
|
}, 400);
|
|
706
891
|
}}
|
|
707
892
|
/>
|
|
708
893
|
${media["--"]
|
|
709
|
-
? html`<span
|
|
894
|
+
? html`<span
|
|
895
|
+
class="kv-del"
|
|
896
|
+
@click=${() =>
|
|
897
|
+
transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, "--", undefined))}
|
|
710
898
|
>✕</span
|
|
711
899
|
>`
|
|
712
900
|
: nothing}
|
|
@@ -757,7 +945,7 @@ function renderMediaFieldsTemplate(/** @type {any} */ node) {
|
|
|
757
945
|
if (key && queryVal) {
|
|
758
946
|
view.showAddBreakpointForm = false;
|
|
759
947
|
view.addBreakpointPreview = "";
|
|
760
|
-
|
|
948
|
+
transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, key, queryVal));
|
|
761
949
|
}
|
|
762
950
|
}}
|
|
763
951
|
>
|
|
@@ -805,10 +993,10 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
805
993
|
const queryEl = e.target
|
|
806
994
|
.closest("div[style]")
|
|
807
995
|
?.parentElement?.querySelector(".bp-query-input");
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
996
|
+
transactDoc(activeTab.value, (t) => {
|
|
997
|
+
mutateUpdateMedia(t, name, undefined);
|
|
998
|
+
mutateUpdateMedia(t, newKey, queryEl?.value || query);
|
|
999
|
+
});
|
|
812
1000
|
}
|
|
813
1001
|
}, 600);
|
|
814
1002
|
}}
|
|
@@ -818,7 +1006,9 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
818
1006
|
style="font-size:10px;color:var(--fg-dim);font-family:'SF Mono','Fira Code',monospace;white-space:nowrap"
|
|
819
1007
|
>${name}</span
|
|
820
1008
|
>
|
|
821
|
-
<span
|
|
1009
|
+
<span
|
|
1010
|
+
class="kv-del"
|
|
1011
|
+
@click=${() => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, name, undefined))}
|
|
822
1012
|
>✕</span
|
|
823
1013
|
>
|
|
824
1014
|
</div>
|
|
@@ -830,7 +1020,7 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
830
1020
|
@input=${(/** @type {any} */ e) => {
|
|
831
1021
|
clearTimeout(debounceTimer);
|
|
832
1022
|
debounceTimer = setTimeout(
|
|
833
|
-
() =>
|
|
1023
|
+
() => transactDoc(activeTab.value, (t) => mutateUpdateMedia(t, name, e.target.value)),
|
|
834
1024
|
400,
|
|
835
1025
|
);
|
|
836
1026
|
}}
|
|
@@ -840,6 +1030,153 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
840
1030
|
`;
|
|
841
1031
|
}
|
|
842
1032
|
|
|
1033
|
+
// ─── Layout picker ──────────────────────────────────────────────────────────
|
|
1034
|
+
|
|
1035
|
+
/** @type {{ name: string; path: string }[] | null} */
|
|
1036
|
+
let layoutEntries = null;
|
|
1037
|
+
|
|
1038
|
+
async function loadLayoutEntries() {
|
|
1039
|
+
try {
|
|
1040
|
+
const platform = getPlatform();
|
|
1041
|
+
const listing = await platform.listDirectory("layouts");
|
|
1042
|
+
layoutEntries = listing
|
|
1043
|
+
.filter((/** @type {any} */ f) => f.type === "file" && f.name.endsWith(".json"))
|
|
1044
|
+
.map((/** @type {any} */ f) => ({
|
|
1045
|
+
name: f.name.replace(/\.json$/, ""),
|
|
1046
|
+
path: `./layouts/${f.name}`,
|
|
1047
|
+
}));
|
|
1048
|
+
} catch {
|
|
1049
|
+
layoutEntries = [];
|
|
1050
|
+
}
|
|
1051
|
+
renderOnly("rightPanel");
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export function invalidateLayoutPickerCache() {
|
|
1055
|
+
layoutEntries = null;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function isPageDocument(/** @type {any} */ documentPath) {
|
|
1059
|
+
if (!documentPath || !projectState?.isSiteProject) return false;
|
|
1060
|
+
return documentPath.startsWith("pages/") || documentPath.startsWith("./pages/");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function renderPageSection(/** @type {any} */ node) {
|
|
1064
|
+
const tab = activeTab.value;
|
|
1065
|
+
if (!isPageDocument(tab.documentPath)) return nothing;
|
|
1066
|
+
|
|
1067
|
+
if (layoutEntries === null) {
|
|
1068
|
+
loadLayoutEntries();
|
|
1069
|
+
return nothing;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const currentLayout = node.$layout;
|
|
1073
|
+
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|
|
1074
|
+
const effectivePath = getEffectiveLayoutPath(currentLayout);
|
|
1075
|
+
const displayValue =
|
|
1076
|
+
currentLayout === false ? "__none__" : currentLayout ? currentLayout : "__default__";
|
|
1077
|
+
|
|
1078
|
+
return html`
|
|
1079
|
+
<sp-accordion-item label="Page" open>
|
|
1080
|
+
<div class="style-section-body">
|
|
1081
|
+
<div class="style-row" data-prop="$layout">
|
|
1082
|
+
<div class="style-row-label">
|
|
1083
|
+
${currentLayout !== undefined
|
|
1084
|
+
? html`<span
|
|
1085
|
+
class="set-dot"
|
|
1086
|
+
title="Reset to default"
|
|
1087
|
+
@click=${(/** @type {any} */ e) => {
|
|
1088
|
+
e.stopPropagation();
|
|
1089
|
+
transactDoc(activeTab.value, (t) =>
|
|
1090
|
+
mutateUpdateProperty(t, [], "$layout", undefined),
|
|
1091
|
+
);
|
|
1092
|
+
}}
|
|
1093
|
+
></span>`
|
|
1094
|
+
: nothing}
|
|
1095
|
+
<sp-field-label size="s">Layout</sp-field-label>
|
|
1096
|
+
</div>
|
|
1097
|
+
<sp-picker
|
|
1098
|
+
size="s"
|
|
1099
|
+
value=${displayValue}
|
|
1100
|
+
@change=${(/** @type {any} */ e) => {
|
|
1101
|
+
const val = e.target.value;
|
|
1102
|
+
if (val === "__default__") {
|
|
1103
|
+
transactDoc(activeTab.value, (t) =>
|
|
1104
|
+
mutateUpdateProperty(t, [], "$layout", undefined),
|
|
1105
|
+
);
|
|
1106
|
+
} else if (val === "__none__") {
|
|
1107
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, [], "$layout", false));
|
|
1108
|
+
} else {
|
|
1109
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, [], "$layout", val));
|
|
1110
|
+
}
|
|
1111
|
+
invalidateLayoutCache();
|
|
1112
|
+
}}
|
|
1113
|
+
>
|
|
1114
|
+
<sp-menu-item value="__default__"
|
|
1115
|
+
>Default${defaultLayout
|
|
1116
|
+
? ` (${defaultLayout.replace(/^\.\/layouts\//, "").replace(/\.json$/, "")})`
|
|
1117
|
+
: ""}</sp-menu-item
|
|
1118
|
+
>
|
|
1119
|
+
<sp-menu-item value="__none__">None</sp-menu-item>
|
|
1120
|
+
<sp-menu-divider></sp-menu-divider>
|
|
1121
|
+
${layoutEntries.map(
|
|
1122
|
+
(/** @type {any} */ l) =>
|
|
1123
|
+
html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
|
|
1124
|
+
)}
|
|
1125
|
+
</sp-picker>
|
|
1126
|
+
</div>
|
|
1127
|
+
${effectivePath
|
|
1128
|
+
? html`<div style="font-size:10px;color:var(--fg-dim);padding:2px 0;font-style:italic">
|
|
1129
|
+
Wraps page content via <slot> distribution
|
|
1130
|
+
</div>`
|
|
1131
|
+
: nothing}
|
|
1132
|
+
</div>
|
|
1133
|
+
</sp-accordion-item>
|
|
1134
|
+
`;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// ─── Layout selection panel ─────────────────────────────────────────────────
|
|
1138
|
+
|
|
1139
|
+
function renderLayoutSelectionPanel(/** @type {any} */ ctx) {
|
|
1140
|
+
const { el, layoutPath } = view.layoutSelection;
|
|
1141
|
+
const tagName = el?.tagName?.toLowerCase() || "element";
|
|
1142
|
+
const className = el?.className || "";
|
|
1143
|
+
const displayPath = layoutPath || "layout";
|
|
1144
|
+
|
|
1145
|
+
return html`
|
|
1146
|
+
<div class="style-sidebar">
|
|
1147
|
+
<sp-accordion allow-multiple size="s">
|
|
1148
|
+
<sp-accordion-item label="Layout Element" open>
|
|
1149
|
+
<div class="style-section-body">
|
|
1150
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px">
|
|
1151
|
+
<span
|
|
1152
|
+
style="font-size:9px;padding:2px 6px;background:var(--spectrum-purple-600);color:white;border-radius:3px;text-transform:uppercase;letter-spacing:0.5px"
|
|
1153
|
+
>Layout</span
|
|
1154
|
+
>
|
|
1155
|
+
<code style="font-size:12px;font-family:monospace"><${tagName}></code>
|
|
1156
|
+
</div>
|
|
1157
|
+
${className
|
|
1158
|
+
? html`<div class="style-row">
|
|
1159
|
+
<div class="style-row-label">
|
|
1160
|
+
<sp-field-label size="s">Class</sp-field-label>
|
|
1161
|
+
</div>
|
|
1162
|
+
<span style="font-size:11px;color:var(--fg-dim);word-break:break-all"
|
|
1163
|
+
>${className}</span
|
|
1164
|
+
>
|
|
1165
|
+
</div>`
|
|
1166
|
+
: nothing}
|
|
1167
|
+
<div style="font-size:10px;color:var(--fg-dim);padding:4px 0;font-style:italic">
|
|
1168
|
+
This element is part of the page layout. Edit it by opening the layout file.
|
|
1169
|
+
</div>
|
|
1170
|
+
<span class="kv-add" @click=${() => ctx.navigateToComponent(displayPath)}
|
|
1171
|
+
>Open Layout →</span
|
|
1172
|
+
>
|
|
1173
|
+
</div>
|
|
1174
|
+
</sp-accordion-item>
|
|
1175
|
+
</sp-accordion>
|
|
1176
|
+
</div>
|
|
1177
|
+
`;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
843
1180
|
// ─── Main entry point ───────────────────────────────────────────────────────
|
|
844
1181
|
|
|
845
1182
|
/**
|
|
@@ -848,18 +1185,24 @@ function mediaBreakpointRowTemplate(/** @type {any} */ name, /** @type {any} */
|
|
|
848
1185
|
* @param {{ navigateToComponent: (path: string) => void }} ctx
|
|
849
1186
|
*/
|
|
850
1187
|
export function renderPropertiesPanelTemplate(ctx) {
|
|
851
|
-
const
|
|
1188
|
+
const tab = activeTab.value;
|
|
1189
|
+
if (!tab) return html`<div class="empty-state">No document loaded</div>`;
|
|
1190
|
+
|
|
1191
|
+
// Layout element selected — show read-only info with link to open layout
|
|
1192
|
+
if (view.layoutSelection) {
|
|
1193
|
+
return renderLayoutSelectionPanel(ctx);
|
|
1194
|
+
}
|
|
852
1195
|
|
|
853
|
-
if (!
|
|
854
|
-
if (
|
|
1196
|
+
if (!tab.session.selection) {
|
|
1197
|
+
if (tab.doc.mode === "content") {
|
|
855
1198
|
return renderFrontmatterOnlyPanel();
|
|
856
1199
|
}
|
|
857
1200
|
return html`<div class="empty-state">Select an element to inspect</div>`;
|
|
858
1201
|
}
|
|
859
|
-
const node = getNodeAtPath(
|
|
1202
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
860
1203
|
if (!node) return html`<div class="empty-state">Node not found</div>`;
|
|
861
1204
|
|
|
862
|
-
const path =
|
|
1205
|
+
const path = tab.session.selection;
|
|
863
1206
|
const isMapNode = node.$prototype === "Array";
|
|
864
1207
|
const isMapParent =
|
|
865
1208
|
node.children && typeof node.children === "object" && node.children.$prototype === "Array";
|
|
@@ -889,13 +1232,16 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
889
1232
|
prop: attr,
|
|
890
1233
|
label: attrLabel(entry, attr),
|
|
891
1234
|
hasValue: hasVal,
|
|
892
|
-
onClear: () =>
|
|
1235
|
+
onClear: () =>
|
|
1236
|
+
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
|
|
893
1237
|
widget: html`
|
|
894
1238
|
<sp-checkbox
|
|
895
1239
|
size="s"
|
|
896
1240
|
.checked=${live(!!value)}
|
|
897
1241
|
@change=${(/** @type {any} */ e) =>
|
|
898
|
-
|
|
1242
|
+
transactDoc(activeTab.value, (t) =>
|
|
1243
|
+
mutateUpdateAttribute(t, path, attr, e.target.checked || undefined),
|
|
1244
|
+
)}
|
|
899
1245
|
>
|
|
900
1246
|
</sp-checkbox>
|
|
901
1247
|
`,
|
|
@@ -906,9 +1252,10 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
906
1252
|
prop: attr,
|
|
907
1253
|
label: attrLabel(entry, attr),
|
|
908
1254
|
hasValue: hasVal,
|
|
909
|
-
onClear: () =>
|
|
1255
|
+
onClear: () =>
|
|
1256
|
+
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, undefined)),
|
|
910
1257
|
widget: widgetForType(type, entry, attr, value || "", (/** @type {any} */ v) =>
|
|
911
|
-
|
|
1258
|
+
transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
|
|
912
1259
|
),
|
|
913
1260
|
});
|
|
914
1261
|
}
|
|
@@ -936,7 +1283,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
936
1283
|
const knownAttrNames = new Set(Object.keys(applicableAttrs));
|
|
937
1284
|
if (isCustomInstance) {
|
|
938
1285
|
const comp = componentRegistry.find((c) => c.tagName === node.tagName);
|
|
939
|
-
if (comp) for (const p of comp.props) knownAttrNames.add(p.name);
|
|
1286
|
+
if (comp?.props) for (const p of comp.props) knownAttrNames.add(p.name);
|
|
940
1287
|
}
|
|
941
1288
|
const customAttrs = Object.entries(attrs).filter(([k]) => !knownAttrNames.has(k));
|
|
942
1289
|
|
|
@@ -948,13 +1295,17 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
948
1295
|
if (customAttrs.length > 0) autoOpen.add("__custom");
|
|
949
1296
|
|
|
950
1297
|
function isSectionOpen(/** @type {any} */ key) {
|
|
951
|
-
if (
|
|
1298
|
+
if (tab.session.ui.inspectorSections[key] !== undefined)
|
|
1299
|
+
return tab.session.ui.inspectorSections[key];
|
|
952
1300
|
return autoOpen.has(key);
|
|
953
1301
|
}
|
|
954
1302
|
|
|
955
1303
|
function toggleSection(/** @type {any} */ key) {
|
|
956
1304
|
const current = isSectionOpen(key);
|
|
957
|
-
|
|
1305
|
+
activeTab.value.session.ui.inspectorSections = {
|
|
1306
|
+
...activeTab.value.session.ui.inspectorSections,
|
|
1307
|
+
[key]: !current,
|
|
1308
|
+
};
|
|
958
1309
|
}
|
|
959
1310
|
|
|
960
1311
|
// ── Build section templates ─────────────────────────────────────────
|
|
@@ -976,7 +1327,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
976
1327
|
autocomplete="off"
|
|
977
1328
|
list="tag-names"
|
|
978
1329
|
@input=${debouncedStyleCommit("prop:tagName", 400, (/** @type {any} */ e) => {
|
|
979
|
-
|
|
1330
|
+
transactDoc(activeTab.value, (t) =>
|
|
1331
|
+
mutateUpdateProperty(t, path, "tagName", e.target.value || undefined),
|
|
1332
|
+
);
|
|
980
1333
|
})}
|
|
981
1334
|
></sp-textfield>
|
|
982
1335
|
</div>
|
|
@@ -988,7 +1341,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
988
1341
|
title="Clear $id"
|
|
989
1342
|
@click=${(/** @type {any} */ e) => {
|
|
990
1343
|
e.stopPropagation();
|
|
991
|
-
|
|
1344
|
+
transactDoc(activeTab.value, (t) =>
|
|
1345
|
+
mutateUpdateProperty(t, path, "$id", undefined),
|
|
1346
|
+
);
|
|
992
1347
|
}}
|
|
993
1348
|
></span>`
|
|
994
1349
|
: nothing}
|
|
@@ -998,7 +1353,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
998
1353
|
size="s"
|
|
999
1354
|
.value=${live(node.$id || "")}
|
|
1000
1355
|
@input=${debouncedStyleCommit("prop:$id", 400, (/** @type {any} */ e) => {
|
|
1001
|
-
|
|
1356
|
+
transactDoc(activeTab.value, (t) =>
|
|
1357
|
+
mutateUpdateProperty(t, path, "$id", e.target.value || undefined),
|
|
1358
|
+
);
|
|
1002
1359
|
})}
|
|
1003
1360
|
></sp-textfield>
|
|
1004
1361
|
</div>
|
|
@@ -1010,7 +1367,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1010
1367
|
title="Clear class"
|
|
1011
1368
|
@click=${(/** @type {any} */ e) => {
|
|
1012
1369
|
e.stopPropagation();
|
|
1013
|
-
|
|
1370
|
+
transactDoc(activeTab.value, (t) =>
|
|
1371
|
+
mutateUpdateProperty(t, path, "className", undefined),
|
|
1372
|
+
);
|
|
1014
1373
|
}}
|
|
1015
1374
|
></span>`
|
|
1016
1375
|
: nothing}
|
|
@@ -1020,7 +1379,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1020
1379
|
size="s"
|
|
1021
1380
|
.value=${live(node.className || "")}
|
|
1022
1381
|
@input=${debouncedStyleCommit("prop:className", 400, (/** @type {any} */ e) => {
|
|
1023
|
-
|
|
1382
|
+
transactDoc(activeTab.value, (t) =>
|
|
1383
|
+
mutateUpdateProperty(t, path, "className", e.target.value || undefined),
|
|
1384
|
+
);
|
|
1024
1385
|
})}
|
|
1025
1386
|
></sp-textfield>
|
|
1026
1387
|
</div>
|
|
@@ -1034,7 +1395,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1034
1395
|
title="Clear text"
|
|
1035
1396
|
@click=${(/** @type {any} */ e) => {
|
|
1036
1397
|
e.stopPropagation();
|
|
1037
|
-
|
|
1398
|
+
transactDoc(activeTab.value, (t) =>
|
|
1399
|
+
mutateUpdateProperty(t, path, "textContent", undefined),
|
|
1400
|
+
);
|
|
1038
1401
|
}}
|
|
1039
1402
|
></span>`
|
|
1040
1403
|
: nothing}
|
|
@@ -1049,8 +1412,8 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1049
1412
|
: (node.textContent ?? ""),
|
|
1050
1413
|
)}
|
|
1051
1414
|
@input=${debouncedStyleCommit("prop:textContent", 400, (/** @type {any} */ e) => {
|
|
1052
|
-
|
|
1053
|
-
|
|
1415
|
+
transactDoc(activeTab.value, (t) =>
|
|
1416
|
+
mutateUpdateProperty(t, path, "textContent", e.target.value || undefined),
|
|
1054
1417
|
);
|
|
1055
1418
|
})}
|
|
1056
1419
|
></sp-textfield>
|
|
@@ -1065,7 +1428,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1065
1428
|
title="Clear hidden"
|
|
1066
1429
|
@click=${(/** @type {any} */ e) => {
|
|
1067
1430
|
e.stopPropagation();
|
|
1068
|
-
|
|
1431
|
+
transactDoc(activeTab.value, (t) =>
|
|
1432
|
+
mutateUpdateProperty(t, path, "hidden", undefined),
|
|
1433
|
+
);
|
|
1069
1434
|
}}
|
|
1070
1435
|
></span>`
|
|
1071
1436
|
: nothing}
|
|
@@ -1075,7 +1440,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1075
1440
|
size="s"
|
|
1076
1441
|
.checked=${live(!!node.hidden)}
|
|
1077
1442
|
@change=${(/** @type {any} */ e) =>
|
|
1078
|
-
|
|
1443
|
+
transactDoc(activeTab.value, (t) =>
|
|
1444
|
+
mutateUpdateProperty(t, path, "hidden", e.target.checked || undefined),
|
|
1445
|
+
)}
|
|
1079
1446
|
>
|
|
1080
1447
|
</sp-checkbox>
|
|
1081
1448
|
</div>
|
|
@@ -1111,9 +1478,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1111
1478
|
: nothing;
|
|
1112
1479
|
|
|
1113
1480
|
const observedAttrsT =
|
|
1114
|
-
isCustomElementDoc(
|
|
1481
|
+
isCustomElementDoc({ document: tab.doc.document }) && isRoot
|
|
1115
1482
|
? (() => {
|
|
1116
|
-
const state =
|
|
1483
|
+
const state = tab.doc.document.state || {};
|
|
1117
1484
|
const entries = Object.entries(state).filter(([, d]) => d.attribute);
|
|
1118
1485
|
return html`
|
|
1119
1486
|
<sp-accordion-item label="Observed Attributes" ?open=${isSectionOpen("__observed")}>
|
|
@@ -1216,7 +1583,7 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1216
1583
|
: nothing;
|
|
1217
1584
|
|
|
1218
1585
|
const cssPropsT =
|
|
1219
|
-
isCustomElementDoc(
|
|
1586
|
+
isCustomElementDoc({ document: tab.doc.document }) && isRoot
|
|
1220
1587
|
? (() => {
|
|
1221
1588
|
const style = node.style || {};
|
|
1222
1589
|
const cssProps = Object.entries(style).filter(([k]) => k.startsWith("--"));
|
|
@@ -1245,9 +1612,9 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1245
1612
|
: nothing;
|
|
1246
1613
|
|
|
1247
1614
|
const cssPartsT =
|
|
1248
|
-
isCustomElementDoc(
|
|
1615
|
+
isCustomElementDoc({ document: tab.doc.document }) && isRoot
|
|
1249
1616
|
? (() => {
|
|
1250
|
-
const parts = collectCssParts(
|
|
1617
|
+
const parts = collectCssParts(tab.doc.document);
|
|
1251
1618
|
if (parts.length === 0) return nothing;
|
|
1252
1619
|
return html`
|
|
1253
1620
|
<sp-accordion-item
|
|
@@ -1273,10 +1640,10 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1273
1640
|
: nothing;
|
|
1274
1641
|
|
|
1275
1642
|
const frontmatterT =
|
|
1276
|
-
|
|
1643
|
+
tab.doc.mode === "content"
|
|
1277
1644
|
? (() => {
|
|
1278
|
-
const fm =
|
|
1279
|
-
const col = findContentTypeSchema(
|
|
1645
|
+
const fm = tab.doc.content?.frontmatter || {};
|
|
1646
|
+
const col = findContentTypeSchema(tab.documentPath, projectState?.projectConfig);
|
|
1280
1647
|
const schemaProps = col?.schema?.properties;
|
|
1281
1648
|
const requiredFields = new Set(col?.schema?.required || []);
|
|
1282
1649
|
|
|
@@ -1323,15 +1690,17 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1323
1690
|
})()
|
|
1324
1691
|
: nothing;
|
|
1325
1692
|
|
|
1693
|
+
const pageT = isRoot ? renderPageSection(node) : nothing;
|
|
1694
|
+
|
|
1326
1695
|
// ── Assemble ──
|
|
1327
1696
|
const tpl = html`
|
|
1328
1697
|
<div class="style-sidebar">
|
|
1329
1698
|
<sp-accordion allow-multiple size="s">
|
|
1330
|
-
${
|
|
1331
|
-
${isMapNode ? nothing :
|
|
1332
|
-
${isMapNode ? nothing :
|
|
1333
|
-
${isMapNode ? nothing :
|
|
1334
|
-
${isMapNode ? nothing : cssPartsT}
|
|
1699
|
+
${pageT} ${frontmatterT} ${isMapNode ? repeaterT : elemT}
|
|
1700
|
+
${isMapNode ? nothing : observedAttrsT} ${isMapNode ? nothing : switchT}
|
|
1701
|
+
${isMapNode ? nothing : compPropsT} ${isMapNode ? nothing : attrSectionTemplates}
|
|
1702
|
+
${isMapNode ? nothing : customSectionT} ${isMapNode ? nothing : mediaT}
|
|
1703
|
+
${isMapNode ? nothing : cssPropsT} ${isMapNode ? nothing : cssPartsT}
|
|
1335
1704
|
</sp-accordion>
|
|
1336
1705
|
</div>
|
|
1337
1706
|
`;
|