@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
package/src/panels/dnd.ts
CHANGED
|
@@ -16,14 +16,15 @@ import {
|
|
|
16
16
|
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
-
leftPanel,
|
|
20
|
-
getNodeAtPath,
|
|
21
|
-
parentElementPath,
|
|
22
19
|
childIndex,
|
|
20
|
+
childList,
|
|
21
|
+
getNodeAtPath,
|
|
23
22
|
isAncestor,
|
|
23
|
+
leftPanel,
|
|
24
|
+
parentElementPath,
|
|
24
25
|
renderOnly,
|
|
25
26
|
} from "../store";
|
|
26
|
-
import {
|
|
27
|
+
import { mutateInsertNode, mutateMoveNode, transact, transactDoc } from "../tabs/transact";
|
|
27
28
|
import { activeTab } from "../workspace/workspace";
|
|
28
29
|
import { view } from "../view";
|
|
29
30
|
import { componentRegistry, computeRelativePath } from "../files/components";
|
|
@@ -31,6 +32,7 @@ import { renderComponentPreview } from "./stylebook-panel";
|
|
|
31
32
|
import { defaultDef, unsafeTags } from "./shared";
|
|
32
33
|
import type { JxPath } from "../state";
|
|
33
34
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
35
|
+
import type { ComponentEntry } from "../files/components.js";
|
|
34
36
|
|
|
35
37
|
interface DragCanDragArgs {
|
|
36
38
|
element: HTMLElement;
|
|
@@ -54,26 +56,30 @@ interface DragMonitorDropArgs {
|
|
|
54
56
|
export function registerLayersDnD() {
|
|
55
57
|
requestAnimationFrame(() => {
|
|
56
58
|
const container = leftPanel?.querySelector(".layers-container") as HTMLElement | null;
|
|
57
|
-
if (!container)
|
|
59
|
+
if (!container) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
|
|
59
|
-
(container.querySelectorAll("[data-dnd-row]") as NodeListOf<HTMLElement>)
|
|
63
|
+
for (const row of container.querySelectorAll("[data-dnd-row]") as NodeListOf<HTMLElement>) {
|
|
60
64
|
const rowPath = (row.dataset.path as string)
|
|
61
65
|
.split("/")
|
|
62
|
-
.map((s: string) => (/^\d+$/.test(s) ? parseInt(s) : s)) as JxPath;
|
|
63
|
-
const rowDepth = parseInt(row.dataset.dndDepth as string) || 0;
|
|
64
|
-
const isVoid = row.
|
|
65
|
-
const isExpanded = row.
|
|
66
|
+
.map((s: string) => (/^\d+$/.test(s) ? Number.parseInt(s, 10) : s)) as JxPath;
|
|
67
|
+
const rowDepth = Number.parseInt(row.dataset.dndDepth as string, 10) || 0;
|
|
68
|
+
const isVoid = Object.hasOwn(row.dataset, "dndVoid");
|
|
69
|
+
const isExpanded = Object.hasOwn(row.dataset, "dndExpanded");
|
|
66
70
|
|
|
67
71
|
const cleanup = combine(
|
|
68
72
|
draggable({
|
|
69
|
-
element: row,
|
|
70
73
|
canDrag({ element: _el, input }: DragCanDragArgs) {
|
|
71
74
|
const target = document.elementFromPoint(input.clientX, input.clientY) as HTMLElement;
|
|
72
|
-
if (target?.closest(".layer-actions"))
|
|
75
|
+
if (target?.closest(".layer-actions")) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
73
78
|
return true;
|
|
74
79
|
},
|
|
80
|
+
element: row,
|
|
75
81
|
getInitialData() {
|
|
76
|
-
return { type: "tree-node"
|
|
82
|
+
return { path: rowPath, type: "tree-node" };
|
|
77
83
|
},
|
|
78
84
|
onDragStart() {
|
|
79
85
|
row.classList.add("dragging");
|
|
@@ -93,19 +99,27 @@ export function registerLayersDnD() {
|
|
|
93
99
|
element: row,
|
|
94
100
|
canDrop({ source }: DragDropSourceArgs) {
|
|
95
101
|
const srcPath = source.data.path as JxPath | undefined;
|
|
96
|
-
if (srcPath && isAncestor(srcPath, rowPath))
|
|
102
|
+
if (srcPath && isAncestor(srcPath, rowPath)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
97
105
|
return true;
|
|
98
106
|
},
|
|
99
|
-
getData({
|
|
107
|
+
getData({
|
|
108
|
+
input,
|
|
109
|
+
element,
|
|
110
|
+
}: {
|
|
111
|
+
input: Parameters<typeof attachInstruction>[1]["input"];
|
|
112
|
+
element: Element;
|
|
113
|
+
}) {
|
|
100
114
|
return attachInstruction(
|
|
101
115
|
{ path: rowPath },
|
|
102
116
|
{
|
|
103
|
-
|
|
104
|
-
element,
|
|
117
|
+
block: isVoid ? ["make-child"] : [],
|
|
105
118
|
currentLevel: rowDepth,
|
|
119
|
+
element,
|
|
106
120
|
indentPerLevel: 16,
|
|
121
|
+
input,
|
|
107
122
|
mode: isExpanded ? "expanded" : "standard",
|
|
108
|
-
block: isVoid ? ["make-child"] : [],
|
|
109
123
|
},
|
|
110
124
|
) as Record<string | symbol, unknown>;
|
|
111
125
|
},
|
|
@@ -116,35 +130,33 @@ export function registerLayersDnD() {
|
|
|
116
130
|
showLayerDropGap(row, self.data, container);
|
|
117
131
|
},
|
|
118
132
|
// No onDragLeave clear — the gap persists while the pointer crosses dead space
|
|
119
|
-
//
|
|
133
|
+
// Between rows; the monitor clears it when the drag moves to a non-tree target.
|
|
120
134
|
onDrop() {
|
|
121
135
|
clearLayerDropGap(container);
|
|
122
136
|
},
|
|
123
137
|
}),
|
|
124
138
|
);
|
|
125
139
|
view.dndCleanups.push(cleanup);
|
|
126
|
-
}
|
|
140
|
+
}
|
|
127
141
|
|
|
128
142
|
// Global monitor
|
|
129
143
|
const monitorCleanup = monitorForElements({
|
|
130
|
-
onDropTargetChange({ location }: DragMonitorDropArgs) {
|
|
131
|
-
// Clear the layer gap when the drag moves onto a non-tree target (e.g. a canvas
|
|
132
|
-
// element). When there is no target at all, keep the gap so it persists.
|
|
133
|
-
const inner = location.current.dropTargets[0];
|
|
134
|
-
if (inner && !extractInstruction(inner.data)) clearLayerDropGap(container);
|
|
135
|
-
},
|
|
136
144
|
onDrop({ source, location }: DragMonitorDropArgs) {
|
|
137
145
|
clearLayerDropGap(container);
|
|
138
|
-
const target = location.current.dropTargets
|
|
139
|
-
if (!target)
|
|
146
|
+
const [target] = location.current.dropTargets;
|
|
147
|
+
if (!target) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
140
150
|
const instruction = extractInstruction(target.data);
|
|
141
|
-
if (!instruction || instruction.type === "instruction-blocked")
|
|
151
|
+
if (!instruction || instruction.type === "instruction-blocked") {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
142
154
|
const srcData = source.data;
|
|
143
155
|
const targetPath = target.data.path as JxPath;
|
|
144
156
|
|
|
145
157
|
// If the source had children, persist collapse at the new location
|
|
146
158
|
const srcRow = srcData.type === "tree-node" && source.element;
|
|
147
|
-
const wasExpanded = srcRow && srcRow.
|
|
159
|
+
const wasExpanded = srcRow && Object.hasOwn(srcRow.dataset, "dndExpanded");
|
|
148
160
|
|
|
149
161
|
applyDropInstruction(instruction, srcData, targetPath);
|
|
150
162
|
|
|
@@ -152,11 +164,19 @@ export function registerLayersDnD() {
|
|
|
152
164
|
const tab = activeTab.value;
|
|
153
165
|
const newPath = tab?.session.selection;
|
|
154
166
|
if (newPath) {
|
|
155
|
-
const collapsed =
|
|
167
|
+
const collapsed = (view._layersCollapsed ||= new Set());
|
|
156
168
|
collapsed.add(newPath.join("/"));
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
},
|
|
172
|
+
onDropTargetChange({ location }: DragMonitorDropArgs) {
|
|
173
|
+
// Clear the layer gap when the drag moves onto a non-tree target (e.g. a canvas
|
|
174
|
+
// Element). When there is no target at all, keep the gap so it persists.
|
|
175
|
+
const [inner] = location.current.dropTargets;
|
|
176
|
+
if (inner && !extractInstruction(inner.data)) {
|
|
177
|
+
clearLayerDropGap(container);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
160
180
|
});
|
|
161
181
|
view.dndCleanups.push(monitorCleanup);
|
|
162
182
|
});
|
|
@@ -166,48 +186,52 @@ export function registerLayersDnD() {
|
|
|
166
186
|
export function registerComponentsDnD() {
|
|
167
187
|
requestAnimationFrame(() => {
|
|
168
188
|
const container = leftPanel?.querySelector(".components-section") as HTMLElement | null;
|
|
169
|
-
if (!container)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
(row) => {
|
|
173
|
-
const tagName = row.dataset.componentTag;
|
|
174
|
-
if (!tagName) return;
|
|
175
|
-
const comp = componentRegistry.find(
|
|
176
|
-
(c: import("../files/components.js").ComponentEntry) => c.tagName === tagName,
|
|
177
|
-
);
|
|
178
|
-
if (!comp) return;
|
|
189
|
+
if (!container) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
179
192
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
for (const row of container.querySelectorAll(
|
|
194
|
+
"[data-component-tag]",
|
|
195
|
+
) as NodeListOf<HTMLElement>) {
|
|
196
|
+
const tagName = row.dataset.componentTag;
|
|
197
|
+
if (!tagName) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const comp = componentRegistry.find((c: ComponentEntry) => c.tagName === tagName);
|
|
201
|
+
if (!comp) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
188
204
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
p.name,
|
|
196
|
-
p.default !== undefined ? p.default : "",
|
|
197
|
-
],
|
|
198
|
-
),
|
|
199
|
-
)
|
|
200
|
-
: {},
|
|
201
|
-
};
|
|
202
|
-
const cleanup = draggable({
|
|
203
|
-
element: row,
|
|
204
|
-
getInitialData() {
|
|
205
|
-
return { type: "block", fragment: structuredClone(instanceDef) };
|
|
206
|
-
},
|
|
205
|
+
// Fill preview with live rendered component
|
|
206
|
+
const preview = row.querySelector(".element-card-preview");
|
|
207
|
+
if (preview && !preview.querySelector(tagName)) {
|
|
208
|
+
renderComponentPreview(comp).then((el: HTMLElement) => {
|
|
209
|
+
preview.textContent = "";
|
|
210
|
+
preview.append(el);
|
|
207
211
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const instanceDef = {
|
|
215
|
+
$props: comp.props
|
|
216
|
+
? Object.fromEntries(
|
|
217
|
+
comp.props.map(
|
|
218
|
+
(/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
|
|
219
|
+
p.name,
|
|
220
|
+
p.default !== undefined ? p.default : "",
|
|
221
|
+
],
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
: {},
|
|
225
|
+
tagName: comp.tagName,
|
|
226
|
+
};
|
|
227
|
+
const cleanup = draggable({
|
|
228
|
+
element: row,
|
|
229
|
+
getInitialData() {
|
|
230
|
+
return { fragment: structuredClone(instanceDef), type: "block" };
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
view.dndCleanups.push(cleanup);
|
|
234
|
+
}
|
|
211
235
|
});
|
|
212
236
|
}
|
|
213
237
|
|
|
@@ -215,24 +239,26 @@ export function registerComponentsDnD() {
|
|
|
215
239
|
export function registerElementsDnD() {
|
|
216
240
|
requestAnimationFrame(() => {
|
|
217
241
|
const container = leftPanel?.querySelector(".panel-body") as HTMLElement | null;
|
|
218
|
-
if (!container)
|
|
219
|
-
|
|
242
|
+
if (!container) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const row of container.querySelectorAll("[data-block-tag]") as NodeListOf<HTMLElement>) {
|
|
220
246
|
const tag = row.dataset.blockTag as string;
|
|
221
247
|
const preview = row.querySelector(".element-card-preview");
|
|
222
248
|
if (preview && !preview.firstChild) {
|
|
223
249
|
const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
|
|
224
250
|
el.textContent = tag;
|
|
225
|
-
preview.
|
|
251
|
+
preview.append(el);
|
|
226
252
|
}
|
|
227
253
|
const def = defaultDef(tag);
|
|
228
254
|
const cleanup = draggable({
|
|
229
255
|
element: row,
|
|
230
256
|
getInitialData() {
|
|
231
|
-
return {
|
|
257
|
+
return { fragment: structuredClone(def), type: "block" };
|
|
232
258
|
},
|
|
233
259
|
});
|
|
234
260
|
view.dndCleanups.push(cleanup);
|
|
235
|
-
}
|
|
261
|
+
}
|
|
236
262
|
});
|
|
237
263
|
}
|
|
238
264
|
|
|
@@ -243,7 +269,7 @@ export function registerElementsDnD() {
|
|
|
243
269
|
* @param {HTMLElement} container
|
|
244
270
|
*/
|
|
245
271
|
function hideDescendantRows(parentRow: HTMLElement, container: HTMLElement) {
|
|
246
|
-
const prefix = parentRow.dataset.path
|
|
272
|
+
const prefix = `${parentRow.dataset.path}/`;
|
|
247
273
|
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
248
274
|
for (const r of rows) {
|
|
249
275
|
if ((r as HTMLElement).dataset.path?.startsWith(prefix)) {
|
|
@@ -285,12 +311,14 @@ export function showLayerDropGap(
|
|
|
285
311
|
view._currentDropTargetRow = rowEl;
|
|
286
312
|
|
|
287
313
|
// Shift rows to create gap
|
|
288
|
-
const rows =
|
|
314
|
+
const rows = [...container.querySelectorAll(".layers-tree .layer-row")];
|
|
289
315
|
const targetIdx = rows.indexOf(rowEl);
|
|
290
316
|
const gap = view.layerDragSourceHeight;
|
|
291
317
|
|
|
292
318
|
for (let i = 0; i < rows.length; i++) {
|
|
293
|
-
if ((rows[i] as HTMLElement).classList.contains("dragging"))
|
|
319
|
+
if ((rows[i] as HTMLElement).classList.contains("dragging")) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
294
322
|
if (instruction.type === "reorder-above") {
|
|
295
323
|
(rows[i] as HTMLElement).style.transform = i >= targetIdx ? `translateY(${gap}px)` : "";
|
|
296
324
|
} else {
|
|
@@ -306,7 +334,9 @@ export function clearLayerDropGap(container: HTMLElement) {
|
|
|
306
334
|
view._currentDropTargetRow = null;
|
|
307
335
|
}
|
|
308
336
|
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
309
|
-
for (const r of rows)
|
|
337
|
+
for (const r of rows) {
|
|
338
|
+
(r as HTMLElement).style.transform = "";
|
|
339
|
+
}
|
|
310
340
|
}
|
|
311
341
|
|
|
312
342
|
/**
|
|
@@ -327,34 +357,41 @@ export function applyDropInstruction(
|
|
|
327
357
|
const targetParent = parentElementPath(targetPath) as JxPath;
|
|
328
358
|
const targetIdx = childIndex(targetPath) as number;
|
|
329
359
|
// Reordering requires a parent and a numeric index; root-level paths can't be
|
|
330
|
-
//
|
|
331
|
-
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number"))
|
|
360
|
+
// Reordered around.
|
|
361
|
+
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number")) {
|
|
332
362
|
return;
|
|
363
|
+
}
|
|
333
364
|
|
|
334
365
|
switch (instruction.type) {
|
|
335
|
-
case "reorder-above":
|
|
366
|
+
case "reorder-above": {
|
|
336
367
|
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetParent, targetIdx));
|
|
337
368
|
break;
|
|
338
|
-
|
|
369
|
+
}
|
|
370
|
+
case "reorder-below": {
|
|
339
371
|
transactDoc(activeTab.value, (t) =>
|
|
340
372
|
mutateMoveNode(t, fromPath, targetParent, targetIdx + 1),
|
|
341
373
|
);
|
|
342
374
|
break;
|
|
375
|
+
}
|
|
343
376
|
case "make-child": {
|
|
344
377
|
const target = getNodeAtPath(doc, targetPath);
|
|
345
|
-
const len = target
|
|
378
|
+
const len = childList(target).length;
|
|
346
379
|
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
|
|
347
380
|
break;
|
|
348
381
|
}
|
|
382
|
+
default: {
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
349
385
|
}
|
|
350
386
|
} else if (srcData.type === "block") {
|
|
351
387
|
const targetParent = parentElementPath(targetPath) as JxPath;
|
|
352
388
|
const targetIdx = childIndex(targetPath) as number;
|
|
353
|
-
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number"))
|
|
389
|
+
if (instruction.type !== "make-child" && (!targetParent || typeof targetIdx !== "number")) {
|
|
354
390
|
return;
|
|
391
|
+
}
|
|
355
392
|
|
|
356
393
|
switch (instruction.type) {
|
|
357
|
-
case "reorder-above":
|
|
394
|
+
case "reorder-above": {
|
|
358
395
|
transactDoc(activeTab.value, (t) =>
|
|
359
396
|
mutateInsertNode(
|
|
360
397
|
t,
|
|
@@ -364,7 +401,8 @@ export function applyDropInstruction(
|
|
|
364
401
|
),
|
|
365
402
|
);
|
|
366
403
|
break;
|
|
367
|
-
|
|
404
|
+
}
|
|
405
|
+
case "reorder-below": {
|
|
368
406
|
transactDoc(activeTab.value, (t) =>
|
|
369
407
|
mutateInsertNode(
|
|
370
408
|
t,
|
|
@@ -374,51 +412,61 @@ export function applyDropInstruction(
|
|
|
374
412
|
),
|
|
375
413
|
);
|
|
376
414
|
break;
|
|
415
|
+
}
|
|
377
416
|
case "make-child": {
|
|
378
417
|
const target = getNodeAtPath(doc, targetPath);
|
|
379
|
-
const len = target
|
|
418
|
+
const len = childList(target).length;
|
|
380
419
|
transactDoc(activeTab.value, (t) =>
|
|
381
420
|
mutateInsertNode(t, targetPath, len, structuredClone(srcData.fragment as JxMutableNode)),
|
|
382
421
|
);
|
|
383
422
|
break;
|
|
384
423
|
}
|
|
424
|
+
default: {
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
385
427
|
}
|
|
386
428
|
|
|
387
429
|
// Auto-import to $elements if the dropped block is a custom component
|
|
388
430
|
const fragment = srcData.fragment as JxMutableNode | undefined;
|
|
389
431
|
const tag = fragment?.tagName;
|
|
390
432
|
if (tag && tag.includes("-")) {
|
|
391
|
-
const comp = componentRegistry.find(
|
|
392
|
-
(c: import("../files/components.js").ComponentEntry) => c.tagName === tag,
|
|
393
|
-
);
|
|
433
|
+
const comp = componentRegistry.find((c: ComponentEntry) => c.tagName === tag);
|
|
394
434
|
if (comp) {
|
|
395
435
|
const tab = activeTab.value;
|
|
396
436
|
const elements = tab?.doc.document?.$elements || [];
|
|
397
437
|
if (comp.source === "npm") {
|
|
398
438
|
const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
|
|
399
|
-
if (!specifier)
|
|
439
|
+
if (!specifier) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
400
442
|
const alreadyImported = elements.some(
|
|
401
443
|
(e: JxMutableNode | string | { $ref: string }) => e === specifier || e === comp.package,
|
|
402
444
|
);
|
|
403
445
|
if (!alreadyImported) {
|
|
404
|
-
transact(activeTab.value, (
|
|
405
|
-
if (!
|
|
406
|
-
|
|
446
|
+
transact(activeTab.value, (d: JxMutableNode) => {
|
|
447
|
+
if (!d.$elements) {
|
|
448
|
+
d.$elements = [];
|
|
449
|
+
}
|
|
450
|
+
d.$elements.push(specifier);
|
|
407
451
|
});
|
|
408
452
|
}
|
|
409
453
|
} else {
|
|
410
454
|
const alreadyImported = elements.some((e: JxMutableNode | string | { $ref: string }) => {
|
|
411
455
|
const ref = typeof e === "object" && e !== null ? e.$ref : undefined;
|
|
456
|
+
const compPath = comp.path;
|
|
412
457
|
return (
|
|
413
458
|
ref &&
|
|
414
|
-
|
|
459
|
+
compPath &&
|
|
460
|
+
(ref === `./${compPath}` || ref.endsWith(compPath.split("/").pop() as string))
|
|
415
461
|
);
|
|
416
462
|
});
|
|
417
|
-
if (!alreadyImported) {
|
|
463
|
+
if (!alreadyImported && comp.path) {
|
|
418
464
|
const relPath = computeRelativePath(tab?.documentPath ?? null, comp.path);
|
|
419
|
-
transact(activeTab.value, (
|
|
420
|
-
if (!
|
|
421
|
-
|
|
465
|
+
transact(activeTab.value, (d: JxMutableNode) => {
|
|
466
|
+
if (!d.$elements) {
|
|
467
|
+
d.$elements = [];
|
|
468
|
+
}
|
|
469
|
+
d.$elements.push({ $ref: relPath });
|
|
422
470
|
});
|
|
423
471
|
}
|
|
424
472
|
}
|
package/src/panels/editors.ts
CHANGED
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
|
|
8
|
+
import { isJsonObject } from "@jxsuite/schema/guards";
|
|
8
9
|
import { html, render as litRender, nothing } from "lit-html";
|
|
9
10
|
import { ref } from "lit-html/directives/ref.js";
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import { canvasPanels, canvasWrap, getNodeAtPath, renderOnly } from "../store";
|
|
12
13
|
import { activeTab } from "../workspace/workspace";
|
|
13
|
-
import {
|
|
14
|
+
import { mutateUpdateDef, mutateUpdateProperty, transactDoc } from "../tabs/transact";
|
|
14
15
|
import { view } from "../view";
|
|
15
|
-
import { codeService,
|
|
16
|
+
import { codeService, getFunctionArgs, setLintMarkers } from "../services/code-services";
|
|
16
17
|
|
|
17
18
|
import type { OxLintDiagnostic } from "../services/code-services";
|
|
18
|
-
import type {
|
|
19
|
+
import type { JxMutableNode, JxPrototypeDef } from "@jxsuite/schema/types";
|
|
19
20
|
import type { JxPath } from "../state";
|
|
20
21
|
|
|
21
22
|
type EditingTarget =
|
|
@@ -25,11 +26,14 @@ type EditingTarget =
|
|
|
25
26
|
/** @param {EditingTarget | null | undefined} editing */
|
|
26
27
|
function getFunctionBody(editing: EditingTarget | null | undefined) {
|
|
27
28
|
const document = activeTab.value?.doc.document;
|
|
29
|
+
// Read body off any object-shaped def (covers legacy entries without $prototype).
|
|
30
|
+
const bodyOf = (def: unknown) =>
|
|
31
|
+
isJsonObject(def) && typeof def.body === "string" ? def.body : "";
|
|
28
32
|
if (editing?.type === "def") {
|
|
29
|
-
return document?.state?.[editing.defName]
|
|
33
|
+
return bodyOf(document?.state?.[editing.defName]);
|
|
30
34
|
} else if (editing?.type === "event") {
|
|
31
35
|
const node = getNodeAtPath(document!, editing.path);
|
|
32
|
-
return node
|
|
36
|
+
return node ? bodyOf(node[editing.eventKey]) : "";
|
|
33
37
|
}
|
|
34
38
|
return "";
|
|
35
39
|
}
|
|
@@ -60,9 +64,13 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
// Clean up canvas DnD and event handlers
|
|
63
|
-
for (const fn of view.canvasDndCleanups)
|
|
67
|
+
for (const fn of view.canvasDndCleanups) {
|
|
68
|
+
fn();
|
|
69
|
+
}
|
|
64
70
|
view.canvasDndCleanups = [];
|
|
65
|
-
for (const fn of view.canvasEventCleanups)
|
|
71
|
+
for (const fn of view.canvasEventCleanups) {
|
|
72
|
+
fn();
|
|
73
|
+
}
|
|
66
74
|
view.canvasEventCleanups = [];
|
|
67
75
|
canvasPanels.length = 0;
|
|
68
76
|
|
|
@@ -95,7 +103,9 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
95
103
|
<div
|
|
96
104
|
class="source-editor"
|
|
97
105
|
${ref((el) => {
|
|
98
|
-
if (el)
|
|
106
|
+
if (el) {
|
|
107
|
+
editorContainer = el as HTMLDivElement;
|
|
108
|
+
}
|
|
99
109
|
})}
|
|
100
110
|
></div>
|
|
101
111
|
</div>`,
|
|
@@ -109,31 +119,32 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
109
119
|
);
|
|
110
120
|
|
|
111
121
|
view.functionEditor = monaco.editor.create(editorContainer as unknown as HTMLElement, {
|
|
112
|
-
value: body,
|
|
113
|
-
language: "javascript",
|
|
114
|
-
theme: "vs-dark",
|
|
115
122
|
automaticLayout: true,
|
|
116
|
-
minimap: { enabled: false },
|
|
117
|
-
fontSize: 12,
|
|
118
123
|
fontFamily: "'SF Mono', 'Fira Code', 'Consolas', monospace",
|
|
124
|
+
fontSize: 12,
|
|
125
|
+
language: "javascript",
|
|
119
126
|
lineNumbers: "on",
|
|
127
|
+
minimap: { enabled: false },
|
|
120
128
|
scrollBeyondLastLine: false,
|
|
121
|
-
wordWrap: "on",
|
|
122
129
|
tabSize: 2,
|
|
130
|
+
theme: "vs-dark",
|
|
131
|
+
value: body,
|
|
132
|
+
wordWrap: "on",
|
|
123
133
|
});
|
|
124
134
|
view.functionEditor._editingTarget = JSON.stringify(editing);
|
|
125
135
|
const editor = view.functionEditor;
|
|
126
136
|
|
|
127
137
|
// Format on open — show pretty-printed code, then run initial lint
|
|
128
|
-
codeService("format", { code: body
|
|
138
|
+
codeService("format", { args, code: body }).then((result) => {
|
|
129
139
|
if (result?.code != null && view.functionEditor) {
|
|
130
140
|
view.functionEditor._ignoreNextChange = true;
|
|
131
141
|
view.functionEditor.setValue(result.code);
|
|
132
142
|
}
|
|
133
143
|
});
|
|
134
|
-
codeService("lint", { code: body
|
|
135
|
-
if (result?.diagnostics && view.functionEditor)
|
|
144
|
+
codeService("lint", { args, code: body }).then((result) => {
|
|
145
|
+
if (result?.diagnostics && view.functionEditor) {
|
|
136
146
|
setLintMarkers(view.functionEditor, result.diagnostics as OxLintDiagnostic[]);
|
|
147
|
+
}
|
|
137
148
|
});
|
|
138
149
|
|
|
139
150
|
// Debounced sync back to state + lint on edit
|
|
@@ -151,14 +162,14 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
151
162
|
clearTimeout(syncDebounce);
|
|
152
163
|
syncDebounce = setTimeout(() => {
|
|
153
164
|
const newBody = editor.getValue();
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
transactDoc(activeTab.value, (t) => mutateUpdateDef(t,
|
|
157
|
-
} else if (
|
|
158
|
-
const node = getNodeAtPath(activeTab.value?.doc.document as JxMutableNode,
|
|
159
|
-
const current = node?.[
|
|
165
|
+
const target = editing as EditingTarget;
|
|
166
|
+
if (target.type === "def") {
|
|
167
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, target.defName, { body: newBody }));
|
|
168
|
+
} else if (target.type === "event") {
|
|
169
|
+
const node = getNodeAtPath(activeTab.value?.doc.document as JxMutableNode, target.path);
|
|
170
|
+
const current = node?.[target.eventKey] || {};
|
|
160
171
|
transactDoc(activeTab.value, (t) =>
|
|
161
|
-
mutateUpdateProperty(t,
|
|
172
|
+
mutateUpdateProperty(t, target.path, target.eventKey, {
|
|
162
173
|
...current,
|
|
163
174
|
$prototype: "Function",
|
|
164
175
|
body: newBody,
|
|
@@ -170,12 +181,15 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
170
181
|
|
|
171
182
|
clearTimeout(lintDebounce);
|
|
172
183
|
lintDebounce = setTimeout(() => {
|
|
173
|
-
const gen =
|
|
184
|
+
const gen = (lintGen += 1);
|
|
174
185
|
const currentCode = editor.getValue();
|
|
175
|
-
codeService("lint", { code: currentCode
|
|
176
|
-
if (gen !== lintGen)
|
|
177
|
-
|
|
186
|
+
codeService("lint", { args, code: currentCode }).then((result) => {
|
|
187
|
+
if (gen !== lintGen) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (result?.diagnostics && view.functionEditor) {
|
|
178
191
|
setLintMarkers(view.functionEditor, result.diagnostics as OxLintDiagnostic[]);
|
|
192
|
+
}
|
|
179
193
|
});
|
|
180
194
|
}, 750);
|
|
181
195
|
});
|
|
@@ -183,18 +197,19 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
183
197
|
|
|
184
198
|
// Register Monaco JS completion provider for state scope variables (once)
|
|
185
199
|
export function registerFunctionCompletions() {
|
|
186
|
-
if (view._completionRegistered)
|
|
200
|
+
if (view._completionRegistered) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
187
203
|
view._completionRegistered = true;
|
|
188
204
|
monaco.languages.registerCompletionItemProvider("javascript", {
|
|
189
|
-
triggerCharacters: ["."],
|
|
190
205
|
provideCompletionItems(model, position) {
|
|
191
206
|
const defs = activeTab.value?.doc.document?.state || {};
|
|
192
207
|
const word = model.getWordUntilPosition(position);
|
|
193
208
|
const range = {
|
|
194
|
-
|
|
209
|
+
endColumn: word.endColumn,
|
|
195
210
|
endLineNumber: position.lineNumber,
|
|
196
211
|
startColumn: word.startColumn,
|
|
197
|
-
|
|
212
|
+
startLineNumber: position.lineNumber,
|
|
198
213
|
};
|
|
199
214
|
|
|
200
215
|
const suggestions = Object.entries(defs).map(([key, def]) => {
|
|
@@ -202,18 +217,20 @@ export function registerFunctionCompletions() {
|
|
|
202
217
|
if (
|
|
203
218
|
(def as JxPrototypeDef)?.$prototype === "Function" ||
|
|
204
219
|
(def as Record<string, unknown>)?.$handler
|
|
205
|
-
)
|
|
220
|
+
) {
|
|
206
221
|
kind = monaco.languages.CompletionItemKind.Function;
|
|
207
|
-
else if ((def as JxPrototypeDef)?.$prototype)
|
|
222
|
+
} else if ((def as JxPrototypeDef)?.$prototype) {
|
|
208
223
|
kind = monaco.languages.CompletionItemKind.Property;
|
|
224
|
+
}
|
|
209
225
|
return {
|
|
210
|
-
label: `state.${key}`,
|
|
211
|
-
kind,
|
|
212
226
|
insertText: `state.${key}`,
|
|
227
|
+
kind,
|
|
228
|
+
label: `state.${key}`,
|
|
213
229
|
range,
|
|
214
230
|
};
|
|
215
231
|
});
|
|
216
232
|
return { suggestions };
|
|
217
233
|
},
|
|
234
|
+
triggerCharacters: ["."],
|
|
218
235
|
});
|
|
219
236
|
}
|