@jxsuite/studio 0.28.2 → 0.28.4
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 +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
|
@@ -5,19 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { html, nothing } from "lit-html";
|
|
8
|
+
import { getNestedStyle } from "@jxsuite/schema/guards";
|
|
8
9
|
import { live } from "lit-html/directives/live.js";
|
|
9
10
|
import { ifDefined } from "lit-html/directives/if-defined.js";
|
|
10
|
-
import {
|
|
11
|
+
import { COMMON_SELECTORS, debouncedStyleCommit, getNodeAtPath, isNestedSelector } from "../store";
|
|
11
12
|
import { activeTab } from "../workspace/workspace";
|
|
12
13
|
import { selectStylebookTag } from "./stylebook-panel";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
mutateUpdateMediaNestedStyle,
|
|
16
|
+
mutateUpdateMediaNestedStylePath,
|
|
16
17
|
mutateUpdateMediaStyle,
|
|
17
18
|
mutateUpdateNestedStyle,
|
|
18
|
-
mutateUpdateMediaNestedStyle,
|
|
19
19
|
mutateUpdateNestedStylePath,
|
|
20
|
-
|
|
20
|
+
mutateUpdateStyle,
|
|
21
|
+
transactDoc,
|
|
21
22
|
} from "../tabs/transact";
|
|
22
23
|
import { inferInputType, propLabel } from "../utils/studio-utils";
|
|
23
24
|
import { renderFieldRow } from "../ui/field-row";
|
|
@@ -26,21 +27,21 @@ import { getEffectiveMedia, getEffectiveStyle } from "../site-context";
|
|
|
26
27
|
import { computeInheritedStyle } from "../utils/inherited-style";
|
|
27
28
|
import { mediaDisplayName } from "./shared";
|
|
28
29
|
import {
|
|
29
|
-
cssMeta,
|
|
30
|
-
getCssInitialMap,
|
|
31
30
|
allConditionsPass,
|
|
32
31
|
autoOpenSections,
|
|
33
|
-
|
|
34
|
-
expandShorthand,
|
|
32
|
+
compressBorderSide,
|
|
35
33
|
compressShorthand,
|
|
34
|
+
cssMeta,
|
|
36
35
|
expandBorderSide,
|
|
37
|
-
|
|
36
|
+
expandShorthand,
|
|
37
|
+
getCssInitialMap,
|
|
38
|
+
getLonghands,
|
|
38
39
|
} from "./style-utils";
|
|
39
40
|
import { widgetForType } from "./style-inputs";
|
|
40
41
|
|
|
41
42
|
import type { Tab } from "../tabs/tab";
|
|
42
43
|
import type { JxPath } from "../state";
|
|
43
|
-
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
44
|
+
import type { JxMutableNode, JxStyle } from "@jxsuite/schema/types";
|
|
44
45
|
|
|
45
46
|
interface CssLonghand {
|
|
46
47
|
name: string;
|
|
@@ -52,7 +53,7 @@ type CssPropertyEntry = Record<string, unknown>;
|
|
|
52
53
|
type StyleMutateFn = (
|
|
53
54
|
t: Tab,
|
|
54
55
|
prop: string,
|
|
55
|
-
val
|
|
56
|
+
val?: string | Record<string, unknown> | undefined,
|
|
56
57
|
) => void;
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -69,18 +70,19 @@ function isTagPath(selector: string) {
|
|
|
69
70
|
/**
|
|
70
71
|
* Resolve a style object by traversing a nested tag path. e.g., "table th" → style["table"]["th"]
|
|
71
72
|
*
|
|
72
|
-
* @param {
|
|
73
|
+
* @param {JxStyle} style
|
|
73
74
|
* @param {string} tagPath
|
|
74
|
-
* @returns {
|
|
75
|
+
* @returns {JxStyle}
|
|
75
76
|
*/
|
|
76
|
-
function resolveNestedTagStyle(style:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!obj
|
|
81
|
-
|
|
77
|
+
function resolveNestedTagStyle(style: JxStyle, tagPath: string): JxStyle {
|
|
78
|
+
let obj: JxStyle | undefined = style;
|
|
79
|
+
for (const part of tagPath.split(" ")) {
|
|
80
|
+
obj = getNestedStyle(obj, part);
|
|
81
|
+
if (!obj) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
82
84
|
}
|
|
83
|
-
return obj
|
|
85
|
+
return obj;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
// ─── Row renderers ──────────────────────────────────────────────────────────
|
|
@@ -144,9 +146,13 @@ function renderShorthandRow(
|
|
|
144
146
|
@click=${(e: Event) => {
|
|
145
147
|
e.stopPropagation();
|
|
146
148
|
transactDoc(activeTab.value, (t) => {
|
|
147
|
-
if (shortVal !== undefined)
|
|
149
|
+
if (shortVal !== undefined) {
|
|
150
|
+
mutateFn(t, shortProp);
|
|
151
|
+
}
|
|
148
152
|
for (const l of longhands) {
|
|
149
|
-
if (style[l.name] !== undefined)
|
|
153
|
+
if (style[l.name] !== undefined) {
|
|
154
|
+
mutateFn(t, l.name);
|
|
155
|
+
}
|
|
150
156
|
}
|
|
151
157
|
});
|
|
152
158
|
}}
|
|
@@ -168,7 +174,9 @@ function renderShorthandRow(
|
|
|
168
174
|
@input=${debouncedStyleCommit(`short:${shortProp}`, 400, (e: Event) => {
|
|
169
175
|
transactDoc(activeTab.value, (t) => {
|
|
170
176
|
for (const l of longhands) {
|
|
171
|
-
if (style[l.name] !== undefined)
|
|
177
|
+
if (style[l.name] !== undefined) {
|
|
178
|
+
mutateFn(t, l.name);
|
|
179
|
+
}
|
|
172
180
|
}
|
|
173
181
|
mutateFn(t, shortProp, (e.target as HTMLInputElement).value || undefined);
|
|
174
182
|
});
|
|
@@ -219,7 +227,9 @@ function renderShorthandRow(
|
|
|
219
227
|
);
|
|
220
228
|
transactDoc(activeTab.value, (t) => {
|
|
221
229
|
for (const l of longhands) {
|
|
222
|
-
if (style[l.name] !== undefined)
|
|
230
|
+
if (style[l.name] !== undefined) {
|
|
231
|
+
mutateFn(t, l.name);
|
|
232
|
+
}
|
|
223
233
|
}
|
|
224
234
|
mutateFn(t, shortProp, compress(vals as string[]));
|
|
225
235
|
});
|
|
@@ -232,7 +242,7 @@ function renderShorthandRow(
|
|
|
232
242
|
inferInputType(lEntry),
|
|
233
243
|
lEntry,
|
|
234
244
|
name,
|
|
235
|
-
lVal,
|
|
245
|
+
lVal as string,
|
|
236
246
|
(newVal: string) => {
|
|
237
247
|
const vals = longhands.map((l: CssLonghand, i: number) =>
|
|
238
248
|
i === idx
|
|
@@ -241,7 +251,9 @@ function renderShorthandRow(
|
|
|
241
251
|
);
|
|
242
252
|
transactDoc(activeTab.value, (t) => {
|
|
243
253
|
for (const l of longhands) {
|
|
244
|
-
if (style[l.name] !== undefined)
|
|
254
|
+
if (style[l.name] !== undefined) {
|
|
255
|
+
mutateFn(t, l.name);
|
|
256
|
+
}
|
|
245
257
|
}
|
|
246
258
|
mutateFn(t, shortProp, compress(vals as string[]));
|
|
247
259
|
});
|
|
@@ -264,20 +276,20 @@ function renderShorthandRow(
|
|
|
264
276
|
* @param {JxMutableNode} node
|
|
265
277
|
* @param {string | null} activeMediaTab
|
|
266
278
|
* @param {string | null} activeSelector
|
|
267
|
-
* @param {
|
|
279
|
+
* @param {JxStyle} [effectiveStyle]
|
|
268
280
|
*/
|
|
269
281
|
function styleSidebarTemplate(
|
|
270
282
|
node: JxMutableNode,
|
|
271
283
|
activeMediaTab: string | null,
|
|
272
284
|
activeSelector: string | null,
|
|
273
|
-
effectiveStyle?:
|
|
285
|
+
effectiveStyle?: JxStyle,
|
|
274
286
|
) {
|
|
275
287
|
const tab = activeTab.value!;
|
|
276
288
|
const sel = tab.session.selection as JxPath;
|
|
277
289
|
const style = effectiveStyle || node.style || {};
|
|
278
290
|
const { sizeBreakpoints } = parseMediaEntries(getEffectiveMedia(tab.doc.document.$media));
|
|
279
291
|
const mediaNames = sizeBreakpoints.map((bp) => bp.name);
|
|
280
|
-
const mediaTab = activeMediaTab;
|
|
292
|
+
const mediaTab = activeMediaTab || null;
|
|
281
293
|
|
|
282
294
|
// ── Media tabs template ──────────────────────────────────────────────────
|
|
283
295
|
const mediaTabsT =
|
|
@@ -304,7 +316,7 @@ function styleSidebarTemplate(
|
|
|
304
316
|
|
|
305
317
|
// ── Selector dropdown ──────────────────────────────────────────────────────
|
|
306
318
|
const contextStyle = mediaTab ? (style[`@${mediaTab}`] as Record<string, unknown>) || {} : style;
|
|
307
|
-
const existingSelectors = Object.keys(contextStyle).filter(isNestedSelector);
|
|
319
|
+
const existingSelectors = Object.keys(contextStyle).filter((s) => isNestedSelector(s));
|
|
308
320
|
const existingSet = new Set(existingSelectors);
|
|
309
321
|
const commonSet = new Set(COMMON_SELECTORS);
|
|
310
322
|
const extraSelectors = existingSelectors.filter((s) => !commonSet.has(s));
|
|
@@ -332,11 +344,13 @@ function styleSidebarTemplate(
|
|
|
332
344
|
inp.type = "text";
|
|
333
345
|
inp.className = "selector-custom-input";
|
|
334
346
|
inp.placeholder = ":hover, .child, &.active, [attr]";
|
|
335
|
-
bar.
|
|
347
|
+
bar.append(inp);
|
|
336
348
|
inp.focus();
|
|
337
349
|
let done = false;
|
|
338
350
|
const finish = (accept: boolean) => {
|
|
339
|
-
if (done)
|
|
351
|
+
if (done) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
340
354
|
done = true;
|
|
341
355
|
const v = inp.value.trim();
|
|
342
356
|
inp.remove();
|
|
@@ -346,8 +360,11 @@ function styleSidebarTemplate(
|
|
|
346
360
|
}
|
|
347
361
|
};
|
|
348
362
|
inp.addEventListener("keydown", (ev) => {
|
|
349
|
-
if (ev.key === "Enter")
|
|
350
|
-
|
|
363
|
+
if (ev.key === "Enter") {
|
|
364
|
+
finish(true);
|
|
365
|
+
} else if (ev.key === "Escape") {
|
|
366
|
+
finish(false);
|
|
367
|
+
}
|
|
351
368
|
});
|
|
352
369
|
inp.addEventListener("blur", () => finish(inp.value.trim().length > 0));
|
|
353
370
|
return;
|
|
@@ -409,11 +426,10 @@ function styleSidebarTemplate(
|
|
|
409
426
|
`;
|
|
410
427
|
|
|
411
428
|
// ── Determine the active style object ──────────────────────────────────────
|
|
412
|
-
let activeStyle:
|
|
413
|
-
let commitStyle: (prop: string, val: string | Record<string, unknown> | undefined) => void;
|
|
429
|
+
let activeStyle: JxStyle;
|
|
414
430
|
let commitMutate: StyleMutateFn;
|
|
415
431
|
if (activeSelector && isTagPath(activeSelector) && mediaTab && mediaNames.length > 0) {
|
|
416
|
-
const mediaObj = (style
|
|
432
|
+
const mediaObj = getNestedStyle(style, `@${mediaTab}`) ?? {};
|
|
417
433
|
activeStyle = resolveNestedTagStyle(mediaObj, activeSelector);
|
|
418
434
|
const stylePath = activeSelector.split(" ");
|
|
419
435
|
commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
@@ -425,18 +441,14 @@ function styleSidebarTemplate(
|
|
|
425
441
|
prop,
|
|
426
442
|
val as string | undefined,
|
|
427
443
|
);
|
|
428
|
-
commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
429
|
-
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
430
444
|
} else if (activeSelector && isTagPath(activeSelector)) {
|
|
431
445
|
activeStyle = resolveNestedTagStyle(style, activeSelector);
|
|
432
446
|
const stylePath = activeSelector.split(" ");
|
|
433
447
|
commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
434
448
|
mutateUpdateNestedStylePath(t, sel, stylePath, prop, val as string | undefined);
|
|
435
|
-
commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
436
|
-
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
437
449
|
} else if (activeSelector && mediaTab && mediaNames.length > 0) {
|
|
438
|
-
const mediaObj = (style
|
|
439
|
-
activeStyle = (mediaObj
|
|
450
|
+
const mediaObj = getNestedStyle(style, `@${mediaTab}`) ?? {};
|
|
451
|
+
activeStyle = getNestedStyle(mediaObj, activeSelector) ?? {};
|
|
440
452
|
commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
441
453
|
mutateUpdateMediaNestedStyle(
|
|
442
454
|
t,
|
|
@@ -446,33 +458,27 @@ function styleSidebarTemplate(
|
|
|
446
458
|
prop,
|
|
447
459
|
val as string | undefined,
|
|
448
460
|
);
|
|
449
|
-
commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
450
|
-
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
451
461
|
} else if (activeSelector) {
|
|
452
|
-
activeStyle = (style
|
|
462
|
+
activeStyle = getNestedStyle(style, activeSelector) ?? {};
|
|
453
463
|
commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
454
464
|
mutateUpdateNestedStyle(t, sel, activeSelector, prop, val as string | undefined);
|
|
455
|
-
commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
456
|
-
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
457
|
-
} else if (mediaTab !== null && mediaNames.length > 0) {
|
|
458
|
-
activeStyle = {};
|
|
459
|
-
for (const [p, v] of Object.entries((style[`@${mediaTab}`] as Record<string, unknown>) || {})) {
|
|
460
|
-
if (typeof v !== "object") activeStyle[p] = v;
|
|
461
|
-
}
|
|
462
|
-
commitMutate = (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
463
|
-
mutateUpdateMediaStyle(t, sel, mediaTab, prop, val as string | undefined);
|
|
464
|
-
commitStyle = (prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
465
|
-
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
466
465
|
} else {
|
|
467
466
|
activeStyle = {};
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
const inMediaTab = mediaTab !== null && mediaNames.length > 0;
|
|
468
|
+
const flatSource = inMediaTab ? (getNestedStyle(style, `@${mediaTab}`) ?? {}) : style;
|
|
469
|
+
for (const [p, v] of Object.entries(flatSource)) {
|
|
470
|
+
if (typeof v !== "object") {
|
|
471
|
+
activeStyle[p] = v;
|
|
472
|
+
}
|
|
470
473
|
}
|
|
471
|
-
commitMutate =
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
474
|
+
commitMutate = inMediaTab
|
|
475
|
+
? (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
476
|
+
mutateUpdateMediaStyle(t, sel, mediaTab, prop, val as string | undefined)
|
|
477
|
+
: (t: Tab, prop: string, val: string | Record<string, unknown> | undefined) =>
|
|
478
|
+
mutateUpdateStyle(t, sel, prop, val as string | undefined);
|
|
475
479
|
}
|
|
480
|
+
const commitStyle = (prop: string, val?: string | Record<string, unknown> | undefined) =>
|
|
481
|
+
transactDoc(activeTab.value, (t) => commitMutate(t, prop, val));
|
|
476
482
|
|
|
477
483
|
// ── Compute inherited style from higher breakpoints ──────────────────────
|
|
478
484
|
const inheritedStyle: Record<string, string | number> = computeInheritedStyle(
|
|
@@ -490,12 +496,16 @@ function styleSidebarTemplate(
|
|
|
490
496
|
|
|
491
497
|
// Partition properties into sections
|
|
492
498
|
const sectionProps: Record<string, { prop: string; entry: CssPropertyEntry }[]> = {};
|
|
493
|
-
for (const sec of cssMeta.$sections)
|
|
499
|
+
for (const sec of cssMeta.$sections) {
|
|
500
|
+
sectionProps[sec.key] = [];
|
|
501
|
+
}
|
|
494
502
|
|
|
495
503
|
for (const [prop, entry] of Object.entries(cssMeta.$defs) as [string, CssPropertyEntry][]) {
|
|
496
|
-
if (typeof (entry as Record<string, unknown>).$shorthand === "string")
|
|
504
|
+
if (typeof (entry as Record<string, unknown>).$shorthand === "string") {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
497
507
|
const sec = ((entry as Record<string, unknown>).$section as string) || "other";
|
|
498
|
-
sectionProps[sec].push({
|
|
508
|
+
sectionProps[sec].push({ entry, prop });
|
|
499
509
|
}
|
|
500
510
|
for (const sec of cssMeta.$sections) {
|
|
501
511
|
sectionProps[sec.key].sort(
|
|
@@ -512,7 +522,9 @@ function styleSidebarTemplate(
|
|
|
512
522
|
for (const prop of Object.keys(activeStyle)) {
|
|
513
523
|
if (!(cssMeta.$defs as Record<string, unknown>)[prop]) {
|
|
514
524
|
const val = activeStyle[prop];
|
|
515
|
-
if (val !== null && typeof val === "object")
|
|
525
|
+
if (val !== null && typeof val === "object") {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
516
528
|
otherProps.push(prop);
|
|
517
529
|
}
|
|
518
530
|
}
|
|
@@ -537,7 +549,9 @@ function styleSidebarTemplate(
|
|
|
537
549
|
|
|
538
550
|
const sectionActiveProps = entries.filter(
|
|
539
551
|
({ prop, entry }: { prop: string; entry: CssPropertyEntry }) => {
|
|
540
|
-
if (activeStyle[prop] !== undefined)
|
|
552
|
+
if (activeStyle[prop] !== undefined) {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
541
555
|
if (inferInputType(entry) === "shorthand") {
|
|
542
556
|
return (getLonghands(prop) as CssLonghand[]).some(
|
|
543
557
|
(l: CssLonghand) => activeStyle[l.name] !== undefined,
|
|
@@ -573,11 +587,14 @@ function styleSidebarTemplate(
|
|
|
573
587
|
e.preventDefault();
|
|
574
588
|
transactDoc(activeTab.value, (t) => {
|
|
575
589
|
for (const { prop, entry } of sectionActiveProps) {
|
|
576
|
-
if (activeStyle[prop] !== undefined)
|
|
590
|
+
if (activeStyle[prop] !== undefined) {
|
|
591
|
+
commitMutate(t, prop);
|
|
592
|
+
}
|
|
577
593
|
if (inferInputType(entry) === "shorthand") {
|
|
578
594
|
for (const l of getLonghands(prop) as CssLonghand[]) {
|
|
579
|
-
if (activeStyle[l.name] !== undefined)
|
|
580
|
-
commitMutate(t, l.name
|
|
595
|
+
if (activeStyle[l.name] !== undefined) {
|
|
596
|
+
commitMutate(t, l.name);
|
|
597
|
+
}
|
|
581
598
|
}
|
|
582
599
|
}
|
|
583
600
|
}
|
|
@@ -597,26 +614,36 @@ function styleSidebarTemplate(
|
|
|
597
614
|
const hasVal = val !== undefined;
|
|
598
615
|
const condMet = allConditionsPass(entry, activeStyle);
|
|
599
616
|
const type = inferInputType(entry);
|
|
600
|
-
if (!hasVal && !condMet)
|
|
617
|
+
if (!hasVal && !condMet) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
601
620
|
|
|
602
621
|
if (filterText) {
|
|
603
622
|
const label = propLabel(entry, prop).toLowerCase();
|
|
604
|
-
if (!prop.includes(filterText) && !label.includes(filterText))
|
|
623
|
+
if (!prop.includes(filterText) && !label.includes(filterText)) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
605
626
|
}
|
|
606
627
|
if (filterActive) {
|
|
607
628
|
if (type === "shorthand") {
|
|
608
629
|
const longhands = getLonghands(prop) as CssLonghand[];
|
|
609
630
|
const hasAnySet =
|
|
610
631
|
hasVal || longhands.some((l: CssLonghand) => activeStyle[l.name] !== undefined);
|
|
611
|
-
if (!hasAnySet)
|
|
612
|
-
|
|
632
|
+
if (!hasAnySet) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
} else if (!hasVal) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
613
638
|
}
|
|
614
639
|
|
|
615
640
|
if (type === "shorthand") {
|
|
616
641
|
const longhands = getLonghands(prop) as CssLonghand[];
|
|
617
642
|
const hasAny =
|
|
618
643
|
hasVal || longhands.some((l: CssLonghand) => activeStyle[l.name] !== undefined);
|
|
619
|
-
if (!hasAny && !condMet)
|
|
644
|
+
if (!hasAny && !condMet) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
620
647
|
rows.push(
|
|
621
648
|
renderShorthandRow(prop, entry, activeStyle, commitMutate, () => {}, inheritedStyle),
|
|
622
649
|
);
|
|
@@ -629,7 +656,7 @@ function styleSidebarTemplate(
|
|
|
629
656
|
prop,
|
|
630
657
|
(val as string) ?? "",
|
|
631
658
|
(newVal: string | undefined) => commitStyle(prop, newVal || undefined),
|
|
632
|
-
() => commitStyle(prop
|
|
659
|
+
() => commitStyle(prop),
|
|
633
660
|
isWarning,
|
|
634
661
|
sec.$layout === "grid",
|
|
635
662
|
inheritedStyle[prop] as string | undefined,
|
|
@@ -639,7 +666,9 @@ function styleSidebarTemplate(
|
|
|
639
666
|
}
|
|
640
667
|
}
|
|
641
668
|
|
|
642
|
-
if (isFiltering && rows.length === 0)
|
|
669
|
+
if (isFiltering && rows.length === 0) {
|
|
670
|
+
return nothing;
|
|
671
|
+
}
|
|
643
672
|
|
|
644
673
|
return html`
|
|
645
674
|
<sp-accordion-item
|
|
@@ -664,11 +693,14 @@ function styleSidebarTemplate(
|
|
|
664
693
|
e.preventDefault();
|
|
665
694
|
transactDoc(activeTab.value, (t) => {
|
|
666
695
|
for (const { prop, entry } of sectionActiveProps) {
|
|
667
|
-
if (activeStyle[prop] !== undefined)
|
|
696
|
+
if (activeStyle[prop] !== undefined) {
|
|
697
|
+
commitMutate(t, prop);
|
|
698
|
+
}
|
|
668
699
|
if (inferInputType(entry) === "shorthand") {
|
|
669
700
|
for (const l of getLonghands(prop) as CssLonghand[]) {
|
|
670
|
-
if (activeStyle[l.name] !== undefined)
|
|
671
|
-
commitMutate(t, l.name
|
|
701
|
+
if (activeStyle[l.name] !== undefined) {
|
|
702
|
+
commitMutate(t, l.name);
|
|
703
|
+
}
|
|
672
704
|
}
|
|
673
705
|
}
|
|
674
706
|
}
|
|
@@ -709,7 +741,7 @@ function styleSidebarTemplate(
|
|
|
709
741
|
const newProp = (e.target as HTMLInputElement).value.trim();
|
|
710
742
|
if (newProp && newProp !== prop) {
|
|
711
743
|
transactDoc(activeTab.value, (t) => {
|
|
712
|
-
commitMutate(t, prop
|
|
744
|
+
commitMutate(t, prop);
|
|
713
745
|
commitMutate(t, newProp, String(activeStyle[prop]));
|
|
714
746
|
});
|
|
715
747
|
}
|
|
@@ -724,7 +756,7 @@ function styleSidebarTemplate(
|
|
|
724
756
|
commitStyle(prop, (e.target as HTMLInputElement).value);
|
|
725
757
|
})}
|
|
726
758
|
></sp-textfield>
|
|
727
|
-
<sp-action-button size="xs" quiet @click=${() => commitStyle(prop
|
|
759
|
+
<sp-action-button size="xs" quiet @click=${() => commitStyle(prop)}>
|
|
728
760
|
<sp-icon-close slot="icon"></sp-icon-close>
|
|
729
761
|
</sp-action-button>
|
|
730
762
|
</div>
|
|
@@ -782,7 +814,7 @@ function styleSidebarTemplate(
|
|
|
782
814
|
>
|
|
783
815
|
${rule}
|
|
784
816
|
</button>
|
|
785
|
-
<sp-action-button size="xs" quiet @click=${() => commitStyle(rule
|
|
817
|
+
<sp-action-button size="xs" quiet @click=${() => commitStyle(rule)}>
|
|
786
818
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
787
819
|
</sp-action-button>
|
|
788
820
|
</div>
|
|
@@ -791,6 +823,7 @@ function styleSidebarTemplate(
|
|
|
791
823
|
<button
|
|
792
824
|
style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:4px;color:var(--spectrum-gray-700, #a1a1aa);font-size:12px;cursor:pointer"
|
|
793
825
|
@click=${() => {
|
|
826
|
+
// oxlint-disable-next-line no-alert -- native prompt is the intended quick-input UX here
|
|
794
827
|
const name = prompt("Selector name (e.g. th, :hover, .active):");
|
|
795
828
|
if (name && name.trim()) {
|
|
796
829
|
commitStyle(name.trim(), {});
|
|
@@ -824,10 +857,14 @@ function styleSidebarTemplate(
|
|
|
824
857
|
*/
|
|
825
858
|
export function renderStylePanelTemplate(ctx: { getCanvasMode: () => string }) {
|
|
826
859
|
const tab = activeTab.value;
|
|
827
|
-
if (!tab)
|
|
860
|
+
if (!tab) {
|
|
861
|
+
return html`<div class="empty-state">No document loaded</div>`;
|
|
862
|
+
}
|
|
828
863
|
if (ctx.getCanvasMode() === "stylebook" && tab.session.ui.stylebookSelection) {
|
|
829
864
|
const node = tab.doc.document;
|
|
830
|
-
if (!node)
|
|
865
|
+
if (!node) {
|
|
866
|
+
return html`<div class="empty-state">No document loaded</div>`;
|
|
867
|
+
}
|
|
831
868
|
return html`
|
|
832
869
|
<div class="stylebook-style-header">
|
|
833
870
|
Styling: <${tab.session.ui.stylebookSelection}>
|
|
@@ -840,10 +877,13 @@ export function renderStylePanelTemplate(ctx: { getCanvasMode: () => string }) {
|
|
|
840
877
|
)}
|
|
841
878
|
`;
|
|
842
879
|
}
|
|
843
|
-
if (!tab.session.selection)
|
|
880
|
+
if (!tab.session.selection) {
|
|
844
881
|
return html`<div class="empty-state">Select an element to style</div>`;
|
|
882
|
+
}
|
|
845
883
|
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
846
|
-
if (!node)
|
|
884
|
+
if (!node) {
|
|
885
|
+
return html`<div class="empty-state">Select an element to style</div>`;
|
|
886
|
+
}
|
|
847
887
|
return styleSidebarTemplate(node, tab.session.ui.activeMedia, tab.session.ui.activeSelector);
|
|
848
888
|
}
|
|
849
889
|
|
|
@@ -870,7 +910,7 @@ export function _fieldRow(
|
|
|
870
910
|
></sp-textfield>`
|
|
871
911
|
: type === "checkbox"
|
|
872
912
|
? html`<sp-checkbox
|
|
873
|
-
?checked=${
|
|
913
|
+
?checked=${Boolean(value)}
|
|
874
914
|
@change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
|
|
875
915
|
></sp-checkbox>`
|
|
876
916
|
: html`<sp-textfield
|
|
@@ -7,7 +7,7 @@ import { activeTab } from "../workspace/workspace";
|
|
|
7
7
|
import { camelToKebab } from "../utils/studio-utils";
|
|
8
8
|
import cssMeta from "../../data/css-meta.json";
|
|
9
9
|
|
|
10
|
-
let cssInitialMap
|
|
10
|
+
let cssInitialMap = new Map<string, string>();
|
|
11
11
|
|
|
12
12
|
/** Initialise cssInitialMap from webdata — call once during bootstrap. */
|
|
13
13
|
export function initCssData(webdata: { cssProps: string[][] }) {
|
|
@@ -27,7 +27,9 @@ export function conditionPasses(
|
|
|
27
27
|
styles: Record<string, unknown>,
|
|
28
28
|
) {
|
|
29
29
|
const val = (styles[cond.prop] ?? "") as string;
|
|
30
|
-
if (cond.values.length === 0)
|
|
30
|
+
if (cond.values.length === 0) {
|
|
31
|
+
return val !== "" && val !== "initial";
|
|
32
|
+
}
|
|
31
33
|
return cond.values.includes(val);
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -51,10 +53,14 @@ export function autoOpenSections(node: JxMutableNode, currentSections: Record<st
|
|
|
51
53
|
const style = node.style || {};
|
|
52
54
|
const result = { ...currentSections };
|
|
53
55
|
for (const prop of Object.keys(style)) {
|
|
54
|
-
if (typeof style[prop] === "object")
|
|
56
|
+
if (typeof style[prop] === "object") {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
55
59
|
const entry = (cssMeta.$defs as Record<string, Record<string, unknown>>)[prop];
|
|
56
60
|
const section = (entry?.$section as string) ?? "other";
|
|
57
|
-
if (!result[section])
|
|
61
|
+
if (!result[section]) {
|
|
62
|
+
result[section] = true;
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
return result;
|
|
60
66
|
}
|
|
@@ -67,12 +73,12 @@ export function getLonghands(shorthandProp: string) {
|
|
|
67
73
|
if (entry?.$longhands) {
|
|
68
74
|
return (entry.$longhands as string[])
|
|
69
75
|
.map((name: string) => ({
|
|
70
|
-
name,
|
|
71
76
|
entry: (cssMeta.$defs as Record<string, Record<string, unknown>>)[name] || {
|
|
72
77
|
$order: 0,
|
|
73
78
|
},
|
|
79
|
+
name,
|
|
74
80
|
}))
|
|
75
|
-
.
|
|
81
|
+
.toSorted(
|
|
76
82
|
(
|
|
77
83
|
/** @type {{ entry: Record<string, unknown> }} */ a,
|
|
78
84
|
/** @type {{ entry: Record<string, unknown> }} */ b,
|
|
@@ -81,7 +87,9 @@ export function getLonghands(shorthandProp: string) {
|
|
|
81
87
|
}
|
|
82
88
|
const result = [];
|
|
83
89
|
for (const [name, e] of Object.entries(cssMeta.$defs) as [string, Record<string, unknown>][]) {
|
|
84
|
-
if (e.$shorthand === shorthandProp)
|
|
90
|
+
if (e.$shorthand === shorthandProp) {
|
|
91
|
+
result.push({ entry: e, name });
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
94
|
result.sort((a, b) => (a.entry.$order as number) - (b.entry.$order as number));
|
|
87
95
|
return result;
|
|
@@ -92,21 +100,37 @@ export function getLonghands(shorthandProp: string) {
|
|
|
92
100
|
* TRBL pattern.
|
|
93
101
|
*/
|
|
94
102
|
export function expandShorthand(shortVal: string, count: number) {
|
|
95
|
-
if (!shortVal)
|
|
103
|
+
if (!shortVal) {
|
|
104
|
+
return Array.from({ length: count }, () => "");
|
|
105
|
+
}
|
|
96
106
|
const parts = shortVal.trim().split(/\s+/);
|
|
97
|
-
if (count !== 4 || parts.length === 0)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (parts.length ===
|
|
107
|
+
if (count !== 4 || parts.length === 0) {
|
|
108
|
+
return Array.from({ length: count }, () => "");
|
|
109
|
+
}
|
|
110
|
+
if (parts.length === 1) {
|
|
111
|
+
return [parts[0], parts[0], parts[0], parts[0]];
|
|
112
|
+
}
|
|
113
|
+
if (parts.length === 2) {
|
|
114
|
+
return [parts[0], parts[1], parts[0], parts[1]];
|
|
115
|
+
}
|
|
116
|
+
if (parts.length === 3) {
|
|
117
|
+
return [parts[0], parts[1], parts[2], parts[1]];
|
|
118
|
+
}
|
|
101
119
|
return [parts[0], parts[1], parts[2], parts[3]];
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
/** Compress 4 TRBL values back into the shortest valid CSS shorthand string. */
|
|
105
123
|
export function compressShorthand(vals: string[]) {
|
|
106
124
|
const [t, r, b, l] = vals;
|
|
107
|
-
if (t === r && r === b && b === l)
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
if (t === r && r === b && b === l) {
|
|
126
|
+
return t;
|
|
127
|
+
}
|
|
128
|
+
if (t === b && r === l) {
|
|
129
|
+
return `${t} ${r}`;
|
|
130
|
+
}
|
|
131
|
+
if (r === l) {
|
|
132
|
+
return `${t} ${r} ${b}`;
|
|
133
|
+
}
|
|
110
134
|
return `${t} ${r} ${b} ${l}`;
|
|
111
135
|
}
|
|
112
136
|
|
|
@@ -132,21 +156,31 @@ export const BORDER_STYLES = new Set([
|
|
|
132
156
|
* @returns {string[]}
|
|
133
157
|
*/
|
|
134
158
|
export function expandBorderSide(value: string) {
|
|
135
|
-
if (!value)
|
|
159
|
+
if (!value) {
|
|
160
|
+
return ["", "", ""];
|
|
161
|
+
}
|
|
136
162
|
const tokens = [];
|
|
137
163
|
let current = "";
|
|
138
164
|
let depth = 0;
|
|
139
165
|
for (const ch of value.trim()) {
|
|
140
|
-
if (ch === "(")
|
|
141
|
-
|
|
166
|
+
if (ch === "(") {
|
|
167
|
+
depth += 1;
|
|
168
|
+
}
|
|
169
|
+
if (ch === ")") {
|
|
170
|
+
depth -= 1;
|
|
171
|
+
}
|
|
142
172
|
if (ch === " " && depth === 0) {
|
|
143
|
-
if (current)
|
|
173
|
+
if (current) {
|
|
174
|
+
tokens.push(current);
|
|
175
|
+
}
|
|
144
176
|
current = "";
|
|
145
177
|
} else {
|
|
146
178
|
current += ch;
|
|
147
179
|
}
|
|
148
180
|
}
|
|
149
|
-
if (current)
|
|
181
|
+
if (current) {
|
|
182
|
+
tokens.push(current);
|
|
183
|
+
}
|
|
150
184
|
|
|
151
185
|
let width = "";
|
|
152
186
|
let style = "";
|
|
@@ -180,7 +214,9 @@ export function compressBorderSide(vals: string[]) {
|
|
|
180
214
|
/** Extract --font-* CSS custom properties from the document root style. */
|
|
181
215
|
export function getFontVars() {
|
|
182
216
|
const style = activeTab.value?.doc.document?.style;
|
|
183
|
-
if (!style)
|
|
217
|
+
if (!style) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
184
220
|
const vars = [];
|
|
185
221
|
for (const [k, v] of Object.entries(style)) {
|
|
186
222
|
if (k.startsWith("--font") && (typeof v === "string" || typeof v === "number")) {
|
|
@@ -205,9 +241,13 @@ export function currentFontFamily() {
|
|
|
205
241
|
? getNodeAtPath(tab.doc.document, tab.session.selection)
|
|
206
242
|
: null;
|
|
207
243
|
const raw = node?.style?.fontFamily;
|
|
208
|
-
if (!raw)
|
|
244
|
+
if (!raw) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
209
247
|
const m = typeof raw === "string" && raw.match(/^var\((--[^)]+)\)$/);
|
|
210
|
-
if (m)
|
|
248
|
+
if (m) {
|
|
249
|
+
return tab?.doc.document?.style?.[m[1]] || "";
|
|
250
|
+
}
|
|
211
251
|
return raw;
|
|
212
252
|
}
|
|
213
253
|
|