@jxsuite/studio 0.22.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +3449 -336
- package/dist/studio.js.map +11 -9
- package/package.json +4 -4
- package/src/panels/events-panel.js +58 -32
- package/src/panels/right-panel.js +5 -1
- package/src/panels/signals-panel.js +18 -0
- package/src/store.js +5 -1
- package/src/studio.js +13 -11
- package/src/ui/expression-editor.js +528 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
33
33
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
34
|
-
"@jxsuite/parser": "^0.
|
|
35
|
-
"@jxsuite/runtime": "^0.
|
|
36
|
-
"@jxsuite/schema": "^0.
|
|
34
|
+
"@jxsuite/parser": "^0.23.0",
|
|
35
|
+
"@jxsuite/runtime": "^0.23.0",
|
|
36
|
+
"@jxsuite/schema": "^0.23.0",
|
|
37
37
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
38
38
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
39
39
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -3,6 +3,7 @@ import { html, nothing } from "lit-html";
|
|
|
3
3
|
import { live } from "lit-html/directives/live.js";
|
|
4
4
|
import { activeTab } from "../workspace/workspace.js";
|
|
5
5
|
import { transactDoc, mutateUpdateProperty } from "../tabs/transact.js";
|
|
6
|
+
import { renderExpressionEditor } from "../ui/expression-editor.js";
|
|
6
7
|
|
|
7
8
|
export const EVENT_NAMES = [
|
|
8
9
|
"onclick",
|
|
@@ -66,7 +67,7 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
66
67
|
if (!k.startsWith("on")) return false;
|
|
67
68
|
const v = node[k];
|
|
68
69
|
if (!v || typeof v !== "object") return false;
|
|
69
|
-
return v.$ref || v.$prototype === "Function";
|
|
70
|
+
return v.$ref || v.$prototype === "Function" || v.$expression;
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
return html`
|
|
@@ -79,6 +80,8 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
79
80
|
${eventKeys.map((evKey) => {
|
|
80
81
|
const evVal = node[evKey];
|
|
81
82
|
const isInline = evVal.$prototype === "Function";
|
|
83
|
+
const isExpression = evVal.$expression != null;
|
|
84
|
+
const currentMode = isInline ? "inline" : isExpression ? "$expression" : "ref";
|
|
82
85
|
return html`
|
|
83
86
|
<div class="event-binding">
|
|
84
87
|
<div class="event-row">
|
|
@@ -103,9 +106,10 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
103
106
|
<sp-picker
|
|
104
107
|
size="s"
|
|
105
108
|
class="event-mode"
|
|
106
|
-
.value=${live(
|
|
109
|
+
.value=${live(currentMode)}
|
|
107
110
|
@change=${(/** @type {Event} */ e) => {
|
|
108
|
-
|
|
111
|
+
const newMode = /** @type {HTMLInputElement} */ (e.target).value;
|
|
112
|
+
if (newMode === "inline") {
|
|
109
113
|
transactDoc(activeTab.value, (t) =>
|
|
110
114
|
mutateUpdateProperty(t, selection, evKey, {
|
|
111
115
|
$prototype: "Function",
|
|
@@ -113,6 +117,12 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
113
117
|
parameters: [],
|
|
114
118
|
}),
|
|
115
119
|
);
|
|
120
|
+
} else if (newMode === "$expression") {
|
|
121
|
+
transactDoc(activeTab.value, (t) =>
|
|
122
|
+
mutateUpdateProperty(t, selection, evKey, {
|
|
123
|
+
$expression: { operator: "=", target: null },
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
116
126
|
} else {
|
|
117
127
|
const firstFn = functionDefs[0];
|
|
118
128
|
transactDoc(activeTab.value, (t) =>
|
|
@@ -127,6 +137,7 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
127
137
|
}}
|
|
128
138
|
>
|
|
129
139
|
<sp-menu-item value="inline">inline</sp-menu-item>
|
|
140
|
+
<sp-menu-item value="$expression">$expression</sp-menu-item>
|
|
130
141
|
<sp-menu-item value="ref">$ref</sp-menu-item>
|
|
131
142
|
</sp-picker>
|
|
132
143
|
<sp-action-button
|
|
@@ -176,35 +187,50 @@ export function eventsSidebarTemplate(helpers) {
|
|
|
176
187
|
</sp-action-button>
|
|
177
188
|
</div>
|
|
178
189
|
`
|
|
179
|
-
:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
: isExpression
|
|
191
|
+
? html`
|
|
192
|
+
<div class="event-body-row">
|
|
193
|
+
${renderExpressionEditor(
|
|
194
|
+
evVal.$expression,
|
|
195
|
+
(/** @type {any} */ newNode) =>
|
|
196
|
+
transactDoc(activeTab.value, (t) =>
|
|
197
|
+
mutateUpdateProperty(t, selection, evKey, {
|
|
198
|
+
$expression: newNode,
|
|
199
|
+
}),
|
|
200
|
+
),
|
|
201
|
+
{ stateDefs: Object.keys(defs), allowEventRef: true },
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
`
|
|
205
|
+
: html`
|
|
206
|
+
<sp-picker
|
|
207
|
+
size="s"
|
|
208
|
+
class="event-handler"
|
|
209
|
+
.value=${live(evVal.$ref || "__none__")}
|
|
210
|
+
@change=${(/** @type {Event} */ e) => {
|
|
211
|
+
if (
|
|
212
|
+
/** @type {HTMLInputElement} */ (e.target).value &&
|
|
213
|
+
/** @type {HTMLInputElement} */ (e.target).value !== "__none__"
|
|
214
|
+
) {
|
|
215
|
+
transactDoc(activeTab.value, (t) =>
|
|
216
|
+
mutateUpdateProperty(t, selection, evKey, {
|
|
217
|
+
$ref: /** @type {HTMLInputElement} */ (e.target).value,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
transactDoc(activeTab.value, (t) =>
|
|
222
|
+
mutateUpdateProperty(t, selection, evKey, undefined),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
<sp-menu-item value="__none__">— none —</sp-menu-item>
|
|
228
|
+
${functionDefs.map(
|
|
229
|
+
([fName]) =>
|
|
230
|
+
html`<sp-menu-item value=${`#/state/${fName}`}>${fName}</sp-menu-item>`,
|
|
231
|
+
)}
|
|
232
|
+
</sp-picker>
|
|
233
|
+
`}
|
|
208
234
|
</div>
|
|
209
235
|
`;
|
|
210
236
|
})}
|
|
@@ -108,12 +108,14 @@ export function unmount() {
|
|
|
108
108
|
_lastTab = null;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
let _rafId = 0;
|
|
112
|
+
|
|
111
113
|
export function render() {
|
|
112
114
|
if (!_ctx) return;
|
|
113
115
|
if (_rendering) return;
|
|
114
116
|
if (!_scheduled) {
|
|
115
117
|
_scheduled = true;
|
|
116
|
-
|
|
118
|
+
_rafId = requestAnimationFrame(_flush);
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -143,8 +145,10 @@ function _ensureContainers() {
|
|
|
143
145
|
|
|
144
146
|
function _flush() {
|
|
145
147
|
_scheduled = false;
|
|
148
|
+
_rafId = 0;
|
|
146
149
|
if (!_ctx) return;
|
|
147
150
|
if (_rendering) return;
|
|
151
|
+
if (_hasFocus || isColorPopoverOpen()) return;
|
|
148
152
|
_rendering = true;
|
|
149
153
|
try {
|
|
150
154
|
const ctx = /** @type {RightPanelCtx} */ (_ctx);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
mutateRenameDef,
|
|
19
19
|
} from "../tabs/transact.js";
|
|
20
20
|
import { renderFieldRow } from "../ui/field-row.js";
|
|
21
|
+
import { renderExpressionEditor, expressionHint } from "../ui/expression-editor.js";
|
|
21
22
|
import { renderMediaPicker } from "../ui/media-picker.js";
|
|
22
23
|
import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services.js";
|
|
23
24
|
|
|
@@ -118,6 +119,7 @@ const DEF_TEMPLATES = /** @type {Record<string, SignalDef>} */ ({
|
|
|
118
119
|
map: { $prototype: "Map", default: {} },
|
|
119
120
|
formData: { $prototype: "FormData", fields: {} },
|
|
120
121
|
function: { $prototype: "Function", body: "", parameters: [] },
|
|
122
|
+
expression: { $expression: { operator: "=", target: null } },
|
|
121
123
|
external: { $prototype: "", $src: "" },
|
|
122
124
|
});
|
|
123
125
|
|
|
@@ -148,6 +150,7 @@ const STUDIO_RESERVED_KEYS = new Set([
|
|
|
148
150
|
export function defCategory(def) {
|
|
149
151
|
if (!def) return "state";
|
|
150
152
|
const d = /** @type {SignalDef} */ (def);
|
|
153
|
+
if (d.$expression) return "expression";
|
|
151
154
|
if (d.$handler || d.$prototype === "Function") return "function";
|
|
152
155
|
if (d.$compute) return "computed";
|
|
153
156
|
if (d.$prototype) return "data";
|
|
@@ -162,6 +165,7 @@ export function defCategory(def) {
|
|
|
162
165
|
export function defBadgeLabel(def) {
|
|
163
166
|
if (!def) return "S";
|
|
164
167
|
const d = /** @type {SignalDef} */ (def);
|
|
168
|
+
if (d.$expression) return "E";
|
|
165
169
|
if (d.$handler || d.$prototype === "Function") return "F";
|
|
166
170
|
if (d.$compute) return "C";
|
|
167
171
|
if (d.$prototype) return d.$prototype.charAt(0);
|
|
@@ -176,6 +180,7 @@ export function defBadgeLabel(def) {
|
|
|
176
180
|
*/
|
|
177
181
|
export function defHint(name, def) {
|
|
178
182
|
if (!def) return "";
|
|
183
|
+
if (def.$expression) return expressionHint(def.$expression);
|
|
179
184
|
if (def.$prototype === "Function") {
|
|
180
185
|
if (def.body) return def.body.length > 20 ? def.body.slice(0, 20) + "..." : def.body;
|
|
181
186
|
if (def.$src) return def.$src;
|
|
@@ -314,6 +319,7 @@ export function renderSignalsTemplate(S, ctx) {
|
|
|
314
319
|
state: [],
|
|
315
320
|
computed: [],
|
|
316
321
|
data: [],
|
|
322
|
+
expression: [],
|
|
317
323
|
function: [],
|
|
318
324
|
});
|
|
319
325
|
for (const [name, def] of entries) {
|
|
@@ -324,6 +330,7 @@ export function renderSignalsTemplate(S, ctx) {
|
|
|
324
330
|
{ key: "state", label: "State", items: groups.state },
|
|
325
331
|
{ key: "computed", label: "Computed", items: groups.computed },
|
|
326
332
|
{ key: "data", label: "Data", items: groups.data },
|
|
333
|
+
{ key: "expression", label: "Expressions", items: groups.expression },
|
|
327
334
|
{ key: "function", label: "Functions", items: groups.function },
|
|
328
335
|
];
|
|
329
336
|
|
|
@@ -462,6 +469,7 @@ export function renderSignalsTemplate(S, ctx) {
|
|
|
462
469
|
)}`
|
|
463
470
|
: nothing}
|
|
464
471
|
<sp-menu-divider></sp-menu-divider>
|
|
472
|
+
<sp-menu-item value="expression">Expression</sp-menu-item>
|
|
465
473
|
<sp-menu-item value="function">Function</sp-menu-item>
|
|
466
474
|
</sp-picker>
|
|
467
475
|
</div>
|
|
@@ -697,6 +705,16 @@ function renderSignalEditorTemplate(
|
|
|
697
705
|
fields = renderDataSourceFields(S, name, def, textareaRow, pickerRow, ctx);
|
|
698
706
|
} else if (cat === "function") {
|
|
699
707
|
fields = renderFunctionFields(S, name, def, textareaRow, ctx);
|
|
708
|
+
} else if (cat === "expression") {
|
|
709
|
+
const exprNode = def.$expression || { operator: "=", target: null };
|
|
710
|
+
fields = html`
|
|
711
|
+
${renderExpressionEditor(
|
|
712
|
+
exprNode,
|
|
713
|
+
(/** @type {any} */ newNode) =>
|
|
714
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $expression: newNode })),
|
|
715
|
+
{ stateDefs: Object.keys(S.document.state || {}), allowEventRef: false },
|
|
716
|
+
)}
|
|
717
|
+
`;
|
|
700
718
|
}
|
|
701
719
|
|
|
702
720
|
return html`${nameField}${fields}`;
|
package/src/store.js
CHANGED
|
@@ -131,7 +131,11 @@ export function stripEventHandlers(node) {
|
|
|
131
131
|
/** @type {Record<string, unknown>} */
|
|
132
132
|
const out = {};
|
|
133
133
|
for (const [k, v] of Object.entries(node)) {
|
|
134
|
-
if (
|
|
134
|
+
if (
|
|
135
|
+
k.startsWith("on") &&
|
|
136
|
+
typeof v === "object" &&
|
|
137
|
+
(v?.$ref || v?.$prototype === "Function" || v?.$expression)
|
|
138
|
+
)
|
|
135
139
|
continue;
|
|
136
140
|
if (k === "children") {
|
|
137
141
|
out.children = Array.isArray(v) ? v.map(stripEventHandlers) : stripEventHandlers(v);
|
package/src/studio.js
CHANGED
|
@@ -368,8 +368,9 @@ initWelcome({
|
|
|
368
368
|
cloneRepository: () => cloneRepository({ openRecentProject }),
|
|
369
369
|
});
|
|
370
370
|
|
|
371
|
-
// Effect-driven canvas rendering: auto-triggers renderCanvas when reactive deps change
|
|
372
|
-
|
|
371
|
+
// Effect-driven canvas rendering: auto-triggers renderCanvas when reactive deps change.
|
|
372
|
+
// Uses double-RAF so the canvas render yields to higher-priority panel paints first.
|
|
373
|
+
let _canvasRafId = 0;
|
|
373
374
|
effect(() => {
|
|
374
375
|
const tab = activeTab.value;
|
|
375
376
|
if (tab) {
|
|
@@ -383,15 +384,16 @@ effect(() => {
|
|
|
383
384
|
void tab.session.ui.stylebookFilter;
|
|
384
385
|
void tab.session.ui.stylebookCustomizedOnly;
|
|
385
386
|
}
|
|
386
|
-
if (!
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
387
|
+
if (!_canvasRafId) {
|
|
388
|
+
_canvasRafId = requestAnimationFrame(() => {
|
|
389
|
+
_canvasRafId = requestAnimationFrame(() => {
|
|
390
|
+
_canvasRafId = 0;
|
|
391
|
+
try {
|
|
392
|
+
renderCanvas();
|
|
393
|
+
} catch (e) {
|
|
394
|
+
console.error("renderCanvas error:", e);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
395
397
|
});
|
|
396
398
|
}
|
|
397
399
|
});
|