@dragcraft/designer 0.0.1
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/LICENSE +21 -0
- package/dist/index.d.mts +502 -0
- package/dist/index.mjs +2228 -0
- package/dist/structure.css +613 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2228 @@
|
|
|
1
|
+
import { CommandType, CommandType as CommandType$1, DEFAULT_LAYOUT_REGION, DEFAULT_SORT_SCOPE, EventName, buildSchemaIndex, clampInsertIndex, createContainerPlan, createEngine, createEngine as createEngine$1, createLayoutPlan, findNearestValidIndex, getLockedIndices, getLockedIndicesFromNodes, getSortScopeEntries, getValidDropIndices, resolveBehavior, resolveCreatable, resolveCreatable as resolveCreatable$1, resolveDestination, resolveNodeLayout, resolvePlacementDecision } from "@dragcraft/core";
|
|
2
|
+
import { ActionKey, ActionKey as ActionKey$1, ContainerRegionOutlet, DefaultEmptyState, DefaultNodeHandle, DefaultNodeMask, DefaultNodeToolbar, RootRenderer, RootRenderer as RootRenderer$1, createConfirmActionInterceptor, createDefaultActions, createDefaultActions as createDefaultActions$1, createNodeActionRegistry, createNodeActionRegistry as createNodeActionRegistry$1, hideNativeDragImage, rendererMessages, useContainerRuntime, useNodeActions, useNodeDrag, useNodeInteractionGeometry, useWidgetNode } from "@dragcraft/renderer";
|
|
3
|
+
import { computed, defineComponent, h, inject, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from "vue";
|
|
4
|
+
import { I18N_KEY, I18N_KEY as I18N_KEY$1, createI18n, createI18n as createI18n$1, useI18n, useI18n as useI18n$1 } from "@dragcraft/i18n";
|
|
5
|
+
import { IconCenter, IconChevronDown, IconChevronLeft, IconChevronRight, IconClose, IconGlobalConfig, IconHand, IconMaterial, IconPointer, IconProperties, IconRedo, IconSearch, IconStructureTree, IconUndo } from "@dragcraft/icons";
|
|
6
|
+
import { cloneDeep, generateShortId } from "@dragcraft/utils";
|
|
7
|
+
import { FormGenerator, FormGenerator as FormGenerator$1 } from "@dragcraft/form-generator";
|
|
8
|
+
//#region src/bindings/field-binding.ts
|
|
9
|
+
const BLOCKED_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
10
|
+
"__proto__",
|
|
11
|
+
"prototype",
|
|
12
|
+
"constructor"
|
|
13
|
+
]);
|
|
14
|
+
function toPathSegments(path) {
|
|
15
|
+
return path.split(".").map((segment) => segment.trim()).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
function isSafePath(path) {
|
|
18
|
+
return toPathSegments(path).every((segment) => !BLOCKED_PATH_SEGMENTS.has(segment));
|
|
19
|
+
}
|
|
20
|
+
function safePathSegments(path) {
|
|
21
|
+
if (!isSafePath(path)) return [];
|
|
22
|
+
return toPathSegments(path);
|
|
23
|
+
}
|
|
24
|
+
function splitHead(path) {
|
|
25
|
+
const [head, ...rest] = safePathSegments(path);
|
|
26
|
+
return [head, rest.join(".")];
|
|
27
|
+
}
|
|
28
|
+
function setPatchPath(path, value) {
|
|
29
|
+
const segments = safePathSegments(path);
|
|
30
|
+
if (segments.length === 0) return null;
|
|
31
|
+
const root = {};
|
|
32
|
+
let cursor = root;
|
|
33
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
34
|
+
const next = {};
|
|
35
|
+
cursor[segments[i]] = next;
|
|
36
|
+
cursor = next;
|
|
37
|
+
}
|
|
38
|
+
cursor[segments[segments.length - 1]] = value;
|
|
39
|
+
return root;
|
|
40
|
+
}
|
|
41
|
+
function resolveFieldBinding(binding, fallback) {
|
|
42
|
+
if (typeof binding === "string") return {
|
|
43
|
+
scope: fallback.scope,
|
|
44
|
+
path: binding
|
|
45
|
+
};
|
|
46
|
+
if (binding) return {
|
|
47
|
+
scope: binding.scope ?? fallback.scope,
|
|
48
|
+
path: binding.path
|
|
49
|
+
};
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
function readPath(source, path) {
|
|
53
|
+
if (!isSafePath(path)) return void 0;
|
|
54
|
+
let current = source;
|
|
55
|
+
for (const segment of toPathSegments(path)) {
|
|
56
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
57
|
+
current = current[segment];
|
|
58
|
+
}
|
|
59
|
+
return current;
|
|
60
|
+
}
|
|
61
|
+
function readBindingValue(binding, schema, node) {
|
|
62
|
+
if (binding.scope === "container") return node && binding.path === "variant" ? node.container?.variant : void 0;
|
|
63
|
+
if (binding.scope === "globalConfig") return readPath(schema.globalConfig, binding.path);
|
|
64
|
+
if (binding.scope === "schema") return readPath(schema, binding.path);
|
|
65
|
+
return node ? readPath(node, binding.path) : void 0;
|
|
66
|
+
}
|
|
67
|
+
function createNodeBindingCommand(nodeId, path, value) {
|
|
68
|
+
const [head, rest] = splitHead(path);
|
|
69
|
+
if (head === "props") {
|
|
70
|
+
const props = setPatchPath(rest, value);
|
|
71
|
+
return props ? {
|
|
72
|
+
type: CommandType$1.UPDATE_PROPS,
|
|
73
|
+
payload: {
|
|
74
|
+
nodeId,
|
|
75
|
+
props
|
|
76
|
+
}
|
|
77
|
+
} : null;
|
|
78
|
+
}
|
|
79
|
+
if (head === "style") {
|
|
80
|
+
const style = setPatchPath(rest, value);
|
|
81
|
+
return style ? {
|
|
82
|
+
type: CommandType$1.UPDATE_PROPS,
|
|
83
|
+
payload: {
|
|
84
|
+
nodeId,
|
|
85
|
+
props: {},
|
|
86
|
+
style
|
|
87
|
+
}
|
|
88
|
+
} : null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function createBindingCommand(binding, value, nodeId) {
|
|
93
|
+
if (!isSafePath(binding.path)) return null;
|
|
94
|
+
if (binding.scope === "container") {
|
|
95
|
+
if (!nodeId || binding.path !== "variant" || typeof value !== "string") return null;
|
|
96
|
+
return {
|
|
97
|
+
type: CommandType$1.CHANGE_CONTAINER_VARIANT,
|
|
98
|
+
payload: {
|
|
99
|
+
containerId: nodeId,
|
|
100
|
+
variant: value
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (binding.scope === "globalConfig") {
|
|
105
|
+
const config = setPatchPath(binding.path, value);
|
|
106
|
+
return config ? {
|
|
107
|
+
type: CommandType$1.SET_GLOBAL_CONFIG,
|
|
108
|
+
payload: { config }
|
|
109
|
+
} : null;
|
|
110
|
+
}
|
|
111
|
+
if (binding.scope === "schema") {
|
|
112
|
+
const [head, rest] = splitHead(binding.path);
|
|
113
|
+
if (head === "globalConfig") {
|
|
114
|
+
const config = setPatchPath(rest, value);
|
|
115
|
+
return config ? {
|
|
116
|
+
type: CommandType$1.SET_GLOBAL_CONFIG,
|
|
117
|
+
payload: { config }
|
|
118
|
+
} : null;
|
|
119
|
+
}
|
|
120
|
+
if (head === "root") return createNodeBindingCommand("root", rest, value);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return nodeId ? createNodeBindingCommand(nodeId, binding.path, value) : null;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/composables/useCanvasPan.ts
|
|
127
|
+
const EDITABLE_SELECTOR$1 = "input, textarea, select, [contenteditable=\"true\"], [contenteditable=\"\"]";
|
|
128
|
+
function isEditableTarget$1(target) {
|
|
129
|
+
return target instanceof Element && Boolean(target.closest(EDITABLE_SELECTOR$1));
|
|
130
|
+
}
|
|
131
|
+
function resolvePixelCorrection(value, devicePixelRatio) {
|
|
132
|
+
const ratio = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
133
|
+
const correction = Math.floor(value * ratio + .5 - 1e-7) / ratio - value;
|
|
134
|
+
return Math.abs(correction) < 1e-7 ? 0 : correction;
|
|
135
|
+
}
|
|
136
|
+
function resolveCanvasStagePixelSnap(geometry, devicePixelRatio) {
|
|
137
|
+
const left = geometry.viewport.left + (geometry.viewport.width - geometry.stage.width) / 2 + geometry.offset.x;
|
|
138
|
+
const top = geometry.viewport.top + (geometry.viewport.height - geometry.stage.height) / 2 + geometry.offset.y;
|
|
139
|
+
return {
|
|
140
|
+
x: resolvePixelCorrection(left, devicePixelRatio),
|
|
141
|
+
y: resolvePixelCorrection(top, devicePixelRatio)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function useCanvasPan(viewportRef, stageRef) {
|
|
145
|
+
const mode = ref("pointer");
|
|
146
|
+
const offset = ref({
|
|
147
|
+
x: 0,
|
|
148
|
+
y: 0
|
|
149
|
+
});
|
|
150
|
+
const pixelSnap = ref({
|
|
151
|
+
x: 0,
|
|
152
|
+
y: 0
|
|
153
|
+
});
|
|
154
|
+
const spacePressed = ref(false);
|
|
155
|
+
const isPanning = ref(false);
|
|
156
|
+
const pointerInside = ref(false);
|
|
157
|
+
const panEnabled = computed(() => mode.value === "hand" || spacePressed.value);
|
|
158
|
+
let pointerId = null;
|
|
159
|
+
let startX = 0;
|
|
160
|
+
let startY = 0;
|
|
161
|
+
let startOffsetX = 0;
|
|
162
|
+
let startOffsetY = 0;
|
|
163
|
+
let suppressClick = false;
|
|
164
|
+
let resizeObserver = null;
|
|
165
|
+
let pixelSnapFrame = null;
|
|
166
|
+
function updatePixelSnap() {
|
|
167
|
+
const viewport = viewportRef.value;
|
|
168
|
+
const stage = stageRef.value;
|
|
169
|
+
if (!viewport || !stage) return;
|
|
170
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
171
|
+
const stageRect = stage.getBoundingClientRect();
|
|
172
|
+
const next = resolveCanvasStagePixelSnap({
|
|
173
|
+
viewport: {
|
|
174
|
+
left: viewportRect.left,
|
|
175
|
+
top: viewportRect.top,
|
|
176
|
+
width: viewportRect.width,
|
|
177
|
+
height: viewportRect.height
|
|
178
|
+
},
|
|
179
|
+
stage: {
|
|
180
|
+
width: stageRect.width,
|
|
181
|
+
height: stageRect.height
|
|
182
|
+
},
|
|
183
|
+
offset: offset.value
|
|
184
|
+
}, window.devicePixelRatio);
|
|
185
|
+
if (next.x !== pixelSnap.value.x || next.y !== pixelSnap.value.y) pixelSnap.value = next;
|
|
186
|
+
}
|
|
187
|
+
function schedulePixelSnap() {
|
|
188
|
+
if (pixelSnapFrame !== null) return;
|
|
189
|
+
pixelSnapFrame = window.requestAnimationFrame(() => {
|
|
190
|
+
pixelSnapFrame = null;
|
|
191
|
+
updatePixelSnap();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function observePixelGeometry(viewport, stage) {
|
|
195
|
+
resizeObserver?.disconnect();
|
|
196
|
+
if (viewport) resizeObserver?.observe(viewport);
|
|
197
|
+
if (stage) resizeObserver?.observe(stage);
|
|
198
|
+
schedulePixelSnap();
|
|
199
|
+
}
|
|
200
|
+
function setMode(nextMode) {
|
|
201
|
+
mode.value = nextMode;
|
|
202
|
+
}
|
|
203
|
+
function reset() {
|
|
204
|
+
offset.value = {
|
|
205
|
+
x: 0,
|
|
206
|
+
y: 0
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function handleWindowKeydown(event) {
|
|
210
|
+
const viewport = viewportRef.value;
|
|
211
|
+
const focusedInside = viewport != null && document.activeElement != null && viewport.contains(document.activeElement);
|
|
212
|
+
if (event.code !== "Space" || isEditableTarget$1(event.target) || !pointerInside.value && !focusedInside) return;
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
spacePressed.value = true;
|
|
215
|
+
}
|
|
216
|
+
function handleWindowKeyup(event) {
|
|
217
|
+
if (event.code !== "Space") return;
|
|
218
|
+
spacePressed.value = false;
|
|
219
|
+
}
|
|
220
|
+
function resetTemporaryMode() {
|
|
221
|
+
spacePressed.value = false;
|
|
222
|
+
isPanning.value = false;
|
|
223
|
+
pointerId = null;
|
|
224
|
+
}
|
|
225
|
+
function handlePointerEnter() {
|
|
226
|
+
pointerInside.value = true;
|
|
227
|
+
}
|
|
228
|
+
function handlePointerLeave() {
|
|
229
|
+
pointerInside.value = false;
|
|
230
|
+
}
|
|
231
|
+
function handlePointerDown(event) {
|
|
232
|
+
const viewport = viewportRef.value;
|
|
233
|
+
if (!viewport || !panEnabled.value || event.button !== 0) return;
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
event.stopPropagation();
|
|
236
|
+
pointerId = event.pointerId;
|
|
237
|
+
startX = event.clientX;
|
|
238
|
+
startY = event.clientY;
|
|
239
|
+
startOffsetX = offset.value.x;
|
|
240
|
+
startOffsetY = offset.value.y;
|
|
241
|
+
suppressClick = true;
|
|
242
|
+
isPanning.value = true;
|
|
243
|
+
viewport.setPointerCapture?.(event.pointerId);
|
|
244
|
+
}
|
|
245
|
+
function handlePointerMove(event) {
|
|
246
|
+
if (!viewportRef.value || pointerId !== event.pointerId) return;
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
event.stopPropagation();
|
|
249
|
+
offset.value = {
|
|
250
|
+
x: startOffsetX + event.clientX - startX,
|
|
251
|
+
y: startOffsetY + event.clientY - startY
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function handlePointerUp(event) {
|
|
255
|
+
const viewport = viewportRef.value;
|
|
256
|
+
if (!viewport || pointerId !== event.pointerId) return;
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
event.stopPropagation();
|
|
259
|
+
viewport.releasePointerCapture?.(event.pointerId);
|
|
260
|
+
pointerId = null;
|
|
261
|
+
isPanning.value = false;
|
|
262
|
+
}
|
|
263
|
+
function handleClickCapture(event) {
|
|
264
|
+
if (!panEnabled.value && !suppressClick) return;
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
event.stopPropagation();
|
|
267
|
+
suppressClick = false;
|
|
268
|
+
}
|
|
269
|
+
onMounted(() => {
|
|
270
|
+
window.addEventListener("keydown", handleWindowKeydown);
|
|
271
|
+
window.addEventListener("keyup", handleWindowKeyup);
|
|
272
|
+
window.addEventListener("blur", resetTemporaryMode);
|
|
273
|
+
window.addEventListener("resize", schedulePixelSnap);
|
|
274
|
+
if (typeof ResizeObserver !== "undefined") resizeObserver = new ResizeObserver(schedulePixelSnap);
|
|
275
|
+
observePixelGeometry(viewportRef.value, stageRef.value);
|
|
276
|
+
});
|
|
277
|
+
onBeforeUnmount(() => {
|
|
278
|
+
window.removeEventListener("keydown", handleWindowKeydown);
|
|
279
|
+
window.removeEventListener("keyup", handleWindowKeyup);
|
|
280
|
+
window.removeEventListener("blur", resetTemporaryMode);
|
|
281
|
+
window.removeEventListener("resize", schedulePixelSnap);
|
|
282
|
+
resizeObserver?.disconnect();
|
|
283
|
+
if (pixelSnapFrame !== null) window.cancelAnimationFrame(pixelSnapFrame);
|
|
284
|
+
});
|
|
285
|
+
watch([viewportRef, stageRef], ([viewport, stage]) => {
|
|
286
|
+
observePixelGeometry(viewport, stage);
|
|
287
|
+
}, { flush: "post" });
|
|
288
|
+
watch(offset, schedulePixelSnap, { flush: "sync" });
|
|
289
|
+
return {
|
|
290
|
+
mode,
|
|
291
|
+
offset,
|
|
292
|
+
pixelSnap,
|
|
293
|
+
panEnabled,
|
|
294
|
+
isPanning,
|
|
295
|
+
setMode,
|
|
296
|
+
reset,
|
|
297
|
+
handlePointerEnter,
|
|
298
|
+
handlePointerLeave,
|
|
299
|
+
handlePointerDown,
|
|
300
|
+
handlePointerMove,
|
|
301
|
+
handlePointerUp,
|
|
302
|
+
handleClickCapture
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/types.ts
|
|
307
|
+
/**
|
|
308
|
+
* Injection key for the designer context.
|
|
309
|
+
*/
|
|
310
|
+
const DESIGNER_CONTEXT_KEY = Symbol("dc-designer");
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/context.ts
|
|
313
|
+
/**
|
|
314
|
+
* Injects the DesignerContext from the nearest ancestor DcDesigner.
|
|
315
|
+
* Throws if called outside the designer component tree.
|
|
316
|
+
*/
|
|
317
|
+
function useDesignerContext() {
|
|
318
|
+
const ctx = inject(DESIGNER_CONTEXT_KEY);
|
|
319
|
+
if (!ctx) throw new Error("[dragcraft/designer] DesignerContext not found. Ensure this component is a descendant of DcDesigner.");
|
|
320
|
+
return ctx;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/components/DcCanvasControls.ts
|
|
324
|
+
var DcCanvasControls_default = defineComponent({
|
|
325
|
+
name: "DcCanvasControls",
|
|
326
|
+
props: { interactionMode: {
|
|
327
|
+
type: String,
|
|
328
|
+
required: true
|
|
329
|
+
} },
|
|
330
|
+
emits: {
|
|
331
|
+
modeChange: (_mode) => true,
|
|
332
|
+
resetView: () => true
|
|
333
|
+
},
|
|
334
|
+
setup(props, { emit }) {
|
|
335
|
+
const { t } = useI18n$1();
|
|
336
|
+
const { engine } = useDesignerContext();
|
|
337
|
+
const renderHistoryButton = (options) => h("button", {
|
|
338
|
+
"type": "button",
|
|
339
|
+
"class": "dc-canvas-controls__button",
|
|
340
|
+
"data-dc-part": "button",
|
|
341
|
+
"disabled": options.disabled,
|
|
342
|
+
"title": options.label,
|
|
343
|
+
"aria-label": options.label,
|
|
344
|
+
"aria-pressed": options.active,
|
|
345
|
+
"data-dc-workspace-control": options.key,
|
|
346
|
+
"onClick": options.onClick
|
|
347
|
+
}, [h(options.icon, { size: 17 })]);
|
|
348
|
+
return () => {
|
|
349
|
+
const history = engine.history.state.value;
|
|
350
|
+
return h("div", {
|
|
351
|
+
"class": "dc-canvas-controls",
|
|
352
|
+
"data-dc-component": "canvas-controls"
|
|
353
|
+
}, [h("div", {
|
|
354
|
+
"class": "dc-canvas-controls__history",
|
|
355
|
+
"data-dc-part": "toolbar",
|
|
356
|
+
"role": "toolbar",
|
|
357
|
+
"aria-label": t("workspace.canvas.controls", "画布工具")
|
|
358
|
+
}, [
|
|
359
|
+
renderHistoryButton({
|
|
360
|
+
key: "undo",
|
|
361
|
+
label: t("workspace.history.undo", "撤销"),
|
|
362
|
+
icon: IconUndo,
|
|
363
|
+
disabled: !history.canUndo,
|
|
364
|
+
onClick: () => engine.history.undo()
|
|
365
|
+
}),
|
|
366
|
+
renderHistoryButton({
|
|
367
|
+
key: "redo",
|
|
368
|
+
label: t("workspace.history.redo", "重做"),
|
|
369
|
+
icon: IconRedo,
|
|
370
|
+
disabled: !history.canRedo,
|
|
371
|
+
onClick: () => engine.history.redo()
|
|
372
|
+
}),
|
|
373
|
+
h("span", {
|
|
374
|
+
"class": "dc-canvas-controls__divider",
|
|
375
|
+
"data-dc-part": "divider",
|
|
376
|
+
"aria-hidden": "true"
|
|
377
|
+
}),
|
|
378
|
+
renderHistoryButton({
|
|
379
|
+
key: "pointer",
|
|
380
|
+
label: t("workspace.canvas.pointer", "指针模式"),
|
|
381
|
+
icon: IconPointer,
|
|
382
|
+
active: props.interactionMode === "pointer",
|
|
383
|
+
onClick: () => emit("modeChange", "pointer")
|
|
384
|
+
}),
|
|
385
|
+
renderHistoryButton({
|
|
386
|
+
key: "hand",
|
|
387
|
+
label: t("workspace.canvas.hand", "抓手模式(按住空格)"),
|
|
388
|
+
icon: IconHand,
|
|
389
|
+
active: props.interactionMode === "hand",
|
|
390
|
+
onClick: () => emit("modeChange", "hand")
|
|
391
|
+
}),
|
|
392
|
+
h("span", {
|
|
393
|
+
"class": "dc-canvas-controls__divider",
|
|
394
|
+
"data-dc-part": "divider",
|
|
395
|
+
"aria-hidden": "true"
|
|
396
|
+
}),
|
|
397
|
+
renderHistoryButton({
|
|
398
|
+
key: "center",
|
|
399
|
+
label: t("workspace.canvas.reset", "重置画布位置"),
|
|
400
|
+
icon: IconCenter,
|
|
401
|
+
onClick: () => emit("resetView")
|
|
402
|
+
})
|
|
403
|
+
])]);
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/components/DcCanvas.ts
|
|
409
|
+
var DcCanvas_default = defineComponent({
|
|
410
|
+
name: "DcCanvas",
|
|
411
|
+
setup() {
|
|
412
|
+
const { engine, componentMap, extensions, activeDestination, containerDropDecision, dragOverNodeId, dragOverIndex, isForbidden, forbiddenReason, handleCanvasDragOver, handleCanvasDragLeave, handleCanvasDrop, handleContainerDragOver, handleContainerDragLeave, handleContainerDrop, eventHooks, actionInterceptors, actionRegistry } = useDesignerContext();
|
|
413
|
+
const viewportRef = ref(null);
|
|
414
|
+
const stageRef = ref(null);
|
|
415
|
+
const contentRef = ref(null);
|
|
416
|
+
const hasToolbarBoundary = ref(false);
|
|
417
|
+
const canvasPan = useCanvasPan(viewportRef, stageRef);
|
|
418
|
+
let mutationObserver = null;
|
|
419
|
+
let observedTarget = null;
|
|
420
|
+
const rendererExtensions = computed(() => ({ ...extensions.rendererExtensions ?? {} }));
|
|
421
|
+
const handleClick = (event) => {
|
|
422
|
+
const nodeEl = event.target.closest("[data-node-id]");
|
|
423
|
+
if (!nodeEl || nodeEl.dataset.nodeId === "root") engine.store.selectNode(null);
|
|
424
|
+
};
|
|
425
|
+
const isDragging = computed(() => engine.store.dragTarget.value !== null);
|
|
426
|
+
function observeCanvasTarget() {
|
|
427
|
+
const content = contentRef.value;
|
|
428
|
+
const nextTarget = content?.querySelector("[data-dc-toolbar-boundary]") ?? content?.querySelector(".dc-root-renderer") ?? null;
|
|
429
|
+
hasToolbarBoundary.value = nextTarget?.hasAttribute("data-dc-toolbar-boundary") ?? false;
|
|
430
|
+
if (nextTarget && observedTarget && nextTarget !== observedTarget) canvasPan.reset();
|
|
431
|
+
if (!nextTarget || nextTarget === observedTarget) return;
|
|
432
|
+
observedTarget = nextTarget;
|
|
433
|
+
}
|
|
434
|
+
onMounted(() => {
|
|
435
|
+
const content = contentRef.value;
|
|
436
|
+
if (!content) return;
|
|
437
|
+
if (typeof MutationObserver !== "undefined") {
|
|
438
|
+
mutationObserver = new MutationObserver(observeCanvasTarget);
|
|
439
|
+
mutationObserver.observe(content, {
|
|
440
|
+
childList: true,
|
|
441
|
+
subtree: true
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
nextTick(observeCanvasTarget);
|
|
445
|
+
});
|
|
446
|
+
onBeforeUnmount(() => {
|
|
447
|
+
mutationObserver?.disconnect();
|
|
448
|
+
});
|
|
449
|
+
return () => {
|
|
450
|
+
const themeStates = [
|
|
451
|
+
isDragging.value ? "dragging" : null,
|
|
452
|
+
isForbidden.value && isDragging.value ? "forbidden" : null,
|
|
453
|
+
canvasPan.panEnabled.value ? "hand" : null,
|
|
454
|
+
canvasPan.isPanning.value ? "panning" : null
|
|
455
|
+
].filter(Boolean).join(" ") || void 0;
|
|
456
|
+
return h("div", {
|
|
457
|
+
"class": ["dc-canvas", {
|
|
458
|
+
"dc-canvas--dragging": isDragging.value,
|
|
459
|
+
"dc-canvas--forbidden": isForbidden.value && isDragging.value,
|
|
460
|
+
"dc-canvas--hand": canvasPan.panEnabled.value,
|
|
461
|
+
"dc-canvas--panning": canvasPan.isPanning.value
|
|
462
|
+
}],
|
|
463
|
+
"data-dc-component": "canvas",
|
|
464
|
+
"data-dc-state": themeStates
|
|
465
|
+
}, [
|
|
466
|
+
h(DcCanvasControls_default, {
|
|
467
|
+
interactionMode: canvasPan.mode.value,
|
|
468
|
+
onModeChange: canvasPan.setMode,
|
|
469
|
+
onResetView: canvasPan.reset
|
|
470
|
+
}),
|
|
471
|
+
h("div", {
|
|
472
|
+
"ref": viewportRef,
|
|
473
|
+
"class": "dc-canvas__viewport",
|
|
474
|
+
"data-dc-part": "viewport",
|
|
475
|
+
"data-dc-interaction-boundary": "",
|
|
476
|
+
"onDragover": handleCanvasDragOver,
|
|
477
|
+
"onDragleave": handleCanvasDragLeave,
|
|
478
|
+
"onDrop": handleCanvasDrop,
|
|
479
|
+
"onClick": handleClick,
|
|
480
|
+
"onClickCapture": canvasPan.handleClickCapture,
|
|
481
|
+
"onPointerdownCapture": canvasPan.handlePointerDown,
|
|
482
|
+
"onPointerenter": canvasPan.handlePointerEnter,
|
|
483
|
+
"onPointerleave": canvasPan.handlePointerLeave,
|
|
484
|
+
"onPointermoveCapture": canvasPan.handlePointerMove,
|
|
485
|
+
"onPointerupCapture": canvasPan.handlePointerUp,
|
|
486
|
+
"onPointercancelCapture": canvasPan.handlePointerUp
|
|
487
|
+
}, [h("div", {
|
|
488
|
+
"ref": stageRef,
|
|
489
|
+
"class": "dc-canvas__stage",
|
|
490
|
+
"data-dc-part": "stage",
|
|
491
|
+
"data-dc-canvas-stage": "",
|
|
492
|
+
"style": {
|
|
493
|
+
"--_dc-canvas-pan-x": `${canvasPan.offset.value.x}px`,
|
|
494
|
+
"--_dc-canvas-pan-y": `${canvasPan.offset.value.y}px`,
|
|
495
|
+
"--_dc-canvas-snap-x": `${canvasPan.pixelSnap.value.x}px`,
|
|
496
|
+
"--_dc-canvas-snap-y": `${canvasPan.pixelSnap.value.y}px`
|
|
497
|
+
}
|
|
498
|
+
}, [h("div", {
|
|
499
|
+
"ref": contentRef,
|
|
500
|
+
"class": ["dc-canvas__content", { "dc-canvas__content--bounded": hasToolbarBoundary.value }],
|
|
501
|
+
"data-dc-part": "content"
|
|
502
|
+
}, [h(RootRenderer$1, {
|
|
503
|
+
engine,
|
|
504
|
+
componentMap,
|
|
505
|
+
extensions: rendererExtensions.value,
|
|
506
|
+
eventHooks,
|
|
507
|
+
actionInterceptors,
|
|
508
|
+
actionRegistry,
|
|
509
|
+
activeDestination,
|
|
510
|
+
containerDropDecision,
|
|
511
|
+
onContainerDragOver: handleContainerDragOver,
|
|
512
|
+
onContainerDragLeave: handleContainerDragLeave,
|
|
513
|
+
onContainerDrop: handleContainerDrop,
|
|
514
|
+
dragOverNodeId,
|
|
515
|
+
dragOverIndex,
|
|
516
|
+
isForbidden,
|
|
517
|
+
forbiddenReason,
|
|
518
|
+
interactionBoundary: viewportRef
|
|
519
|
+
})])])]),
|
|
520
|
+
h("div", {
|
|
521
|
+
"class": "dc-canvas__interaction-layer",
|
|
522
|
+
"data-dc-part": "interaction-layer",
|
|
523
|
+
"data-dc-canvas-interaction-layer": ""
|
|
524
|
+
})
|
|
525
|
+
]);
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/composables/useDragDrop.ts
|
|
531
|
+
/**
|
|
532
|
+
* Coordinates HTML5 Drag and Drop between the material panel (drag source)
|
|
533
|
+
* and the canvas (drop target), bridging to core commands.
|
|
534
|
+
*
|
|
535
|
+
* Manages all drag-drop state including visual drop index computation
|
|
536
|
+
* and sortable constraint validation.
|
|
537
|
+
*/
|
|
538
|
+
function useDragDrop(engine) {
|
|
539
|
+
const dragOverDestination = ref(null);
|
|
540
|
+
const activeDestination = dragOverDestination;
|
|
541
|
+
const containerDropDecision = ref(null);
|
|
542
|
+
const dragOverNodeId = computed({
|
|
543
|
+
get: () => {
|
|
544
|
+
const destination = dragOverDestination.value;
|
|
545
|
+
return destination?.kind === "container" ? destination.containerId : destination ? "root" : null;
|
|
546
|
+
},
|
|
547
|
+
set: (nodeId) => {
|
|
548
|
+
if (nodeId === null) dragOverDestination.value = null;
|
|
549
|
+
else if (nodeId === "root" && dragOverDestination.value?.kind !== "root") dragOverDestination.value = { kind: "root" };
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
const dragOverIndex = computed({
|
|
553
|
+
get: () => dragOverDestination.value?.index ?? null,
|
|
554
|
+
set: (index) => {
|
|
555
|
+
const current = dragOverDestination.value;
|
|
556
|
+
if (current) {
|
|
557
|
+
dragOverDestination.value = index === null ? {
|
|
558
|
+
...current,
|
|
559
|
+
index: void 0
|
|
560
|
+
} : {
|
|
561
|
+
...current,
|
|
562
|
+
index
|
|
563
|
+
};
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const sortScope = getActiveSortScope();
|
|
567
|
+
dragOverDestination.value = {
|
|
568
|
+
kind: "root",
|
|
569
|
+
sortScope: sortScope === false ? void 0 : sortScope,
|
|
570
|
+
index: index ?? void 0
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
const isForbidden = ref(false);
|
|
575
|
+
const forbiddenReason = ref(null);
|
|
576
|
+
let dropGeometry = null;
|
|
577
|
+
let dropGeometryFrame = null;
|
|
578
|
+
const schemaSnapshot = computed(() => {
|
|
579
|
+
engine.store.schema.value;
|
|
580
|
+
return engine.state.getSchema();
|
|
581
|
+
});
|
|
582
|
+
const lockedIndices = computed(() => {
|
|
583
|
+
const schema = schemaSnapshot.value;
|
|
584
|
+
const children = schema.root.children ?? [];
|
|
585
|
+
const sortScope = getActiveSortScope();
|
|
586
|
+
if (sortScope === false) return /* @__PURE__ */ new Set();
|
|
587
|
+
return getLockedIndices(children, engine.registry, schema, sortScope);
|
|
588
|
+
});
|
|
589
|
+
const validDropIndices = computed(() => {
|
|
590
|
+
const dragTarget = engine.store.dragTarget.value;
|
|
591
|
+
if (!dragTarget) return null;
|
|
592
|
+
const sortScope = getActiveSortScope();
|
|
593
|
+
if (sortScope === false) return null;
|
|
594
|
+
return getValidDropIndices(getActiveSortScopeEntries(sortScope), lockedIndices.value, dragTarget.sourceNodeId);
|
|
595
|
+
});
|
|
596
|
+
const createDecision = computed(() => {
|
|
597
|
+
const target = engine.store.dragTarget.value;
|
|
598
|
+
if (!target?.widgetType) return { allowed: true };
|
|
599
|
+
const meta = engine.registry.getWidget(target.widgetType);
|
|
600
|
+
if (!meta) return { allowed: true };
|
|
601
|
+
return resolveCreatable$1(meta.creatable, {
|
|
602
|
+
widgetType: target.widgetType,
|
|
603
|
+
schema: schemaSnapshot.value
|
|
604
|
+
}, true);
|
|
605
|
+
});
|
|
606
|
+
function resolveMetaSortScope(meta) {
|
|
607
|
+
const placement = meta.defaultLayout?.placement;
|
|
608
|
+
if (!placement || placement.kind === "flow") {
|
|
609
|
+
const region = placement?.region ?? DEFAULT_LAYOUT_REGION;
|
|
610
|
+
return placement?.sortScope === void 0 ? region === DEFAULT_LAYOUT_REGION ? DEFAULT_SORT_SCOPE : false : placement.sortScope;
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
function getActiveSortScopeEntries(sortScope) {
|
|
615
|
+
const schema = schemaSnapshot.value;
|
|
616
|
+
return getSortScopeEntries(createLayoutPlan(schema, engine.registry), sortScope);
|
|
617
|
+
}
|
|
618
|
+
function getActiveSortScope() {
|
|
619
|
+
const target = engine.store.dragTarget.value;
|
|
620
|
+
if (!target) return DEFAULT_SORT_SCOPE;
|
|
621
|
+
if (target.sourceNodeId) {
|
|
622
|
+
const schema = schemaSnapshot.value;
|
|
623
|
+
const node = buildSchemaIndex(schema).index.get(target.sourceNodeId)?.node;
|
|
624
|
+
if (!node) return false;
|
|
625
|
+
return resolveNodeLayout(node, engine.registry, schema).sortScope;
|
|
626
|
+
}
|
|
627
|
+
if (target.widgetType) {
|
|
628
|
+
const meta = engine.registry.getWidget(target.widgetType);
|
|
629
|
+
return meta ? resolveMetaSortScope(meta) : DEFAULT_SORT_SCOPE;
|
|
630
|
+
}
|
|
631
|
+
return DEFAULT_SORT_SCOPE;
|
|
632
|
+
}
|
|
633
|
+
function clearDragOverState() {
|
|
634
|
+
dragOverDestination.value = null;
|
|
635
|
+
containerDropDecision.value = null;
|
|
636
|
+
isForbidden.value = false;
|
|
637
|
+
forbiddenReason.value = null;
|
|
638
|
+
}
|
|
639
|
+
function clearDropGeometry(cancelFrame = false) {
|
|
640
|
+
dropGeometry = null;
|
|
641
|
+
if (cancelFrame && dropGeometryFrame !== null) {
|
|
642
|
+
window.cancelAnimationFrame(dropGeometryFrame);
|
|
643
|
+
dropGeometryFrame = null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function resetDragState() {
|
|
647
|
+
clearDragOverState();
|
|
648
|
+
clearDropGeometry(true);
|
|
649
|
+
engine.store.setDragTarget(null);
|
|
650
|
+
}
|
|
651
|
+
watch(engine.store.dragTarget, (target) => {
|
|
652
|
+
if (!target) clearDragOverState();
|
|
653
|
+
});
|
|
654
|
+
function resolveVisualDropIndex(rawIndex) {
|
|
655
|
+
const valid = validDropIndices.value;
|
|
656
|
+
if (!valid) return rawIndex;
|
|
657
|
+
if (valid.size === 0) return null;
|
|
658
|
+
return valid.has(rawIndex) ? rawIndex : findNearestValidIndex(rawIndex, valid);
|
|
659
|
+
}
|
|
660
|
+
function setForbidden(reason) {
|
|
661
|
+
isForbidden.value = true;
|
|
662
|
+
const rejection = {
|
|
663
|
+
code: reason.code,
|
|
664
|
+
messageKey: reason.messageKey,
|
|
665
|
+
message: reason.message,
|
|
666
|
+
details: reason.details
|
|
667
|
+
};
|
|
668
|
+
forbiddenReason.value = rejection;
|
|
669
|
+
if (dragOverDestination.value?.kind === "container") containerDropDecision.value = {
|
|
670
|
+
allowed: false,
|
|
671
|
+
...rejection
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function createSchemaNode(meta) {
|
|
675
|
+
return {
|
|
676
|
+
id: generateShortId(),
|
|
677
|
+
type: meta.type,
|
|
678
|
+
props: { ...meta.defaultProps },
|
|
679
|
+
style: meta.defaultStyle ? { ...meta.defaultStyle } : void 0,
|
|
680
|
+
layout: meta.defaultLayout ? { ...meta.defaultLayout } : void 0
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function computeDropIndex(e, sortScope) {
|
|
684
|
+
const canvasEl = e.currentTarget;
|
|
685
|
+
if (!dropGeometry || dropGeometry.canvas !== canvasEl || dropGeometry.sortScope !== sortScope) {
|
|
686
|
+
dropGeometry = {
|
|
687
|
+
canvas: canvasEl,
|
|
688
|
+
sortScope,
|
|
689
|
+
midpoints: Array.from(canvasEl.querySelectorAll("[data-dc-sort-scope]")).filter((element) => element.dataset.dcSortScope === sortScope).map((element) => {
|
|
690
|
+
const rect = element.getBoundingClientRect();
|
|
691
|
+
return rect.top + rect.height / 2;
|
|
692
|
+
})
|
|
693
|
+
};
|
|
694
|
+
if (dropGeometryFrame === null) dropGeometryFrame = window.requestAnimationFrame(() => {
|
|
695
|
+
dropGeometryFrame = null;
|
|
696
|
+
dropGeometry = null;
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const mouseY = e.clientY;
|
|
700
|
+
const midpoints = dropGeometry.midpoints;
|
|
701
|
+
let low = 0;
|
|
702
|
+
let high = midpoints.length;
|
|
703
|
+
while (low < high) {
|
|
704
|
+
const middle = Math.floor((low + high) / 2);
|
|
705
|
+
if (mouseY < midpoints[middle]) high = middle;
|
|
706
|
+
else low = middle + 1;
|
|
707
|
+
}
|
|
708
|
+
return low;
|
|
709
|
+
}
|
|
710
|
+
function handleMaterialDragStart(e, meta) {
|
|
711
|
+
clearDropGeometry(true);
|
|
712
|
+
engine.store.setDragTarget({
|
|
713
|
+
sourceNodeId: null,
|
|
714
|
+
widgetType: meta.type
|
|
715
|
+
});
|
|
716
|
+
if (e.dataTransfer) {
|
|
717
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
718
|
+
e.dataTransfer.setData("text/plain", meta.type);
|
|
719
|
+
hideNativeDragImage(e.dataTransfer);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function handleCanvasDragOver(e) {
|
|
723
|
+
const target = e.target;
|
|
724
|
+
if (target instanceof Element && target.closest("[data-dc-container-region]")) return;
|
|
725
|
+
e.preventDefault();
|
|
726
|
+
const dragTarget = engine.store.dragTarget.value;
|
|
727
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = dragTarget?.sourceNodeId ? "move" : "copy";
|
|
728
|
+
const sortScope = getActiveSortScope();
|
|
729
|
+
dragOverDestination.value = {
|
|
730
|
+
kind: "root",
|
|
731
|
+
sortScope: sortScope === false ? void 0 : sortScope
|
|
732
|
+
};
|
|
733
|
+
containerDropDecision.value = null;
|
|
734
|
+
const decision = createDecision.value;
|
|
735
|
+
if (!decision.allowed) {
|
|
736
|
+
setForbidden({
|
|
737
|
+
ok: false,
|
|
738
|
+
code: decision.code ?? "NODE_NOT_CREATABLE",
|
|
739
|
+
messageKey: decision.messageKey,
|
|
740
|
+
message: decision.message
|
|
741
|
+
});
|
|
742
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "none";
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
isForbidden.value = false;
|
|
746
|
+
forbiddenReason.value = null;
|
|
747
|
+
if (sortScope === false) return;
|
|
748
|
+
const index = resolveVisualDropIndex(computeDropIndex(e, sortScope));
|
|
749
|
+
dragOverDestination.value = {
|
|
750
|
+
kind: "root",
|
|
751
|
+
sortScope,
|
|
752
|
+
index: index ?? void 0
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function handleCanvasDragLeave(e) {
|
|
756
|
+
const relatedTarget = e.relatedTarget;
|
|
757
|
+
const canvasEl = e.currentTarget;
|
|
758
|
+
if (!relatedTarget || !canvasEl.contains(relatedTarget)) clearDragOverState();
|
|
759
|
+
}
|
|
760
|
+
function commitDrop() {
|
|
761
|
+
const destination = dragOverDestination.value;
|
|
762
|
+
const dragTarget = engine.store.dragTarget.value;
|
|
763
|
+
if (!destination || !dragTarget) return {
|
|
764
|
+
ok: false,
|
|
765
|
+
code: "DROP_TARGET_MISSING"
|
|
766
|
+
};
|
|
767
|
+
let result;
|
|
768
|
+
let selectedNodeId = null;
|
|
769
|
+
if (dragTarget.sourceNodeId) result = engine.execute({
|
|
770
|
+
type: CommandType$1.MOVE_NODE,
|
|
771
|
+
payload: {
|
|
772
|
+
nodeId: dragTarget.sourceNodeId,
|
|
773
|
+
destination
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
else {
|
|
777
|
+
const meta = dragTarget.widgetType ? engine.registry.getWidget(dragTarget.widgetType) : void 0;
|
|
778
|
+
if (!meta) result = {
|
|
779
|
+
ok: false,
|
|
780
|
+
code: "DRAGGED_WIDGET_META_MISSING"
|
|
781
|
+
};
|
|
782
|
+
else if (destination.kind === "root" && resolveMetaSortScope(meta) !== false && destination.index === void 0) result = {
|
|
783
|
+
ok: false,
|
|
784
|
+
code: "DROP_TARGET_MISSING"
|
|
785
|
+
};
|
|
786
|
+
else {
|
|
787
|
+
const node = createSchemaNode(meta);
|
|
788
|
+
result = engine.execute({
|
|
789
|
+
type: CommandType$1.ADD_NODE,
|
|
790
|
+
payload: {
|
|
791
|
+
node,
|
|
792
|
+
destination
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
selectedNodeId = node.id;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (!result.ok) setForbidden(result);
|
|
799
|
+
else {
|
|
800
|
+
if (selectedNodeId) engine.store.selectNode(selectedNodeId);
|
|
801
|
+
resetDragState();
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
function handleCanvasDrop(e) {
|
|
806
|
+
e.preventDefault();
|
|
807
|
+
if (!createDecision.value.allowed) {
|
|
808
|
+
const decision = createDecision.value;
|
|
809
|
+
const result = {
|
|
810
|
+
ok: false,
|
|
811
|
+
code: decision.code ?? "NODE_NOT_CREATABLE",
|
|
812
|
+
messageKey: decision.messageKey,
|
|
813
|
+
message: decision.message
|
|
814
|
+
};
|
|
815
|
+
resetDragState();
|
|
816
|
+
return result;
|
|
817
|
+
}
|
|
818
|
+
return commitDrop();
|
|
819
|
+
}
|
|
820
|
+
function preflightContainerDestination(destination) {
|
|
821
|
+
const schema = schemaSnapshot.value;
|
|
822
|
+
const dragTarget = engine.store.dragTarget.value;
|
|
823
|
+
if (!dragTarget) return {
|
|
824
|
+
allowed: false,
|
|
825
|
+
code: "DROP_SOURCE_MISSING"
|
|
826
|
+
};
|
|
827
|
+
const source = dragTarget.sourceNodeId ? buildSchemaIndex(schema).index.get(dragTarget.sourceNodeId) : void 0;
|
|
828
|
+
const child = dragTarget.sourceNodeId ? source?.node : (() => {
|
|
829
|
+
const meta = dragTarget.widgetType ? engine.registry.getWidget(dragTarget.widgetType) : void 0;
|
|
830
|
+
return meta ? createSchemaNode(meta) : null;
|
|
831
|
+
})();
|
|
832
|
+
if (!child) return {
|
|
833
|
+
allowed: false,
|
|
834
|
+
code: "DROP_SOURCE_MISSING"
|
|
835
|
+
};
|
|
836
|
+
const targetResult = resolveDestination(schema, engine.registry, destination);
|
|
837
|
+
if (!targetResult.ok) return {
|
|
838
|
+
allowed: false,
|
|
839
|
+
code: targetResult.code,
|
|
840
|
+
message: targetResult.message
|
|
841
|
+
};
|
|
842
|
+
const target = targetResult.value;
|
|
843
|
+
if (!target.container || !target.definition || !target.variant || !target.region) return {
|
|
844
|
+
allowed: false,
|
|
845
|
+
code: "CONTAINER_DESTINATION_REQUIRED"
|
|
846
|
+
};
|
|
847
|
+
const sameRegion = source?.owner === destination.containerId && source.regionId === destination.regionId;
|
|
848
|
+
return resolvePlacementDecision({
|
|
849
|
+
definition: target.definition,
|
|
850
|
+
region: target.region,
|
|
851
|
+
child,
|
|
852
|
+
childHasContainerCapability: Boolean(engine.registry.getWidget(child.type)?.container),
|
|
853
|
+
targetCount: target.children.length - (sameRegion ? 1 : 0),
|
|
854
|
+
callbackContext: {
|
|
855
|
+
operation: dragTarget.sourceNodeId ? "move" : "add",
|
|
856
|
+
schema,
|
|
857
|
+
container: target.container,
|
|
858
|
+
variant: target.variant,
|
|
859
|
+
region: target.region,
|
|
860
|
+
child,
|
|
861
|
+
targetIndex: clampInsertIndex(destination.index, target.children.length)
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
function handleContainerDragOver(payload) {
|
|
866
|
+
if ("allowed" in payload) {
|
|
867
|
+
dragOverDestination.value = null;
|
|
868
|
+
containerDropDecision.value = {
|
|
869
|
+
allowed: false,
|
|
870
|
+
code: payload.code,
|
|
871
|
+
message: payload.message
|
|
872
|
+
};
|
|
873
|
+
setForbidden({
|
|
874
|
+
ok: false,
|
|
875
|
+
code: payload.code,
|
|
876
|
+
message: payload.message
|
|
877
|
+
});
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
dragOverDestination.value = payload.destination;
|
|
881
|
+
const decision = preflightContainerDestination(payload.destination);
|
|
882
|
+
containerDropDecision.value = decision;
|
|
883
|
+
if (!decision.allowed) {
|
|
884
|
+
setForbidden({
|
|
885
|
+
ok: false,
|
|
886
|
+
code: decision.code ?? "CONTAINER_PLACEMENT_REJECTED",
|
|
887
|
+
messageKey: decision.messageKey,
|
|
888
|
+
message: decision.message,
|
|
889
|
+
details: decision.details
|
|
890
|
+
});
|
|
891
|
+
if (payload.event.dataTransfer) payload.event.dataTransfer.dropEffect = "none";
|
|
892
|
+
} else {
|
|
893
|
+
isForbidden.value = false;
|
|
894
|
+
forbiddenReason.value = null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function handleContainerDragLeave(e) {
|
|
898
|
+
const current = e.currentTarget;
|
|
899
|
+
if (current && e.relatedTarget instanceof Node && current.contains(e.relatedTarget)) return;
|
|
900
|
+
clearDragOverState();
|
|
901
|
+
}
|
|
902
|
+
function handleContainerDrop(e) {
|
|
903
|
+
e.preventDefault();
|
|
904
|
+
e.stopPropagation();
|
|
905
|
+
const destination = dragOverDestination.value;
|
|
906
|
+
const currentTarget = e.currentTarget;
|
|
907
|
+
if (!(destination?.kind === "container" && currentTarget instanceof Element && currentTarget.getAttribute("data-dc-container-id") === destination.containerId && currentTarget.getAttribute("data-dc-container-region") === destination.regionId)) {
|
|
908
|
+
dragOverDestination.value = null;
|
|
909
|
+
const result = {
|
|
910
|
+
ok: false,
|
|
911
|
+
code: "DROP_TARGET_MISSING"
|
|
912
|
+
};
|
|
913
|
+
if (!isForbidden.value) {
|
|
914
|
+
containerDropDecision.value = null;
|
|
915
|
+
setForbidden(result);
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
919
|
+
return commitDrop();
|
|
920
|
+
}
|
|
921
|
+
function handleDragEnd(_e) {
|
|
922
|
+
resetDragState();
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
dragOverDestination,
|
|
926
|
+
activeDestination,
|
|
927
|
+
containerDropDecision,
|
|
928
|
+
dragOverNodeId,
|
|
929
|
+
dragOverIndex,
|
|
930
|
+
lockedIndices,
|
|
931
|
+
validDropIndices,
|
|
932
|
+
handleMaterialDragStart,
|
|
933
|
+
handleCanvasDragOver,
|
|
934
|
+
handleCanvasDragLeave,
|
|
935
|
+
handleCanvasDrop,
|
|
936
|
+
handleContainerDragOver,
|
|
937
|
+
handleContainerDragLeave,
|
|
938
|
+
handleContainerDrop,
|
|
939
|
+
commitDrop,
|
|
940
|
+
handleDragEnd,
|
|
941
|
+
isForbidden,
|
|
942
|
+
forbiddenReason
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region src/material.ts
|
|
947
|
+
function resolveText(key, fallback, t) {
|
|
948
|
+
if (!key) return fallback;
|
|
949
|
+
return t(key, fallback ?? "");
|
|
950
|
+
}
|
|
951
|
+
function resolveMaterialItem(meta, t) {
|
|
952
|
+
const material = meta.material;
|
|
953
|
+
const titleFallback = material?.title ?? meta.title;
|
|
954
|
+
return {
|
|
955
|
+
title: resolveText(material?.titleKey ?? meta.titleKey, titleFallback, t) ?? meta.title,
|
|
956
|
+
icon: material?.icon ?? meta.icon,
|
|
957
|
+
description: resolveText(material?.descriptionKey, material?.description, t),
|
|
958
|
+
thumbnail: material?.thumbnail,
|
|
959
|
+
tags: material?.tags ?? [],
|
|
960
|
+
keywords: material?.keywords ?? []
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function materialItemMatchesQuery(meta, material, query) {
|
|
964
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
965
|
+
if (!normalizedQuery) return true;
|
|
966
|
+
return [
|
|
967
|
+
meta.type,
|
|
968
|
+
meta.title,
|
|
969
|
+
meta.titleKey,
|
|
970
|
+
meta.icon,
|
|
971
|
+
meta.group,
|
|
972
|
+
material.title,
|
|
973
|
+
material.description,
|
|
974
|
+
material.tags.join(" "),
|
|
975
|
+
material.keywords.join(" ")
|
|
976
|
+
].some((value) => typeof value === "string" && value.toLowerCase().includes(normalizedQuery));
|
|
977
|
+
}
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/components/DcMaterialItem.ts
|
|
980
|
+
function renderIcon(icon) {
|
|
981
|
+
if (!icon) return null;
|
|
982
|
+
return h("span", {
|
|
983
|
+
"class": "dc-material-item__icon",
|
|
984
|
+
"data-dc-part": "icon"
|
|
985
|
+
}, [typeof icon === "string" ? icon : h(icon, { size: 20 })]);
|
|
986
|
+
}
|
|
987
|
+
var DcMaterialItem_default = defineComponent({
|
|
988
|
+
name: "DcMaterialItem",
|
|
989
|
+
props: { meta: {
|
|
990
|
+
type: Object,
|
|
991
|
+
required: true
|
|
992
|
+
} },
|
|
993
|
+
setup(props) {
|
|
994
|
+
const ctx = useDesignerContext();
|
|
995
|
+
const { t } = useI18n$1();
|
|
996
|
+
const { extensions, handleMaterialDragStart, handleDragEnd: handleDesignerDragEnd } = ctx;
|
|
997
|
+
const isDragging = ref(false);
|
|
998
|
+
const handleDragStart = (e) => {
|
|
999
|
+
handleMaterialDragStart(e, props.meta);
|
|
1000
|
+
isDragging.value = true;
|
|
1001
|
+
};
|
|
1002
|
+
const handleDragEnd = (e) => {
|
|
1003
|
+
isDragging.value = false;
|
|
1004
|
+
handleDesignerDragEnd(e);
|
|
1005
|
+
};
|
|
1006
|
+
return () => {
|
|
1007
|
+
const meta = props.meta;
|
|
1008
|
+
const material = resolveMaterialItem(meta, t);
|
|
1009
|
+
const draggable = true;
|
|
1010
|
+
const disabled = false;
|
|
1011
|
+
const customContent = extensions.materialItemRenderer?.({
|
|
1012
|
+
meta,
|
|
1013
|
+
material,
|
|
1014
|
+
draggable,
|
|
1015
|
+
disabled,
|
|
1016
|
+
dragging: isDragging.value
|
|
1017
|
+
});
|
|
1018
|
+
const defaultContent = [material.thumbnail ? h("img", {
|
|
1019
|
+
"class": "dc-material-item__thumbnail",
|
|
1020
|
+
"data-dc-part": "thumbnail",
|
|
1021
|
+
"src": material.thumbnail,
|
|
1022
|
+
"alt": material.title
|
|
1023
|
+
}) : renderIcon(material.icon), h("span", {
|
|
1024
|
+
"class": "dc-material-item__content",
|
|
1025
|
+
"data-dc-part": "content"
|
|
1026
|
+
}, [h("span", {
|
|
1027
|
+
"class": "dc-material-item__title",
|
|
1028
|
+
"data-dc-part": "title"
|
|
1029
|
+
}, material.title)])];
|
|
1030
|
+
return h("div", {
|
|
1031
|
+
"class": ["dc-material-item", {
|
|
1032
|
+
"dc-material-item--custom": !!customContent,
|
|
1033
|
+
"dc-material-item--dragging": isDragging.value,
|
|
1034
|
+
"dc-material-item--with-description": !!material.description
|
|
1035
|
+
}],
|
|
1036
|
+
"data-dc-component": "material-item",
|
|
1037
|
+
"data-dc-state": isDragging.value ? "dragging" : void 0,
|
|
1038
|
+
"draggable": draggable,
|
|
1039
|
+
"aria-disabled": disabled,
|
|
1040
|
+
"title": material.description ? `${material.title}: ${material.description}` : material.title,
|
|
1041
|
+
"onDragstart": handleDragStart,
|
|
1042
|
+
"onDragend": handleDragEnd
|
|
1043
|
+
}, customContent ?? defaultContent);
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
//#endregion
|
|
1048
|
+
//#region src/components/DcMaterialGroup.ts
|
|
1049
|
+
var DcMaterialGroup_default = defineComponent({
|
|
1050
|
+
name: "DcMaterialGroup",
|
|
1051
|
+
props: {
|
|
1052
|
+
title: {
|
|
1053
|
+
type: String,
|
|
1054
|
+
required: true
|
|
1055
|
+
},
|
|
1056
|
+
widgets: {
|
|
1057
|
+
type: Array,
|
|
1058
|
+
required: true
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
setup(props) {
|
|
1062
|
+
const collapsed = ref(false);
|
|
1063
|
+
const toggleCollapse = () => {
|
|
1064
|
+
collapsed.value = !collapsed.value;
|
|
1065
|
+
};
|
|
1066
|
+
return () => {
|
|
1067
|
+
const header = h("button", {
|
|
1068
|
+
"type": "button",
|
|
1069
|
+
"class": "dc-material-group__header",
|
|
1070
|
+
"data-dc-part": "header",
|
|
1071
|
+
"aria-expanded": !collapsed.value,
|
|
1072
|
+
"onClick": toggleCollapse
|
|
1073
|
+
}, [h("span", {
|
|
1074
|
+
"class": "dc-material-group__title",
|
|
1075
|
+
"data-dc-part": "title"
|
|
1076
|
+
}, props.title), h("span", {
|
|
1077
|
+
"class": collapsed.value ? "dc-material-group__toggle dc-material-group__toggle--collapsed" : "dc-material-group__toggle",
|
|
1078
|
+
"data-dc-part": "toggle"
|
|
1079
|
+
}, [h(IconChevronDown, { size: 15 })])]);
|
|
1080
|
+
const body = collapsed.value ? null : h("div", {
|
|
1081
|
+
"class": "dc-material-group__body",
|
|
1082
|
+
"data-dc-part": "body"
|
|
1083
|
+
}, props.widgets.map((meta) => h(DcMaterialItem_default, {
|
|
1084
|
+
key: meta.type,
|
|
1085
|
+
meta
|
|
1086
|
+
})));
|
|
1087
|
+
return h("div", {
|
|
1088
|
+
"class": ["dc-material-group", { "dc-material-group--collapsed": collapsed.value }],
|
|
1089
|
+
"data-dc-component": "material-group",
|
|
1090
|
+
"data-dc-state": collapsed.value ? "collapsed" : "expanded"
|
|
1091
|
+
}, [header, body]);
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region src/components/DcMaterialPanel.ts
|
|
1097
|
+
var DcMaterialPanel_default = defineComponent({
|
|
1098
|
+
name: "DcMaterialPanel",
|
|
1099
|
+
setup() {
|
|
1100
|
+
const ctx = useDesignerContext();
|
|
1101
|
+
const { t } = useI18n$1();
|
|
1102
|
+
const { engine, searchQuery, widgetGroups } = ctx;
|
|
1103
|
+
const filteredGroups = computed(() => {
|
|
1104
|
+
const allWidgets = engine.registry.getAllWidgets();
|
|
1105
|
+
const query = searchQuery.value.toLowerCase().trim();
|
|
1106
|
+
const groups = widgetGroups ?? [...new Set(allWidgets.map((w) => w.group))].map((name) => ({
|
|
1107
|
+
name,
|
|
1108
|
+
title: name,
|
|
1109
|
+
titleKey: void 0
|
|
1110
|
+
}));
|
|
1111
|
+
const widgetsByGroup = /* @__PURE__ */ new Map();
|
|
1112
|
+
for (const widget of allWidgets) {
|
|
1113
|
+
if (!materialItemMatchesQuery(widget, resolveMaterialItem(widget, t), query)) continue;
|
|
1114
|
+
const widgets = widgetsByGroup.get(widget.group);
|
|
1115
|
+
if (widgets) widgets.push(widget);
|
|
1116
|
+
else widgetsByGroup.set(widget.group, [widget]);
|
|
1117
|
+
}
|
|
1118
|
+
return groups.map((group) => ({
|
|
1119
|
+
...group,
|
|
1120
|
+
widgets: widgetsByGroup.get(group.name) ?? []
|
|
1121
|
+
})).filter((g) => g.widgets.length > 0);
|
|
1122
|
+
});
|
|
1123
|
+
const handleSearchInput = (e) => {
|
|
1124
|
+
searchQuery.value = e.target.value;
|
|
1125
|
+
};
|
|
1126
|
+
return () => h("div", {
|
|
1127
|
+
"class": "dc-material-panel",
|
|
1128
|
+
"data-dc-component": "material-panel"
|
|
1129
|
+
}, [
|
|
1130
|
+
h("div", {
|
|
1131
|
+
"class": "dc-material-panel__header",
|
|
1132
|
+
"data-dc-part": "header"
|
|
1133
|
+
}, [h("h2", {
|
|
1134
|
+
"class": "dc-material-panel__heading",
|
|
1135
|
+
"data-dc-part": "heading"
|
|
1136
|
+
}, t("panel.materials.title", "物料"))]),
|
|
1137
|
+
h("div", {
|
|
1138
|
+
"class": "dc-material-panel__search",
|
|
1139
|
+
"data-dc-part": "search"
|
|
1140
|
+
}, [
|
|
1141
|
+
h("span", {
|
|
1142
|
+
"class": "dc-material-panel__search-icon",
|
|
1143
|
+
"data-dc-part": "search-icon"
|
|
1144
|
+
}, [h(IconSearch, { size: 15 })]),
|
|
1145
|
+
h("input", {
|
|
1146
|
+
"type": "text",
|
|
1147
|
+
"class": "dc-material-panel__search-input",
|
|
1148
|
+
"data-dc-part": "search-input",
|
|
1149
|
+
"placeholder": t("panel.search.placeholder", "搜索组件..."),
|
|
1150
|
+
"value": searchQuery.value,
|
|
1151
|
+
"onInput": handleSearchInput
|
|
1152
|
+
}),
|
|
1153
|
+
searchQuery.value ? h("button", {
|
|
1154
|
+
"type": "button",
|
|
1155
|
+
"class": "dc-material-panel__search-clear",
|
|
1156
|
+
"data-dc-part": "search-clear",
|
|
1157
|
+
"title": t("panel.search.clear", "清除搜索"),
|
|
1158
|
+
"aria-label": t("panel.search.clear", "清除搜索"),
|
|
1159
|
+
"onClick": () => {
|
|
1160
|
+
searchQuery.value = "";
|
|
1161
|
+
}
|
|
1162
|
+
}, [h(IconClose, { size: 14 })]) : null
|
|
1163
|
+
]),
|
|
1164
|
+
h("div", {
|
|
1165
|
+
"class": "dc-material-panel__groups",
|
|
1166
|
+
"data-dc-part": "groups"
|
|
1167
|
+
}, filteredGroups.value.map((group) => h(DcMaterialGroup_default, {
|
|
1168
|
+
key: group.name,
|
|
1169
|
+
title: group.titleKey ? t(group.titleKey, group.title) : group.title,
|
|
1170
|
+
widgets: group.widgets
|
|
1171
|
+
})))
|
|
1172
|
+
]);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region src/components/DcStructurePanel.ts
|
|
1177
|
+
function isPromiseLike(value) {
|
|
1178
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
1179
|
+
}
|
|
1180
|
+
function createContainerStructureRegions(node, engine, t, schema) {
|
|
1181
|
+
const result = createContainerPlan(node, engine.registry);
|
|
1182
|
+
if (!result.ok) return [];
|
|
1183
|
+
return result.plan.regions.map((region) => ({
|
|
1184
|
+
id: region.definition.id,
|
|
1185
|
+
title: region.definition.titleKey ? t(region.definition.titleKey, region.definition.title) : region.definition.title,
|
|
1186
|
+
owner: {
|
|
1187
|
+
kind: "container",
|
|
1188
|
+
containerId: result.plan.containerId,
|
|
1189
|
+
regionId: region.definition.id
|
|
1190
|
+
},
|
|
1191
|
+
nodes: region.nodes,
|
|
1192
|
+
lockedIndices: getLockedIndicesFromNodes(region.nodes, engine.registry, schema)
|
|
1193
|
+
}));
|
|
1194
|
+
}
|
|
1195
|
+
var DcStructurePanel_default = defineComponent({
|
|
1196
|
+
name: "DcStructurePanel",
|
|
1197
|
+
setup() {
|
|
1198
|
+
const ctx = useDesignerContext();
|
|
1199
|
+
const { t } = useI18n$1();
|
|
1200
|
+
const { engine, actionRegistry, actionInterceptors, eventHooks } = ctx;
|
|
1201
|
+
const selectPending = { value: false };
|
|
1202
|
+
const schemaSnapshot = computed(() => {
|
|
1203
|
+
engine.store.schema.value;
|
|
1204
|
+
return engine.state.getSchema();
|
|
1205
|
+
});
|
|
1206
|
+
const createStructureItem = (node, owner, index, siblingCount, sortScope, schema, lockedIndices) => {
|
|
1207
|
+
const meta = engine.registry.getWidget(node.type);
|
|
1208
|
+
const actionCtx = {
|
|
1209
|
+
node,
|
|
1210
|
+
owner,
|
|
1211
|
+
index,
|
|
1212
|
+
siblingCount,
|
|
1213
|
+
sortScope,
|
|
1214
|
+
meta,
|
|
1215
|
+
engine,
|
|
1216
|
+
schema,
|
|
1217
|
+
lockedIndices
|
|
1218
|
+
};
|
|
1219
|
+
const actions = actionRegistry.resolve(actionCtx, actionInterceptors, owner.kind === "root" ? [ActionKey$1.DELETE] : void 0);
|
|
1220
|
+
return {
|
|
1221
|
+
node,
|
|
1222
|
+
title: meta ? meta.titleKey ? t(meta.titleKey, meta.title) : meta.title : node.type,
|
|
1223
|
+
actions,
|
|
1224
|
+
regions: createContainerStructureRegions(node, engine, t, schema)
|
|
1225
|
+
};
|
|
1226
|
+
};
|
|
1227
|
+
const items = computed(() => {
|
|
1228
|
+
const schema = schemaSnapshot.value;
|
|
1229
|
+
const children = schema.root.children ?? [];
|
|
1230
|
+
const plan = createLayoutPlan(schema, engine.registry);
|
|
1231
|
+
const positions = /* @__PURE__ */ new Map();
|
|
1232
|
+
for (const entries of plan.sortScopes.values()) {
|
|
1233
|
+
const lockedIndices = getLockedIndicesFromNodes(entries.map((entry) => entry.node), engine.registry, schema);
|
|
1234
|
+
entries.forEach((entry, index) => positions.set(entry.node.id, {
|
|
1235
|
+
index,
|
|
1236
|
+
siblingCount: entries.length,
|
|
1237
|
+
lockedIndices
|
|
1238
|
+
}));
|
|
1239
|
+
}
|
|
1240
|
+
return children.map((node, rootIndex) => {
|
|
1241
|
+
const layout = resolveNodeLayout(node, engine.registry, schema);
|
|
1242
|
+
const position = positions.get(node.id);
|
|
1243
|
+
return createStructureItem(node, {
|
|
1244
|
+
kind: "root",
|
|
1245
|
+
sortScope: layout.sortScope === false ? void 0 : layout.sortScope
|
|
1246
|
+
}, position?.index ?? rootIndex, position?.siblingCount ?? children.length, layout.sortScope, schema, position?.lockedIndices ?? /* @__PURE__ */ new Set());
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
const fireAfterSelect = (payload) => {
|
|
1250
|
+
if (!eventHooks.onAfterSelect) return;
|
|
1251
|
+
try {
|
|
1252
|
+
const result = eventHooks.onAfterSelect(payload);
|
|
1253
|
+
if (isPromiseLike(result)) result.catch((err) => {
|
|
1254
|
+
console.error("[dragcraft] Async after-hook error:", err);
|
|
1255
|
+
});
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
console.error("[dragcraft] After-hook error:", err);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
const executeSelect = (payload) => {
|
|
1261
|
+
engine.store.selectNode(payload.nodeId);
|
|
1262
|
+
fireAfterSelect({ nodeId: payload.nodeId });
|
|
1263
|
+
};
|
|
1264
|
+
const resolveSelect = (result, payload) => {
|
|
1265
|
+
if (!isPromiseLike(result)) {
|
|
1266
|
+
if (result !== false) executeSelect(payload);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
selectPending.value = true;
|
|
1270
|
+
result.then((allowed) => {
|
|
1271
|
+
if (allowed !== false) executeSelect(payload);
|
|
1272
|
+
}).catch((err) => {
|
|
1273
|
+
console.error("[dragcraft] Before-hook error (action cancelled):", err);
|
|
1274
|
+
}).finally(() => {
|
|
1275
|
+
selectPending.value = false;
|
|
1276
|
+
});
|
|
1277
|
+
};
|
|
1278
|
+
const handleSelect = (node, e) => {
|
|
1279
|
+
if (selectPending.value) return;
|
|
1280
|
+
const payload = {
|
|
1281
|
+
nodeId: node.id,
|
|
1282
|
+
event: e
|
|
1283
|
+
};
|
|
1284
|
+
if (!eventHooks.onBeforeSelect) {
|
|
1285
|
+
executeSelect(payload);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
resolveSelect(eventHooks.onBeforeSelect(payload), payload);
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
console.error("[dragcraft] Before-hook error (action cancelled):", err);
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
const renderActionButton = (action) => {
|
|
1295
|
+
return h("button", {
|
|
1296
|
+
"type": "button",
|
|
1297
|
+
"class": [
|
|
1298
|
+
"dc-structure-panel__action",
|
|
1299
|
+
{ "dc-structure-panel__delete": action.key === ActionKey$1.DELETE },
|
|
1300
|
+
action.className
|
|
1301
|
+
],
|
|
1302
|
+
"data-dc-part": "action",
|
|
1303
|
+
"data-dc-state": action.key === ActionKey$1.DELETE ? "danger" : void 0,
|
|
1304
|
+
"data-dc-action-key": action.key,
|
|
1305
|
+
"title": action.label,
|
|
1306
|
+
"aria-label": action.label,
|
|
1307
|
+
"disabled": action.disabled,
|
|
1308
|
+
"onClick": (e) => {
|
|
1309
|
+
e.stopPropagation();
|
|
1310
|
+
if (!action.disabled) action.handler(e);
|
|
1311
|
+
}
|
|
1312
|
+
}, typeof action.icon === "string" ? action.icon : action.icon ? h(action.icon) : void 0);
|
|
1313
|
+
};
|
|
1314
|
+
const renderActions = (actions) => {
|
|
1315
|
+
const buttons = actions.filter((action) => action.type === "button").map(renderActionButton);
|
|
1316
|
+
return buttons.length > 0 ? h("div", {
|
|
1317
|
+
"class": "dc-structure-panel__actions",
|
|
1318
|
+
"data-dc-part": "actions"
|
|
1319
|
+
}, buttons) : null;
|
|
1320
|
+
};
|
|
1321
|
+
const renderItem = (item) => {
|
|
1322
|
+
const selected = engine.store.selectedNodeId.value === item.node.id;
|
|
1323
|
+
return h("div", {
|
|
1324
|
+
"class": ["dc-structure-panel__item", { "dc-structure-panel__item--selected": selected }],
|
|
1325
|
+
"data-dc-component": "structure-item",
|
|
1326
|
+
"data-dc-state": selected ? "selected" : void 0,
|
|
1327
|
+
"data-node-id": item.node.id
|
|
1328
|
+
}, [h("button", {
|
|
1329
|
+
"type": "button",
|
|
1330
|
+
"class": "dc-structure-panel__select",
|
|
1331
|
+
"data-dc-part": "select",
|
|
1332
|
+
"aria-pressed": selected,
|
|
1333
|
+
"onClick": (e) => handleSelect(item.node, e)
|
|
1334
|
+
}, [h("span", {
|
|
1335
|
+
"class": "dc-structure-panel__branch",
|
|
1336
|
+
"data-dc-part": "branch"
|
|
1337
|
+
}), h("span", {
|
|
1338
|
+
"class": "dc-structure-panel__main",
|
|
1339
|
+
"data-dc-part": "main"
|
|
1340
|
+
}, [h("span", {
|
|
1341
|
+
"class": "dc-structure-panel__title",
|
|
1342
|
+
"data-dc-part": "title",
|
|
1343
|
+
"title": item.title
|
|
1344
|
+
}, item.title), h("span", {
|
|
1345
|
+
"class": "dc-structure-panel__id",
|
|
1346
|
+
"data-dc-part": "id",
|
|
1347
|
+
"title": item.node.id
|
|
1348
|
+
}, item.node.id)])]), renderActions(item.actions)]);
|
|
1349
|
+
};
|
|
1350
|
+
const renderRegion = (region) => h("div", {
|
|
1351
|
+
"key": region.id,
|
|
1352
|
+
"class": "dc-structure-panel__region-branch",
|
|
1353
|
+
"data-dc-component": "structure-region"
|
|
1354
|
+
}, [h("div", {
|
|
1355
|
+
"class": "dc-structure-panel__region",
|
|
1356
|
+
"data-dc-part": "row",
|
|
1357
|
+
"data-dc-region-id": region.id
|
|
1358
|
+
}, [
|
|
1359
|
+
h("span", {
|
|
1360
|
+
"class": "dc-structure-panel__region-branch-mark",
|
|
1361
|
+
"data-dc-part": "branch",
|
|
1362
|
+
"aria-hidden": "true"
|
|
1363
|
+
}),
|
|
1364
|
+
h("span", {
|
|
1365
|
+
"class": "dc-structure-panel__region-title",
|
|
1366
|
+
"data-dc-part": "title",
|
|
1367
|
+
"title": region.title
|
|
1368
|
+
}, region.title),
|
|
1369
|
+
h("span", {
|
|
1370
|
+
"class": "dc-structure-panel__region-count",
|
|
1371
|
+
"data-dc-part": "count"
|
|
1372
|
+
}, String(region.nodes.length))
|
|
1373
|
+
]), region.nodes.length > 0 ? h("div", {
|
|
1374
|
+
"class": "dc-structure-panel__children",
|
|
1375
|
+
"data-dc-part": "children"
|
|
1376
|
+
}, region.nodes.map((node, index) => {
|
|
1377
|
+
const item = createStructureItem(node, region.owner, index, region.nodes.length, false, schemaSnapshot.value, region.lockedIndices);
|
|
1378
|
+
return h("div", {
|
|
1379
|
+
key: node.id,
|
|
1380
|
+
class: "dc-structure-panel__row"
|
|
1381
|
+
}, [renderItem(item)]);
|
|
1382
|
+
})) : null]);
|
|
1383
|
+
const renderStructureItem = (item) => h("div", {
|
|
1384
|
+
key: item.node.id,
|
|
1385
|
+
class: "dc-structure-panel__row"
|
|
1386
|
+
}, [renderItem(item), item.regions.length > 0 ? h("div", { class: "dc-structure-panel__regions" }, item.regions.map(renderRegion)) : null]);
|
|
1387
|
+
return () => h("div", {
|
|
1388
|
+
"class": "dc-structure-panel",
|
|
1389
|
+
"data-dc-component": "structure-panel"
|
|
1390
|
+
}, [h("div", {
|
|
1391
|
+
"class": "dc-structure-panel__header",
|
|
1392
|
+
"data-dc-part": "header"
|
|
1393
|
+
}, [h("span", {
|
|
1394
|
+
"class": "dc-structure-panel__heading",
|
|
1395
|
+
"data-dc-part": "heading"
|
|
1396
|
+
}, t("panel.structure.title", "结构树"))]), items.value.length === 0 ? h("div", {
|
|
1397
|
+
"class": "dc-structure-panel__empty",
|
|
1398
|
+
"data-dc-part": "empty"
|
|
1399
|
+
}, t("panel.structure.empty", "暂无结构")) : h("div", {
|
|
1400
|
+
"class": "dc-structure-panel__list",
|
|
1401
|
+
"data-dc-part": "list"
|
|
1402
|
+
}, items.value.map(renderStructureItem))]);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
//#endregion
|
|
1406
|
+
//#region src/components/DcLeftSidebar.ts
|
|
1407
|
+
const LEFT_PANEL_TABS = [{
|
|
1408
|
+
key: "materials",
|
|
1409
|
+
labelKey: "panel.left.materials",
|
|
1410
|
+
fallback: "物料",
|
|
1411
|
+
icon: IconMaterial
|
|
1412
|
+
}, {
|
|
1413
|
+
key: "structure",
|
|
1414
|
+
labelKey: "panel.left.structure",
|
|
1415
|
+
fallback: "结构树",
|
|
1416
|
+
icon: IconStructureTree
|
|
1417
|
+
}];
|
|
1418
|
+
var DcLeftSidebar_default = defineComponent({
|
|
1419
|
+
name: "DcLeftSidebar",
|
|
1420
|
+
setup() {
|
|
1421
|
+
const ctx = useDesignerContext();
|
|
1422
|
+
const { t } = useI18n$1();
|
|
1423
|
+
const { engine, extensions, leftPanelActiveTab, workspace } = ctx;
|
|
1424
|
+
const renderTabButton = (tab) => {
|
|
1425
|
+
const label = t(tab.labelKey, tab.fallback);
|
|
1426
|
+
const active = leftPanelActiveTab.value === tab.key;
|
|
1427
|
+
return h("button", {
|
|
1428
|
+
"type": "button",
|
|
1429
|
+
"class": ["dc-left-sidebar__tab", { "dc-left-sidebar__tab--active": active }],
|
|
1430
|
+
"data-dc-part": "tab",
|
|
1431
|
+
"title": label,
|
|
1432
|
+
"aria-label": label,
|
|
1433
|
+
"aria-pressed": active,
|
|
1434
|
+
"onClick": () => {
|
|
1435
|
+
workspace.openLeft(tab.key);
|
|
1436
|
+
}
|
|
1437
|
+
}, [h(tab.icon, { size: 18 })]);
|
|
1438
|
+
};
|
|
1439
|
+
const renderActivePanel = () => {
|
|
1440
|
+
if (leftPanelActiveTab.value === "structure") return h(DcStructurePanel_default);
|
|
1441
|
+
return h(extensions.materialPanelRenderer ?? DcMaterialPanel_default);
|
|
1442
|
+
};
|
|
1443
|
+
return () => {
|
|
1444
|
+
const open = workspace.leftOpen.value;
|
|
1445
|
+
const toggleLabel = open ? t("workspace.left.close", "收起左侧栏") : t("workspace.left.open", "展开左侧栏");
|
|
1446
|
+
const railExtension = extensions.leftRailRenderer?.({
|
|
1447
|
+
engine,
|
|
1448
|
+
workspace,
|
|
1449
|
+
t
|
|
1450
|
+
});
|
|
1451
|
+
return h("div", {
|
|
1452
|
+
"class": "dc-left-sidebar",
|
|
1453
|
+
"data-dc-component": "left-sidebar",
|
|
1454
|
+
"data-dc-state": open ? "open" : "closed"
|
|
1455
|
+
}, [h("div", {
|
|
1456
|
+
"class": "dc-left-sidebar__surface",
|
|
1457
|
+
"data-dc-part": "surface",
|
|
1458
|
+
"aria-hidden": workspace.mode.value === "compact" && !open,
|
|
1459
|
+
"inert": workspace.mode.value === "compact" && !open ? "" : void 0
|
|
1460
|
+
}, [h("div", {
|
|
1461
|
+
"class": "dc-left-sidebar__rail",
|
|
1462
|
+
"data-dc-part": "rail",
|
|
1463
|
+
"role": "tablist",
|
|
1464
|
+
"aria-label": t("workspace.left.label", "物料与结构")
|
|
1465
|
+
}, [...LEFT_PANEL_TABS.map((tab) => renderTabButton(tab)), railExtension ? h("div", {
|
|
1466
|
+
"class": "dc-sidebar-rail__extension",
|
|
1467
|
+
"data-dc-part": "rail-extension"
|
|
1468
|
+
}, [railExtension]) : null]), h("div", {
|
|
1469
|
+
"class": "dc-left-sidebar__content",
|
|
1470
|
+
"data-dc-part": "content"
|
|
1471
|
+
}, [renderActivePanel()])]), h("button", {
|
|
1472
|
+
"type": "button",
|
|
1473
|
+
"class": "dc-sidebar-toggle dc-sidebar-toggle--left",
|
|
1474
|
+
"data-dc-part": "toggle",
|
|
1475
|
+
"title": toggleLabel,
|
|
1476
|
+
"aria-label": toggleLabel,
|
|
1477
|
+
"aria-expanded": open,
|
|
1478
|
+
"data-dc-workspace-control": "left",
|
|
1479
|
+
"onMousedown": (event) => event.preventDefault(),
|
|
1480
|
+
"onClick": () => workspace.toggleLeft()
|
|
1481
|
+
}, [h(open ? IconChevronLeft : IconChevronRight, { size: 14 })])]);
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
//#endregion
|
|
1486
|
+
//#region src/composables/usePropertyBinding.ts
|
|
1487
|
+
function findField(schema, key) {
|
|
1488
|
+
for (const section of schema?.sections ?? []) {
|
|
1489
|
+
const field = section.fields.find((item) => item.key === key);
|
|
1490
|
+
if (field) return field;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function getFieldBinding(field) {
|
|
1494
|
+
return field?.bindTo;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Bridges the currently selected node's props and formSchema
|
|
1498
|
+
* to the form-generator, and dispatches property updates as commands.
|
|
1499
|
+
*/
|
|
1500
|
+
function usePropertyBinding(engine, options = {}) {
|
|
1501
|
+
const translate = options.t ?? ((key, fallback) => fallback ?? key);
|
|
1502
|
+
const selectedNode = computed(() => {
|
|
1503
|
+
const nodeId = engine.store.selectedNodeId.value;
|
|
1504
|
+
if (!nodeId) return null;
|
|
1505
|
+
return engine.state.getNodeById(nodeId);
|
|
1506
|
+
});
|
|
1507
|
+
const selectedWidgetMeta = computed(() => {
|
|
1508
|
+
const node = selectedNode.value;
|
|
1509
|
+
if (!node) return void 0;
|
|
1510
|
+
return engine.registry.getWidget(node.type);
|
|
1511
|
+
});
|
|
1512
|
+
const selectedFormSchema = computed(() => {
|
|
1513
|
+
const meta = selectedWidgetMeta.value;
|
|
1514
|
+
if (!meta) return null;
|
|
1515
|
+
const schema = cloneDeep(meta.formSchema);
|
|
1516
|
+
if (!meta.container) return schema;
|
|
1517
|
+
const variantOptions = Object.entries(meta.container.variants).map(([value, variant]) => ({
|
|
1518
|
+
value,
|
|
1519
|
+
label: variant.titleKey ? translate(variant.titleKey, variant.title) : variant.title
|
|
1520
|
+
}));
|
|
1521
|
+
for (const section of schema.sections) for (const field of section.fields) {
|
|
1522
|
+
const binding = resolveFieldBinding(getFieldBinding(field), {
|
|
1523
|
+
scope: "node",
|
|
1524
|
+
path: `props.${field.key}`
|
|
1525
|
+
});
|
|
1526
|
+
if (binding.scope !== "container" || binding.path !== "variant") continue;
|
|
1527
|
+
const original = field.componentProps;
|
|
1528
|
+
field.componentProps = (ctx) => ({
|
|
1529
|
+
...typeof original === "function" ? original(ctx) : original ?? {},
|
|
1530
|
+
options: variantOptions
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return schema;
|
|
1534
|
+
});
|
|
1535
|
+
const selectedNodeProps = computed(() => {
|
|
1536
|
+
const node = selectedNode.value;
|
|
1537
|
+
if (!node) return {};
|
|
1538
|
+
const schema = engine.state.getSchema();
|
|
1539
|
+
const values = { ...node.props };
|
|
1540
|
+
for (const section of selectedFormSchema.value?.sections ?? []) for (const field of section.fields) {
|
|
1541
|
+
const value = readBindingValue(resolveFieldBinding(getFieldBinding(field), {
|
|
1542
|
+
scope: "node",
|
|
1543
|
+
path: `props.${field.key}`
|
|
1544
|
+
}), schema, node);
|
|
1545
|
+
if (value !== void 0) values[field.key] = value;
|
|
1546
|
+
}
|
|
1547
|
+
return values;
|
|
1548
|
+
});
|
|
1549
|
+
const globalConfigValues = computed(() => {
|
|
1550
|
+
const schema = engine.state.getSchema();
|
|
1551
|
+
const values = { ...schema.globalConfig };
|
|
1552
|
+
for (const section of options.globalConfigSchema?.sections ?? []) for (const field of section.fields) {
|
|
1553
|
+
const value = readBindingValue(resolveFieldBinding(getFieldBinding(field), {
|
|
1554
|
+
scope: "globalConfig",
|
|
1555
|
+
path: field.key
|
|
1556
|
+
}), schema, null);
|
|
1557
|
+
if (value !== void 0) values[field.key] = value;
|
|
1558
|
+
}
|
|
1559
|
+
return values;
|
|
1560
|
+
});
|
|
1561
|
+
function dispatchBinding(binding, value, nodeId) {
|
|
1562
|
+
const command = createBindingCommand(binding, value, nodeId);
|
|
1563
|
+
if (!command) {
|
|
1564
|
+
console.warn(`[dragcraft/designer] Unsupported binding path "${binding.path}"`);
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
return engine.execute(command);
|
|
1568
|
+
}
|
|
1569
|
+
function handlePropertyChange(key, value) {
|
|
1570
|
+
const nodeId = engine.store.selectedNodeId.value;
|
|
1571
|
+
if (!nodeId) return null;
|
|
1572
|
+
return dispatchBinding(resolveFieldBinding(getFieldBinding(findField(selectedFormSchema.value, key)), {
|
|
1573
|
+
scope: "node",
|
|
1574
|
+
path: `props.${key}`
|
|
1575
|
+
}), value, nodeId);
|
|
1576
|
+
}
|
|
1577
|
+
function handleGlobalConfigChange(key, value) {
|
|
1578
|
+
return dispatchBinding(resolveFieldBinding(getFieldBinding(findField(options.globalConfigSchema, key)), {
|
|
1579
|
+
scope: "globalConfig",
|
|
1580
|
+
path: key
|
|
1581
|
+
}), value);
|
|
1582
|
+
}
|
|
1583
|
+
return {
|
|
1584
|
+
selectedNode,
|
|
1585
|
+
selectedFormSchema,
|
|
1586
|
+
selectedWidgetMeta,
|
|
1587
|
+
selectedNodeProps,
|
|
1588
|
+
globalConfigValues,
|
|
1589
|
+
handlePropertyChange,
|
|
1590
|
+
handleGlobalConfigChange
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
//#endregion
|
|
1594
|
+
//#region src/components/DcPropertyPanel.ts
|
|
1595
|
+
var DcPropertyPanel_default = defineComponent({
|
|
1596
|
+
name: "DcPropertyPanel",
|
|
1597
|
+
setup() {
|
|
1598
|
+
const ctx = useDesignerContext();
|
|
1599
|
+
const { t } = useI18n$1();
|
|
1600
|
+
const { engine, fieldComponentMap, globalConfigSchema, activeTab } = ctx;
|
|
1601
|
+
const { selectedNode, selectedFormSchema, selectedNodeProps, globalConfigValues, handlePropertyChange, handleGlobalConfigChange } = usePropertyBinding(engine, {
|
|
1602
|
+
globalConfigSchema,
|
|
1603
|
+
t
|
|
1604
|
+
});
|
|
1605
|
+
watch(() => engine.store.selectedNodeId.value, (newId) => {
|
|
1606
|
+
if (newId) activeTab.value = "widget";
|
|
1607
|
+
});
|
|
1608
|
+
return () => {
|
|
1609
|
+
const currentTab = activeTab.value;
|
|
1610
|
+
const effectiveTab = currentTab === "widget" && !selectedNode.value ? "global" : currentTab;
|
|
1611
|
+
let tabContent = null;
|
|
1612
|
+
if (effectiveTab === "global" && globalConfigSchema) tabContent = h(FormGenerator$1, {
|
|
1613
|
+
key: "__global__",
|
|
1614
|
+
schema: globalConfigSchema,
|
|
1615
|
+
values: globalConfigValues.value,
|
|
1616
|
+
fieldComponentMap,
|
|
1617
|
+
onChange: (payload) => {
|
|
1618
|
+
handleGlobalConfigChange(payload.key, payload.value);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
else if (effectiveTab === "widget" && selectedFormSchema.value && selectedNode.value) tabContent = h(FormGenerator$1, {
|
|
1622
|
+
key: selectedNode.value.id,
|
|
1623
|
+
schema: selectedFormSchema.value,
|
|
1624
|
+
values: selectedNodeProps.value,
|
|
1625
|
+
fieldComponentMap,
|
|
1626
|
+
onChange: (payload) => {
|
|
1627
|
+
handlePropertyChange(payload.key, payload.value);
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
else tabContent = h("div", {
|
|
1631
|
+
"class": "dc-property-panel__empty",
|
|
1632
|
+
"data-dc-part": "empty"
|
|
1633
|
+
}, effectiveTab === "global" ? t("panel.empty.no-global-config", "暂无全局配置") : t("panel.empty.select-widget", "请选择组件"));
|
|
1634
|
+
return h("div", {
|
|
1635
|
+
"class": "dc-property-panel",
|
|
1636
|
+
"data-dc-component": "property-panel"
|
|
1637
|
+
}, [h("div", {
|
|
1638
|
+
"id": `dc-property-panel-${effectiveTab}`,
|
|
1639
|
+
"class": "dc-property-panel__content",
|
|
1640
|
+
"data-dc-part": "content",
|
|
1641
|
+
"role": "tabpanel",
|
|
1642
|
+
"aria-labelledby": `dc-property-tab-${effectiveTab}`
|
|
1643
|
+
}, [tabContent])]);
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
//#endregion
|
|
1648
|
+
//#region src/components/DcRightSidebar.ts
|
|
1649
|
+
const RIGHT_PANEL_TABS = [{
|
|
1650
|
+
key: "global",
|
|
1651
|
+
labelKey: "panel.tab.global",
|
|
1652
|
+
fallback: "全局配置",
|
|
1653
|
+
icon: IconGlobalConfig
|
|
1654
|
+
}, {
|
|
1655
|
+
key: "widget",
|
|
1656
|
+
labelKey: "panel.tab.widget",
|
|
1657
|
+
fallback: "组件配置",
|
|
1658
|
+
icon: IconProperties
|
|
1659
|
+
}];
|
|
1660
|
+
var DcRightSidebar_default = defineComponent({
|
|
1661
|
+
name: "DcRightSidebar",
|
|
1662
|
+
setup() {
|
|
1663
|
+
const { t } = useI18n$1();
|
|
1664
|
+
const { engine, extensions, activeTab, workspace } = useDesignerContext();
|
|
1665
|
+
const hasSelectedNode = computed(() => engine.store.selectedNodeId.value !== null);
|
|
1666
|
+
const renderTabButton = (tab) => {
|
|
1667
|
+
const label = t(tab.labelKey, tab.fallback);
|
|
1668
|
+
const disabled = tab.key === "widget" && !hasSelectedNode.value;
|
|
1669
|
+
const active = activeTab.value === tab.key && !disabled;
|
|
1670
|
+
return h("button", {
|
|
1671
|
+
"id": `dc-property-tab-${tab.key}`,
|
|
1672
|
+
"type": "button",
|
|
1673
|
+
"role": "tab",
|
|
1674
|
+
"class": ["dc-right-sidebar__tab", { "dc-right-sidebar__tab--active": active }],
|
|
1675
|
+
"data-dc-part": "tab",
|
|
1676
|
+
"title": label,
|
|
1677
|
+
"aria-label": label,
|
|
1678
|
+
"aria-selected": active,
|
|
1679
|
+
"aria-controls": `dc-property-panel-${tab.key}`,
|
|
1680
|
+
"disabled": disabled,
|
|
1681
|
+
"onClick": () => workspace.openRight(tab.key)
|
|
1682
|
+
}, [h(tab.icon, { size: 18 })]);
|
|
1683
|
+
};
|
|
1684
|
+
return () => {
|
|
1685
|
+
const PropertyPanel = extensions.propertyPanelRenderer ?? DcPropertyPanel_default;
|
|
1686
|
+
const open = workspace.rightOpen.value;
|
|
1687
|
+
const toggleLabel = open ? t("workspace.right.close", "收起属性栏") : t("workspace.right.open", "展开属性栏");
|
|
1688
|
+
const railExtension = extensions.rightRailRenderer?.({
|
|
1689
|
+
engine,
|
|
1690
|
+
workspace,
|
|
1691
|
+
t
|
|
1692
|
+
});
|
|
1693
|
+
return h("div", {
|
|
1694
|
+
"class": "dc-right-sidebar",
|
|
1695
|
+
"data-dc-component": "right-sidebar",
|
|
1696
|
+
"data-dc-state": open ? "open" : "closed"
|
|
1697
|
+
}, [h("div", {
|
|
1698
|
+
"class": "dc-right-sidebar__surface",
|
|
1699
|
+
"data-dc-part": "surface",
|
|
1700
|
+
"aria-hidden": workspace.mode.value === "compact" && !open,
|
|
1701
|
+
"inert": workspace.mode.value === "compact" && !open ? "" : void 0
|
|
1702
|
+
}, [h("div", {
|
|
1703
|
+
"class": "dc-right-sidebar__rail",
|
|
1704
|
+
"data-dc-part": "rail",
|
|
1705
|
+
"role": "tablist",
|
|
1706
|
+
"aria-label": t("workspace.right.label", "属性检查器")
|
|
1707
|
+
}, [...RIGHT_PANEL_TABS.map(renderTabButton), railExtension ? h("div", {
|
|
1708
|
+
"class": "dc-sidebar-rail__extension",
|
|
1709
|
+
"data-dc-part": "rail-extension"
|
|
1710
|
+
}, [railExtension]) : null]), h("div", {
|
|
1711
|
+
"class": "dc-right-sidebar__content",
|
|
1712
|
+
"data-dc-part": "content"
|
|
1713
|
+
}, [h(PropertyPanel)])]), h("button", {
|
|
1714
|
+
"type": "button",
|
|
1715
|
+
"class": "dc-sidebar-toggle dc-sidebar-toggle--right",
|
|
1716
|
+
"data-dc-part": "toggle",
|
|
1717
|
+
"title": toggleLabel,
|
|
1718
|
+
"aria-label": toggleLabel,
|
|
1719
|
+
"aria-expanded": open,
|
|
1720
|
+
"data-dc-workspace-control": "right",
|
|
1721
|
+
"onMousedown": (event) => event.preventDefault(),
|
|
1722
|
+
"onClick": () => workspace.toggleRight()
|
|
1723
|
+
}, [h(open ? IconChevronRight : IconChevronLeft, { size: 14 })])]);
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
});
|
|
1727
|
+
//#endregion
|
|
1728
|
+
//#region src/components/DcDesigner.ts
|
|
1729
|
+
const EDITABLE_SELECTOR = "input, textarea, select, [contenteditable=\"true\"], [contenteditable=\"\"]";
|
|
1730
|
+
const INTERACTIVE_SELECTOR = `${EDITABLE_SELECTOR}, button, a[href]`;
|
|
1731
|
+
function isEditableTarget(target) {
|
|
1732
|
+
return target instanceof Element && Boolean(target.closest(EDITABLE_SELECTOR));
|
|
1733
|
+
}
|
|
1734
|
+
var DcDesigner_default = defineComponent({
|
|
1735
|
+
name: "DcDesigner",
|
|
1736
|
+
props: { instance: {
|
|
1737
|
+
type: Object,
|
|
1738
|
+
required: true
|
|
1739
|
+
} },
|
|
1740
|
+
setup(props) {
|
|
1741
|
+
const { engine, componentMap, widgetGroups, extensions, fieldComponentMap, globalConfigSchema, eventHooks, actionInterceptors, actionRegistry, i18n, workspace } = props.instance;
|
|
1742
|
+
const rootRef = ref(null);
|
|
1743
|
+
const leftPanelRef = ref(null);
|
|
1744
|
+
const rightPanelRef = ref(null);
|
|
1745
|
+
const searchQuery = ref("");
|
|
1746
|
+
const dragDrop = useDragDrop(engine);
|
|
1747
|
+
let resizeObserver = null;
|
|
1748
|
+
const focusTimers = /* @__PURE__ */ new Set();
|
|
1749
|
+
provide(DESIGNER_CONTEXT_KEY, {
|
|
1750
|
+
engine,
|
|
1751
|
+
componentMap,
|
|
1752
|
+
widgetGroups,
|
|
1753
|
+
extensions,
|
|
1754
|
+
fieldComponentMap,
|
|
1755
|
+
globalConfigSchema,
|
|
1756
|
+
eventHooks,
|
|
1757
|
+
actionInterceptors,
|
|
1758
|
+
actionRegistry,
|
|
1759
|
+
workspace,
|
|
1760
|
+
activeDestination: dragDrop.activeDestination,
|
|
1761
|
+
containerDropDecision: dragDrop.containerDropDecision,
|
|
1762
|
+
dragOverNodeId: dragDrop.dragOverNodeId,
|
|
1763
|
+
dragOverIndex: dragDrop.dragOverIndex,
|
|
1764
|
+
handleMaterialDragStart: dragDrop.handleMaterialDragStart,
|
|
1765
|
+
handleDragEnd: dragDrop.handleDragEnd,
|
|
1766
|
+
handleCanvasDragOver: dragDrop.handleCanvasDragOver,
|
|
1767
|
+
handleCanvasDragLeave: dragDrop.handleCanvasDragLeave,
|
|
1768
|
+
handleCanvasDrop: dragDrop.handleCanvasDrop,
|
|
1769
|
+
handleContainerDragOver: dragDrop.handleContainerDragOver,
|
|
1770
|
+
handleContainerDragLeave: dragDrop.handleContainerDragLeave,
|
|
1771
|
+
handleContainerDrop: dragDrop.handleContainerDrop,
|
|
1772
|
+
isForbidden: dragDrop.isForbidden,
|
|
1773
|
+
forbiddenReason: dragDrop.forbiddenReason,
|
|
1774
|
+
searchQuery,
|
|
1775
|
+
activeTab: workspace.activeRightPanel,
|
|
1776
|
+
leftPanelActiveTab: workspace.activeLeftPanel
|
|
1777
|
+
});
|
|
1778
|
+
provide(I18N_KEY$1, i18n);
|
|
1779
|
+
const { t } = i18n;
|
|
1780
|
+
function updateLayoutMode(width) {
|
|
1781
|
+
workspace.setMode(width < workspace.compactBreakpoint ? "compact" : "wide");
|
|
1782
|
+
}
|
|
1783
|
+
function focusPanel(panel) {
|
|
1784
|
+
(panel?.querySelector("button:not(:disabled), input:not(:disabled), [tabindex=\"0\"]"))?.focus({ preventScroll: true });
|
|
1785
|
+
}
|
|
1786
|
+
function restoreControlFocus(control) {
|
|
1787
|
+
rootRef.value?.querySelector(`[data-dc-workspace-control="${control}"]`)?.focus({ preventScroll: true });
|
|
1788
|
+
}
|
|
1789
|
+
function afterRender(callback) {
|
|
1790
|
+
nextTick(() => {
|
|
1791
|
+
const timer = setTimeout(() => {
|
|
1792
|
+
focusTimers.delete(timer);
|
|
1793
|
+
callback();
|
|
1794
|
+
}, 0);
|
|
1795
|
+
focusTimers.add(timer);
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
watch(workspace.leftOpen, (open, previous) => {
|
|
1799
|
+
if (workspace.mode.value !== "compact") return;
|
|
1800
|
+
if (open) afterRender(() => workspace.leftOpen.value && focusPanel(leftPanelRef.value));
|
|
1801
|
+
else if (previous) afterRender(() => !workspace.rightOpen.value && restoreControlFocus("left"));
|
|
1802
|
+
});
|
|
1803
|
+
watch(workspace.rightOpen, (open, previous) => {
|
|
1804
|
+
if (workspace.mode.value !== "compact") return;
|
|
1805
|
+
if (open) afterRender(() => workspace.rightOpen.value && focusPanel(rightPanelRef.value));
|
|
1806
|
+
else if (previous) afterRender(() => !workspace.leftOpen.value && restoreControlFocus("right"));
|
|
1807
|
+
});
|
|
1808
|
+
function handleKeydown(event) {
|
|
1809
|
+
if (event.key === "Escape" && workspace.mode.value === "compact" && (workspace.leftOpen.value || workspace.rightOpen.value)) {
|
|
1810
|
+
event.preventDefault();
|
|
1811
|
+
workspace.closeDrawers();
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (!workspace.keyboardShortcuts || isEditableTarget(event.target)) return;
|
|
1815
|
+
if (!(event.metaKey || event.ctrlKey)) return;
|
|
1816
|
+
const key = event.key.toLowerCase();
|
|
1817
|
+
if (key === "z") {
|
|
1818
|
+
event.preventDefault();
|
|
1819
|
+
if (event.shiftKey) engine.history.redo();
|
|
1820
|
+
else engine.history.undo();
|
|
1821
|
+
} else if (key === "y" && event.ctrlKey) {
|
|
1822
|
+
event.preventDefault();
|
|
1823
|
+
engine.history.redo();
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function handlePointerdown(event) {
|
|
1827
|
+
const target = event.target;
|
|
1828
|
+
if (target instanceof Element && !target.closest(INTERACTIVE_SELECTOR)) rootRef.value?.focus({ preventScroll: true });
|
|
1829
|
+
}
|
|
1830
|
+
onMounted(() => {
|
|
1831
|
+
const root = rootRef.value;
|
|
1832
|
+
if (!root) return;
|
|
1833
|
+
updateLayoutMode(root.getBoundingClientRect().width);
|
|
1834
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1835
|
+
resizeObserver = new ResizeObserver((entries) => updateLayoutMode(entries[0]?.contentRect.width ?? root.clientWidth));
|
|
1836
|
+
resizeObserver.observe(root);
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
onBeforeUnmount(() => {
|
|
1840
|
+
resizeObserver?.disconnect();
|
|
1841
|
+
focusTimers.forEach((timer) => clearTimeout(timer));
|
|
1842
|
+
focusTimers.clear();
|
|
1843
|
+
});
|
|
1844
|
+
return () => {
|
|
1845
|
+
const compact = workspace.mode.value === "compact";
|
|
1846
|
+
const drawerOpen = compact && (workspace.leftOpen.value || workspace.rightOpen.value);
|
|
1847
|
+
const themeStates = [
|
|
1848
|
+
workspace.mode.value,
|
|
1849
|
+
workspace.leftOpen.value ? "left-open" : null,
|
|
1850
|
+
workspace.rightOpen.value ? "right-open" : null
|
|
1851
|
+
].filter(Boolean).join(" ");
|
|
1852
|
+
return h("div", {
|
|
1853
|
+
"ref": rootRef,
|
|
1854
|
+
"class": [
|
|
1855
|
+
"dc-designer",
|
|
1856
|
+
`dc-designer--${workspace.mode.value}`,
|
|
1857
|
+
{
|
|
1858
|
+
"dc-designer--left-open": workspace.leftOpen.value,
|
|
1859
|
+
"dc-designer--right-open": workspace.rightOpen.value
|
|
1860
|
+
}
|
|
1861
|
+
],
|
|
1862
|
+
"data-dc-component": "designer",
|
|
1863
|
+
"data-dc-state": themeStates,
|
|
1864
|
+
"tabindex": -1,
|
|
1865
|
+
"data-dc-workspace-mode": workspace.mode.value,
|
|
1866
|
+
"style": {
|
|
1867
|
+
"--_dc-workspace-left-width": `${workspace.leftPanelWidth}px`,
|
|
1868
|
+
"--_dc-workspace-right-width": `${workspace.rightPanelWidth}px`,
|
|
1869
|
+
"--_dc-workspace-rail-width": `${workspace.railWidth}px`,
|
|
1870
|
+
"--_dc-workspace-drawer-width": `${workspace.drawerWidth}px`
|
|
1871
|
+
},
|
|
1872
|
+
"onKeydown": handleKeydown,
|
|
1873
|
+
"onPointerdown": handlePointerdown
|
|
1874
|
+
}, [h("div", {
|
|
1875
|
+
"class": "dc-designer__body",
|
|
1876
|
+
"data-dc-part": "body"
|
|
1877
|
+
}, [
|
|
1878
|
+
drawerOpen ? h("button", {
|
|
1879
|
+
"type": "button",
|
|
1880
|
+
"class": "dc-workspace-backdrop",
|
|
1881
|
+
"data-dc-part": "backdrop",
|
|
1882
|
+
"aria-label": t("workspace.drawer.close", "关闭面板"),
|
|
1883
|
+
"onClick": workspace.closeDrawers
|
|
1884
|
+
}) : null,
|
|
1885
|
+
h("aside", {
|
|
1886
|
+
"ref": leftPanelRef,
|
|
1887
|
+
"class": [
|
|
1888
|
+
"dc-designer__panel",
|
|
1889
|
+
"dc-designer__panel--left",
|
|
1890
|
+
{ "dc-designer__panel--open": workspace.leftOpen.value }
|
|
1891
|
+
],
|
|
1892
|
+
"data-dc-part": "left-panel",
|
|
1893
|
+
"aria-label": t("workspace.left.label", "物料与结构"),
|
|
1894
|
+
"onTransitionend": (event) => {
|
|
1895
|
+
if (event.propertyName === "transform" && compact && workspace.leftOpen.value) focusPanel(leftPanelRef.value);
|
|
1896
|
+
}
|
|
1897
|
+
}, [h(DcLeftSidebar_default)]),
|
|
1898
|
+
h("main", {
|
|
1899
|
+
"class": "dc-designer__panel dc-designer__panel--center",
|
|
1900
|
+
"data-dc-part": "center-panel"
|
|
1901
|
+
}, [h(DcCanvas_default)]),
|
|
1902
|
+
h("aside", {
|
|
1903
|
+
"ref": rightPanelRef,
|
|
1904
|
+
"class": [
|
|
1905
|
+
"dc-designer__panel",
|
|
1906
|
+
"dc-designer__panel--right",
|
|
1907
|
+
{ "dc-designer__panel--open": workspace.rightOpen.value }
|
|
1908
|
+
],
|
|
1909
|
+
"data-dc-part": "right-panel",
|
|
1910
|
+
"aria-label": t("workspace.right.label", "属性检查器"),
|
|
1911
|
+
"onTransitionend": (event) => {
|
|
1912
|
+
if (event.propertyName === "transform" && compact && workspace.rightOpen.value) focusPanel(rightPanelRef.value);
|
|
1913
|
+
}
|
|
1914
|
+
}, [h(DcRightSidebar_default)])
|
|
1915
|
+
])]);
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region src/composables/useDesigner.ts
|
|
1921
|
+
/**
|
|
1922
|
+
* Composable that provides reactive access to designer state and operations.
|
|
1923
|
+
* Thin wrapper around the engine's reactive store and API.
|
|
1924
|
+
*
|
|
1925
|
+
* @example
|
|
1926
|
+
* ```ts
|
|
1927
|
+
* const designer = createDesigner()
|
|
1928
|
+
* const { schema, selectedNodeId, undo, redo } = useDesigner(designer)
|
|
1929
|
+
* ```
|
|
1930
|
+
*/
|
|
1931
|
+
function useDesigner(instance) {
|
|
1932
|
+
const { engine } = instance;
|
|
1933
|
+
return {
|
|
1934
|
+
schema: engine.store.schema,
|
|
1935
|
+
selectedNodeId: engine.store.selectedNodeId,
|
|
1936
|
+
hoveredNodeId: engine.store.hoveredNodeId,
|
|
1937
|
+
execute: engine.execute,
|
|
1938
|
+
undo: () => engine.history.undo(),
|
|
1939
|
+
redo: () => engine.history.redo(),
|
|
1940
|
+
canUndo: () => engine.history.canUndo(),
|
|
1941
|
+
canRedo: () => engine.history.canRedo(),
|
|
1942
|
+
importSchema: (schema) => engine.importSchema(schema),
|
|
1943
|
+
exportSchema: () => engine.exportSchema(),
|
|
1944
|
+
on: engine.eventHub.on.bind(engine.eventHub),
|
|
1945
|
+
off: engine.eventHub.off.bind(engine.eventHub)
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
//#endregion
|
|
1949
|
+
//#region src/messages.ts
|
|
1950
|
+
const designerMessages = {
|
|
1951
|
+
"zh-CN": {
|
|
1952
|
+
panel: {
|
|
1953
|
+
materials: { title: "物料" },
|
|
1954
|
+
left: {
|
|
1955
|
+
materials: "物料",
|
|
1956
|
+
structure: "结构树"
|
|
1957
|
+
},
|
|
1958
|
+
tab: {
|
|
1959
|
+
global: "全局配置",
|
|
1960
|
+
widget: "组件配置"
|
|
1961
|
+
},
|
|
1962
|
+
structure: {
|
|
1963
|
+
title: "结构树",
|
|
1964
|
+
empty: "暂无结构"
|
|
1965
|
+
},
|
|
1966
|
+
empty: {
|
|
1967
|
+
"no-global-config": "暂无全局配置",
|
|
1968
|
+
"select-widget": "请选择组件"
|
|
1969
|
+
},
|
|
1970
|
+
search: {
|
|
1971
|
+
placeholder: "搜索组件...",
|
|
1972
|
+
clear: "清除搜索"
|
|
1973
|
+
},
|
|
1974
|
+
inspector: {
|
|
1975
|
+
title: "属性检查器",
|
|
1976
|
+
page: "页面"
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
workspace: {
|
|
1980
|
+
history: {
|
|
1981
|
+
label: "历史操作",
|
|
1982
|
+
undo: "撤销",
|
|
1983
|
+
redo: "重做"
|
|
1984
|
+
},
|
|
1985
|
+
canvas: {
|
|
1986
|
+
controls: "画布工具",
|
|
1987
|
+
pointer: "指针模式",
|
|
1988
|
+
hand: "抓手模式(按住空格)",
|
|
1989
|
+
reset: "重置画布位置"
|
|
1990
|
+
},
|
|
1991
|
+
left: {
|
|
1992
|
+
label: "物料与结构",
|
|
1993
|
+
open: "展开左侧栏",
|
|
1994
|
+
close: "收起左侧栏"
|
|
1995
|
+
},
|
|
1996
|
+
right: {
|
|
1997
|
+
label: "属性检查器",
|
|
1998
|
+
open: "展开属性栏",
|
|
1999
|
+
close: "收起属性栏"
|
|
2000
|
+
},
|
|
2001
|
+
drawer: { close: "关闭面板" }
|
|
2002
|
+
},
|
|
2003
|
+
device: {
|
|
2004
|
+
group: "预览设备",
|
|
2005
|
+
iphone: "iPhone",
|
|
2006
|
+
android: "Android",
|
|
2007
|
+
tablet: "平板",
|
|
2008
|
+
desktop: "桌面"
|
|
2009
|
+
}
|
|
2010
|
+
},
|
|
2011
|
+
"en": {
|
|
2012
|
+
panel: {
|
|
2013
|
+
materials: { title: "Materials" },
|
|
2014
|
+
left: {
|
|
2015
|
+
materials: "Materials",
|
|
2016
|
+
structure: "Structure"
|
|
2017
|
+
},
|
|
2018
|
+
tab: {
|
|
2019
|
+
global: "Global",
|
|
2020
|
+
widget: "Widget"
|
|
2021
|
+
},
|
|
2022
|
+
structure: {
|
|
2023
|
+
title: "Structure",
|
|
2024
|
+
empty: "No nodes"
|
|
2025
|
+
},
|
|
2026
|
+
empty: {
|
|
2027
|
+
"no-global-config": "No global config",
|
|
2028
|
+
"select-widget": "Select a widget"
|
|
2029
|
+
},
|
|
2030
|
+
search: {
|
|
2031
|
+
placeholder: "Search widgets...",
|
|
2032
|
+
clear: "Clear search"
|
|
2033
|
+
},
|
|
2034
|
+
inspector: {
|
|
2035
|
+
title: "Inspector",
|
|
2036
|
+
page: "Page"
|
|
2037
|
+
}
|
|
2038
|
+
},
|
|
2039
|
+
workspace: {
|
|
2040
|
+
history: {
|
|
2041
|
+
label: "History actions",
|
|
2042
|
+
undo: "Undo",
|
|
2043
|
+
redo: "Redo"
|
|
2044
|
+
},
|
|
2045
|
+
canvas: {
|
|
2046
|
+
controls: "Canvas tools",
|
|
2047
|
+
pointer: "Pointer mode",
|
|
2048
|
+
hand: "Hand mode (hold Space)",
|
|
2049
|
+
reset: "Reset canvas position"
|
|
2050
|
+
},
|
|
2051
|
+
left: {
|
|
2052
|
+
label: "Materials and structure",
|
|
2053
|
+
open: "Open left panel",
|
|
2054
|
+
close: "Collapse left panel"
|
|
2055
|
+
},
|
|
2056
|
+
right: {
|
|
2057
|
+
label: "Inspector",
|
|
2058
|
+
open: "Open inspector",
|
|
2059
|
+
close: "Collapse inspector"
|
|
2060
|
+
},
|
|
2061
|
+
drawer: { close: "Close panel" }
|
|
2062
|
+
},
|
|
2063
|
+
device: {
|
|
2064
|
+
group: "Preview device",
|
|
2065
|
+
iphone: "iPhone",
|
|
2066
|
+
android: "Android",
|
|
2067
|
+
tablet: "Tablet",
|
|
2068
|
+
desktop: "Desktop"
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
//#endregion
|
|
2073
|
+
//#region src/workspace.ts
|
|
2074
|
+
const DEFAULT_COMPACT_BREAKPOINT = 1100;
|
|
2075
|
+
const DEFAULT_LEFT_PANEL_WIDTH = 280;
|
|
2076
|
+
const DEFAULT_RIGHT_PANEL_WIDTH = 320;
|
|
2077
|
+
const DEFAULT_RAIL_WIDTH = 44;
|
|
2078
|
+
const DEFAULT_DRAWER_WIDTH = 320;
|
|
2079
|
+
function createDesignerWorkspace(options = {}) {
|
|
2080
|
+
const compactBreakpoint = options.compactBreakpoint ?? DEFAULT_COMPACT_BREAKPOINT;
|
|
2081
|
+
const keyboardShortcuts = options.keyboardShortcuts ?? true;
|
|
2082
|
+
const leftPanelWidth = options.leftPanelWidth ?? DEFAULT_LEFT_PANEL_WIDTH;
|
|
2083
|
+
const rightPanelWidth = options.rightPanelWidth ?? DEFAULT_RIGHT_PANEL_WIDTH;
|
|
2084
|
+
const railWidth = options.railWidth ?? DEFAULT_RAIL_WIDTH;
|
|
2085
|
+
const drawerWidth = options.drawerWidth ?? DEFAULT_DRAWER_WIDTH;
|
|
2086
|
+
const mode = ref("wide");
|
|
2087
|
+
const leftOpen = ref(options.defaultLeftOpen ?? true);
|
|
2088
|
+
const rightOpen = ref(options.defaultRightOpen ?? true);
|
|
2089
|
+
const activeLeftPanel = ref("materials");
|
|
2090
|
+
const activeRightPanel = ref("global");
|
|
2091
|
+
let wideLeftOpen = leftOpen.value;
|
|
2092
|
+
let wideRightOpen = rightOpen.value;
|
|
2093
|
+
function setMode(nextMode) {
|
|
2094
|
+
if (mode.value === nextMode) return;
|
|
2095
|
+
if (nextMode === "compact") {
|
|
2096
|
+
wideLeftOpen = leftOpen.value;
|
|
2097
|
+
wideRightOpen = rightOpen.value;
|
|
2098
|
+
leftOpen.value = false;
|
|
2099
|
+
rightOpen.value = false;
|
|
2100
|
+
} else {
|
|
2101
|
+
leftOpen.value = wideLeftOpen;
|
|
2102
|
+
rightOpen.value = wideRightOpen;
|
|
2103
|
+
}
|
|
2104
|
+
mode.value = nextMode;
|
|
2105
|
+
}
|
|
2106
|
+
function openLeft(panel) {
|
|
2107
|
+
if (panel) activeLeftPanel.value = panel;
|
|
2108
|
+
leftOpen.value = true;
|
|
2109
|
+
if (mode.value === "compact") rightOpen.value = false;
|
|
2110
|
+
else wideLeftOpen = true;
|
|
2111
|
+
}
|
|
2112
|
+
function closeLeft() {
|
|
2113
|
+
leftOpen.value = false;
|
|
2114
|
+
if (mode.value === "wide") wideLeftOpen = false;
|
|
2115
|
+
}
|
|
2116
|
+
function toggleLeft(panel) {
|
|
2117
|
+
if (leftOpen.value && (!panel || activeLeftPanel.value === panel)) closeLeft();
|
|
2118
|
+
else openLeft(panel);
|
|
2119
|
+
}
|
|
2120
|
+
function openRight(panel) {
|
|
2121
|
+
if (panel) activeRightPanel.value = panel;
|
|
2122
|
+
rightOpen.value = true;
|
|
2123
|
+
if (mode.value === "compact") leftOpen.value = false;
|
|
2124
|
+
else wideRightOpen = true;
|
|
2125
|
+
}
|
|
2126
|
+
function closeRight() {
|
|
2127
|
+
rightOpen.value = false;
|
|
2128
|
+
if (mode.value === "wide") wideRightOpen = false;
|
|
2129
|
+
}
|
|
2130
|
+
function toggleRight(panel) {
|
|
2131
|
+
if (rightOpen.value && (!panel || activeRightPanel.value === panel)) closeRight();
|
|
2132
|
+
else openRight(panel);
|
|
2133
|
+
}
|
|
2134
|
+
function closeDrawers() {
|
|
2135
|
+
if (mode.value !== "compact") return;
|
|
2136
|
+
leftOpen.value = false;
|
|
2137
|
+
rightOpen.value = false;
|
|
2138
|
+
}
|
|
2139
|
+
return {
|
|
2140
|
+
compactBreakpoint,
|
|
2141
|
+
keyboardShortcuts,
|
|
2142
|
+
leftPanelWidth,
|
|
2143
|
+
rightPanelWidth,
|
|
2144
|
+
railWidth,
|
|
2145
|
+
drawerWidth,
|
|
2146
|
+
mode,
|
|
2147
|
+
leftOpen,
|
|
2148
|
+
rightOpen,
|
|
2149
|
+
activeLeftPanel,
|
|
2150
|
+
activeRightPanel,
|
|
2151
|
+
setMode,
|
|
2152
|
+
openLeft,
|
|
2153
|
+
closeLeft,
|
|
2154
|
+
toggleLeft,
|
|
2155
|
+
openRight,
|
|
2156
|
+
closeRight,
|
|
2157
|
+
toggleRight,
|
|
2158
|
+
closeDrawers
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
//#endregion
|
|
2162
|
+
//#region src/factory.ts
|
|
2163
|
+
function mergeDefaultMessages() {
|
|
2164
|
+
const merged = {};
|
|
2165
|
+
const locales = /* @__PURE__ */ new Set([...Object.keys(rendererMessages), ...Object.keys(designerMessages)]);
|
|
2166
|
+
for (const locale of locales) merged[locale] = {
|
|
2167
|
+
...rendererMessages[locale] ?? {},
|
|
2168
|
+
...designerMessages[locale] ?? {}
|
|
2169
|
+
};
|
|
2170
|
+
return merged;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Creates a designer instance by initializing the core engine,
|
|
2174
|
+
* registering widgets, and resolving configuration.
|
|
2175
|
+
*
|
|
2176
|
+
* Users must explicitly provide widget metas, component maps, and field maps.
|
|
2177
|
+
*
|
|
2178
|
+
* @example
|
|
2179
|
+
* ```ts
|
|
2180
|
+
* const designer = createDesigner({
|
|
2181
|
+
* widgetMetas: myWidgetMetas,
|
|
2182
|
+
* componentMap: myComponentMap,
|
|
2183
|
+
* fieldComponentMap: myFieldComponentMap,
|
|
2184
|
+
* globalConfigSchema: myGlobalSchema,
|
|
2185
|
+
* })
|
|
2186
|
+
* ```
|
|
2187
|
+
*/
|
|
2188
|
+
function createDesigner(options = {}) {
|
|
2189
|
+
const { initialSchema, ...engineOptions } = options.engineOptions ?? {};
|
|
2190
|
+
const engine = createEngine$1(engineOptions);
|
|
2191
|
+
if (options.widgetMetas) for (const meta of options.widgetMetas) engine.registerWidget(meta);
|
|
2192
|
+
if (initialSchema) {
|
|
2193
|
+
const result = engine.importSchema(initialSchema);
|
|
2194
|
+
if (!result.ok) console.warn("[dragcraft/designer] initial schema rejected", result.diagnostics);
|
|
2195
|
+
}
|
|
2196
|
+
const componentMap = options.componentMap ?? {};
|
|
2197
|
+
const widgetGroups = options.widgetGroups;
|
|
2198
|
+
const extensions = options.extensions ?? {};
|
|
2199
|
+
const fieldComponentMap = options.fieldComponentMap;
|
|
2200
|
+
const globalConfigSchema = options.globalConfigSchema ?? null;
|
|
2201
|
+
if (globalConfigSchema) engine.registry.registerGlobalConfigFormSchema(globalConfigSchema);
|
|
2202
|
+
const eventHooks = options.eventHooks ?? {};
|
|
2203
|
+
const actionInterceptors = options.actionInterceptors ?? [];
|
|
2204
|
+
const i18n = createI18n$1(options.locale ?? "zh-CN", mergeDefaultMessages());
|
|
2205
|
+
const workspace = createDesignerWorkspace(options.workspace);
|
|
2206
|
+
if (options.messages) for (const [locale, msgs] of Object.entries(options.messages)) i18n.mergeMessages(locale, msgs);
|
|
2207
|
+
const actionRegistry = createNodeActionRegistry$1(createDefaultActions$1(i18n.t));
|
|
2208
|
+
if (options.customActions) for (const action of options.customActions) actionRegistry.register(action);
|
|
2209
|
+
function dispose() {
|
|
2210
|
+
engine.dispose();
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
engine,
|
|
2214
|
+
componentMap,
|
|
2215
|
+
widgetGroups,
|
|
2216
|
+
extensions,
|
|
2217
|
+
fieldComponentMap,
|
|
2218
|
+
globalConfigSchema,
|
|
2219
|
+
eventHooks,
|
|
2220
|
+
actionInterceptors,
|
|
2221
|
+
actionRegistry,
|
|
2222
|
+
i18n,
|
|
2223
|
+
workspace,
|
|
2224
|
+
dispose
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
//#endregion
|
|
2228
|
+
export { ActionKey, CommandType, ContainerRegionOutlet, DESIGNER_CONTEXT_KEY, DcCanvas_default as DcCanvas, DcCanvasControls_default as DcCanvasControls, DcDesigner_default as DcDesigner, DcLeftSidebar_default as DcLeftSidebar, DcMaterialGroup_default as DcMaterialGroup, DcMaterialItem_default as DcMaterialItem, DcMaterialPanel_default as DcMaterialPanel, DcPropertyPanel_default as DcPropertyPanel, DcRightSidebar_default as DcRightSidebar, DcStructurePanel_default as DcStructurePanel, DefaultEmptyState, DefaultNodeHandle, DefaultNodeMask, DefaultNodeToolbar, EventName, FormGenerator, I18N_KEY, RootRenderer, createBindingCommand, createConfirmActionInterceptor, createDefaultActions, createDesigner, createDesignerWorkspace, createEngine, createI18n, createNodeActionRegistry, designerMessages, materialItemMatchesQuery, readBindingValue, resolveBehavior, resolveCreatable, resolveFieldBinding, resolveMaterialItem, useContainerRuntime, useDesigner, useDesignerContext, useDragDrop, useI18n, useNodeActions, useNodeDrag, useNodeInteractionGeometry, usePropertyBinding, useWidgetNode };
|