@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/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 };