@jxsuite/studio 1.1.0 → 1.2.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/iframe-entry.js +139 -17
- package/dist/iframe-entry.js.map +7 -6
- package/dist/studio.js +22601 -22425
- package/dist/studio.js.map +52 -52
- package/package.json +8 -8
- package/src/canvas/iframe-entry.ts +17 -1
- package/src/canvas/iframe-host.ts +62 -1
- package/src/canvas/iframe-protocol.ts +13 -0
- package/src/canvas/iframe-render.ts +35 -14
- package/src/canvas/site-style-css.ts +121 -0
- package/src/panels/style-panel.ts +52 -22
- package/src/panels/tab-bar.ts +39 -4
- package/src/settings/css-vars-editor.ts +122 -4
- package/src/studio.ts +7 -0
- package/src/tabs/tab.ts +7 -0
- package/src/utils/canvas-media.ts +23 -0
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* CSS Variables editor — form-based editor for managing design tokens. Colors
|
|
4
|
-
*
|
|
3
|
+
* CSS Variables editor — form-based editor for managing design tokens. Colors are scheme-aware
|
|
4
|
+
* (per-scheme overrides in `@--dark`-style blocks, spec §9.5); sizes/spacing are media-aware; fonts
|
|
5
|
+
* are neither.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { html, render as litRender, nothing } from "lit-html";
|
|
8
9
|
import { ref } from "lit-html/directives/ref.js";
|
|
9
10
|
import { projectState } from "../store";
|
|
10
11
|
import { getEffectiveMedia, updateSiteConfig } from "../site-context";
|
|
12
|
+
import { postSiteStyleToLiveHosts } from "../canvas/iframe-host";
|
|
13
|
+
import { isSchemeQuery, schemeOfQuery } from "../utils/canvas-media";
|
|
11
14
|
import { friendlyNameToVar, varDisplayName } from "../utils/studio-utils";
|
|
12
15
|
|
|
13
16
|
import type { JxStyle } from "@jxsuite/schema/types";
|
|
14
17
|
|
|
18
|
+
/** A declared scheme query in $media: the block key name plus the scheme it targets. */
|
|
19
|
+
export interface SchemeEntry {
|
|
20
|
+
name: string;
|
|
21
|
+
scheme: "light" | "dark";
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
/** @param {HTMLElement} container */
|
|
16
25
|
export function renderCssVarsEditor(container: HTMLElement) {
|
|
17
26
|
const config = projectState?.projectConfig || {};
|
|
@@ -50,10 +59,20 @@ export function renderCssVarsEditor(container: HTMLElement) {
|
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
const mediaNames = media
|
|
62
|
+
const mediaNames = media
|
|
63
|
+
? Object.keys(media).filter((m) => m !== "--" && !isSchemeQuery(String(media[m])))
|
|
64
|
+
: [];
|
|
65
|
+
const schemeEntries: SchemeEntry[] = media
|
|
66
|
+
? Object.entries(media).flatMap(([name, query]) => {
|
|
67
|
+
const scheme = schemeOfQuery(String(query));
|
|
68
|
+
return scheme ? [{ name, scheme }] : [];
|
|
69
|
+
})
|
|
70
|
+
: [];
|
|
54
71
|
|
|
55
72
|
const save = () => {
|
|
56
73
|
void updateSiteConfig({ style: { ...rootStyle } });
|
|
74
|
+
// Live canvas feedback: replace the site-style sheet in place — no re-render needed.
|
|
75
|
+
postSiteStyleToLiveHosts();
|
|
57
76
|
};
|
|
58
77
|
|
|
59
78
|
const updateVar = (name: string, val: string) => {
|
|
@@ -61,6 +80,35 @@ export function renderCssVarsEditor(container: HTMLElement) {
|
|
|
61
80
|
save();
|
|
62
81
|
};
|
|
63
82
|
|
|
83
|
+
// Write/clear a per-scheme token override in the scheme's `@--name` block (spec §9.5). An empty
|
|
84
|
+
// Value clears the override (and drops the block when it empties) — the token inherits the base.
|
|
85
|
+
const updateSchemeVar = (schemeName: string, varName: string, val: string) => {
|
|
86
|
+
const key = `@${schemeName}`;
|
|
87
|
+
if (val) {
|
|
88
|
+
const block = (rootStyle[key] ??= {}) as Record<string, unknown>;
|
|
89
|
+
block[varName] = val;
|
|
90
|
+
} else {
|
|
91
|
+
const block = rootStyle[key];
|
|
92
|
+
if (block && typeof block === "object") {
|
|
93
|
+
delete (block as Record<string, unknown>)[varName];
|
|
94
|
+
if (Object.keys(block).length === 0) {
|
|
95
|
+
delete rootStyle[key];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
save();
|
|
100
|
+
renderCssVarsEditor(container);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Without a declared scheme query nothing ever shows the scheme UI (here or in the tab bar) —
|
|
104
|
+
// This is the opt-in affordance for existing projects. Re-render after the async config update
|
|
105
|
+
// Lands (unlike style edits, $media is not mutated in place).
|
|
106
|
+
const enableDarkScheme = () => {
|
|
107
|
+
void updateSiteConfig({
|
|
108
|
+
$media: { ...config.$media, "--dark": "(prefers-color-scheme: dark)" },
|
|
109
|
+
}).then(() => renderCssVarsEditor(container));
|
|
110
|
+
};
|
|
111
|
+
|
|
64
112
|
const deleteVar = (name: string) => {
|
|
65
113
|
delete rootStyle[name];
|
|
66
114
|
save();
|
|
@@ -81,7 +129,16 @@ export function renderCssVarsEditor(container: HTMLElement) {
|
|
|
81
129
|
<div class="settings-section">
|
|
82
130
|
<h3 class="settings-section-title">CSS Variables</h3>
|
|
83
131
|
|
|
84
|
-
${renderColorSection(
|
|
132
|
+
${renderColorSection(
|
|
133
|
+
groups.color,
|
|
134
|
+
updateVar,
|
|
135
|
+
deleteVar,
|
|
136
|
+
addVar,
|
|
137
|
+
rootStyle,
|
|
138
|
+
schemeEntries,
|
|
139
|
+
updateSchemeVar,
|
|
140
|
+
enableDarkScheme,
|
|
141
|
+
)}
|
|
85
142
|
${renderFontSection(groups.font, updateVar, deleteVar, addVar)}
|
|
86
143
|
${renderSizeSection(groups.size, updateVar, deleteVar, addVar, rootStyle, mediaNames)}
|
|
87
144
|
${groups.other.length > 0
|
|
@@ -104,6 +161,10 @@ function renderColorSection(
|
|
|
104
161
|
updateVar: (name: string, val: string) => void,
|
|
105
162
|
deleteVar: (name: string) => void,
|
|
106
163
|
addVar: (prefix: string, friendlyName: string, val: string) => void,
|
|
164
|
+
rootStyle: JxStyle,
|
|
165
|
+
schemeEntries: SchemeEntry[],
|
|
166
|
+
updateSchemeVar: (schemeName: string, varName: string, val: string) => void,
|
|
167
|
+
enableDarkScheme: () => void,
|
|
107
168
|
) {
|
|
108
169
|
return html`
|
|
109
170
|
<div class="css-vars-group">
|
|
@@ -129,9 +190,66 @@ function renderColorSection(
|
|
|
129
190
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
130
191
|
</sp-action-button>
|
|
131
192
|
</div>
|
|
193
|
+
${schemeEntries.map((entry) =>
|
|
194
|
+
renderSchemeOverride(name, rootStyle, entry, updateSchemeVar),
|
|
195
|
+
)}
|
|
132
196
|
`,
|
|
133
197
|
)}
|
|
134
198
|
${renderAddRow("--color-", "Primary Blue", "#3b82f6", addVar)}
|
|
199
|
+
${schemeEntries.length === 0
|
|
200
|
+
? html`
|
|
201
|
+
<sp-action-button
|
|
202
|
+
size="s"
|
|
203
|
+
class="css-vars-enable-dark"
|
|
204
|
+
title="Add a dark color scheme: declares --dark in $media so every color token can carry a dark value"
|
|
205
|
+
@click=${enableDarkScheme}
|
|
206
|
+
>
|
|
207
|
+
Enable dark scheme
|
|
208
|
+
</sp-action-button>
|
|
209
|
+
`
|
|
210
|
+
: nothing}
|
|
211
|
+
</div>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* One per-scheme override row under a color token: swatch + field bound to the scheme block's
|
|
217
|
+
* value. Empty shows "inherits" (the base value applies); clearing deletes the override.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} varName
|
|
220
|
+
* @param {JxStyle} rootStyle
|
|
221
|
+
* @param {SchemeEntry} entry
|
|
222
|
+
* @param {(schemeName: string, varName: string, val: string) => void} updateSchemeVar
|
|
223
|
+
*/
|
|
224
|
+
function renderSchemeOverride(
|
|
225
|
+
varName: string,
|
|
226
|
+
rootStyle: JxStyle,
|
|
227
|
+
entry: SchemeEntry,
|
|
228
|
+
updateSchemeVar: (schemeName: string, varName: string, val: string) => void,
|
|
229
|
+
) {
|
|
230
|
+
const block = rootStyle[`@${entry.name}`];
|
|
231
|
+
const current =
|
|
232
|
+
block && typeof block === "object" ? (block as Record<string, unknown>)[varName] : undefined;
|
|
233
|
+
const label = entry.scheme === "dark" ? "Dark" : "Light";
|
|
234
|
+
return html`
|
|
235
|
+
<div class="css-var-media-row css-var-scheme-row">
|
|
236
|
+
<span class="css-var-media-label">${label}</span>
|
|
237
|
+
<div class="css-var-swatch" style="background:${current ?? "transparent"}">
|
|
238
|
+
<input
|
|
239
|
+
type="color"
|
|
240
|
+
.value=${current && String(current).startsWith("#") ? String(current) : "#111111"}
|
|
241
|
+
@input=${(e: Event) =>
|
|
242
|
+
updateSchemeVar(entry.name, varName, (e.target as HTMLInputElement).value)}
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
<sp-textfield
|
|
246
|
+
size="s"
|
|
247
|
+
placeholder="inherits"
|
|
248
|
+
.value=${current == null ? "" : String(current)}
|
|
249
|
+
@change=${(e: Event) =>
|
|
250
|
+
updateSchemeVar(entry.name, varName, (e.target as HTMLInputElement).value)}
|
|
251
|
+
style="flex:1;max-width:160px"
|
|
252
|
+
></sp-textfield>
|
|
135
253
|
</div>
|
|
136
254
|
`;
|
|
137
255
|
}
|
package/src/studio.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { consumePatchedDocument, initCanvasPatcher } from "./canvas/canvas-patch
|
|
|
48
48
|
import {
|
|
49
49
|
commitActiveEditSession,
|
|
50
50
|
getEditSnapshot,
|
|
51
|
+
postColorSchemeToLiveHosts,
|
|
51
52
|
setCanvasContextMenuHandler,
|
|
52
53
|
setCanvasSlashHandler,
|
|
53
54
|
setIframePatchEscalation,
|
|
@@ -581,6 +582,12 @@ effect(() => {
|
|
|
581
582
|
}
|
|
582
583
|
scheduleCanvasRender();
|
|
583
584
|
});
|
|
585
|
+
// Color-scheme preview is a document-level attribute flip inside the iframe — deliberately its
|
|
586
|
+
// Own effect, never part of the ui-effect above: flipping the scheme must not re-render.
|
|
587
|
+
effect(() => {
|
|
588
|
+
const s = activeTab.value?.session.ui.previewColorScheme;
|
|
589
|
+
postColorSchemeToLiveHosts(s === "light" || s === "dark" ? s : null);
|
|
590
|
+
});
|
|
584
591
|
|
|
585
592
|
rightPanelMod.mount({
|
|
586
593
|
getCanvasMode,
|
package/src/tabs/tab.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface TabUi {
|
|
|
43
43
|
/** Full-screen formula workspace target ($expression editing); editingFunction wins if both set. */
|
|
44
44
|
editingFormula: FormulaEditDef | null;
|
|
45
45
|
featureToggles: Record<string, boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Canvas color-scheme preview: follow the OS ("auto") or force a scheme. Drives the
|
|
48
|
+
* data-color-scheme attribute on the canvas iframe root (spec §9.5) and, in the style sidebar,
|
|
49
|
+
* which scheme layer edits target.
|
|
50
|
+
*/
|
|
51
|
+
previewColorScheme: "auto" | "light" | "dark";
|
|
46
52
|
styleSections: Record<string, boolean>;
|
|
47
53
|
inspectorSections: Record<string, boolean>;
|
|
48
54
|
styleShorthands: Record<string, boolean>;
|
|
@@ -156,6 +162,7 @@ function createDefaultUi(canvasMode: string, preview = false) {
|
|
|
156
162
|
inspectorSections: {},
|
|
157
163
|
pendingInlineEdit: null,
|
|
158
164
|
preview,
|
|
165
|
+
previewColorScheme: "auto" as const,
|
|
159
166
|
previewParams: {},
|
|
160
167
|
previewProps: null,
|
|
161
168
|
rightTab: "properties",
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
/// <reference lib="dom.iterable" />
|
|
3
3
|
/** Canvas media/breakpoint utilities — pure functions extracted for testability. */
|
|
4
4
|
|
|
5
|
+
import { pureSchemeOf } from "@jxsuite/runtime";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* True when a feature query participates in the forced-scheme contract (spec §9.5) — a pure
|
|
9
|
+
* prefers-color-scheme query. These surface as the Auto/Light/Dark control, not generic toggles.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} query
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function isSchemeQuery(query: string): boolean {
|
|
15
|
+
return pureSchemeOf(query) !== null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The scheme a feature query targets ("light" | "dark"), or null for non-scheme queries.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} query
|
|
22
|
+
* @returns {"light" | "dark" | null}
|
|
23
|
+
*/
|
|
24
|
+
export function schemeOfQuery(query: string): "light" | "dark" | null {
|
|
25
|
+
return pureSchemeOf(query);
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
/**
|
|
6
29
|
* Classify $media entries into size breakpoints (get a canvas each) and feature queries (rendered
|
|
7
30
|
* as toolbar toggles).
|