@jxsuite/studio 0.17.0 → 0.18.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.js +1678 -1512
- package/dist/studio.js.map +20 -16
- package/package.json +2 -2
- package/src/canvas/canvas-render.js +2 -2
- package/src/canvas/canvas-utils.js +1 -1
- package/src/files/files.js +13 -1
- package/src/panels/activity-bar.js +12 -0
- package/src/panels/left-panel.js +2 -2
- package/src/panels/overlays.js +2 -2
- package/src/panels/style-panel.js +1 -1
- package/src/panels/stylebook-panel.js +30 -529
- package/src/panels/toolbar.js +7 -2
- package/src/settings/css-vars-editor.js +307 -0
- package/src/settings/general-settings.js +94 -0
- package/src/settings/head-editor.js +184 -0
- package/src/settings/settings-modal.js +117 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stylebook panel — renders the
|
|
3
|
-
*
|
|
2
|
+
* Stylebook panel — renders the Stylebook mode canvas (element catalog with per-file style
|
|
3
|
+
* defaults). Extracted from studio.js Phase 4e.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
7
7
|
import { ref } from "lit-html/directives/ref.js";
|
|
8
|
-
import { styleMap } from "lit-html/directives/style-map.js";
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
updateSession,
|
|
@@ -16,16 +15,12 @@ import {
|
|
|
16
15
|
projectState,
|
|
17
16
|
} from "../store.js";
|
|
18
17
|
import { activeTab } from "../workspace/workspace.js";
|
|
19
|
-
import { transactDoc, mutateUpdateStyle } from "../tabs/transact.js";
|
|
20
18
|
import { view } from "../view.js";
|
|
21
19
|
import { defineElement, setSkipServerFunctions } from "@jxsuite/runtime";
|
|
22
20
|
import { componentRegistry } from "../files/components.js";
|
|
23
21
|
import { getEffectiveStyle, getEffectiveMedia } from "../site-context.js";
|
|
24
22
|
import { parseMediaEntries, activeBreakpointsForWidth } from "../utils/canvas-media.js";
|
|
25
23
|
import { mediaDisplayName } from "./shared.js";
|
|
26
|
-
import { friendlyNameToVar, varDisplayName } from "../utils/studio-utils.js";
|
|
27
|
-
import { renderDefsEditor } from "../settings/defs-editor.js";
|
|
28
|
-
import { renderContentTypesEditor } from "../settings/content-types-editor.js";
|
|
29
24
|
import stylebookMeta from "../../data/stylebook-meta.json";
|
|
30
25
|
|
|
31
26
|
export { stylebookMeta };
|
|
@@ -49,48 +44,7 @@ let _ctx = null;
|
|
|
49
44
|
export function renderStylebookMode(ctx) {
|
|
50
45
|
_ctx = ctx;
|
|
51
46
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const settingsChromeBarTpl = html`
|
|
55
|
-
<div
|
|
56
|
-
class="sb-chrome settings-top-chrome"
|
|
57
|
-
style="position:absolute;top:0;left:0;right:0;z-index:16;background:var(--bg-panel);border-bottom:1px solid var(--border)"
|
|
58
|
-
>
|
|
59
|
-
<sp-tabs
|
|
60
|
-
size="s"
|
|
61
|
-
selected=${settingsTab}
|
|
62
|
-
@change=${(/** @type {any} */ e) => {
|
|
63
|
-
updateUi("settingsTab", e.target.selected);
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
<sp-tab label="Stylebook" value="stylebook"></sp-tab>
|
|
67
|
-
<sp-tab label="Definitions" value="definitions"></sp-tab>
|
|
68
|
-
<sp-tab label="Content Types" value="contentTypes"></sp-tab>
|
|
69
|
-
</sp-tabs>
|
|
70
|
-
</div>
|
|
71
|
-
`;
|
|
72
|
-
|
|
73
|
-
if (settingsTab === "definitions" || settingsTab === "contentTypes") {
|
|
74
|
-
/** @type {any} */ (canvasWrap).style.overflow = "hidden";
|
|
75
|
-
|
|
76
|
-
litRender(
|
|
77
|
-
html`${settingsChromeBarTpl}
|
|
78
|
-
<div
|
|
79
|
-
class="settings-editor-container"
|
|
80
|
-
style="position:absolute;inset:40px 0 0 0;overflow:auto"
|
|
81
|
-
></div>`,
|
|
82
|
-
/** @type {any} */ (canvasWrap),
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const container = /** @type {HTMLElement} */ (
|
|
86
|
-
canvasWrap.querySelector(".settings-editor-container")
|
|
87
|
-
);
|
|
88
|
-
if (settingsTab === "definitions") renderDefsEditor(container);
|
|
89
|
-
else renderContentTypesEditor(container);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Stylebook tab — element catalog / variables
|
|
47
|
+
// Stylebook mode — element catalog only
|
|
94
48
|
view.stylebookElToTag = new WeakMap();
|
|
95
49
|
const tab = activeTab.value;
|
|
96
50
|
const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
|
|
@@ -102,10 +56,6 @@ export function renderStylebookMode(ctx) {
|
|
|
102
56
|
);
|
|
103
57
|
const hasMedia = sizeBreakpoints.length > 0;
|
|
104
58
|
|
|
105
|
-
const onTabClick = (/** @type {string} */ t) => {
|
|
106
|
-
updateUi("stylebookTab", t);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
59
|
const onFilterInput = (/** @type {any} */ e) => {
|
|
110
60
|
updateUi("stylebookFilter", e.target.value);
|
|
111
61
|
};
|
|
@@ -115,42 +65,25 @@ export function renderStylebookMode(ctx) {
|
|
|
115
65
|
};
|
|
116
66
|
|
|
117
67
|
const chromeBarTpl = html`
|
|
118
|
-
${settingsChromeBarTpl}
|
|
119
68
|
<div
|
|
120
69
|
class="sb-chrome"
|
|
121
|
-
style="position:absolute;top:
|
|
70
|
+
style="position:absolute;top:0;left:0;right:0;z-index:15;background:var(--bg-panel);border-bottom:1px solid var(--border)"
|
|
122
71
|
>
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<input
|
|
139
|
-
class="field-input"
|
|
140
|
-
style="flex:1;max-width:200px;margin-left:8px"
|
|
141
|
-
placeholder="Filter…"
|
|
142
|
-
.value=${tab?.session.ui.stylebookFilter}
|
|
143
|
-
@input=${onFilterInput}
|
|
144
|
-
/>
|
|
145
|
-
<button
|
|
146
|
-
class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
|
|
147
|
-
style="margin-left:4px"
|
|
148
|
-
@click=${onCustomizedToggle}
|
|
149
|
-
>
|
|
150
|
-
Customized
|
|
151
|
-
</button>
|
|
152
|
-
`
|
|
153
|
-
: nothing}
|
|
72
|
+
<div style="display:flex;align-items:center;padding:4px 8px;gap:4px">
|
|
73
|
+
<input
|
|
74
|
+
class="field-input"
|
|
75
|
+
style="flex:1;max-width:200px"
|
|
76
|
+
placeholder="Filter…"
|
|
77
|
+
.value=${tab?.session.ui.stylebookFilter}
|
|
78
|
+
@input=${onFilterInput}
|
|
79
|
+
/>
|
|
80
|
+
<button
|
|
81
|
+
class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
|
|
82
|
+
@click=${onCustomizedToggle}
|
|
83
|
+
>
|
|
84
|
+
Customized
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
154
87
|
</div>
|
|
155
88
|
`;
|
|
156
89
|
|
|
@@ -177,22 +110,17 @@ export function renderStylebookMode(ctx) {
|
|
|
177
110
|
|
|
178
111
|
const renderIntoPanel = (/** @type {any} */ panel, /** @type {any} */ activeBreakpoints) => {
|
|
179
112
|
panel.canvas.classList.add("sb-canvas");
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
child.style.pointerEvents = "none";
|
|
190
|
-
}
|
|
191
|
-
registerStylebookPanelEvents(panel);
|
|
192
|
-
} else {
|
|
193
|
-
renderStylebookVarsIntoCanvas(panel.canvas, rootStyle);
|
|
194
|
-
panel.overlayClk.style.pointerEvents = "none";
|
|
113
|
+
renderStylebookElementsIntoCanvas(
|
|
114
|
+
panel.canvas,
|
|
115
|
+
rootStyle,
|
|
116
|
+
filter,
|
|
117
|
+
customizedOnly,
|
|
118
|
+
activeBreakpoints,
|
|
119
|
+
);
|
|
120
|
+
for (const child of panel.canvas.querySelectorAll("*")) {
|
|
121
|
+
child.style.pointerEvents = "none";
|
|
195
122
|
}
|
|
123
|
+
registerStylebookPanelEvents(panel);
|
|
196
124
|
};
|
|
197
125
|
|
|
198
126
|
/** @type {{ tpl: any; panel: any; activeSet: any }[]} */
|
|
@@ -221,7 +149,7 @@ export function renderStylebookMode(ctx) {
|
|
|
221
149
|
${chromeBarTpl}
|
|
222
150
|
<div
|
|
223
151
|
class="panzoom-wrap"
|
|
224
|
-
style="transform-origin:0 0;padding-top:
|
|
152
|
+
style="transform-origin:0 0;padding-top:40px"
|
|
225
153
|
${ref((el) => {
|
|
226
154
|
if (el) view.panzoomWrap = /** @type {HTMLDivElement} */ (el);
|
|
227
155
|
})}
|
|
@@ -584,433 +512,6 @@ export function renderStylebookElementsIntoCanvas(
|
|
|
584
512
|
}
|
|
585
513
|
}
|
|
586
514
|
|
|
587
|
-
/**
|
|
588
|
-
* Render variables into the canvas (card-based layout matching Elements tab)
|
|
589
|
-
*
|
|
590
|
-
* @param {any} canvasEl
|
|
591
|
-
* @param {any} rootStyle
|
|
592
|
-
*/
|
|
593
|
-
function renderStylebookVarsIntoCanvas(canvasEl, rootStyle) {
|
|
594
|
-
const varCats = stylebookMeta.$variables;
|
|
595
|
-
|
|
596
|
-
/** @type {Record<string, any>} */
|
|
597
|
-
const groups = {};
|
|
598
|
-
for (const key of Object.keys(varCats)) groups[key] = [];
|
|
599
|
-
for (const [k, v] of Object.entries(rootStyle)) {
|
|
600
|
-
if (!k.startsWith("--")) continue;
|
|
601
|
-
if (typeof v !== "string" && typeof v !== "number") continue;
|
|
602
|
-
if (k.startsWith("--color")) groups.color.push([k, v]);
|
|
603
|
-
else if (k.startsWith("--font")) groups.font.push([k, v]);
|
|
604
|
-
else if (k.startsWith("--size") || k.startsWith("--spacing") || k.startsWith("--radius"))
|
|
605
|
-
groups.size.push([k, v]);
|
|
606
|
-
else groups.other.push([k, v]);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/** @type {Map<string, HTMLElement | null>} */
|
|
610
|
-
const bodyRefs = new Map();
|
|
611
|
-
|
|
612
|
-
const sectionTemplates = Object.entries(varCats).map(([catKey, catMeta]) => {
|
|
613
|
-
const vars = groups[catKey];
|
|
614
|
-
|
|
615
|
-
const onAdd = () => {
|
|
616
|
-
const bodyEl = bodyRefs.get(catKey);
|
|
617
|
-
if (!bodyEl) return;
|
|
618
|
-
const addBtn = bodyEl.querySelector(".sb-var-add-btn");
|
|
619
|
-
const row = renderVarRow(catKey, /** @type {any} */ (catMeta), null, "", true);
|
|
620
|
-
bodyEl.insertBefore(row, addBtn);
|
|
621
|
-
if (addBtn) /** @type {any} */ (addBtn).style.display = "none";
|
|
622
|
-
const nameField = /** @type {any} */ (row.querySelector("sp-textfield"));
|
|
623
|
-
if (nameField) requestAnimationFrame(() => nameField.focus());
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
return html`
|
|
627
|
-
<div class="sb-section">
|
|
628
|
-
<div class="sb-label">${/** @type {any} */ (catMeta).label}</div>
|
|
629
|
-
<div
|
|
630
|
-
class="sb-body"
|
|
631
|
-
${ref((el) => {
|
|
632
|
-
if (el) bodyRefs.set(catKey, /** @type {HTMLElement} */ (el));
|
|
633
|
-
})}
|
|
634
|
-
>
|
|
635
|
-
${vars.length > 0
|
|
636
|
-
? vars.map((/** @type {[string, any]} */ [varName, varVal]) =>
|
|
637
|
-
renderVarRow(catKey, /** @type {any} */ (catMeta), varName, String(varVal), false),
|
|
638
|
-
)
|
|
639
|
-
: html`<div class="sb-var-empty">
|
|
640
|
-
No ${/** @type {any} */ (catMeta).label.toLowerCase()} variables yet.
|
|
641
|
-
</div>`}
|
|
642
|
-
<button class="sb-var-add-btn" @click=${onAdd}>
|
|
643
|
-
<span class="sb-var-add-icon">+</span> Add ${/** @type {any} */ (catMeta).label}
|
|
644
|
-
</button>
|
|
645
|
-
</div>
|
|
646
|
-
</div>
|
|
647
|
-
`;
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
litRender(html`${sectionTemplates}`, canvasEl);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Render a single variable row — used for both existing and add-new.
|
|
655
|
-
*
|
|
656
|
-
* @param {string} catKey
|
|
657
|
-
* @param {any} catMeta
|
|
658
|
-
* @param {string | null} varName
|
|
659
|
-
* @param {string} varVal
|
|
660
|
-
* @param {boolean} isNew
|
|
661
|
-
*/
|
|
662
|
-
function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
|
|
663
|
-
const row = document.createElement("div");
|
|
664
|
-
row.className = isNew ? "sb-var-row is-new" : "sb-var-row";
|
|
665
|
-
|
|
666
|
-
/** @type {any} */
|
|
667
|
-
let colorPicker = null;
|
|
668
|
-
/** @type {any} */
|
|
669
|
-
let nameField = null;
|
|
670
|
-
/** @type {any} */
|
|
671
|
-
let getValueFn;
|
|
672
|
-
/** @type {any} */
|
|
673
|
-
let hexField = null;
|
|
674
|
-
|
|
675
|
-
const swatchTpl =
|
|
676
|
-
catKey === "color"
|
|
677
|
-
? html`
|
|
678
|
-
<div
|
|
679
|
-
class="sb-var-swatch"
|
|
680
|
-
style=${styleMap({ backgroundColor: varVal || "var(--accent)" })}
|
|
681
|
-
>
|
|
682
|
-
<input
|
|
683
|
-
type="color"
|
|
684
|
-
.value=${varVal && varVal.startsWith("#") ? varVal : "#007acc"}
|
|
685
|
-
${ref((el) => {
|
|
686
|
-
if (el) colorPicker = el;
|
|
687
|
-
})}
|
|
688
|
-
@input=${() => {
|
|
689
|
-
if (!colorPicker || !hexField) return;
|
|
690
|
-
hexField.value = colorPicker.value;
|
|
691
|
-
const swatch = /** @type {any} */ (row.querySelector(".sb-var-swatch"));
|
|
692
|
-
if (swatch) swatch.style.backgroundColor = colorPicker.value;
|
|
693
|
-
if (!isNew && varName) {
|
|
694
|
-
transactDoc(activeTab.value, (t) =>
|
|
695
|
-
mutateUpdateStyle(t, [], varName, colorPicker.value),
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
}}
|
|
699
|
-
/>
|
|
700
|
-
</div>
|
|
701
|
-
`
|
|
702
|
-
: nothing;
|
|
703
|
-
|
|
704
|
-
const namePlaceholder =
|
|
705
|
-
catKey === "color"
|
|
706
|
-
? "Primary Blue"
|
|
707
|
-
: catKey === "font"
|
|
708
|
-
? "Body Serif"
|
|
709
|
-
: catKey === "size"
|
|
710
|
-
? "Spacing Large"
|
|
711
|
-
: "Border Radius";
|
|
712
|
-
|
|
713
|
-
const nameColTpl = isNew
|
|
714
|
-
? html`
|
|
715
|
-
<div class="sb-var-col-name">
|
|
716
|
-
<div class="sb-var-col-label">Name</div>
|
|
717
|
-
<sp-textfield
|
|
718
|
-
size="s"
|
|
719
|
-
placeholder=${namePlaceholder}
|
|
720
|
-
style="pointer-events:auto"
|
|
721
|
-
${ref((el) => {
|
|
722
|
-
if (el) nameField = el;
|
|
723
|
-
})}
|
|
724
|
-
></sp-textfield>
|
|
725
|
-
</div>
|
|
726
|
-
`
|
|
727
|
-
: nothing;
|
|
728
|
-
|
|
729
|
-
/** @type {any} */
|
|
730
|
-
let valueContent;
|
|
731
|
-
|
|
732
|
-
if (catKey === "color") {
|
|
733
|
-
/** @type {any} */
|
|
734
|
-
let debounce;
|
|
735
|
-
valueContent = html`
|
|
736
|
-
<sp-textfield
|
|
737
|
-
size="s"
|
|
738
|
-
.value=${varVal || "#007acc"}
|
|
739
|
-
placeholder="#007acc"
|
|
740
|
-
style="pointer-events:auto"
|
|
741
|
-
${ref((el) => {
|
|
742
|
-
if (el) hexField = el;
|
|
743
|
-
})}
|
|
744
|
-
@input=${() => {
|
|
745
|
-
clearTimeout(debounce);
|
|
746
|
-
debounce = setTimeout(() => {
|
|
747
|
-
if (!hexField) return;
|
|
748
|
-
const v = hexField.value;
|
|
749
|
-
try {
|
|
750
|
-
if (colorPicker) colorPicker.value = v.startsWith("#") ? v : colorPicker.value;
|
|
751
|
-
} catch {}
|
|
752
|
-
const swatch = /** @type {any} */ (row.querySelector(".sb-var-swatch"));
|
|
753
|
-
if (swatch) swatch.style.backgroundColor = v;
|
|
754
|
-
if (!isNew && varName) {
|
|
755
|
-
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
|
|
756
|
-
}
|
|
757
|
-
}, 400);
|
|
758
|
-
}}
|
|
759
|
-
></sp-textfield>
|
|
760
|
-
`;
|
|
761
|
-
getValueFn = () => hexField?.value?.trim() || "";
|
|
762
|
-
} else if (catKey === "size") {
|
|
763
|
-
const ui = createUnitInput(varVal || "16px", {
|
|
764
|
-
onChange: (/** @type {any} */ newVal) => {
|
|
765
|
-
const bar = /** @type {any} */ (row.querySelector(".sb-var-size-bar"));
|
|
766
|
-
if (bar) bar.style.width = newVal;
|
|
767
|
-
if (!isNew && varName) {
|
|
768
|
-
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, newVal));
|
|
769
|
-
}
|
|
770
|
-
},
|
|
771
|
-
});
|
|
772
|
-
if (isNew) ui.textfield.value = "";
|
|
773
|
-
valueContent = html`<div
|
|
774
|
-
${ref((el) => {
|
|
775
|
-
if (el && !el.firstChild) el.appendChild(ui.wrap);
|
|
776
|
-
})}
|
|
777
|
-
></div>`;
|
|
778
|
-
getValueFn = () => ui.getValue();
|
|
779
|
-
} else {
|
|
780
|
-
/** @type {any} */
|
|
781
|
-
let textFieldEl = null;
|
|
782
|
-
/** @type {any} */
|
|
783
|
-
let debounce;
|
|
784
|
-
valueContent = html`
|
|
785
|
-
<sp-textfield
|
|
786
|
-
size="s"
|
|
787
|
-
.value=${varVal}
|
|
788
|
-
placeholder=${catMeta.placeholder}
|
|
789
|
-
style="pointer-events:auto"
|
|
790
|
-
${ref((el) => {
|
|
791
|
-
if (el) textFieldEl = el;
|
|
792
|
-
})}
|
|
793
|
-
@input=${() => {
|
|
794
|
-
if (!textFieldEl || isNew || !varName) return;
|
|
795
|
-
clearTimeout(debounce);
|
|
796
|
-
debounce = setTimeout(() => {
|
|
797
|
-
const v = textFieldEl.value;
|
|
798
|
-
const fontPrev = /** @type {any} */ (row.querySelector(".sb-var-font-preview"));
|
|
799
|
-
if (fontPrev) fontPrev.style.fontFamily = v;
|
|
800
|
-
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
|
|
801
|
-
}, 400);
|
|
802
|
-
}}
|
|
803
|
-
></sp-textfield>
|
|
804
|
-
`;
|
|
805
|
-
getValueFn = () => textFieldEl?.value?.trim() || "";
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const valColTpl = html`
|
|
809
|
-
<div class="sb-var-col-value">
|
|
810
|
-
${isNew ? html`<div class="sb-var-col-label">Value</div>` : nothing} ${valueContent}
|
|
811
|
-
</div>
|
|
812
|
-
`;
|
|
813
|
-
|
|
814
|
-
const actionsTpl = isNew
|
|
815
|
-
? html`
|
|
816
|
-
<div class="sb-var-add-actions">
|
|
817
|
-
<sp-action-button
|
|
818
|
-
size="s"
|
|
819
|
-
style="pointer-events:auto"
|
|
820
|
-
@click=${() => {
|
|
821
|
-
const name = (nameField?.value || "").trim();
|
|
822
|
-
const val = getValueFn();
|
|
823
|
-
const generatedVar = friendlyNameToVar(name, catMeta.prefix);
|
|
824
|
-
if (!generatedVar || !val) return;
|
|
825
|
-
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], generatedVar, val));
|
|
826
|
-
}}
|
|
827
|
-
>Add</sp-action-button
|
|
828
|
-
>
|
|
829
|
-
<sp-action-button
|
|
830
|
-
size="s"
|
|
831
|
-
quiet
|
|
832
|
-
style="pointer-events:auto"
|
|
833
|
-
@click=${() => {
|
|
834
|
-
const body = row.parentElement;
|
|
835
|
-
row.remove();
|
|
836
|
-
const addBtn = /** @type {any} */ (body?.querySelector(".sb-var-add-btn"));
|
|
837
|
-
if (addBtn) addBtn.style.display = "";
|
|
838
|
-
}}
|
|
839
|
-
>
|
|
840
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
841
|
-
</sp-action-button>
|
|
842
|
-
</div>
|
|
843
|
-
`
|
|
844
|
-
: nothing;
|
|
845
|
-
|
|
846
|
-
const headerTpl =
|
|
847
|
-
!isNew && varName
|
|
848
|
-
? html`
|
|
849
|
-
<div class="sb-var-row-header">
|
|
850
|
-
<span class="sb-var-row-title">${varDisplayName(varName, catMeta.prefix)}</span>
|
|
851
|
-
<span class="sb-var-row-ref">${varName}</span>
|
|
852
|
-
<sp-action-button
|
|
853
|
-
size="s"
|
|
854
|
-
quiet
|
|
855
|
-
class="sb-var-del"
|
|
856
|
-
style="pointer-events:auto"
|
|
857
|
-
@click=${() => {
|
|
858
|
-
transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, undefined));
|
|
859
|
-
}}
|
|
860
|
-
>
|
|
861
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
862
|
-
</sp-action-button>
|
|
863
|
-
</div>
|
|
864
|
-
`
|
|
865
|
-
: nothing;
|
|
866
|
-
|
|
867
|
-
const addPreviewTpl = isNew
|
|
868
|
-
? html`
|
|
869
|
-
<div
|
|
870
|
-
class="sb-var-add-preview"
|
|
871
|
-
${ref((el) => {
|
|
872
|
-
if (!el || !nameField) return;
|
|
873
|
-
nameField.addEventListener("input", () => {
|
|
874
|
-
el.textContent = friendlyNameToVar(nameField.value || "", catMeta.prefix);
|
|
875
|
-
});
|
|
876
|
-
})}
|
|
877
|
-
></div>
|
|
878
|
-
`
|
|
879
|
-
: nothing;
|
|
880
|
-
|
|
881
|
-
const typePrevTpl =
|
|
882
|
-
catKey === "font" && varVal
|
|
883
|
-
? html`
|
|
884
|
-
<div class="sb-var-preview">
|
|
885
|
-
<div class="sb-var-font-preview" style=${styleMap({ fontFamily: varVal })}>
|
|
886
|
-
The quick brown fox jumps over the lazy dog
|
|
887
|
-
</div>
|
|
888
|
-
</div>
|
|
889
|
-
`
|
|
890
|
-
: catKey === "size" && varVal
|
|
891
|
-
? html`
|
|
892
|
-
<div class="sb-var-preview">
|
|
893
|
-
<div class="sb-var-size-track">
|
|
894
|
-
<div class="sb-var-size-bar" style=${styleMap({ width: varVal })}></div>
|
|
895
|
-
</div>
|
|
896
|
-
</div>
|
|
897
|
-
`
|
|
898
|
-
: nothing;
|
|
899
|
-
|
|
900
|
-
litRender(
|
|
901
|
-
html`
|
|
902
|
-
${headerTpl}
|
|
903
|
-
<div class="sb-var-input-row">${swatchTpl} ${nameColTpl} ${valColTpl} ${actionsTpl}</div>
|
|
904
|
-
${addPreviewTpl} ${typePrevTpl}
|
|
905
|
-
`,
|
|
906
|
-
row,
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
return row;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
/**
|
|
913
|
-
* Creates a combined textfield + quiet sp-picker for CSS values with units.
|
|
914
|
-
*
|
|
915
|
-
* @param {any} initialValue
|
|
916
|
-
* @param {any} [options]
|
|
917
|
-
*/
|
|
918
|
-
function createUnitInput(initialValue, { onChange, size = "s" } = {}) {
|
|
919
|
-
const match = String(initialValue).match(
|
|
920
|
-
/^(-?[\d.]+)\s*(px|em|rem|vw|vh|%|ch|ex|vmin|vmax|pt|cm|mm|in)?$/,
|
|
921
|
-
);
|
|
922
|
-
let numVal = match ? match[1] : initialValue;
|
|
923
|
-
let unitVal = match ? match[2] || "px" : "";
|
|
924
|
-
const isNumeric = !!match;
|
|
925
|
-
|
|
926
|
-
const wrap = document.createElement("div");
|
|
927
|
-
wrap.className = "sb-unit-input";
|
|
928
|
-
wrap.style.pointerEvents = "auto";
|
|
929
|
-
|
|
930
|
-
/** @type {any} */
|
|
931
|
-
let textfield = null;
|
|
932
|
-
/** @type {any} */
|
|
933
|
-
let picker = null;
|
|
934
|
-
|
|
935
|
-
const units = [
|
|
936
|
-
{ value: "px", label: "px" },
|
|
937
|
-
{ value: "rem", label: "rem" },
|
|
938
|
-
{ value: "em", label: "em" },
|
|
939
|
-
{ value: "%", label: "%" },
|
|
940
|
-
{ value: "vw", label: "vw" },
|
|
941
|
-
{ value: "vh", label: "vh" },
|
|
942
|
-
{ value: "ch", label: "ch" },
|
|
943
|
-
{ value: "pt", label: "pt" },
|
|
944
|
-
{ divider: true },
|
|
945
|
-
{ value: "auto", label: "auto" },
|
|
946
|
-
{ value: "fit-content", label: "fit-content" },
|
|
947
|
-
];
|
|
948
|
-
|
|
949
|
-
/** @type {any} */
|
|
950
|
-
let debounce;
|
|
951
|
-
|
|
952
|
-
function getValue() {
|
|
953
|
-
const num = textfield?.value;
|
|
954
|
-
const unit = picker?.value;
|
|
955
|
-
if (unit === "auto" || unit === "fit-content") return unit;
|
|
956
|
-
return num ? `${num}${unit}` : "";
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
litRender(
|
|
960
|
-
html`
|
|
961
|
-
<sp-textfield
|
|
962
|
-
.value=${numVal}
|
|
963
|
-
size=${size}
|
|
964
|
-
${ref((el) => {
|
|
965
|
-
if (el) textfield = el;
|
|
966
|
-
})}
|
|
967
|
-
@input=${() => {
|
|
968
|
-
clearTimeout(debounce);
|
|
969
|
-
const raw = textfield?.value?.trim();
|
|
970
|
-
const looksNumeric = /^-?[\d.]+$/.test(raw || "");
|
|
971
|
-
if (picker) picker.style.display = looksNumeric ? "" : "none";
|
|
972
|
-
debounce = setTimeout(() => {
|
|
973
|
-
if (onChange) onChange(looksNumeric ? `${raw}${picker?.value}` : raw);
|
|
974
|
-
}, 400);
|
|
975
|
-
}}
|
|
976
|
-
></sp-textfield>
|
|
977
|
-
<sp-picker
|
|
978
|
-
quiet
|
|
979
|
-
size=${size}
|
|
980
|
-
style=${styleMap({ display: isNumeric ? "" : "none" })}
|
|
981
|
-
${ref((el) => {
|
|
982
|
-
if (el) {
|
|
983
|
-
picker = el;
|
|
984
|
-
requestAnimationFrame(() => {
|
|
985
|
-
/** @type {any} */ (el).value = unitVal || "px";
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
})}
|
|
989
|
-
@change=${() => {
|
|
990
|
-
const unit = picker?.value;
|
|
991
|
-
if (unit === "auto" || unit === "fit-content") {
|
|
992
|
-
if (textfield) textfield.value = unit;
|
|
993
|
-
if (picker) picker.style.display = "none";
|
|
994
|
-
if (onChange) onChange(unit);
|
|
995
|
-
} else {
|
|
996
|
-
unitVal = unit;
|
|
997
|
-
if (onChange) onChange(getValue());
|
|
998
|
-
}
|
|
999
|
-
}}
|
|
1000
|
-
>
|
|
1001
|
-
${units.map((u) =>
|
|
1002
|
-
u.divider
|
|
1003
|
-
? html`<sp-menu-divider></sp-menu-divider>`
|
|
1004
|
-
: html`<sp-menu-item value=${u.value}>${u.label}</sp-menu-item>`,
|
|
1005
|
-
)}
|
|
1006
|
-
</sp-picker>
|
|
1007
|
-
`,
|
|
1008
|
-
wrap,
|
|
1009
|
-
);
|
|
1010
|
-
|
|
1011
|
-
return { wrap, textfield, picker, getValue };
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
515
|
/**
|
|
1015
516
|
* Click handler for stylebook canvas — selects elements via elToPath/view.stylebookElToTag mapping
|
|
1016
517
|
*
|
package/src/panels/toolbar.js
CHANGED
|
@@ -194,9 +194,12 @@ function toolbarTemplate() {
|
|
|
194
194
|
{ key: "design", label: "Design", iconTag: "sp-icon-artboard" },
|
|
195
195
|
{ key: "preview", label: "Preview", iconTag: "sp-icon-preview" },
|
|
196
196
|
{ key: "source", label: "Code", iconTag: "sp-icon-code" },
|
|
197
|
-
{ key: "
|
|
197
|
+
{ key: "stylebook", label: "Stylebook", iconTag: "sp-icon-brush" },
|
|
198
198
|
];
|
|
199
199
|
|
|
200
|
+
const isProjectFile = S.documentPath === "project.json";
|
|
201
|
+
const allowedModes = isProjectFile ? new Set(["stylebook", "source"]) : null;
|
|
202
|
+
|
|
200
203
|
const modeSwitcherTpl = html`
|
|
201
204
|
<sp-action-group selects="single" size="s" compact>
|
|
202
205
|
${modes.map(
|
|
@@ -204,8 +207,10 @@ function toolbarTemplate() {
|
|
|
204
207
|
<sp-action-button
|
|
205
208
|
size="s"
|
|
206
209
|
?selected=${canvasMode === m.key}
|
|
210
|
+
?disabled=${allowedModes && !allowedModes.has(m.key)}
|
|
207
211
|
@click=${() => {
|
|
208
212
|
if (canvasMode === m.key) return;
|
|
213
|
+
if (allowedModes && !allowedModes.has(m.key)) return;
|
|
209
214
|
if (S.ui.editingFunction) {
|
|
210
215
|
if (view.functionEditor) {
|
|
211
216
|
view.functionEditor.dispose();
|
|
@@ -217,7 +222,7 @@ function toolbarTemplate() {
|
|
|
217
222
|
view.panY = 0;
|
|
218
223
|
/** @type {Record<string, any>} */
|
|
219
224
|
const uiPatch = { editingFunction: null };
|
|
220
|
-
if (m.key === "
|
|
225
|
+
if (m.key === "stylebook") uiPatch.rightTab = "style";
|
|
221
226
|
if (m.key === "manage") view.leftTab = "files";
|
|
222
227
|
updateSession({ ui: uiPatch });
|
|
223
228
|
_ctx.renderCanvas();
|