@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
|
@@ -13,19 +13,25 @@ import { projectState } from "../state";
|
|
|
13
13
|
import type { JsonValue } from "../types";
|
|
14
14
|
import { activeTab } from "../workspace/workspace";
|
|
15
15
|
import {
|
|
16
|
-
transactDoc,
|
|
17
|
-
mutateUpdateDef,
|
|
18
16
|
mutateAddDef,
|
|
19
17
|
mutateRemoveDef,
|
|
20
18
|
mutateRenameDef,
|
|
19
|
+
mutateUpdateDef,
|
|
20
|
+
transactDoc,
|
|
21
21
|
} from "../tabs/transact";
|
|
22
22
|
import { renderFieldRow } from "../ui/field-row";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { rawTextArea, spTextField } from "../ui/field-input";
|
|
24
|
+
import { expressionHint, renderExpressionEditor } from "../ui/expression-editor";
|
|
25
25
|
import { renderMediaPicker } from "../ui/media-picker";
|
|
26
26
|
import type { TabUi } from "../tabs/tab";
|
|
27
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
CemEvent,
|
|
29
|
+
CemParameter,
|
|
30
|
+
JxMutableNode,
|
|
31
|
+
JxStateDefinition,
|
|
32
|
+
} from "@jxsuite/schema/types";
|
|
28
33
|
import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services";
|
|
34
|
+
import type { TemplateResult } from "lit-html";
|
|
29
35
|
|
|
30
36
|
interface SignalsPanelState {
|
|
31
37
|
document: JxMutableNode;
|
|
@@ -38,22 +44,9 @@ interface SignalsPanelState {
|
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
interface SignalsPanelCtx {
|
|
41
|
-
renderLeftPanel()
|
|
42
|
-
renderCanvas()
|
|
43
|
-
updateSession(patch: Record<string, unknown>)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface CemParameter {
|
|
47
|
-
name: string;
|
|
48
|
-
type?: { text?: string };
|
|
49
|
-
description?: string;
|
|
50
|
-
optional?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface CemEvent {
|
|
54
|
-
name: string;
|
|
55
|
-
type?: { text?: string };
|
|
56
|
-
description?: string;
|
|
47
|
+
renderLeftPanel: () => void;
|
|
48
|
+
renderCanvas: () => void;
|
|
49
|
+
updateSession: (patch: Record<string, unknown>) => void;
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
interface JsonSchema {
|
|
@@ -120,19 +113,19 @@ const advancedParamOpen = new Set();
|
|
|
120
113
|
|
|
121
114
|
/** Default templates for creating new signal definitions. */
|
|
122
115
|
const DEF_TEMPLATES = {
|
|
123
|
-
state: { type: "string", default: "" },
|
|
124
116
|
computed: { $compute: "", $deps: [] },
|
|
125
|
-
|
|
126
|
-
localStorage: { $prototype: "LocalStorage", key: "", default: null },
|
|
127
|
-
sessionStorage: { $prototype: "SessionStorage", key: "", default: null },
|
|
128
|
-
indexedDB: { $prototype: "IndexedDB", database: "", store: "", version: 1 },
|
|
129
|
-
cookie: { $prototype: "Cookie", name: "", default: "" },
|
|
130
|
-
set: { $prototype: "Set", default: [] },
|
|
131
|
-
map: { $prototype: "Map", default: {} },
|
|
132
|
-
formData: { $prototype: "FormData", fields: {} },
|
|
133
|
-
function: { $prototype: "Function", body: "", parameters: [] },
|
|
117
|
+
cookie: { $prototype: "Cookie", default: "", name: "" },
|
|
134
118
|
expression: { $expression: { operator: "=", target: null } },
|
|
135
119
|
external: { $prototype: "", $src: "" },
|
|
120
|
+
formData: { $prototype: "FormData", fields: {} },
|
|
121
|
+
function: { $prototype: "Function", body: "", parameters: [] },
|
|
122
|
+
indexedDB: { $prototype: "IndexedDB", database: "", store: "", version: 1 },
|
|
123
|
+
localStorage: { $prototype: "LocalStorage", default: null, key: "" },
|
|
124
|
+
map: { $prototype: "Map", default: {} },
|
|
125
|
+
request: { $prototype: "Request", method: "GET", timing: "client", url: "" },
|
|
126
|
+
sessionStorage: { $prototype: "SessionStorage", default: null, key: "" },
|
|
127
|
+
set: { $prototype: "Set", default: [] },
|
|
128
|
+
state: { default: "", type: "string" },
|
|
136
129
|
} as Record<string, SignalDef>;
|
|
137
130
|
|
|
138
131
|
/** Keys handled by the framework — skip when rendering schema fields. */
|
|
@@ -154,18 +147,39 @@ const STUDIO_RESERVED_KEYS = new Set([
|
|
|
154
147
|
|
|
155
148
|
// ─── Signals / defs helpers ──────────────────────────────────────────────────
|
|
156
149
|
|
|
150
|
+
/**
|
|
151
|
+
* View a state entry through the panel's flattened editing lens. Naked primitive and array entries
|
|
152
|
+
* surface as an empty view — the renderers guard every field access.
|
|
153
|
+
*
|
|
154
|
+
* @param {import("@jxsuite/schema/types").JxStateDefinition} def
|
|
155
|
+
* @returns {SignalDef}
|
|
156
|
+
*/
|
|
157
|
+
function asSignalDef(def: JxStateDefinition): SignalDef {
|
|
158
|
+
return (typeof def === "object" && def !== null && !Array.isArray(def) ? def : {}) as SignalDef;
|
|
159
|
+
}
|
|
160
|
+
|
|
157
161
|
/**
|
|
158
162
|
* Classify a state entry into a category string.
|
|
159
163
|
*
|
|
160
164
|
* @param {SignalDef | unknown} def
|
|
161
165
|
*/
|
|
162
166
|
export function defCategory(def: SignalDef | unknown) {
|
|
163
|
-
if (!def)
|
|
167
|
+
if (!def) {
|
|
168
|
+
return "state";
|
|
169
|
+
}
|
|
164
170
|
const d = def as SignalDef;
|
|
165
|
-
if (d.$expression)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (d.$prototype
|
|
171
|
+
if (d.$expression) {
|
|
172
|
+
return "expression";
|
|
173
|
+
}
|
|
174
|
+
if (d.$handler || d.$prototype === "Function") {
|
|
175
|
+
return "function";
|
|
176
|
+
}
|
|
177
|
+
if (d.$compute) {
|
|
178
|
+
return "computed";
|
|
179
|
+
}
|
|
180
|
+
if (d.$prototype) {
|
|
181
|
+
return "data";
|
|
182
|
+
}
|
|
169
183
|
return "state";
|
|
170
184
|
}
|
|
171
185
|
|
|
@@ -175,12 +189,22 @@ export function defCategory(def: SignalDef | unknown) {
|
|
|
175
189
|
* @param {SignalDef | unknown} def
|
|
176
190
|
*/
|
|
177
191
|
export function defBadgeLabel(def: SignalDef | unknown) {
|
|
178
|
-
if (!def)
|
|
192
|
+
if (!def) {
|
|
193
|
+
return "S";
|
|
194
|
+
}
|
|
179
195
|
const d = def as SignalDef;
|
|
180
|
-
if (d.$expression)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (d.$
|
|
196
|
+
if (d.$expression) {
|
|
197
|
+
return "E";
|
|
198
|
+
}
|
|
199
|
+
if (d.$handler || d.$prototype === "Function") {
|
|
200
|
+
return "F";
|
|
201
|
+
}
|
|
202
|
+
if (d.$compute) {
|
|
203
|
+
return "C";
|
|
204
|
+
}
|
|
205
|
+
if (d.$prototype) {
|
|
206
|
+
return d.$prototype.charAt(0);
|
|
207
|
+
}
|
|
184
208
|
return "S";
|
|
185
209
|
}
|
|
186
210
|
|
|
@@ -191,24 +215,45 @@ export function defBadgeLabel(def: SignalDef | unknown) {
|
|
|
191
215
|
* @param {SignalDef | null | undefined} def
|
|
192
216
|
*/
|
|
193
217
|
export function defHint(name: string, def: SignalDef | null | undefined) {
|
|
194
|
-
if (!def)
|
|
195
|
-
|
|
218
|
+
if (!def) {
|
|
219
|
+
return "";
|
|
220
|
+
}
|
|
221
|
+
if (def.$expression) {
|
|
222
|
+
return expressionHint(def.$expression);
|
|
223
|
+
}
|
|
196
224
|
if (def.$prototype === "Function") {
|
|
197
|
-
if (def.body)
|
|
198
|
-
|
|
225
|
+
if (def.body) {
|
|
226
|
+
return def.body.length > 20 ? `${def.body.slice(0, 20)}...` : def.body;
|
|
227
|
+
}
|
|
228
|
+
if (def.$src) {
|
|
229
|
+
return def.$src;
|
|
230
|
+
}
|
|
199
231
|
return "function";
|
|
200
232
|
}
|
|
201
|
-
if (def.$handler)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (def.$
|
|
205
|
-
return
|
|
206
|
-
|
|
233
|
+
if (def.$handler) {
|
|
234
|
+
return "handler (legacy)";
|
|
235
|
+
}
|
|
236
|
+
if (def.$compute) {
|
|
237
|
+
return `=${def.$compute.length > 20 ? `${def.$compute.slice(0, 20)}...` : def.$compute}`;
|
|
238
|
+
}
|
|
239
|
+
if (def.$prototype === "Request") {
|
|
240
|
+
return `${def.method || "GET"} ${(def.url || "").slice(0, 20)}`;
|
|
241
|
+
}
|
|
242
|
+
if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage") {
|
|
207
243
|
return def.key || "";
|
|
208
|
-
|
|
209
|
-
if (def.$prototype === "
|
|
210
|
-
|
|
211
|
-
|
|
244
|
+
}
|
|
245
|
+
if (def.$prototype === "IndexedDB") {
|
|
246
|
+
return def.database || "";
|
|
247
|
+
}
|
|
248
|
+
if (def.$prototype === "Cookie") {
|
|
249
|
+
return def.name || "";
|
|
250
|
+
}
|
|
251
|
+
if (def.$prototype) {
|
|
252
|
+
return def.$prototype;
|
|
253
|
+
}
|
|
254
|
+
if (def.attribute) {
|
|
255
|
+
return `[${def.attribute}] ${def.type || ""}`;
|
|
256
|
+
}
|
|
212
257
|
return def.type || "";
|
|
213
258
|
}
|
|
214
259
|
|
|
@@ -231,12 +276,17 @@ export function collectCssParts(
|
|
|
231
276
|
node: JxMutableNode | null | undefined,
|
|
232
277
|
parts: { name: string; tag: string }[] = [],
|
|
233
278
|
) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
279
|
+
const part = node?.attributes?.part;
|
|
280
|
+
if (typeof part === "string" && part) {
|
|
281
|
+
parts.push({ name: part, tag: node?.tagName || "div" });
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(node?.children)) {
|
|
284
|
+
for (const c of node.children) {
|
|
285
|
+
if (typeof c !== "string") {
|
|
286
|
+
collectCssParts(c, parts);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
240
290
|
return parts;
|
|
241
291
|
}
|
|
242
292
|
|
|
@@ -249,40 +299,59 @@ export function collectCssParts(
|
|
|
249
299
|
*/
|
|
250
300
|
export function resolveDefaultForCanvas(
|
|
251
301
|
value: unknown,
|
|
252
|
-
defs: Record<string,
|
|
302
|
+
defs: Record<string, JxStateDefinition> | null | undefined,
|
|
253
303
|
) {
|
|
254
|
-
if (!value || typeof value !== "object" || !(value as Record<string, unknown>).$ref)
|
|
304
|
+
if (!value || typeof value !== "object" || !(value as Record<string, unknown>).$ref) {
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
255
307
|
const ref = (value as Record<string, unknown>).$ref as string;
|
|
256
308
|
/** @type {string | undefined} */
|
|
257
309
|
let defName;
|
|
258
|
-
if (ref.startsWith("#/state/"))
|
|
259
|
-
|
|
260
|
-
else
|
|
310
|
+
if (ref.startsWith("#/state/")) {
|
|
311
|
+
defName = ref.slice(8);
|
|
312
|
+
} else if (ref.startsWith("$")) {
|
|
313
|
+
defName = ref;
|
|
314
|
+
} else {
|
|
315
|
+
return `{${ref}}`;
|
|
316
|
+
}
|
|
261
317
|
|
|
262
|
-
const
|
|
263
|
-
if (!
|
|
318
|
+
const rawDef = defs?.[defName];
|
|
319
|
+
if (!rawDef) {
|
|
320
|
+
return `{${defName}}`;
|
|
321
|
+
}
|
|
322
|
+
const def = asSignalDef(rawDef);
|
|
264
323
|
|
|
265
324
|
// State signal → use default
|
|
266
325
|
if (!def.$compute && !def.$prototype) {
|
|
267
326
|
if (def.default !== undefined && def.default !== null) {
|
|
268
|
-
if (typeof def.default === "object")
|
|
327
|
+
if (typeof def.default === "object") {
|
|
328
|
+
return JSON.stringify(def.default);
|
|
329
|
+
}
|
|
269
330
|
return String(def.default);
|
|
270
331
|
}
|
|
271
332
|
return "";
|
|
272
333
|
}
|
|
273
334
|
// Computed → expression indicator
|
|
274
|
-
if (def.$compute)
|
|
335
|
+
if (def.$compute) {
|
|
336
|
+
return `\u0192(${defName})`;
|
|
337
|
+
}
|
|
275
338
|
// Request → URL hint
|
|
276
|
-
if (def.$prototype === "Request")
|
|
339
|
+
if (def.$prototype === "Request") {
|
|
340
|
+
return `\u27F3 ${def.url || "fetch"}`;
|
|
341
|
+
}
|
|
277
342
|
// Storage → use default or key
|
|
278
343
|
if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage") {
|
|
279
344
|
if (def.default !== undefined && def.default !== null) {
|
|
280
|
-
if (typeof def.default === "object")
|
|
345
|
+
if (typeof def.default === "object") {
|
|
346
|
+
return JSON.stringify(def.default);
|
|
347
|
+
}
|
|
281
348
|
return String(def.default);
|
|
282
349
|
}
|
|
283
350
|
return `[${def.key || "storage"}]`;
|
|
284
351
|
}
|
|
285
|
-
if (def.$prototype)
|
|
352
|
+
if (def.$prototype) {
|
|
353
|
+
return `{${def.$prototype}}`;
|
|
354
|
+
}
|
|
286
355
|
return `{${defName}}`;
|
|
287
356
|
}
|
|
288
357
|
|
|
@@ -294,13 +363,15 @@ export function signalFieldRow(label: string, value: string, onChange: (value: s
|
|
|
294
363
|
prop: label,
|
|
295
364
|
label,
|
|
296
365
|
hasValue: false,
|
|
297
|
-
//
|
|
298
|
-
//
|
|
366
|
+
// CommitMode "blur": signal fields (rename, src, etc.) commit on blur/Enter only — a debounced
|
|
367
|
+
// Mid-typing commit would, e.g., rename the signal on every keystroke pause.
|
|
299
368
|
widget: spTextField(
|
|
300
369
|
`sig:${label}`,
|
|
301
370
|
value,
|
|
302
371
|
(v: string) => {
|
|
303
|
-
if (v !== value)
|
|
372
|
+
if (v !== value) {
|
|
373
|
+
onChange(v);
|
|
374
|
+
}
|
|
304
375
|
},
|
|
305
376
|
{ commitMode: "blur" },
|
|
306
377
|
),
|
|
@@ -308,8 +379,19 @@ export function signalFieldRow(label: string, value: string, onChange: (value: s
|
|
|
308
379
|
}
|
|
309
380
|
|
|
310
381
|
/** Normalize a parameter entry to a CEM object. */
|
|
311
|
-
export function normParam(p: string |
|
|
312
|
-
return typeof p === "string" ? { name: p } :
|
|
382
|
+
export function normParam(p: string | CemParameter): CemParameter {
|
|
383
|
+
return typeof p === "string" ? { name: p } : p;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Extract the display text from a CEM `{ text }` type value, if present. */
|
|
387
|
+
function cemTypeText(type: JsonValue | undefined): string {
|
|
388
|
+
if (typeof type === "object" && type !== null && !Array.isArray(type)) {
|
|
389
|
+
const { text } = type;
|
|
390
|
+
if (typeof text === "string") {
|
|
391
|
+
return text;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return "";
|
|
313
395
|
}
|
|
314
396
|
|
|
315
397
|
// ─── Left panel: Signals ─────────────────────────────────────────────────────
|
|
@@ -324,25 +406,25 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
324
406
|
|
|
325
407
|
// Group by category
|
|
326
408
|
const groups = {
|
|
327
|
-
state: [],
|
|
328
409
|
computed: [],
|
|
329
410
|
data: [],
|
|
330
411
|
expression: [],
|
|
331
412
|
function: [],
|
|
413
|
+
state: [],
|
|
332
414
|
} as Record<string, [string, SignalDef][]>;
|
|
333
415
|
for (const [name, def] of entries) {
|
|
334
|
-
groups[defCategory(def)].push([name, def]);
|
|
416
|
+
groups[defCategory(def)].push([name, asSignalDef(def)]);
|
|
335
417
|
}
|
|
336
418
|
|
|
337
419
|
const categories = [
|
|
338
|
-
{ key: "state", label: "State"
|
|
339
|
-
{ key: "computed", label: "Computed"
|
|
340
|
-
{ key: "data", label: "Data"
|
|
341
|
-
{ key: "expression", label: "Expressions"
|
|
342
|
-
{ key: "function", label: "Functions"
|
|
420
|
+
{ items: groups.state, key: "state", label: "State" },
|
|
421
|
+
{ items: groups.computed, key: "computed", label: "Computed" },
|
|
422
|
+
{ items: groups.data, key: "data", label: "Data" },
|
|
423
|
+
{ items: groups.expression, key: "expression", label: "Expressions" },
|
|
424
|
+
{ items: groups.function, key: "function", label: "Functions" },
|
|
343
425
|
];
|
|
344
426
|
|
|
345
|
-
const collapsedCats =
|
|
427
|
+
const collapsedCats = (S._collapsedSignalCats ||= new Set());
|
|
346
428
|
|
|
347
429
|
const catTemplates = categories
|
|
348
430
|
.filter((c) => c.items.length > 0)
|
|
@@ -352,8 +434,11 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
352
434
|
label="${label} (${items.length})"
|
|
353
435
|
?open=${!collapsedCats.has(key)}
|
|
354
436
|
@sp-accordion-item-toggle=${() => {
|
|
355
|
-
if (collapsedCats.has(key))
|
|
356
|
-
|
|
437
|
+
if (collapsedCats.has(key)) {
|
|
438
|
+
collapsedCats.delete(key);
|
|
439
|
+
} else {
|
|
440
|
+
collapsedCats.add(key);
|
|
441
|
+
}
|
|
357
442
|
ctx.renderLeftPanel();
|
|
358
443
|
}}
|
|
359
444
|
>
|
|
@@ -361,7 +446,7 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
361
446
|
const isExpanded: boolean = expandedSignal === name;
|
|
362
447
|
return html`
|
|
363
448
|
<div
|
|
364
|
-
class=${classMap({ "signal-row": true
|
|
449
|
+
class=${classMap({ expanded: isExpanded, "signal-row": true })}
|
|
365
450
|
@click=${() => {
|
|
366
451
|
expandedSignal = isExpanded ? null : name;
|
|
367
452
|
ctx.renderLeftPanel();
|
|
@@ -404,17 +489,20 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
404
489
|
placeholder="+ Add…"
|
|
405
490
|
@change=${(e: Event) => {
|
|
406
491
|
const type = (e.target as HTMLInputElement).value;
|
|
407
|
-
if (!type)
|
|
492
|
+
if (!type) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
408
495
|
|
|
409
496
|
// Handle import-based prototypes (e.g., "import:ContentCollection")
|
|
410
497
|
if (type.startsWith("import:")) {
|
|
411
498
|
const protoName = type.slice(7);
|
|
412
499
|
const src = projectState?.projectConfig?.imports?.[protoName];
|
|
413
|
-
let n =
|
|
500
|
+
let n = `$${protoName.charAt(0).toLowerCase()}${protoName.slice(1)}`;
|
|
414
501
|
let i = 1;
|
|
415
502
|
const base = n;
|
|
416
503
|
while (S.document.state && S.document.state[n]) {
|
|
417
|
-
n = base + i
|
|
504
|
+
n = base + i;
|
|
505
|
+
i += 1;
|
|
418
506
|
}
|
|
419
507
|
transactDoc(activeTab.value, (t) =>
|
|
420
508
|
mutateAddDef(
|
|
@@ -442,13 +530,16 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
442
530
|
}
|
|
443
531
|
|
|
444
532
|
const template = DEF_TEMPLATES[type];
|
|
445
|
-
if (!template)
|
|
533
|
+
if (!template) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
446
536
|
const isFunction = type === "function";
|
|
447
|
-
|
|
537
|
+
const nameBase = isFunction ? "newFunction" : "$newSignal";
|
|
448
538
|
let n = nameBase;
|
|
449
539
|
let i = 1;
|
|
450
540
|
while (S.document.state && S.document.state[n]) {
|
|
451
|
-
n = nameBase + i
|
|
541
|
+
n = nameBase + i;
|
|
542
|
+
i += 1;
|
|
452
543
|
}
|
|
453
544
|
transactDoc(activeTab.value, (t) =>
|
|
454
545
|
mutateAddDef(t, n, structuredClone(template) as Record<string, JsonValue>),
|
|
@@ -487,12 +578,10 @@ export function renderSignalsTemplate(S: SignalsPanelState, ctx: SignalsPanelCtx
|
|
|
487
578
|
function renderSignalEditorTemplate(
|
|
488
579
|
S: SignalsPanelState,
|
|
489
580
|
name: string,
|
|
490
|
-
|
|
581
|
+
defArg: SignalDef,
|
|
491
582
|
ctx: SignalsPanelCtx,
|
|
492
583
|
) {
|
|
493
|
-
|
|
494
|
-
def = { default: def };
|
|
495
|
-
}
|
|
584
|
+
const def = typeof defArg === "object" && defArg !== null ? defArg : { default: defArg };
|
|
496
585
|
const cat = defCategory(def);
|
|
497
586
|
|
|
498
587
|
// Helper for picker rows
|
|
@@ -501,11 +590,11 @@ function renderSignalEditorTemplate(
|
|
|
501
590
|
options: string[],
|
|
502
591
|
currentVal: string,
|
|
503
592
|
onChange: (value: string) => void,
|
|
504
|
-
) =>
|
|
505
|
-
|
|
506
|
-
prop: label,
|
|
507
|
-
label,
|
|
593
|
+
) =>
|
|
594
|
+
renderFieldRow({
|
|
508
595
|
hasValue: false,
|
|
596
|
+
label,
|
|
597
|
+
prop: label,
|
|
509
598
|
widget: html`
|
|
510
599
|
<sp-picker
|
|
511
600
|
size="s"
|
|
@@ -516,27 +605,25 @@ function renderSignalEditorTemplate(
|
|
|
516
605
|
</sp-picker>
|
|
517
606
|
`,
|
|
518
607
|
});
|
|
519
|
-
};
|
|
520
608
|
|
|
521
609
|
// Helper for textarea rows — uses the shared draft layer (commits on blur/Enter and a 500ms
|
|
522
|
-
//
|
|
610
|
+
// Debounce) so a panel re-render mid-edit can't truncate the in-progress text.
|
|
523
611
|
const textareaRow = (
|
|
524
612
|
label: string,
|
|
525
613
|
value: string,
|
|
526
614
|
onChange: (value: string) => void,
|
|
527
615
|
opts: { minHeight?: string; mono?: boolean } = {},
|
|
528
|
-
) =>
|
|
529
|
-
|
|
530
|
-
prop: label,
|
|
531
|
-
label,
|
|
616
|
+
) =>
|
|
617
|
+
renderFieldRow({
|
|
532
618
|
hasValue: false,
|
|
619
|
+
label,
|
|
620
|
+
prop: label,
|
|
533
621
|
widget: rawTextArea(`sig:${label}`, value, onChange, {
|
|
534
622
|
debounceMs: 500,
|
|
535
623
|
...(opts.minHeight != null && { minHeight: opts.minHeight }),
|
|
536
624
|
...(opts.mono != null && { mono: opts.mono }),
|
|
537
625
|
}),
|
|
538
626
|
});
|
|
539
|
-
};
|
|
540
627
|
|
|
541
628
|
// Name field (common to all)
|
|
542
629
|
const nameField = signalFieldRow("Name", name, (v: string) => {
|
|
@@ -546,7 +633,7 @@ function renderSignalEditorTemplate(
|
|
|
546
633
|
}
|
|
547
634
|
});
|
|
548
635
|
|
|
549
|
-
let fields:
|
|
636
|
+
let fields: TemplateResult | typeof nothing = nothing;
|
|
550
637
|
|
|
551
638
|
if (cat === "state") {
|
|
552
639
|
const defaultVal =
|
|
@@ -564,13 +651,13 @@ function renderSignalEditorTemplate(
|
|
|
564
651
|
),
|
|
565
652
|
)}
|
|
566
653
|
${renderFieldRow({
|
|
567
|
-
prop: "reflects",
|
|
568
|
-
label: "Reflects",
|
|
569
654
|
hasValue: false,
|
|
655
|
+
label: "Reflects",
|
|
656
|
+
prop: "reflects",
|
|
570
657
|
widget: html`
|
|
571
658
|
<sp-checkbox
|
|
572
659
|
class="field-check"
|
|
573
|
-
?checked=${
|
|
660
|
+
?checked=${Boolean(def.reflects)}
|
|
574
661
|
@change=${(e: Event) =>
|
|
575
662
|
transactDoc(activeTab.value, (t) =>
|
|
576
663
|
mutateUpdateDef(t, name, {
|
|
@@ -607,9 +694,9 @@ function renderSignalEditorTemplate(
|
|
|
607
694
|
: nothing}
|
|
608
695
|
${def.format === "image"
|
|
609
696
|
? renderFieldRow({
|
|
610
|
-
prop: "Default",
|
|
611
|
-
label: "Default",
|
|
612
697
|
hasValue: false,
|
|
698
|
+
label: "Default",
|
|
699
|
+
prop: "Default",
|
|
613
700
|
widget: renderMediaPicker("default", defaultVal, (v: string) => {
|
|
614
701
|
transactDoc(activeTab.value, (t) =>
|
|
615
702
|
mutateUpdateDef(t, name, { default: v || undefined }),
|
|
@@ -618,10 +705,13 @@ function renderSignalEditorTemplate(
|
|
|
618
705
|
})
|
|
619
706
|
: signalFieldRow("Default", defaultVal, (v: string) => {
|
|
620
707
|
let parsed: unknown = v;
|
|
621
|
-
if (def.type === "integer")
|
|
622
|
-
|
|
623
|
-
else if (def.type === "
|
|
624
|
-
|
|
708
|
+
if (def.type === "integer") {
|
|
709
|
+
parsed = Number.parseInt(v, 10) || 0;
|
|
710
|
+
} else if (def.type === "number") {
|
|
711
|
+
parsed = Number.parseFloat(v) || 0;
|
|
712
|
+
} else if (def.type === "boolean") {
|
|
713
|
+
parsed = v === "true";
|
|
714
|
+
} else if (def.type === "array" || def.type === "object") {
|
|
625
715
|
try {
|
|
626
716
|
parsed = JSON.parse(v);
|
|
627
717
|
} catch {
|
|
@@ -643,9 +733,9 @@ function renderSignalEditorTemplate(
|
|
|
643
733
|
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
644
734
|
fields = html`
|
|
645
735
|
${renderFieldRow({
|
|
646
|
-
prop: "expression",
|
|
647
|
-
label: "Expression",
|
|
648
736
|
hasValue: false,
|
|
737
|
+
label: "Expression",
|
|
738
|
+
prop: "expression",
|
|
649
739
|
widget: html`
|
|
650
740
|
<textarea
|
|
651
741
|
class="field-input"
|
|
@@ -667,9 +757,9 @@ function renderSignalEditorTemplate(
|
|
|
667
757
|
})}
|
|
668
758
|
${def.$deps && def.$deps.length > 0
|
|
669
759
|
? renderFieldRow({
|
|
670
|
-
prop: "dependencies",
|
|
671
|
-
label: "Dependencies",
|
|
672
760
|
hasValue: false,
|
|
761
|
+
label: "Dependencies",
|
|
762
|
+
prop: "dependencies",
|
|
673
763
|
widget: html`
|
|
674
764
|
<span class="signal-hint" style="flex:1;max-width:none"
|
|
675
765
|
>${def.$deps.map((d: string) => d.replace("#/state/", "")).join(", ")}</span
|
|
@@ -687,11 +777,14 @@ function renderSignalEditorTemplate(
|
|
|
687
777
|
fields = html`
|
|
688
778
|
${renderExpressionEditor(
|
|
689
779
|
exprNode,
|
|
690
|
-
(newNode:
|
|
691
|
-
transactDoc(activeTab.value, (t) =>
|
|
780
|
+
(newNode: unknown) =>
|
|
781
|
+
transactDoc(activeTab.value, (t) =>
|
|
782
|
+
// Expression editors emit JSON expression nodes.
|
|
783
|
+
mutateUpdateDef(t, name, { $expression: newNode as JsonValue }),
|
|
784
|
+
),
|
|
692
785
|
{
|
|
693
|
-
stateDefs: Object.keys(S.document.state || {}),
|
|
694
786
|
allowEventRef: false,
|
|
787
|
+
stateDefs: Object.keys(S.document.state || {}),
|
|
695
788
|
},
|
|
696
789
|
)}
|
|
697
790
|
`;
|
|
@@ -710,13 +803,13 @@ function renderDataSourceFields(
|
|
|
710
803
|
value: string,
|
|
711
804
|
onChange: (value: string) => void,
|
|
712
805
|
opts?: { minHeight?: string; mono?: boolean },
|
|
713
|
-
) =>
|
|
806
|
+
) => TemplateResult,
|
|
714
807
|
pickerRow: (
|
|
715
808
|
label: string,
|
|
716
809
|
options: string[],
|
|
717
810
|
currentVal: string,
|
|
718
811
|
onChange: (value: string) => void,
|
|
719
|
-
) =>
|
|
812
|
+
) => TemplateResult,
|
|
720
813
|
ctx: SignalsPanelCtx,
|
|
721
814
|
) {
|
|
722
815
|
const proto = def.$prototype;
|
|
@@ -767,7 +860,7 @@ function renderDataSourceFields(
|
|
|
767
860
|
)}
|
|
768
861
|
${signalFieldRow("Version", String(def.version || 1), (v: string) =>
|
|
769
862
|
transactDoc(activeTab.value, (t) =>
|
|
770
|
-
mutateUpdateDef(t, name, { version: parseInt(v, 10) || 1 }),
|
|
863
|
+
mutateUpdateDef(t, name, { version: Number.parseInt(v, 10) || 1 }),
|
|
771
864
|
),
|
|
772
865
|
)}
|
|
773
866
|
`;
|
|
@@ -813,7 +906,7 @@ function renderFunctionFields(
|
|
|
813
906
|
value: string,
|
|
814
907
|
onChange: (value: string) => void,
|
|
815
908
|
opts?: { minHeight?: string; mono?: boolean },
|
|
816
|
-
) =>
|
|
909
|
+
) => TemplateResult,
|
|
817
910
|
ctx: SignalsPanelCtx,
|
|
818
911
|
) {
|
|
819
912
|
const descriptionField = signalFieldRow("Description", def.description || "", (v: string) =>
|
|
@@ -840,7 +933,7 @@ function renderFunctionFields(
|
|
|
840
933
|
title="Open in code editor"
|
|
841
934
|
@click=${() => {
|
|
842
935
|
ctx.updateSession({
|
|
843
|
-
ui: { editingFunction: { type: "def"
|
|
936
|
+
ui: { editingFunction: { defName: name, type: "def" } },
|
|
844
937
|
});
|
|
845
938
|
ctx.renderCanvas();
|
|
846
939
|
}}
|
|
@@ -874,15 +967,15 @@ function renderParameterEditorTemplate(
|
|
|
874
967
|
def: SignalDef,
|
|
875
968
|
ctx: SignalsPanelCtx,
|
|
876
969
|
) {
|
|
877
|
-
const params = (def.parameters || []).map(normParam);
|
|
970
|
+
const params = (def.parameters || []).map((p) => normParam(p));
|
|
878
971
|
const isAdvanced = advancedParamOpen.has(name);
|
|
879
972
|
|
|
880
973
|
if (!isAdvanced) {
|
|
881
974
|
// Basic mode: name chips
|
|
882
975
|
return renderFieldRow({
|
|
883
|
-
prop: "parameters",
|
|
884
|
-
label: "Parameters",
|
|
885
976
|
hasValue: false,
|
|
977
|
+
label: "Parameters",
|
|
978
|
+
prop: "parameters",
|
|
886
979
|
widget: html`
|
|
887
980
|
<div style="display:flex;flex-wrap:wrap;gap:4px;align-items:center">
|
|
888
981
|
${params.map(
|
|
@@ -896,7 +989,7 @@ function renderParameterEditorTemplate(
|
|
|
896
989
|
@click=${() => {
|
|
897
990
|
transactDoc(activeTab.value, (t) =>
|
|
898
991
|
mutateUpdateDef(t, name, {
|
|
899
|
-
parameters: params.
|
|
992
|
+
parameters: params.some((_: unknown, j: number) => j !== i)
|
|
900
993
|
? params.filter((_: unknown, j: number) => j !== i)
|
|
901
994
|
: undefined,
|
|
902
995
|
}),
|
|
@@ -936,9 +1029,9 @@ function renderParameterEditorTemplate(
|
|
|
936
1029
|
|
|
937
1030
|
// Advanced mode: full rows
|
|
938
1031
|
return renderFieldRow({
|
|
939
|
-
prop: "parameters",
|
|
940
|
-
label: "Parameters",
|
|
941
1032
|
hasValue: false,
|
|
1033
|
+
label: "Parameters",
|
|
1034
|
+
prop: "parameters",
|
|
942
1035
|
widget: html`
|
|
943
1036
|
<div style="display:flex;flex-direction:column;gap:4px">
|
|
944
1037
|
${params.map(
|
|
@@ -962,7 +1055,7 @@ function renderParameterEditorTemplate(
|
|
|
962
1055
|
/>
|
|
963
1056
|
<input
|
|
964
1057
|
class="field-input"
|
|
965
|
-
.value=${p.type
|
|
1058
|
+
.value=${cemTypeText(p.type)}
|
|
966
1059
|
placeholder="type"
|
|
967
1060
|
style="flex:1"
|
|
968
1061
|
@change=${(e: Event) => {
|
|
@@ -993,10 +1086,10 @@ function renderParameterEditorTemplate(
|
|
|
993
1086
|
<input
|
|
994
1087
|
type="checkbox"
|
|
995
1088
|
title="optional"
|
|
996
|
-
.checked=${
|
|
1089
|
+
.checked=${Boolean(p.optional)}
|
|
997
1090
|
@change=${(e: Event) => {
|
|
998
1091
|
const next = [...params];
|
|
999
|
-
const checked =
|
|
1092
|
+
const { checked } = e.target as HTMLInputElement;
|
|
1000
1093
|
const { optional: _o, ...rest } = next[i];
|
|
1001
1094
|
next[i] = checked ? { ...rest, optional: true } : rest;
|
|
1002
1095
|
transactDoc(activeTab.value, (t) =>
|
|
@@ -1010,7 +1103,7 @@ function renderParameterEditorTemplate(
|
|
|
1010
1103
|
const next = params.filter((_: unknown, j: number) => j !== i);
|
|
1011
1104
|
transactDoc(activeTab.value, (t) =>
|
|
1012
1105
|
mutateUpdateDef(t, name, {
|
|
1013
|
-
parameters: next.length ? next : undefined,
|
|
1106
|
+
parameters: next.length > 0 ? next : undefined,
|
|
1014
1107
|
}),
|
|
1015
1108
|
);
|
|
1016
1109
|
}}
|
|
@@ -1046,7 +1139,9 @@ function renderParameterEditorTemplate(
|
|
|
1046
1139
|
/** Render CEM emits editor for function state entries. */
|
|
1047
1140
|
function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: SignalDef) {
|
|
1048
1141
|
const emits = def.emits || ([] as CemEvent[]);
|
|
1049
|
-
if (emits.length === 0 && !isCustomElementDoc(S))
|
|
1142
|
+
if (emits.length === 0 && !isCustomElementDoc(S)) {
|
|
1143
|
+
return nothing;
|
|
1144
|
+
}
|
|
1050
1145
|
|
|
1051
1146
|
return html`
|
|
1052
1147
|
<div
|
|
@@ -1073,7 +1168,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1073
1168
|
/>
|
|
1074
1169
|
<input
|
|
1075
1170
|
class="field-input"
|
|
1076
|
-
.value=${ev.type
|
|
1171
|
+
.value=${cemTypeText(ev.type)}
|
|
1077
1172
|
placeholder="type"
|
|
1078
1173
|
style="flex:1"
|
|
1079
1174
|
@change=${(e: Event) => {
|
|
@@ -1102,7 +1197,7 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1102
1197
|
@click=${() => {
|
|
1103
1198
|
transactDoc(activeTab.value, (t) =>
|
|
1104
1199
|
mutateUpdateDef(t, name, {
|
|
1105
|
-
emits: emits.
|
|
1200
|
+
emits: emits.some((_: unknown, j: number) => j !== i)
|
|
1106
1201
|
? emits.filter((_: unknown, j: number) => j !== i)
|
|
1107
1202
|
: undefined,
|
|
1108
1203
|
}),
|
|
@@ -1137,8 +1232,13 @@ function renderEmitsEditorTemplate(S: SignalsPanelState, name: string, def: Sign
|
|
|
1137
1232
|
* @param {Record<string, unknown>} [parentDef] - Parent def for resolving dependent refs
|
|
1138
1233
|
* @returns {string[] | undefined}
|
|
1139
1234
|
*/
|
|
1140
|
-
function resolveSchemaEnum(
|
|
1141
|
-
|
|
1235
|
+
function resolveSchemaEnum(
|
|
1236
|
+
enumDef: unknown,
|
|
1237
|
+
parentDef?: Record<string, unknown>,
|
|
1238
|
+
): string[] | undefined {
|
|
1239
|
+
if (Array.isArray(enumDef)) {
|
|
1240
|
+
return enumDef;
|
|
1241
|
+
}
|
|
1142
1242
|
if (enumDef && typeof enumDef === "object") {
|
|
1143
1243
|
const ref = (enumDef as Record<string, unknown>).$ref;
|
|
1144
1244
|
if (ref === "#/$context/contentTypes") {
|
|
@@ -1147,7 +1247,7 @@ function resolveSchemaEnum(enumDef: unknown, parentDef?: Record<string, unknown>
|
|
|
1147
1247
|
if (typeof ref === "string" && ref.startsWith("#/$context/contentTypes/{@")) {
|
|
1148
1248
|
const match = ref.match(/#\/\$context\/contentTypes\/\{@(\w+)\}\/schema\/properties/);
|
|
1149
1249
|
if (match && parentDef) {
|
|
1150
|
-
const paramName = match
|
|
1250
|
+
const [, paramName] = match;
|
|
1151
1251
|
const typeName = parentDef[paramName] as string | undefined;
|
|
1152
1252
|
if (typeName) {
|
|
1153
1253
|
const ct = projectState?.projectConfig?.contentTypes?.[typeName] as
|
|
@@ -1155,7 +1255,9 @@ function resolveSchemaEnum(enumDef: unknown, parentDef?: Record<string, unknown>
|
|
|
1155
1255
|
| undefined;
|
|
1156
1256
|
const schema = ct?.schema as Record<string, unknown> | undefined;
|
|
1157
1257
|
const props = schema?.properties as Record<string, unknown> | undefined;
|
|
1158
|
-
if (props)
|
|
1258
|
+
if (props) {
|
|
1259
|
+
return Object.keys(props);
|
|
1260
|
+
}
|
|
1159
1261
|
}
|
|
1160
1262
|
}
|
|
1161
1263
|
return undefined;
|
|
@@ -1205,7 +1307,7 @@ function renderInlineField(
|
|
|
1205
1307
|
if (schema.type === "boolean") {
|
|
1206
1308
|
return html`<sp-switch
|
|
1207
1309
|
size="s"
|
|
1208
|
-
?checked=${
|
|
1310
|
+
?checked=${Boolean(value)}
|
|
1209
1311
|
@change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
|
|
1210
1312
|
>${key}</sp-switch
|
|
1211
1313
|
>`;
|
|
@@ -1219,9 +1321,9 @@ function renderInlineField(
|
|
|
1219
1321
|
@change=${(e: Event) => {
|
|
1220
1322
|
const parsed =
|
|
1221
1323
|
schema.type === "integer"
|
|
1222
|
-
? parseInt((e.target as HTMLInputElement).value, 10)
|
|
1223
|
-
: parseFloat((e.target as HTMLInputElement).value);
|
|
1224
|
-
onChange(isNaN(parsed) ? undefined : parsed);
|
|
1324
|
+
? Number.parseInt((e.target as HTMLInputElement).value, 10)
|
|
1325
|
+
: Number.parseFloat((e.target as HTMLInputElement).value);
|
|
1326
|
+
onChange(Number.isNaN(parsed) ? undefined : parsed);
|
|
1225
1327
|
}}
|
|
1226
1328
|
></sp-number-field>`;
|
|
1227
1329
|
}
|
|
@@ -1234,6 +1336,36 @@ function renderInlineField(
|
|
|
1234
1336
|
></sp-textfield>`;
|
|
1235
1337
|
}
|
|
1236
1338
|
|
|
1339
|
+
/** Render a debounced multiline JSON text field for array/object schema properties. */
|
|
1340
|
+
function renderJsonTextField(
|
|
1341
|
+
currentValue: unknown,
|
|
1342
|
+
ps: SchemaProperty,
|
|
1343
|
+
name: string,
|
|
1344
|
+
prop: string,
|
|
1345
|
+
) {
|
|
1346
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
1347
|
+
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
1348
|
+
return html`<sp-textfield
|
|
1349
|
+
multiline
|
|
1350
|
+
size="s"
|
|
1351
|
+
style="min-height:40px"
|
|
1352
|
+
.value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
|
|
1353
|
+
placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
|
|
1354
|
+
@input=${(e: Event) => {
|
|
1355
|
+
clearTimeout(debounce);
|
|
1356
|
+
debounce = setTimeout(() => {
|
|
1357
|
+
try {
|
|
1358
|
+
transactDoc(activeTab.value, (t) =>
|
|
1359
|
+
mutateUpdateDef(t, name, {
|
|
1360
|
+
[prop]: JSON.parse((e.target as HTMLInputElement).value),
|
|
1361
|
+
}),
|
|
1362
|
+
);
|
|
1363
|
+
} catch {}
|
|
1364
|
+
}, 500);
|
|
1365
|
+
}}
|
|
1366
|
+
></sp-textfield>`;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1237
1369
|
/**
|
|
1238
1370
|
* Render config form fields from a JSON Schema `properties` object. Maps schema types to
|
|
1239
1371
|
* appropriate form controls.
|
|
@@ -1245,9 +1377,11 @@ export function renderSchemaFieldsTemplate(
|
|
|
1245
1377
|
_S: SignalsPanelState,
|
|
1246
1378
|
ctx: SignalsPanelCtx | null = null,
|
|
1247
1379
|
) {
|
|
1248
|
-
if (!schema?.properties)
|
|
1380
|
+
if (!schema?.properties) {
|
|
1381
|
+
return nothing;
|
|
1382
|
+
}
|
|
1249
1383
|
|
|
1250
|
-
const required = new Set(schema.required
|
|
1384
|
+
const required = new Set(schema.required);
|
|
1251
1385
|
|
|
1252
1386
|
const propertyFields = Object.entries(schema.properties)
|
|
1253
1387
|
.filter(([prop]) => !STUDIO_RESERVED_KEYS.has(prop))
|
|
@@ -1307,11 +1441,11 @@ export function renderSchemaFieldsTemplate(
|
|
|
1307
1441
|
debounce = setTimeout(() => {
|
|
1308
1442
|
const parsed =
|
|
1309
1443
|
ps.type === "integer"
|
|
1310
|
-
? parseInt((e.target as HTMLInputElement).value, 10)
|
|
1311
|
-
: parseFloat((e.target as HTMLInputElement).value);
|
|
1444
|
+
? Number.parseInt((e.target as HTMLInputElement).value, 10)
|
|
1445
|
+
: Number.parseFloat((e.target as HTMLInputElement).value);
|
|
1312
1446
|
transactDoc(activeTab.value, (t) =>
|
|
1313
1447
|
mutateUpdateDef(t, name, {
|
|
1314
|
-
[prop]: isNaN(parsed) ? undefined : parsed,
|
|
1448
|
+
[prop]: Number.isNaN(parsed) ? undefined : parsed,
|
|
1315
1449
|
}),
|
|
1316
1450
|
);
|
|
1317
1451
|
}, 400);
|
|
@@ -1344,9 +1478,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1344
1478
|
multiline
|
|
1345
1479
|
size="s"
|
|
1346
1480
|
style=${styleMap({
|
|
1347
|
-
minHeight: hasValue ? "80px" : "40px",
|
|
1348
1481
|
fontFamily: "monospace",
|
|
1349
1482
|
fontSize: "11px",
|
|
1483
|
+
minHeight: hasValue ? "80px" : "40px",
|
|
1350
1484
|
})}
|
|
1351
1485
|
.value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
|
|
1352
1486
|
placeholder=${ps.description ?? "JSON Schema defining the data shape\u2026"}
|
|
@@ -1399,7 +1533,7 @@ export function renderSchemaFieldsTemplate(
|
|
|
1399
1533
|
const updated = rows.filter((_: unknown, i: number) => i !== idx);
|
|
1400
1534
|
transactDoc(activeTab.value, (t) =>
|
|
1401
1535
|
mutateUpdateDef(t, name, {
|
|
1402
|
-
[prop]: updated.length ? updated : undefined,
|
|
1536
|
+
[prop]: updated.length > 0 ? updated : undefined,
|
|
1403
1537
|
}),
|
|
1404
1538
|
);
|
|
1405
1539
|
ctx?.renderLeftPanel();
|
|
@@ -1417,8 +1551,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1417
1551
|
e.stopPropagation();
|
|
1418
1552
|
const newRow: Record<string, unknown> = {};
|
|
1419
1553
|
for (const [k, v] of Object.entries(itemProps)) {
|
|
1420
|
-
if ((v as Record<string, unknown>).default !== undefined)
|
|
1554
|
+
if ((v as Record<string, unknown>).default !== undefined) {
|
|
1421
1555
|
newRow[k] = (v as Record<string, unknown>).default;
|
|
1556
|
+
}
|
|
1422
1557
|
}
|
|
1423
1558
|
transactDoc(activeTab.value, (t) =>
|
|
1424
1559
|
mutateUpdateDef(t, name, { [prop]: [...rows, newRow] }),
|
|
@@ -1430,27 +1565,7 @@ export function renderSchemaFieldsTemplate(
|
|
|
1430
1565
|
</div>
|
|
1431
1566
|
`;
|
|
1432
1567
|
} else if (ps.type === "array" || ps.type === "object") {
|
|
1433
|
-
|
|
1434
|
-
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
1435
|
-
control = html`<sp-textfield
|
|
1436
|
-
multiline
|
|
1437
|
-
size="s"
|
|
1438
|
-
style="min-height:40px"
|
|
1439
|
-
.value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
|
|
1440
|
-
placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
|
|
1441
|
-
@input=${(e: Event) => {
|
|
1442
|
-
clearTimeout(debounce);
|
|
1443
|
-
debounce = setTimeout(() => {
|
|
1444
|
-
try {
|
|
1445
|
-
transactDoc(activeTab.value, (t) =>
|
|
1446
|
-
mutateUpdateDef(t, name, {
|
|
1447
|
-
[prop]: JSON.parse((e.target as HTMLInputElement).value),
|
|
1448
|
-
}),
|
|
1449
|
-
);
|
|
1450
|
-
} catch {}
|
|
1451
|
-
}, 500);
|
|
1452
|
-
}}
|
|
1453
|
-
></sp-textfield>`;
|
|
1568
|
+
control = renderJsonTextField(currentValue, ps, name, prop);
|
|
1454
1569
|
} else {
|
|
1455
1570
|
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
1456
1571
|
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -1476,9 +1591,9 @@ export function renderSchemaFieldsTemplate(
|
|
|
1476
1591
|
}
|
|
1477
1592
|
|
|
1478
1593
|
return renderFieldRow({
|
|
1479
|
-
prop: ps.name || prop,
|
|
1480
|
-
label: labelText,
|
|
1481
1594
|
hasValue: false,
|
|
1595
|
+
label: labelText,
|
|
1596
|
+
prop: ps.name || prop,
|
|
1482
1597
|
widget: control,
|
|
1483
1598
|
});
|
|
1484
1599
|
});
|
|
@@ -1497,7 +1612,7 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1497
1612
|
ctx: SignalsPanelCtx,
|
|
1498
1613
|
) {
|
|
1499
1614
|
// Schema-driven config fields (async with cache)
|
|
1500
|
-
let schemaContent:
|
|
1615
|
+
let schemaContent: TemplateResult | typeof nothing = nothing;
|
|
1501
1616
|
const importedPath = def.$prototype
|
|
1502
1617
|
? projectState?.projectConfig?.imports?.[def.$prototype]
|
|
1503
1618
|
: null;
|
|
@@ -1524,7 +1639,9 @@ export function renderExternalPrototypeEditorTemplate(
|
|
|
1524
1639
|
fetchPluginSchema(def, {
|
|
1525
1640
|
...(S.documentPath != null && { documentPath: S.documentPath }),
|
|
1526
1641
|
}).then((schema) => {
|
|
1527
|
-
if (schema)
|
|
1642
|
+
if (schema) {
|
|
1643
|
+
ctx.renderLeftPanel();
|
|
1644
|
+
}
|
|
1528
1645
|
});
|
|
1529
1646
|
}
|
|
1530
1647
|
}
|