@jxsuite/studio 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.css +98 -98
- package/dist/studio.js +4628 -2543
- package/dist/studio.js.map +58 -32
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +13 -11
- package/src/about/about-modal.ts +156 -0
- package/src/editor/context-menu.ts +4 -2
- package/src/files/files.ts +50 -18
- package/src/files/fs-events.ts +162 -0
- package/src/packages/ensure-deps.ts +39 -0
- package/src/packages/jxsuite-update.ts +131 -0
- package/src/packages/semver.ts +54 -0
- package/src/panels/activity-bar.ts +13 -1
- package/src/panels/ai-panel.ts +3 -1
- package/src/panels/dnd.ts +2 -2
- package/src/panels/elements-panel.ts +2 -1
- package/src/panels/layers-panel.ts +5 -1
- package/src/panels/preview-render.ts +1 -1
- package/src/panels/properties-panel.ts +20 -14
- package/src/panels/signals-panel.ts +29 -18
- package/src/panels/style-panel.ts +2 -2
- package/src/panels/stylebook-layers-panel.ts +5 -5
- package/src/panels/stylebook-panel.ts +1 -1
- package/src/platforms/devserver.ts +92 -2
- package/src/settings/dependencies-editor.ts +262 -0
- package/src/settings/general-settings.ts +5 -3
- package/src/settings/settings-modal.ts +6 -0
- package/src/studio.ts +21 -2
- package/src/tabs/tab.ts +10 -4
- package/src/types.ts +82 -1
- package/src/ui/expression-editor.ts +4 -3
- package/src/ui/field-input.ts +1 -1
- package/src/ui/media-picker.ts +1 -1
- package/src/ui/panel-resize.ts +8 -7
- package/src/ui/progress-modal.ts +94 -0
- package/src/ui/spectrum.ts +4 -0
- package/src/ui/unit-selector.ts +3 -0
- package/src/utils/canvas-media.ts +3 -3
- package/src/version.ts +34 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* On project open, compare the project's `@jxsuite/*` dependency ranges to the version this Studio
|
|
4
|
+
* build embeds (`VERSION`). If the project is behind, prompt the user to bump them to match and —
|
|
5
|
+
* on confirm — rewrite the ranges and reinstall. The target is the embedded version (not npm
|
|
6
|
+
* latest) so a project always lines up with the runtime/compiler the running app actually uses.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { html } from "lit-html";
|
|
10
|
+
import { getPlatform } from "../platform";
|
|
11
|
+
import { VERSION } from "../version";
|
|
12
|
+
import { showConfirmDialog } from "../ui/layers";
|
|
13
|
+
import { showProgressModal } from "../ui/progress-modal";
|
|
14
|
+
import { statusMessage } from "../panels/statusbar";
|
|
15
|
+
import { isComparable, isUpgrade } from "./semver";
|
|
16
|
+
import type { PackageInfo } from "../types";
|
|
17
|
+
|
|
18
|
+
const JXSUITE_PREFIX = "@jxsuite/";
|
|
19
|
+
|
|
20
|
+
export interface JxsuiteUpdate {
|
|
21
|
+
name: string;
|
|
22
|
+
current: string;
|
|
23
|
+
dev: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compare the project's @jxsuite/* deps to the embedded version. Returns the packages behind the
|
|
28
|
+
* target plus the target version, or null when there's nothing to do (dev build, no project, or all
|
|
29
|
+
* already current/ahead).
|
|
30
|
+
*/
|
|
31
|
+
export async function checkJxsuiteUpdate(): Promise<{
|
|
32
|
+
target: string;
|
|
33
|
+
outdated: JxsuiteUpdate[];
|
|
34
|
+
} | null> {
|
|
35
|
+
const target = VERSION;
|
|
36
|
+
if (!isComparable(target)) {
|
|
37
|
+
return null; // "dev" / non-semver build — no reliable target
|
|
38
|
+
}
|
|
39
|
+
const platform = getPlatform();
|
|
40
|
+
let pkgs: PackageInfo[];
|
|
41
|
+
try {
|
|
42
|
+
pkgs = await platform.listPackages();
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const outdated: JxsuiteUpdate[] = [];
|
|
47
|
+
for (const p of pkgs) {
|
|
48
|
+
if (!p.name.startsWith(JXSUITE_PREFIX) || !isComparable(p.version)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (isUpgrade(p.version, target)) {
|
|
52
|
+
outdated.push({ current: p.version, dev: Boolean(p.dev), name: p.name });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return outdated.length > 0 ? { outdated, target } : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function dismissKey(root: string, target: string): string {
|
|
59
|
+
return `jx:jxsuite-update-dismissed:${root}:${target}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isDismissed(root: string, target: string): boolean {
|
|
63
|
+
try {
|
|
64
|
+
return localStorage.getItem(dismissKey(root, target)) === "1";
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setDismissed(root: string, target: string): void {
|
|
71
|
+
try {
|
|
72
|
+
localStorage.setItem(dismissKey(root, target), "1");
|
|
73
|
+
} catch {
|
|
74
|
+
/* Ignore storage errors */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Apply a set of @jxsuite bumps to `^target` and reinstall, behind a progress modal. */
|
|
79
|
+
export async function applyJxsuiteUpdate(outdated: JxsuiteUpdate[], target: string): Promise<void> {
|
|
80
|
+
const platform = getPlatform();
|
|
81
|
+
if (!platform.setPackageVersions) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const progress = showProgressModal({
|
|
85
|
+
status: "Updating @jxsuite packages…",
|
|
86
|
+
title: "Updating dependencies",
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
const result = await platform.setPackageVersions(
|
|
90
|
+
outdated.map((p) => ({ dev: p.dev, name: p.name, version: `^${target}` })),
|
|
91
|
+
);
|
|
92
|
+
if (result.ok) {
|
|
93
|
+
progress.done();
|
|
94
|
+
statusMessage(`Updated ${outdated.length} @jxsuite package(s) to ${target}`);
|
|
95
|
+
} else {
|
|
96
|
+
progress.fail(result.log ?? "Update failed");
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
progress.fail(error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Prompt to update @jxsuite packages on open. Skips when there's nothing to do or the user already
|
|
105
|
+
* declined this exact target for this project (remembered in localStorage).
|
|
106
|
+
*/
|
|
107
|
+
export async function maybePromptJxsuiteUpdate(projectRoot: string): Promise<void> {
|
|
108
|
+
const platform = getPlatform();
|
|
109
|
+
if (!platform.setPackageVersions) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const check = await checkJxsuiteUpdate();
|
|
113
|
+
if (!check || isDismissed(projectRoot, check.target)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const list = check.outdated.map((p) => `${p.name} ${p.current} → ^${check.target}`).join("\n");
|
|
117
|
+
const confirmed = await showConfirmDialog(
|
|
118
|
+
"Update @jxsuite packages?",
|
|
119
|
+
html`This project uses older @jxsuite packages. Update them to match Studio ${check.target}?
|
|
120
|
+
<br /><br /><span
|
|
121
|
+
style="font-size:var(--spectrum-font-size-75, 12px);color:var(--fg-dim);white-space:pre-line"
|
|
122
|
+
>${list}</span
|
|
123
|
+
>`,
|
|
124
|
+
{ cancelLabel: "Not now", confirmLabel: "Update" },
|
|
125
|
+
);
|
|
126
|
+
if (!confirmed) {
|
|
127
|
+
setDismissed(projectRoot, check.target);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await applyJxsuiteUpdate(check.outdated, check.target);
|
|
131
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal semver helpers for comparing dependency versions in the UI. Deliberately tiny — we only
|
|
3
|
+
* need range stripping and ordering of `x.y.z` triples, not full semver range satisfaction, so we
|
|
4
|
+
* avoid pulling in a dependency.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Strip a range prefix to its base version: "^0.19.0" → "0.19.0", "~1.2.3" → "1.2.3". */
|
|
8
|
+
export function stripRange(range: string): string {
|
|
9
|
+
const base = range.replace(/^[\^~>=<v\s]+/, "").trim();
|
|
10
|
+
return base.split(/\s+/)[0] ?? "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether a spec is a comparable registry semver (a plain `x.y.z` range), as opposed to
|
|
15
|
+
* `workspace:`, `file:`, `link:`, a git/URL spec, or a wildcard like `*`/`latest`.
|
|
16
|
+
*/
|
|
17
|
+
export function isComparable(spec: string): boolean {
|
|
18
|
+
if (spec.includes(":")) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return /^\d+\.\d+/.test(stripRange(spec));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse a version into numeric [major, minor, patch], ignoring any pre-release/build suffix. */
|
|
25
|
+
function parts(version: string): [number, number, number] {
|
|
26
|
+
const core = stripRange(version).split(/[-+]/)[0] ?? "";
|
|
27
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map((n) => Math.trunc(Number(n)) || 0);
|
|
28
|
+
return [major, minor, patch];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compare two versions (range prefixes tolerated). Returns -1 if a < b, 1 if a > b, 0 if equal.
|
|
33
|
+
* Pre-release/build metadata is ignored.
|
|
34
|
+
*/
|
|
35
|
+
export function compareSemver(a: string, b: string): number {
|
|
36
|
+
const pa = parts(a);
|
|
37
|
+
const pb = parts(b);
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const da = pa[i] ?? 0;
|
|
40
|
+
const db = pb[i] ?? 0;
|
|
41
|
+
if (da !== db) {
|
|
42
|
+
return da < db ? -1 : 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Whether `latest` is strictly newer than the base version of `current` (both comparable). */
|
|
49
|
+
export function isUpgrade(current: string, latest: string): boolean {
|
|
50
|
+
if (!isComparable(current) || !isComparable(latest)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return compareSemver(latest, current) > 0;
|
|
54
|
+
}
|
|
@@ -7,6 +7,7 @@ import { effect, effectScope } from "../reactivity";
|
|
|
7
7
|
import { activeTab } from "../workspace/workspace";
|
|
8
8
|
import { applyPanelCollapse, view } from "../view";
|
|
9
9
|
import { openSettingsModal } from "../settings/settings-modal";
|
|
10
|
+
import { openAboutModal } from "../about/about-modal";
|
|
10
11
|
import { refreshGitStatus } from "./git-panel";
|
|
11
12
|
import type { EffectScope } from "@vue/reactivity";
|
|
12
13
|
import type { TemplateResult } from "lit-html";
|
|
@@ -129,7 +130,18 @@ export function renderActivityBar() {
|
|
|
129
130
|
`,
|
|
130
131
|
)}
|
|
131
132
|
</sp-tabs>
|
|
132
|
-
<div
|
|
133
|
+
<div
|
|
134
|
+
style="margin-top:auto;padding:8px 0;display:flex;flex-direction:column;align-items:center;gap:4px"
|
|
135
|
+
>
|
|
136
|
+
<sp-action-button
|
|
137
|
+
quiet
|
|
138
|
+
size="m"
|
|
139
|
+
title="About"
|
|
140
|
+
aria-label="About"
|
|
141
|
+
@click=${() => openAboutModal()}
|
|
142
|
+
>
|
|
143
|
+
<sp-icon-info slot="icon"></sp-icon-info>
|
|
144
|
+
</sp-action-button>
|
|
133
145
|
<sp-action-button
|
|
134
146
|
quiet
|
|
135
147
|
size="m"
|
package/src/panels/ai-panel.ts
CHANGED
|
@@ -457,7 +457,9 @@ export function renderAiPanelTemplate() {
|
|
|
457
457
|
<div class="ai-status-center">
|
|
458
458
|
<sp-icon-artboard style="font-size:32px"></sp-icon-artboard>
|
|
459
459
|
<div>Claude authentication required</div>
|
|
460
|
-
<div
|
|
460
|
+
<div
|
|
461
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--spectrum-gray-600, #808080)"
|
|
462
|
+
>
|
|
461
463
|
Run the following in your terminal:
|
|
462
464
|
</div>
|
|
463
465
|
<code class="ai-code-snippet">npx @anthropic-ai/claude-code login</code>
|
package/src/panels/dnd.ts
CHANGED
|
@@ -67,8 +67,8 @@ export function registerLayersDnD() {
|
|
|
67
67
|
for (const row of container.querySelectorAll("[data-dnd-row]") as NodeListOf<HTMLElement>) {
|
|
68
68
|
const rowPath = (row.dataset.path as string)
|
|
69
69
|
.split("/")
|
|
70
|
-
.map((s: string) => (/^\d+$/.test(s) ?
|
|
71
|
-
const rowDepth =
|
|
70
|
+
.map((s: string) => (/^\d+$/.test(s) ? Math.trunc(Number(s)) : s)) as JxPath;
|
|
71
|
+
const rowDepth = Math.trunc(Number(row.dataset.dndDepth as string)) || 0;
|
|
72
72
|
const isVoid = Object.hasOwn(row.dataset, "dndVoid");
|
|
73
73
|
const isExpanded = Object.hasOwn(row.dataset, "dndExpanded");
|
|
74
74
|
|
|
@@ -141,7 +141,8 @@ export function renderElementsTemplate(ctx: {
|
|
|
141
141
|
}}
|
|
142
142
|
>
|
|
143
143
|
<div class="element-card-preview">
|
|
144
|
-
<span
|
|
144
|
+
<span
|
|
145
|
+
style="color:var(--fg-dim);font-size:var(--spectrum-font-size-50, 11px);font-style:italic"
|
|
145
146
|
><${comp.tagName}></span
|
|
146
147
|
>
|
|
147
148
|
</div>
|
|
@@ -151,7 +151,11 @@ export function renderLayersTemplate(ctx: {
|
|
|
151
151
|
class="layer-row"
|
|
152
152
|
style="padding-left:${depth * 16 + 8}px; opacity: 0.6; font-style: italic;"
|
|
153
153
|
>
|
|
154
|
-
<span
|
|
154
|
+
<span
|
|
155
|
+
class="layer-tag"
|
|
156
|
+
style="background: var(--spectrum-gray-500, #64748b); font-size: 0.65rem;"
|
|
157
|
+
>text</span
|
|
158
|
+
>
|
|
155
159
|
<span class="layer-label">${textPreview}</span>
|
|
156
160
|
</div>
|
|
157
161
|
`,
|
|
@@ -122,7 +122,7 @@ export function renderCanvasNode(
|
|
|
122
122
|
const placeholder = document.createElement("div");
|
|
123
123
|
placeholder.textContent = `[$switch: ${keys.join(" | ")}]`;
|
|
124
124
|
placeholder.style.cssText =
|
|
125
|
-
"font-family:
|
|
125
|
+
"font-family:var(--font-mono);font-size:var(--spectrum-font-size-50,11px);padding:6px 10px;background:color-mix(in srgb, var(--danger) 8%, transparent);border:1px dashed color-mix(in srgb, var(--danger) 40%, transparent);border-radius:var(--radius);color:var(--danger);font-style:italic";
|
|
126
126
|
el.append(placeholder);
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -331,7 +331,7 @@ function renderSwitchFieldsTemplate(
|
|
|
331
331
|
mapSignals,
|
|
332
332
|
)}
|
|
333
333
|
<div
|
|
334
|
-
style="font-size:11px;font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
334
|
+
style="font-size:var(--spectrum-font-size-50, 11px);font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
335
335
|
>
|
|
336
336
|
Cases
|
|
337
337
|
</div>
|
|
@@ -369,7 +369,7 @@ function renderSwitchFieldsTemplate(
|
|
|
369
369
|
>→</span
|
|
370
370
|
>
|
|
371
371
|
<span
|
|
372
|
-
style="cursor:pointer;color:var(--danger);font-size:11px"
|
|
372
|
+
style="cursor:pointer;color:var(--danger);font-size:var(--spectrum-font-size-50, 11px)"
|
|
373
373
|
@click=${(e: Event) => {
|
|
374
374
|
e.stopPropagation();
|
|
375
375
|
transactDoc(activeTab.value!, (t) => mutateRemoveSwitchCase(t, path, caseName));
|
|
@@ -657,7 +657,7 @@ function renderMediaFieldsTemplate(node: JxMutableNode) {
|
|
|
657
657
|
}}
|
|
658
658
|
/>
|
|
659
659
|
<span
|
|
660
|
-
style="font-size:10px;color:var(--fg-dim);font-family:
|
|
660
|
+
style="font-size:10px;color:var(--fg-dim);font-family:var(--font-mono);white-space:nowrap"
|
|
661
661
|
>${view.addBreakpointPreview}</span
|
|
662
662
|
>
|
|
663
663
|
</div>
|
|
@@ -714,7 +714,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
|
|
|
714
714
|
<input
|
|
715
715
|
class="field-input"
|
|
716
716
|
.value=${live(mediaDisplayName(name))}
|
|
717
|
-
style="flex:1;font-weight:600;font-size:12px"
|
|
717
|
+
style="flex:1;font-weight:600;font-size:var(--spectrum-font-size-75, 12px)"
|
|
718
718
|
@input=${(e: Event) => {
|
|
719
719
|
const newKey = friendlyNameToMedia((e.target as HTMLInputElement).value);
|
|
720
720
|
currentRawLabel = newKey || "";
|
|
@@ -738,7 +738,7 @@ function mediaBreakpointRowTemplate(name: string, query: string) {
|
|
|
738
738
|
/>
|
|
739
739
|
<span
|
|
740
740
|
class="bp-raw-label"
|
|
741
|
-
style="font-size:10px;color:var(--fg-dim);font-family:
|
|
741
|
+
style="font-size:10px;color:var(--fg-dim);font-family:var(--font-mono);white-space:nowrap"
|
|
742
742
|
>${name}</span
|
|
743
743
|
>
|
|
744
744
|
<span
|
|
@@ -892,14 +892,18 @@ function renderLayoutSelectionPanel(ctx: { navigateToComponent: (path: string) =
|
|
|
892
892
|
style="font-size:9px;padding:2px 6px;background:var(--spectrum-purple-600);color:white;border-radius:3px;text-transform:uppercase;letter-spacing:0.5px"
|
|
893
893
|
>Layout</span
|
|
894
894
|
>
|
|
895
|
-
<code
|
|
895
|
+
<code
|
|
896
|
+
style="font-size:var(--spectrum-font-size-75, 12px);font-family:var(--font-mono)"
|
|
897
|
+
><${tagName}></code
|
|
898
|
+
>
|
|
896
899
|
</div>
|
|
897
900
|
${className
|
|
898
901
|
? html`<div class="style-row">
|
|
899
902
|
<div class="style-row-label">
|
|
900
903
|
<sp-field-label size="s">Class</sp-field-label>
|
|
901
904
|
</div>
|
|
902
|
-
<span
|
|
905
|
+
<span
|
|
906
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);word-break:break-all"
|
|
903
907
|
>${className}</span
|
|
904
908
|
>
|
|
905
909
|
</div>`
|
|
@@ -1234,9 +1238,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1234
1238
|
const def = d as Record<string, unknown>;
|
|
1235
1239
|
return html`
|
|
1236
1240
|
<div
|
|
1237
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1241
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1238
1242
|
>
|
|
1239
|
-
<code style="font-family:
|
|
1243
|
+
<code style="font-family:var(--font-mono);color:var(--accent)"
|
|
1240
1244
|
>${def.attribute}</code
|
|
1241
1245
|
>
|
|
1242
1246
|
<span style="color:var(--fg-dim)"> → </span>
|
|
@@ -1248,7 +1252,7 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1248
1252
|
: nothing}
|
|
1249
1253
|
${def.reflects
|
|
1250
1254
|
? html`<span
|
|
1251
|
-
style="font-size:9px;background:var(--bg
|
|
1255
|
+
style="font-size:9px;background:var(--hover-bg);padding:1px 4px;border-radius:var(--radius)"
|
|
1252
1256
|
>reflects</span
|
|
1253
1257
|
>`
|
|
1254
1258
|
: nothing}
|
|
@@ -1344,9 +1348,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1344
1348
|
${cssProps.map(
|
|
1345
1349
|
([prop, val]) => html`
|
|
1346
1350
|
<div
|
|
1347
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1351
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1348
1352
|
>
|
|
1349
|
-
<code style="font-family:
|
|
1353
|
+
<code style="font-family:var(--font-mono);color:var(--accent)">${prop}</code>
|
|
1350
1354
|
<span style="margin-left:auto;color:var(--fg-dim)">${String(val)}</span>
|
|
1351
1355
|
</div>
|
|
1352
1356
|
`,
|
|
@@ -1374,9 +1378,11 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1374
1378
|
${parts.map(
|
|
1375
1379
|
(p) => html`
|
|
1376
1380
|
<div
|
|
1377
|
-
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:11px"
|
|
1381
|
+
style="display:flex;gap:6px;align-items:center;padding:2px 0;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1378
1382
|
>
|
|
1379
|
-
<code style="font-family:
|
|
1383
|
+
<code style="font-family:var(--font-mono);color:var(--accent)"
|
|
1384
|
+
>${p.name}</code
|
|
1385
|
+
>
|
|
1380
1386
|
<span style="color:var(--fg-dim)"><${p.tag}></span>
|
|
1381
1387
|
</div>
|
|
1382
1388
|
`,
|
|
@@ -706,9 +706,9 @@ function renderSignalEditorTemplate(
|
|
|
706
706
|
: signalFieldRow("Default", defaultVal, (v: string) => {
|
|
707
707
|
let parsed: unknown = v;
|
|
708
708
|
if (def.type === "integer") {
|
|
709
|
-
parsed =
|
|
709
|
+
parsed = Math.trunc(Number(v)) || 0;
|
|
710
710
|
} else if (def.type === "number") {
|
|
711
|
-
parsed = Number
|
|
711
|
+
parsed = Number(v) || 0;
|
|
712
712
|
} else if (def.type === "boolean") {
|
|
713
713
|
parsed = v === "true";
|
|
714
714
|
} else if (def.type === "array" || def.type === "object") {
|
|
@@ -860,7 +860,7 @@ function renderDataSourceFields(
|
|
|
860
860
|
)}
|
|
861
861
|
${signalFieldRow("Version", String(def.version || 1), (v: string) =>
|
|
862
862
|
transactDoc(activeTab.value, (t) =>
|
|
863
|
-
mutateUpdateDef(t, name, { version:
|
|
863
|
+
mutateUpdateDef(t, name, { version: Math.trunc(Number(v)) || 1 }),
|
|
864
864
|
),
|
|
865
865
|
)}
|
|
866
866
|
`;
|
|
@@ -943,7 +943,7 @@ function renderFunctionFields(
|
|
|
943
943
|
</div>
|
|
944
944
|
<textarea
|
|
945
945
|
class="field-input"
|
|
946
|
-
style="min-height:60px;font-family:
|
|
946
|
+
style="min-height:60px;font-family:var(--font-mono);font-size:var(--spectrum-font-size-50, 11px)"
|
|
947
947
|
.value=${def.body || ""}
|
|
948
948
|
@input=${(e: Event) => {
|
|
949
949
|
const v = (e.target as HTMLInputElement).value;
|
|
@@ -981,7 +981,7 @@ function renderParameterEditorTemplate(
|
|
|
981
981
|
${params.map(
|
|
982
982
|
(p: CemParameter, i: number) => html`
|
|
983
983
|
<span
|
|
984
|
-
style="display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:
|
|
984
|
+
style="display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:var(--radius);background:var(--hover-bg);font-size:var(--spectrum-font-size-50, 11px);font-family:var(--font-mono)"
|
|
985
985
|
>
|
|
986
986
|
${p.name || "?"}
|
|
987
987
|
<span
|
|
@@ -1002,7 +1002,7 @@ function renderParameterEditorTemplate(
|
|
|
1002
1002
|
)}
|
|
1003
1003
|
<input
|
|
1004
1004
|
class="field-input"
|
|
1005
|
-
style="width:60px;flex:0 0 auto;font-size:11px"
|
|
1005
|
+
style="width:60px;flex:0 0 auto;font-size:var(--spectrum-font-size-50, 11px)"
|
|
1006
1006
|
placeholder="+"
|
|
1007
1007
|
@keydown=${(e: KeyboardEvent) => {
|
|
1008
1008
|
if (e.key === "Enter" && (e.target as HTMLInputElement).value.trim()) {
|
|
@@ -1145,7 +1145,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1145
1145
|
|
|
1146
1146
|
return html`
|
|
1147
1147
|
<div
|
|
1148
|
-
style="font-size:11px;font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
1148
|
+
style="font-size:var(--spectrum-font-size-50, 11px);font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
1149
1149
|
>
|
|
1150
1150
|
Emits
|
|
1151
1151
|
</div>
|
|
@@ -1279,6 +1279,14 @@ function resolveSchemaEnum(
|
|
|
1279
1279
|
* @param {(val: unknown) => void} onChange
|
|
1280
1280
|
* @param {Record<string, unknown>} [parentDef] - Parent def for resolving dependent enum refs
|
|
1281
1281
|
*/
|
|
1282
|
+
/** Parse a numeric field value, returning NaN for blank input (so callers can treat it as unset). */
|
|
1283
|
+
function parseNumericField(raw: string, integer: boolean): number {
|
|
1284
|
+
if (raw.trim() === "") {
|
|
1285
|
+
return Number.NaN;
|
|
1286
|
+
}
|
|
1287
|
+
return integer ? Math.trunc(Number(raw)) : Number(raw);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1282
1290
|
function renderInlineField(
|
|
1283
1291
|
key: string,
|
|
1284
1292
|
schema: Record<string, unknown>,
|
|
@@ -1319,10 +1327,10 @@ function renderInlineField(
|
|
|
1319
1327
|
.value=${value !== undefined ? value : nothing}
|
|
1320
1328
|
step=${schema.type === "integer" ? "1" : nothing}
|
|
1321
1329
|
@change=${(e: Event) => {
|
|
1322
|
-
const parsed =
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1330
|
+
const parsed = parseNumericField(
|
|
1331
|
+
(e.target as HTMLInputElement).value,
|
|
1332
|
+
schema.type === "integer",
|
|
1333
|
+
);
|
|
1326
1334
|
onChange(Number.isNaN(parsed) ? undefined : parsed);
|
|
1327
1335
|
}}
|
|
1328
1336
|
></sp-number-field>`;
|
|
@@ -1439,10 +1447,10 @@ export function renderSchemaFieldsTemplate(
|
|
|
1439
1447
|
@change=${(e: Event) => {
|
|
1440
1448
|
clearTimeout(debounce);
|
|
1441
1449
|
debounce = setTimeout(() => {
|
|
1442
|
-
const parsed =
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1450
|
+
const parsed = parseNumericField(
|
|
1451
|
+
(e.target as HTMLInputElement).value,
|
|
1452
|
+
ps.type === "integer",
|
|
1453
|
+
);
|
|
1446
1454
|
transactDoc(activeTab.value, (t) =>
|
|
1447
1455
|
mutateUpdateDef(t, name, {
|
|
1448
1456
|
[prop]: Number.isNaN(parsed) ? undefined : parsed,
|
|
@@ -1466,7 +1474,7 @@ export function renderSchemaFieldsTemplate(
|
|
|
1466
1474
|
${Object.entries(cv.properties as Record<string, Record<string, unknown>>).map(
|
|
1467
1475
|
([k, v]) => html`
|
|
1468
1476
|
<span
|
|
1469
|
-
style="background:var(--bg
|
|
1477
|
+
style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
|
|
1470
1478
|
>${k}: ${v.type ?? "any"}</span
|
|
1471
1479
|
>
|
|
1472
1480
|
`,
|
|
@@ -1634,7 +1642,7 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1634
1642
|
} else {
|
|
1635
1643
|
// Trigger async load — will re-render when cached
|
|
1636
1644
|
schemaContent = html`<div
|
|
1637
|
-
style="padding:4px 0;font-size:11px;color:var(--fg-dim);font-style:italic"
|
|
1645
|
+
style="padding:4px 0;font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);font-style:italic"
|
|
1638
1646
|
>
|
|
1639
1647
|
Loading schema…
|
|
1640
1648
|
</div>`;
|
|
@@ -1650,7 +1658,10 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1650
1658
|
|
|
1651
1659
|
return html`
|
|
1652
1660
|
${importedPath
|
|
1653
|
-
? html`<div
|
|
1661
|
+
? html`<div
|
|
1662
|
+
class="signal-hint"
|
|
1663
|
+
style="padding:4px 0 2px;font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim)"
|
|
1664
|
+
>
|
|
1654
1665
|
${def.$prototype}
|
|
1655
1666
|
</div>`
|
|
1656
1667
|
: html`
|
|
@@ -804,7 +804,7 @@ function styleSidebarTemplate(
|
|
|
804
804
|
(rule) => html`
|
|
805
805
|
<div style="display:flex;align-items:center;gap:4px">
|
|
806
806
|
<button
|
|
807
|
-
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:
|
|
807
|
+
style="flex:1;text-align:left;padding:6px 10px;background:var(--spectrum-gray-200, #1a1a1a);border:none;border-radius:var(--radius);color:var(--spectrum-gray-900, #fafafa);font-size:var(--spectrum-font-size-75, 12px);cursor:pointer"
|
|
808
808
|
@click=${() => {
|
|
809
809
|
const newSelector = activeSelector ? `${activeSelector} ${rule}` : rule;
|
|
810
810
|
selectStylebookTag(newSelector, undefined, {
|
|
@@ -821,7 +821,7 @@ function styleSidebarTemplate(
|
|
|
821
821
|
`,
|
|
822
822
|
)}
|
|
823
823
|
<button
|
|
824
|
-
style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:
|
|
824
|
+
style="padding:6px 10px;background:none;border:1px dashed var(--spectrum-gray-400, #333);border-radius:var(--radius);color:var(--spectrum-gray-700, #a1a1aa);font-size:var(--spectrum-font-size-75, 12px);cursor:pointer"
|
|
825
825
|
@click=${() => {
|
|
826
826
|
// oxlint-disable-next-line no-alert -- native prompt is the intended quick-input UX here
|
|
827
827
|
const name = prompt("Selector name (e.g. th, :hover, .active):");
|
|
@@ -111,19 +111,19 @@ export function renderStylebookLayersTemplate(ctx: {
|
|
|
111
111
|
const style = rootStyle;
|
|
112
112
|
const vars = Object.entries(style).filter(([k]) => k.startsWith("--"));
|
|
113
113
|
if (vars.length === 0) {
|
|
114
|
-
return html`<div
|
|
114
|
+
return html`<div
|
|
115
|
+
style="padding:16px;text-align:center;color:var(--fg-dim);font-size:var(--spectrum-font-size-75, 12px)"
|
|
116
|
+
>
|
|
115
117
|
No variables defined
|
|
116
118
|
</div>`;
|
|
117
119
|
}
|
|
118
120
|
return html`${vars.map(
|
|
119
121
|
([k, v]) => html`
|
|
120
122
|
<div class="layer-row">
|
|
121
|
-
<span class="layer-tag" style="font-size:10px;font-family:
|
|
122
|
-
>var</span
|
|
123
|
-
>
|
|
123
|
+
<span class="layer-tag" style="font-size:10px;font-family:var(--font-mono)">var</span>
|
|
124
124
|
<span class="layer-label">${k}</span>
|
|
125
125
|
<span
|
|
126
|
-
style="font-size:11px;color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:80px"
|
|
126
|
+
style="font-size:var(--spectrum-font-size-50, 11px);color:var(--fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:80px"
|
|
127
127
|
>${String(v)}</span
|
|
128
128
|
>
|
|
129
129
|
</div>
|
|
@@ -592,7 +592,7 @@ export async function renderComponentPreview(comp: ComponentEntry) {
|
|
|
592
592
|
function _componentFallback(tagName: string) {
|
|
593
593
|
const fallback = document.createElement("div");
|
|
594
594
|
fallback.style.cssText =
|
|
595
|
-
"padding:12px;border:1px dashed var(--border);border-radius:
|
|
595
|
+
"padding:12px;border:1px dashed var(--border);border-radius:var(--radius);color:var(--fg-dim)";
|
|
596
596
|
fallback.textContent = `<${tagName}>`;
|
|
597
597
|
return fallback;
|
|
598
598
|
}
|