@dragcraft/renderer 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 +1419 -0
- package/dist/index.mjs +2590 -0
- package/dist/structure.css +383 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2590 @@
|
|
|
1
|
+
import { CommandType, DEFAULT_LAYOUT_REGION, DEFAULT_SORT_SCOPE, buildSchemaIndex, createContainerPlan, createLayoutPlan, getLockedIndices, getLockedIndicesFromNodes, getSortScopeEntries, isInsertAllowed, isMoveAllowed, isRemoveAllowed, normalizeStyleValueMap, resolveBehavior, resolveNodeLayout } from "@dragcraft/core";
|
|
2
|
+
import { IconArrowDown, IconArrowUp, IconComponent, IconCopy, IconDelete, IconDrag, IconPlus } from "@dragcraft/icons";
|
|
3
|
+
import { Teleport, computed, defineComponent, h, inject, mergeProps, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, ref, watch } from "vue";
|
|
4
|
+
import { cloneDeep } from "@dragcraft/utils";
|
|
5
|
+
import { useI18n } from "@dragcraft/i18n";
|
|
6
|
+
import { autoUpdate } from "@floating-ui/dom";
|
|
7
|
+
//#region src/action-runtime.ts
|
|
8
|
+
function isPromiseLike$1(value) {
|
|
9
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
10
|
+
}
|
|
11
|
+
function resolveActionDecision(result) {
|
|
12
|
+
if (result === false) return { allowed: false };
|
|
13
|
+
if (result && typeof result === "object" && "allowed" in result) return result;
|
|
14
|
+
return { allowed: true };
|
|
15
|
+
}
|
|
16
|
+
function resolveText(value, invocation) {
|
|
17
|
+
return typeof value === "function" ? value(invocation) : value;
|
|
18
|
+
}
|
|
19
|
+
function reportActionError(interceptors, invocation, error) {
|
|
20
|
+
console.error(`[dragcraft] Action "${invocation.key}" error (action cancelled):`, error);
|
|
21
|
+
for (const interceptor of interceptors) try {
|
|
22
|
+
interceptor.onActionError?.(invocation, error);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error("[dragcraft] Action error handler failed:", err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function fireAfterAction(interceptors, invocation) {
|
|
28
|
+
for (const interceptor of interceptors) {
|
|
29
|
+
if (!interceptor.afterAction) continue;
|
|
30
|
+
try {
|
|
31
|
+
const result = interceptor.afterAction(invocation);
|
|
32
|
+
if (isPromiseLike$1(result)) result.catch((err) => {
|
|
33
|
+
console.error("[dragcraft] Async after-action error:", err);
|
|
34
|
+
});
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error("[dragcraft] After-action error:", err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function runActionPipeline(invocation, execute, interceptors = [], pendingGuard) {
|
|
41
|
+
if (pendingGuard?.value) return;
|
|
42
|
+
const runExecute = () => {
|
|
43
|
+
const result = execute();
|
|
44
|
+
if (isPromiseLike$1(result)) return result.then(() => {
|
|
45
|
+
fireAfterAction(interceptors, invocation);
|
|
46
|
+
});
|
|
47
|
+
fireAfterAction(interceptors, invocation);
|
|
48
|
+
};
|
|
49
|
+
const runBeforeFrom = (startIndex) => {
|
|
50
|
+
for (let index = startIndex; index < interceptors.length; index++) {
|
|
51
|
+
const beforeAction = interceptors[index].beforeAction;
|
|
52
|
+
if (!beforeAction) continue;
|
|
53
|
+
const result = beforeAction(invocation);
|
|
54
|
+
if (isPromiseLike$1(result)) return result.then((resolved) => {
|
|
55
|
+
if (!resolveActionDecision(resolved).allowed) return;
|
|
56
|
+
return runBeforeFrom(index + 1);
|
|
57
|
+
});
|
|
58
|
+
if (!resolveActionDecision(result).allowed) return;
|
|
59
|
+
}
|
|
60
|
+
return runExecute();
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
const result = runBeforeFrom(0);
|
|
64
|
+
if (!isPromiseLike$1(result)) return result;
|
|
65
|
+
if (pendingGuard) pendingGuard.value = true;
|
|
66
|
+
return result.catch((err) => {
|
|
67
|
+
reportActionError(interceptors, invocation, err);
|
|
68
|
+
}).finally(() => {
|
|
69
|
+
if (pendingGuard) pendingGuard.value = false;
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
reportActionError(interceptors, invocation, err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function createConfirmActionInterceptor(options) {
|
|
76
|
+
const shouldConfirm = options.shouldConfirm ?? ((invocation) => invocation.risk === "destructive");
|
|
77
|
+
return { beforeAction(invocation) {
|
|
78
|
+
if (!shouldConfirm(invocation)) return;
|
|
79
|
+
return options.confirm({
|
|
80
|
+
invocation,
|
|
81
|
+
title: resolveText(options.title, invocation),
|
|
82
|
+
message: resolveText(options.message, invocation)
|
|
83
|
+
});
|
|
84
|
+
} };
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/action-registry.ts
|
|
88
|
+
/**
|
|
89
|
+
* Builds an InstanceBehaviorContext from a NodeActionContext.
|
|
90
|
+
* Schema is already read reactively by the calling computed (in useNodeActions).
|
|
91
|
+
*/
|
|
92
|
+
function toInstanceCtx(ctx) {
|
|
93
|
+
return {
|
|
94
|
+
node: ctx.node,
|
|
95
|
+
schema: ctx.schema
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function canReorder(ctx) {
|
|
99
|
+
if (ctx.owner.kind === "root" && ctx.sortScope === false) return false;
|
|
100
|
+
const instanceCtx = toInstanceCtx(ctx);
|
|
101
|
+
return resolveBehavior(ctx.meta?.draggable, instanceCtx) && resolveBehavior(ctx.meta?.sortable, instanceCtx);
|
|
102
|
+
}
|
|
103
|
+
function getScopedLockedIndices(ctx) {
|
|
104
|
+
if (ctx.lockedIndices) return ctx.lockedIndices;
|
|
105
|
+
const schema = ctx.schema;
|
|
106
|
+
if (ctx.owner.kind === "container") {
|
|
107
|
+
const containerOwner = ctx.owner;
|
|
108
|
+
return getLockedIndicesFromNodes((schema.root.children?.find((node) => node.id === containerOwner.containerId))?.container?.regions[containerOwner.regionId] ?? [], ctx.engine.registry, schema);
|
|
109
|
+
}
|
|
110
|
+
return getLockedIndices(schema.root.children ?? [], ctx.engine.registry, schema, ctx.sortScope === false ? void 0 : ctx.sortScope);
|
|
111
|
+
}
|
|
112
|
+
const ActionKey = {
|
|
113
|
+
DRAG: "drag",
|
|
114
|
+
MOVE_UP: "move-up",
|
|
115
|
+
MOVE_DOWN: "move-down",
|
|
116
|
+
DUPLICATE: "duplicate",
|
|
117
|
+
DELETE: "delete"
|
|
118
|
+
};
|
|
119
|
+
function createDefaultActions(t) {
|
|
120
|
+
const _t = t ?? ((_, f) => f ?? "");
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
key: ActionKey.DRAG,
|
|
124
|
+
label: _t("action.drag", "拖拽排序"),
|
|
125
|
+
icon: IconDrag,
|
|
126
|
+
type: "drag-handle",
|
|
127
|
+
order: 100,
|
|
128
|
+
available: canReorder
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: ActionKey.MOVE_UP,
|
|
132
|
+
label: _t("action.move-up", "上移"),
|
|
133
|
+
icon: IconArrowUp,
|
|
134
|
+
type: "button",
|
|
135
|
+
order: 200,
|
|
136
|
+
metadata: {
|
|
137
|
+
commandType: CommandType.MOVE_NODE,
|
|
138
|
+
direction: "up"
|
|
139
|
+
},
|
|
140
|
+
available: canReorder,
|
|
141
|
+
disabled: (ctx) => {
|
|
142
|
+
if (ctx.index === 0) return true;
|
|
143
|
+
const lockedIndices = getScopedLockedIndices(ctx);
|
|
144
|
+
if (lockedIndices.size === 0) return false;
|
|
145
|
+
return !isMoveAllowed(ctx.index, ctx.index - 1, lockedIndices);
|
|
146
|
+
},
|
|
147
|
+
command: (ctx) => {
|
|
148
|
+
if (ctx.index <= 0) return null;
|
|
149
|
+
return {
|
|
150
|
+
type: CommandType.MOVE_NODE,
|
|
151
|
+
payload: {
|
|
152
|
+
nodeId: ctx.node.id,
|
|
153
|
+
destination: {
|
|
154
|
+
...ctx.owner,
|
|
155
|
+
index: ctx.index - 1
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: ActionKey.MOVE_DOWN,
|
|
163
|
+
label: _t("action.move-down", "下移"),
|
|
164
|
+
icon: IconArrowDown,
|
|
165
|
+
type: "button",
|
|
166
|
+
order: 300,
|
|
167
|
+
metadata: {
|
|
168
|
+
commandType: CommandType.MOVE_NODE,
|
|
169
|
+
direction: "down"
|
|
170
|
+
},
|
|
171
|
+
available: canReorder,
|
|
172
|
+
disabled: (ctx) => {
|
|
173
|
+
if (ctx.index >= ctx.siblingCount - 1) return true;
|
|
174
|
+
const lockedIndices = getScopedLockedIndices(ctx);
|
|
175
|
+
if (lockedIndices.size === 0) return false;
|
|
176
|
+
return !isMoveAllowed(ctx.index, ctx.index + 1, lockedIndices);
|
|
177
|
+
},
|
|
178
|
+
command: (ctx) => {
|
|
179
|
+
if (ctx.index >= ctx.siblingCount - 1) return null;
|
|
180
|
+
return {
|
|
181
|
+
type: CommandType.MOVE_NODE,
|
|
182
|
+
payload: {
|
|
183
|
+
nodeId: ctx.node.id,
|
|
184
|
+
destination: {
|
|
185
|
+
...ctx.owner,
|
|
186
|
+
index: ctx.index + 2
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: ActionKey.DUPLICATE,
|
|
194
|
+
label: _t("action.duplicate", "复制"),
|
|
195
|
+
icon: IconCopy,
|
|
196
|
+
type: "button",
|
|
197
|
+
order: 350,
|
|
198
|
+
disabled: (ctx) => {
|
|
199
|
+
if (ctx.owner.kind === "root" && ctx.sortScope === false) return false;
|
|
200
|
+
const lockedIndices = getScopedLockedIndices(ctx);
|
|
201
|
+
return !isInsertAllowed(ctx.index + 1, lockedIndices);
|
|
202
|
+
},
|
|
203
|
+
command: (ctx) => ({
|
|
204
|
+
type: CommandType.DUPLICATE_NODE,
|
|
205
|
+
payload: { nodeId: ctx.node.id }
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
key: ActionKey.DELETE,
|
|
210
|
+
label: _t("action.delete", "删除"),
|
|
211
|
+
icon: IconDelete,
|
|
212
|
+
type: "button",
|
|
213
|
+
order: 400,
|
|
214
|
+
risk: "destructive",
|
|
215
|
+
metadata: { commandType: CommandType.REMOVE_NODE },
|
|
216
|
+
className: "dc-node__toolbar-btn--delete",
|
|
217
|
+
available: (ctx) => resolveBehavior(ctx.meta?.deletable, toInstanceCtx(ctx)),
|
|
218
|
+
disabled: (ctx) => {
|
|
219
|
+
if (ctx.owner.kind === "root" && ctx.sortScope === false) return false;
|
|
220
|
+
const lockedIndices = getScopedLockedIndices(ctx);
|
|
221
|
+
if (lockedIndices.size === 0) return false;
|
|
222
|
+
return !isRemoveAllowed(ctx.index, lockedIndices);
|
|
223
|
+
},
|
|
224
|
+
command: (ctx) => ({
|
|
225
|
+
type: CommandType.REMOVE_NODE,
|
|
226
|
+
payload: { nodeId: ctx.node.id }
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
function createNodeActionRegistry(initialActions) {
|
|
232
|
+
const actions = /* @__PURE__ */ new Map();
|
|
233
|
+
const defaults = initialActions ?? createDefaultActions();
|
|
234
|
+
for (const action of defaults) actions.set(action.key, action);
|
|
235
|
+
return {
|
|
236
|
+
getActions() {
|
|
237
|
+
return Array.from(actions.values()).sort((a, b) => a.order - b.order);
|
|
238
|
+
},
|
|
239
|
+
register(action) {
|
|
240
|
+
actions.set(action.key, action);
|
|
241
|
+
},
|
|
242
|
+
unregister(key) {
|
|
243
|
+
actions.delete(key);
|
|
244
|
+
},
|
|
245
|
+
resolve(ctx, actionInterceptors = [], keys) {
|
|
246
|
+
const widgetActions = ctx.meta?.actions;
|
|
247
|
+
let actionDefs = this.getActions();
|
|
248
|
+
if (widgetActions) {
|
|
249
|
+
if (widgetActions.only) {
|
|
250
|
+
const allowedKeys = new Set(widgetActions.only);
|
|
251
|
+
actionDefs = actionDefs.filter((a) => allowedKeys.has(a.key));
|
|
252
|
+
}
|
|
253
|
+
if (widgetActions.exclude) {
|
|
254
|
+
const excludeKeys = new Set(widgetActions.exclude);
|
|
255
|
+
actionDefs = actionDefs.filter((a) => !excludeKeys.has(a.key));
|
|
256
|
+
}
|
|
257
|
+
if (widgetActions.extra) actionDefs = [...actionDefs, ...widgetActions.extra].sort((a, b) => a.order - b.order);
|
|
258
|
+
}
|
|
259
|
+
if (keys) {
|
|
260
|
+
const requestedKeys = new Set(keys);
|
|
261
|
+
actionDefs = actionDefs.filter((action) => requestedKeys.has(action.key));
|
|
262
|
+
}
|
|
263
|
+
return actionDefs.map((def) => {
|
|
264
|
+
if (!(def.visible ? def.visible(ctx) : true)) return null;
|
|
265
|
+
const disabled = !(def.available ? def.available(ctx) : true) || (def.disabled ? def.disabled(ctx) : false);
|
|
266
|
+
const pending = { value: false };
|
|
267
|
+
return {
|
|
268
|
+
key: def.key,
|
|
269
|
+
label: def.label,
|
|
270
|
+
icon: def.icon,
|
|
271
|
+
type: def.type,
|
|
272
|
+
order: def.order,
|
|
273
|
+
risk: def.risk ?? "normal",
|
|
274
|
+
metadata: def.metadata,
|
|
275
|
+
visible: true,
|
|
276
|
+
disabled,
|
|
277
|
+
handler: (event) => {
|
|
278
|
+
if (pending.value) return;
|
|
279
|
+
event.stopPropagation();
|
|
280
|
+
const command = def.command?.(ctx, event) ?? void 0;
|
|
281
|
+
const execute = () => {
|
|
282
|
+
if (command) ctx.engine.execute(command);
|
|
283
|
+
return def.handler?.(ctx, event);
|
|
284
|
+
};
|
|
285
|
+
return runActionPipeline({
|
|
286
|
+
key: def.key,
|
|
287
|
+
label: def.label,
|
|
288
|
+
ctx,
|
|
289
|
+
event,
|
|
290
|
+
risk: def.risk ?? "normal",
|
|
291
|
+
command,
|
|
292
|
+
metadata: def.metadata
|
|
293
|
+
}, execute, actionInterceptors, pending);
|
|
294
|
+
},
|
|
295
|
+
className: def.className
|
|
296
|
+
};
|
|
297
|
+
}).filter((a) => a !== null);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/container-runtime.ts
|
|
303
|
+
function deepFreeze(value) {
|
|
304
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
305
|
+
Object.freeze(value);
|
|
306
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
function toReadonlySnapshot(value) {
|
|
311
|
+
return Object.isFrozen(value) ? value : deepFreeze(cloneDeep(value));
|
|
312
|
+
}
|
|
313
|
+
const CONTAINER_RUNTIME_CONTEXT_KEY = Symbol("dc-container-runtime");
|
|
314
|
+
function createContainerRuntime(getNode, ctx) {
|
|
315
|
+
const resolveNode = () => {
|
|
316
|
+
ctx.engine.store.schema.value;
|
|
317
|
+
return getNode();
|
|
318
|
+
};
|
|
319
|
+
const plan = computed(() => createContainerPlan(resolveNode(), ctx.engine.registry));
|
|
320
|
+
return {
|
|
321
|
+
nodeId: computed(() => resolveNode().id),
|
|
322
|
+
variant: computed(() => resolveNode().container?.variant ?? ""),
|
|
323
|
+
regionDefinitions: computed(() => deepFreeze(plan.value.ok ? plan.value.plan.variant.regions.map((region) => ({
|
|
324
|
+
...region,
|
|
325
|
+
constraints: region.constraints ? {
|
|
326
|
+
...region.constraints,
|
|
327
|
+
includeTypes: region.constraints.includeTypes ? [...region.constraints.includeTypes] : void 0,
|
|
328
|
+
excludeTypes: region.constraints.excludeTypes ? [...region.constraints.excludeTypes] : void 0
|
|
329
|
+
} : void 0
|
|
330
|
+
})) : [])),
|
|
331
|
+
getRegionNodes: (regionId) => toReadonlySnapshot(resolveNode().container?.regions[regionId] ?? []),
|
|
332
|
+
requestVariantChange: (variant) => ctx.engine.execute({
|
|
333
|
+
type: CommandType.CHANGE_CONTAINER_VARIANT,
|
|
334
|
+
payload: {
|
|
335
|
+
containerId: getNode().id,
|
|
336
|
+
variant
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function useContainerRuntime() {
|
|
342
|
+
const runtime = inject(CONTAINER_RUNTIME_CONTEXT_KEY);
|
|
343
|
+
if (!runtime) throw new Error("[dragcraft/renderer] ContainerRuntime not found. ContainerRegionOutlet must be rendered inside a resolved container material.");
|
|
344
|
+
return runtime;
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/event-hooks.ts
|
|
348
|
+
/**
|
|
349
|
+
* Returns empty event hooks (no-op). Used as the default.
|
|
350
|
+
*/
|
|
351
|
+
function createDefaultEventHooks() {
|
|
352
|
+
return {};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Checks whether a value is a thenable (Promise-like).
|
|
356
|
+
*/
|
|
357
|
+
function isPromiseLike(value) {
|
|
358
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Resolves a before-hook result that may be sync or async.
|
|
362
|
+
* Returns `true` if the action should proceed, `false` if cancelled.
|
|
363
|
+
*
|
|
364
|
+
* Error handling: if the hook throws or the promise rejects,
|
|
365
|
+
* the action is CANCELLED (returns false) and the error is logged.
|
|
366
|
+
* Rationale: a gating hook that crashes should fail closed, not open.
|
|
367
|
+
*/
|
|
368
|
+
async function resolveBeforeHook(result) {
|
|
369
|
+
try {
|
|
370
|
+
return await result !== false;
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.error("[dragcraft] Before-hook error (action cancelled):", err);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Runs an action through an optional before/after hook pair.
|
|
378
|
+
* Handles synchronous and asynchronous before-hooks consistently.
|
|
379
|
+
*/
|
|
380
|
+
function runBeforeAfterHook(beforeHook, payload, execute, afterHook, pendingGuard) {
|
|
381
|
+
if (!beforeHook) {
|
|
382
|
+
execute();
|
|
383
|
+
fireAfterHook(afterHook, payload);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
let result;
|
|
387
|
+
try {
|
|
388
|
+
result = beforeHook(payload);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
console.error("[dragcraft] Before-hook error (action cancelled):", err);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (!isPromiseLike(result)) {
|
|
394
|
+
if (result === false) return;
|
|
395
|
+
execute();
|
|
396
|
+
fireAfterHook(afterHook, payload);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (pendingGuard) pendingGuard.value = true;
|
|
400
|
+
return resolveBeforeHook(result).then((allowed) => {
|
|
401
|
+
if (allowed) {
|
|
402
|
+
execute();
|
|
403
|
+
fireAfterHook(afterHook, payload);
|
|
404
|
+
}
|
|
405
|
+
}).finally(() => {
|
|
406
|
+
if (pendingGuard) pendingGuard.value = false;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Fires an after-hook (fire-and-forget).
|
|
411
|
+
* If the hook returns a promise, any rejection is caught and logged.
|
|
412
|
+
* Errors never propagate to the caller.
|
|
413
|
+
*/
|
|
414
|
+
function fireAfterHook(hook, payload) {
|
|
415
|
+
if (!hook) return;
|
|
416
|
+
try {
|
|
417
|
+
const result = hook(payload);
|
|
418
|
+
if (isPromiseLike(result)) result.catch((err) => {
|
|
419
|
+
console.error("[dragcraft] Async after-hook error:", err);
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
console.error("[dragcraft] After-hook error:", err);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/types.ts
|
|
427
|
+
/**
|
|
428
|
+
* Injection key for the renderer context.
|
|
429
|
+
*/
|
|
430
|
+
const RENDERER_CONTEXT_KEY = Symbol("dc-renderer");
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/context.ts
|
|
433
|
+
/**
|
|
434
|
+
* Creates a RendererContext from options.
|
|
435
|
+
* Called internally by RootRenderer.
|
|
436
|
+
*/
|
|
437
|
+
function createRendererContext(options) {
|
|
438
|
+
const schema = computed(() => {
|
|
439
|
+
options.engine.store.schema.value;
|
|
440
|
+
return options.engine.state.getSchema();
|
|
441
|
+
});
|
|
442
|
+
const mutableSchema = () => schema.value;
|
|
443
|
+
const layoutPlan = computed(() => createLayoutPlan(mutableSchema(), options.engine.registry));
|
|
444
|
+
const schemaIndex = computed(() => buildSchemaIndex(mutableSchema()));
|
|
445
|
+
const rootActionPositions = computed(() => {
|
|
446
|
+
const positions = /* @__PURE__ */ new Map();
|
|
447
|
+
for (const [sortScope, entries] of layoutPlan.value.sortScopes) {
|
|
448
|
+
const lockedIndices = getLockedIndicesFromNodes(entries.map((entry) => entry.node), options.engine.registry, mutableSchema());
|
|
449
|
+
entries.forEach((entry, index) => positions.set(entry.node.id, {
|
|
450
|
+
index,
|
|
451
|
+
siblingCount: entries.length,
|
|
452
|
+
sortScope,
|
|
453
|
+
lockedIndices
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
for (const entry of layoutPlan.value.entries) if (!positions.has(entry.node.id)) positions.set(entry.node.id, {
|
|
457
|
+
index: entry.arrayIndex,
|
|
458
|
+
siblingCount: layoutPlan.value.entries.length,
|
|
459
|
+
sortScope: false,
|
|
460
|
+
lockedIndices: /* @__PURE__ */ new Set()
|
|
461
|
+
});
|
|
462
|
+
return positions;
|
|
463
|
+
});
|
|
464
|
+
const containerLockedIndices = computed(() => {
|
|
465
|
+
const result = /* @__PURE__ */ new Map();
|
|
466
|
+
for (const container of mutableSchema().root.children ?? []) {
|
|
467
|
+
if (!container.container) continue;
|
|
468
|
+
const regions = /* @__PURE__ */ new Map();
|
|
469
|
+
for (const [regionId, nodes] of Object.entries(container.container.regions)) regions.set(regionId, getLockedIndicesFromNodes(nodes, options.engine.registry, mutableSchema()));
|
|
470
|
+
result.set(container.id, regions);
|
|
471
|
+
}
|
|
472
|
+
return result;
|
|
473
|
+
});
|
|
474
|
+
function resolveNodeActionPosition(node, owner) {
|
|
475
|
+
if (owner.kind === "container") {
|
|
476
|
+
const siblings = (schemaIndex.value.index.get(owner.containerId)?.node)?.container?.regions[owner.regionId] ?? [];
|
|
477
|
+
return {
|
|
478
|
+
owner,
|
|
479
|
+
index: schemaIndex.value.index.get(node.id)?.index ?? -1,
|
|
480
|
+
siblingCount: siblings.length,
|
|
481
|
+
sortScope: false,
|
|
482
|
+
lockedIndices: containerLockedIndices.value.get(owner.containerId)?.get(owner.regionId) ?? /* @__PURE__ */ new Set()
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
const position = rootActionPositions.value.get(node.id);
|
|
486
|
+
return {
|
|
487
|
+
owner: {
|
|
488
|
+
kind: "root",
|
|
489
|
+
sortScope: position?.sortScope === false ? void 0 : position?.sortScope
|
|
490
|
+
},
|
|
491
|
+
index: position?.index ?? -1,
|
|
492
|
+
siblingCount: position?.siblingCount ?? 0,
|
|
493
|
+
sortScope: position?.sortScope ?? false,
|
|
494
|
+
lockedIndices: position?.lockedIndices ?? /* @__PURE__ */ new Set()
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
engine: options.engine,
|
|
499
|
+
schema,
|
|
500
|
+
layoutPlan,
|
|
501
|
+
schemaIndex,
|
|
502
|
+
resolveNodeActionPosition,
|
|
503
|
+
componentMap: options.componentMap,
|
|
504
|
+
extensions: options.extensions ?? {},
|
|
505
|
+
eventHooks: options.eventHooks ?? createDefaultEventHooks(),
|
|
506
|
+
actionInterceptors: options.actionInterceptors ?? [],
|
|
507
|
+
actionRegistry: options.actionRegistry ?? createNodeActionRegistry(),
|
|
508
|
+
dragOverNodeId: options.dragOverNodeId ?? ref(null),
|
|
509
|
+
activeDestination: options.activeDestination ?? ref(null),
|
|
510
|
+
containerDropDecision: options.containerDropDecision ?? ref(null),
|
|
511
|
+
onContainerDragOver: options.onContainerDragOver,
|
|
512
|
+
onContainerDragLeave: options.onContainerDragLeave,
|
|
513
|
+
onContainerDrop: options.onContainerDrop,
|
|
514
|
+
interactionBoundary: options.interactionBoundary
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Injects the RendererContext from the nearest ancestor RootRenderer.
|
|
519
|
+
* Throws if called outside the renderer component tree.
|
|
520
|
+
*/
|
|
521
|
+
function useRendererContext() {
|
|
522
|
+
const ctx = inject(RENDERER_CONTEXT_KEY);
|
|
523
|
+
if (!ctx) throw new Error("[dragcraft/renderer] RendererContext not found. Ensure this component is a descendant of RootRenderer.");
|
|
524
|
+
return ctx;
|
|
525
|
+
}
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/components/DefaultDropIndicator.ts
|
|
528
|
+
/**
|
|
529
|
+
* Sort-scope drop indicator shown during drag-over.
|
|
530
|
+
* Renders a dashed-border rectangle placeholder indicating where
|
|
531
|
+
* the widget will be placed. Styled via CSS class `dc-drop-indicator`.
|
|
532
|
+
*/
|
|
533
|
+
var DefaultDropIndicator_default = defineComponent({
|
|
534
|
+
name: "DcDefaultDropIndicator",
|
|
535
|
+
setup() {
|
|
536
|
+
return () => h("div", {
|
|
537
|
+
"class": "dc-drop-indicator",
|
|
538
|
+
"data-dc-component": "drop-indicator"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/components/DefaultEmptyState.ts
|
|
544
|
+
/**
|
|
545
|
+
* Default empty canvas state component.
|
|
546
|
+
* Displayed when the canvas has no widgets and no drag operation is active.
|
|
547
|
+
*/
|
|
548
|
+
var DefaultEmptyState_default = defineComponent({
|
|
549
|
+
name: "DcDefaultEmptyState",
|
|
550
|
+
props: { isDragOver: {
|
|
551
|
+
type: Boolean,
|
|
552
|
+
default: false
|
|
553
|
+
} },
|
|
554
|
+
setup(props) {
|
|
555
|
+
const { t } = useI18n();
|
|
556
|
+
return () => h("div", {
|
|
557
|
+
"class": ["dc-empty-state", { "dc-empty-state--drag-over": props.isDragOver }],
|
|
558
|
+
"data-dc-component": "empty-state",
|
|
559
|
+
"data-dc-state": props.isDragOver ? "drag-over" : void 0
|
|
560
|
+
}, [h("div", {
|
|
561
|
+
"class": "dc-empty-state__icon",
|
|
562
|
+
"data-dc-part": "icon"
|
|
563
|
+
}, h(IconPlus, { size: 32 })), h("div", {
|
|
564
|
+
"class": "dc-empty-state__text",
|
|
565
|
+
"data-dc-part": "text"
|
|
566
|
+
}, t("canvas.empty", "拖拽组件到这里"))]);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/components/DefaultForbiddenOverlay.ts
|
|
571
|
+
/**
|
|
572
|
+
* Default forbidden overlay shown when a widget type cannot be dropped.
|
|
573
|
+
* Renders a red dashed drop zone with the blocked reason in the center.
|
|
574
|
+
*/
|
|
575
|
+
var DefaultForbiddenOverlay_default = defineComponent({
|
|
576
|
+
name: "DcDefaultForbiddenOverlay",
|
|
577
|
+
props: {
|
|
578
|
+
widgetType: {
|
|
579
|
+
type: String,
|
|
580
|
+
required: true
|
|
581
|
+
},
|
|
582
|
+
reason: {
|
|
583
|
+
type: Object,
|
|
584
|
+
default: null
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
setup(props) {
|
|
588
|
+
const { t } = useI18n();
|
|
589
|
+
const getMessage = () => {
|
|
590
|
+
const fallback = props.reason?.message ?? t("forbidden.default", "当前物料不满足创建条件,无法添加到画布");
|
|
591
|
+
return props.reason?.messageKey ? t(props.reason.messageKey, fallback) : fallback;
|
|
592
|
+
};
|
|
593
|
+
return () => h("div", {
|
|
594
|
+
"class": "dc-forbidden-overlay",
|
|
595
|
+
"data-dc-component": "forbidden-overlay"
|
|
596
|
+
}, [h("span", {
|
|
597
|
+
"class": "dc-forbidden-overlay__text",
|
|
598
|
+
"data-dc-part": "text"
|
|
599
|
+
}, getMessage())]);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/composables/useNodeActions.ts
|
|
604
|
+
function resolveUncachedPosition(node, owner, ctx) {
|
|
605
|
+
const schema = ctx.schema.value;
|
|
606
|
+
if (owner.kind === "container") {
|
|
607
|
+
const siblings = (buildSchemaIndex(schema).index.get(owner.containerId)?.node)?.container?.regions[owner.regionId] ?? [];
|
|
608
|
+
return {
|
|
609
|
+
owner,
|
|
610
|
+
index: siblings.findIndex((item) => item.id === node.id),
|
|
611
|
+
siblingCount: siblings.length,
|
|
612
|
+
sortScope: false,
|
|
613
|
+
lockedIndices: getLockedIndicesFromNodes(siblings, ctx.engine.registry, schema)
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const layout = resolveNodeLayout(node, ctx.engine.registry, schema);
|
|
617
|
+
const entries = layout.sortScope === false ? [] : getSortScopeEntries(createLayoutPlan(schema, ctx.engine.registry), layout.sortScope);
|
|
618
|
+
return {
|
|
619
|
+
owner: {
|
|
620
|
+
kind: "root",
|
|
621
|
+
sortScope: layout.sortScope === false ? void 0 : layout.sortScope
|
|
622
|
+
},
|
|
623
|
+
index: entries.findIndex((entry) => entry.node.id === node.id),
|
|
624
|
+
siblingCount: entries.length,
|
|
625
|
+
sortScope: layout.sortScope,
|
|
626
|
+
lockedIndices: layout.sortScope === false ? /* @__PURE__ */ new Set() : getLockedIndices(schema.root.children ?? [], ctx.engine.registry, schema, layout.sortScope)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Composable that resolves the action system for a specific node.
|
|
631
|
+
* Provides the list of visible, resolved actions with their handlers.
|
|
632
|
+
*
|
|
633
|
+
* @param getNode - Getter for the current schema node
|
|
634
|
+
* @param ctx - The renderer context
|
|
635
|
+
*/
|
|
636
|
+
function useNodeActions(getNode, ctx, getOwner = () => ({ kind: "root" })) {
|
|
637
|
+
const { engine, actionRegistry, actionInterceptors } = ctx;
|
|
638
|
+
const actionContext = computed(() => {
|
|
639
|
+
const node = getNode();
|
|
640
|
+
const schema = ctx.schema.value;
|
|
641
|
+
const owner = getOwner();
|
|
642
|
+
const meta = engine.registry.getWidget(node.type);
|
|
643
|
+
return {
|
|
644
|
+
node,
|
|
645
|
+
...ctx.resolveNodeActionPosition?.(node, owner) ?? resolveUncachedPosition(node, owner, ctx),
|
|
646
|
+
meta,
|
|
647
|
+
engine,
|
|
648
|
+
schema
|
|
649
|
+
};
|
|
650
|
+
});
|
|
651
|
+
return {
|
|
652
|
+
actions: computed(() => {
|
|
653
|
+
return actionRegistry.resolve(actionContext.value, actionInterceptors);
|
|
654
|
+
}),
|
|
655
|
+
actionContext
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/drag-image.ts
|
|
660
|
+
let transparentDragImageEl = null;
|
|
661
|
+
function getTransparentDragImageElement() {
|
|
662
|
+
if (typeof document === "undefined") return null;
|
|
663
|
+
if (transparentDragImageEl && document.body.contains(transparentDragImageEl)) return transparentDragImageEl;
|
|
664
|
+
const element = document.createElement("div");
|
|
665
|
+
element.setAttribute("data-dc-transparent-drag-image", "");
|
|
666
|
+
element.style.position = "fixed";
|
|
667
|
+
element.style.left = "-10000px";
|
|
668
|
+
element.style.top = "-10000px";
|
|
669
|
+
element.style.width = "1px";
|
|
670
|
+
element.style.height = "1px";
|
|
671
|
+
element.style.opacity = "0";
|
|
672
|
+
element.style.pointerEvents = "none";
|
|
673
|
+
document.body.appendChild(element);
|
|
674
|
+
transparentDragImageEl = element;
|
|
675
|
+
return element;
|
|
676
|
+
}
|
|
677
|
+
function hideNativeDragImage(dataTransfer) {
|
|
678
|
+
if (!dataTransfer?.setDragImage) return;
|
|
679
|
+
const element = getTransparentDragImageElement();
|
|
680
|
+
if (element) dataTransfer.setDragImage(element, 0, 0);
|
|
681
|
+
}
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/composables/useNodeDrag.ts
|
|
684
|
+
/**
|
|
685
|
+
* Composable that encapsulates drag handle behavior for a widget node.
|
|
686
|
+
* Integrates with event hooks for interceptable drag operations.
|
|
687
|
+
*
|
|
688
|
+
* @param getNode - Getter for the current schema node
|
|
689
|
+
* @param ctx - The renderer context (from useRendererContext)
|
|
690
|
+
*/
|
|
691
|
+
function useNodeDrag(getNode, ctx) {
|
|
692
|
+
const { engine, eventHooks } = ctx;
|
|
693
|
+
const handleDragStart = (e) => {
|
|
694
|
+
e.stopPropagation();
|
|
695
|
+
const nodeId = getNode().id;
|
|
696
|
+
if (eventHooks.onBeforeDrag) {
|
|
697
|
+
if (eventHooks.onBeforeDrag({
|
|
698
|
+
nodeId,
|
|
699
|
+
event: e
|
|
700
|
+
}) === false) {
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
engine.store.setDragTarget({
|
|
706
|
+
sourceNodeId: nodeId,
|
|
707
|
+
widgetType: null
|
|
708
|
+
});
|
|
709
|
+
if (e.dataTransfer) {
|
|
710
|
+
e.dataTransfer.effectAllowed = "move";
|
|
711
|
+
e.dataTransfer.setData("text/plain", nodeId);
|
|
712
|
+
hideNativeDragImage(e.dataTransfer);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
const handleDragEnd = (e) => {
|
|
716
|
+
e.stopPropagation();
|
|
717
|
+
engine.store.setDragTarget(null);
|
|
718
|
+
fireAfterHook(eventHooks.onAfterDrag, {
|
|
719
|
+
nodeId: getNode().id,
|
|
720
|
+
event: e
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
return {
|
|
724
|
+
handleDragStart,
|
|
725
|
+
handleDragEnd
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/composables/useNodeInteractionGeometry.ts
|
|
730
|
+
const CLIP_OVERFLOW_RE = /auto|scroll|hidden|clip|overlay/;
|
|
731
|
+
const EMPTY_RECT = {
|
|
732
|
+
top: 0,
|
|
733
|
+
right: 0,
|
|
734
|
+
bottom: 0,
|
|
735
|
+
left: 0,
|
|
736
|
+
width: 0,
|
|
737
|
+
height: 0
|
|
738
|
+
};
|
|
739
|
+
function makeRect(top, right, bottom, left) {
|
|
740
|
+
return {
|
|
741
|
+
top,
|
|
742
|
+
right,
|
|
743
|
+
bottom,
|
|
744
|
+
left,
|
|
745
|
+
width: Math.max(0, right - left),
|
|
746
|
+
height: Math.max(0, bottom - top)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function getEffectiveClipRect(el) {
|
|
750
|
+
let top = 0;
|
|
751
|
+
let bottom = window.innerHeight;
|
|
752
|
+
let left = 0;
|
|
753
|
+
let right = window.innerWidth;
|
|
754
|
+
let current = el.parentElement;
|
|
755
|
+
while (current) {
|
|
756
|
+
const style = getComputedStyle(current);
|
|
757
|
+
const clipsY = CLIP_OVERFLOW_RE.test(style.overflowY);
|
|
758
|
+
const clipsX = CLIP_OVERFLOW_RE.test(style.overflowX);
|
|
759
|
+
if (clipsX || clipsY) {
|
|
760
|
+
const rect = current.getBoundingClientRect();
|
|
761
|
+
if (clipsY) {
|
|
762
|
+
top = Math.max(top, rect.top);
|
|
763
|
+
bottom = Math.min(bottom, rect.bottom);
|
|
764
|
+
}
|
|
765
|
+
if (clipsX) {
|
|
766
|
+
left = Math.max(left, rect.left);
|
|
767
|
+
right = Math.min(right, rect.right);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
current = current.parentElement;
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
top,
|
|
774
|
+
right,
|
|
775
|
+
bottom,
|
|
776
|
+
left
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function resolveTargetElement$1(hostEl, selfTargetSelector) {
|
|
780
|
+
if (hostEl.dataset.dcLayerMode !== "self" || !selfTargetSelector) return hostEl;
|
|
781
|
+
const candidate = hostEl.querySelector(selfTargetSelector);
|
|
782
|
+
if (!candidate) return hostEl;
|
|
783
|
+
const rect = candidate.getBoundingClientRect();
|
|
784
|
+
return rect.width > 0 && rect.height > 0 ? candidate : hostEl;
|
|
785
|
+
}
|
|
786
|
+
function resolveBoundaryElement(hostEl, targetEl, boundarySelector) {
|
|
787
|
+
if (!boundarySelector) return null;
|
|
788
|
+
return hostEl.closest(boundarySelector) ?? targetEl.closest(boundarySelector);
|
|
789
|
+
}
|
|
790
|
+
function resolvePaintInset(size, requestedInset) {
|
|
791
|
+
return Math.min(requestedInset, Math.max(0, (size - 1) / 2));
|
|
792
|
+
}
|
|
793
|
+
function insetRect(rect, requestedInset) {
|
|
794
|
+
const inlineInset = resolvePaintInset(rect.width, requestedInset);
|
|
795
|
+
const blockInset = resolvePaintInset(rect.height, requestedInset);
|
|
796
|
+
return makeRect(rect.top + blockInset, rect.right - inlineInset, rect.bottom - blockInset, rect.left + inlineInset);
|
|
797
|
+
}
|
|
798
|
+
function isSameRect(left, right) {
|
|
799
|
+
return left.top === right.top && left.right === right.right && left.bottom === right.bottom && left.left === right.left;
|
|
800
|
+
}
|
|
801
|
+
function useNodeInteractionGeometry(elRef, isActive, options) {
|
|
802
|
+
const geometry = ref({
|
|
803
|
+
visibleRect: EMPTY_RECT,
|
|
804
|
+
paintRect: EMPTY_RECT,
|
|
805
|
+
visible: false
|
|
806
|
+
});
|
|
807
|
+
let cleanupAutoUpdate = null;
|
|
808
|
+
function applyGeometry(next) {
|
|
809
|
+
const previous = geometry.value;
|
|
810
|
+
if (previous.visible !== next.visible || !isSameRect(previous.visibleRect, next.visibleRect) || !isSameRect(previous.paintRect, next.paintRect)) geometry.value = next;
|
|
811
|
+
}
|
|
812
|
+
function hide() {
|
|
813
|
+
if (geometry.value.visible) applyGeometry({
|
|
814
|
+
...geometry.value,
|
|
815
|
+
visible: false
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
function update() {
|
|
819
|
+
const hostEl = elRef.value;
|
|
820
|
+
if (!hostEl || !isActive.value) {
|
|
821
|
+
hide();
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const targetEl = resolveTargetElement$1(hostEl, options.selfTargetSelector);
|
|
825
|
+
const targetRect = targetEl.getBoundingClientRect();
|
|
826
|
+
if (targetRect.width <= 0 || targetRect.height <= 0) {
|
|
827
|
+
hide();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const boundaryRect = resolveBoundaryElement(hostEl, targetEl, options.boundarySelector)?.getBoundingClientRect();
|
|
831
|
+
const clip = getEffectiveClipRect(targetEl);
|
|
832
|
+
const clipTop = Math.max(clip.top, boundaryRect?.top ?? -Infinity);
|
|
833
|
+
const clipRight = Math.min(clip.right, boundaryRect?.right ?? Infinity);
|
|
834
|
+
const clipBottom = Math.min(clip.bottom, boundaryRect?.bottom ?? Infinity);
|
|
835
|
+
const clipLeft = Math.max(clip.left, boundaryRect?.left ?? -Infinity);
|
|
836
|
+
const visibleTop = Math.max(targetRect.top, clipTop);
|
|
837
|
+
const visibleBottom = Math.min(targetRect.bottom, clipBottom);
|
|
838
|
+
const targetVisibleLeft = Math.max(targetRect.left, clipLeft);
|
|
839
|
+
const targetVisibleRight = Math.min(targetRect.right, clipRight);
|
|
840
|
+
const visibleLeft = options.mode === "root-band" && boundaryRect ? Math.max(boundaryRect.left, clipLeft) : targetVisibleLeft;
|
|
841
|
+
const visibleRect = makeRect(visibleTop, options.mode === "root-band" && boundaryRect ? Math.min(boundaryRect.right, clipRight) : targetVisibleRight, visibleBottom, visibleLeft);
|
|
842
|
+
if (!(visibleBottom > visibleTop && targetVisibleRight > targetVisibleLeft && visibleRect.width > 0 && visibleRect.height > 0)) {
|
|
843
|
+
hide();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
applyGeometry({
|
|
847
|
+
visibleRect,
|
|
848
|
+
paintRect: insetRect(visibleRect, Math.max(0, options.paintInset ?? 0)),
|
|
849
|
+
visible: true
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
function stopAutoUpdate() {
|
|
853
|
+
cleanupAutoUpdate?.();
|
|
854
|
+
cleanupAutoUpdate = null;
|
|
855
|
+
}
|
|
856
|
+
function startAutoUpdate() {
|
|
857
|
+
stopAutoUpdate();
|
|
858
|
+
const host = elRef.value;
|
|
859
|
+
if (!host || !isActive.value) return;
|
|
860
|
+
cleanupAutoUpdate = autoUpdate(resolveTargetElement$1(host, options.selfTargetSelector), host, update, {
|
|
861
|
+
ancestorScroll: true,
|
|
862
|
+
ancestorResize: true,
|
|
863
|
+
elementResize: true,
|
|
864
|
+
layoutShift: true,
|
|
865
|
+
animationFrame: false
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
watch([elRef, isActive], ([newEl, active]) => {
|
|
869
|
+
stopAutoUpdate();
|
|
870
|
+
if (newEl && active) nextTick(startAutoUpdate);
|
|
871
|
+
else hide();
|
|
872
|
+
}, {
|
|
873
|
+
immediate: true,
|
|
874
|
+
flush: "post"
|
|
875
|
+
});
|
|
876
|
+
onBeforeUnmount(stopAutoUpdate);
|
|
877
|
+
return {
|
|
878
|
+
geometry,
|
|
879
|
+
update
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
//#endregion
|
|
883
|
+
//#region src/selection-presentation.ts
|
|
884
|
+
const NODE_SELECTION_PRESENTATION_KEY = Symbol("dc-node-selection-presentation");
|
|
885
|
+
const NODE_SELECTION_PLANE_KEY = Symbol("dc-node-selection-plane");
|
|
886
|
+
const DETACHED_PLANE = computed(() => null);
|
|
887
|
+
const DETACHED_PRESENTATION = {
|
|
888
|
+
registerPlane: () => {},
|
|
889
|
+
registerFallback: () => {},
|
|
890
|
+
getPlane: () => DETACHED_PLANE
|
|
891
|
+
};
|
|
892
|
+
function createNodeSelectionPresentation() {
|
|
893
|
+
const planes = {
|
|
894
|
+
root: ref(null),
|
|
895
|
+
content: ref(null),
|
|
896
|
+
viewport: ref(null)
|
|
897
|
+
};
|
|
898
|
+
const fallback = ref(null);
|
|
899
|
+
const resolvedPlanes = {
|
|
900
|
+
root: computed(() => planes.root.value ?? fallback.value),
|
|
901
|
+
content: computed(() => planes.content.value ?? fallback.value),
|
|
902
|
+
viewport: computed(() => planes.viewport.value ?? fallback.value)
|
|
903
|
+
};
|
|
904
|
+
return {
|
|
905
|
+
registerPlane(plane, element) {
|
|
906
|
+
planes[plane].value = element;
|
|
907
|
+
},
|
|
908
|
+
registerFallback(element) {
|
|
909
|
+
fallback.value = element;
|
|
910
|
+
},
|
|
911
|
+
getPlane(plane) {
|
|
912
|
+
return resolvedPlanes[plane];
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function useNodeSelectionPresentation() {
|
|
917
|
+
return inject(NODE_SELECTION_PRESENTATION_KEY, DETACHED_PRESENTATION);
|
|
918
|
+
}
|
|
919
|
+
//#endregion
|
|
920
|
+
//#region src/composables/useNodeSelectionProjection.ts
|
|
921
|
+
function resolveTargetElement(host, selfTargetSelector) {
|
|
922
|
+
if (host.dataset.dcLayerMode !== "self" || !selfTargetSelector) return host;
|
|
923
|
+
const candidate = host.querySelector(selfTargetSelector);
|
|
924
|
+
if (!candidate) return host;
|
|
925
|
+
const rect = candidate.getBoundingClientRect();
|
|
926
|
+
return rect.width > 0 && rect.height > 0 ? candidate : host;
|
|
927
|
+
}
|
|
928
|
+
function useNodeSelectionProjection(elRef, isSelected, options) {
|
|
929
|
+
const presentation = useNodeSelectionPresentation();
|
|
930
|
+
const projection = ref(null);
|
|
931
|
+
const target = computed(() => presentation.getPlane(options.plane.value).value);
|
|
932
|
+
let cleanupAutoUpdate = null;
|
|
933
|
+
function update() {
|
|
934
|
+
const host = elRef.value;
|
|
935
|
+
const plane = target.value;
|
|
936
|
+
if (!host || !plane || !isSelected.value) {
|
|
937
|
+
projection.value = null;
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const targetRect = resolveTargetElement(host, options.selfTargetSelector).getBoundingClientRect();
|
|
941
|
+
const planeRect = plane.getBoundingClientRect();
|
|
942
|
+
if (targetRect.width <= 0 || targetRect.height <= 0 || planeRect.width <= 0) {
|
|
943
|
+
projection.value = null;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const materialBounds = {
|
|
947
|
+
top: targetRect.top - planeRect.top,
|
|
948
|
+
left: targetRect.left - planeRect.left,
|
|
949
|
+
width: targetRect.width,
|
|
950
|
+
height: targetRect.height
|
|
951
|
+
};
|
|
952
|
+
const bounds = options.kind === "root-segment" ? {
|
|
953
|
+
top: materialBounds.top,
|
|
954
|
+
left: 0,
|
|
955
|
+
width: planeRect.width,
|
|
956
|
+
height: materialBounds.height
|
|
957
|
+
} : { ...materialBounds };
|
|
958
|
+
projection.value = {
|
|
959
|
+
kind: options.kind,
|
|
960
|
+
plane: options.plane.value,
|
|
961
|
+
materialBounds,
|
|
962
|
+
bounds
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function stopAutoUpdate() {
|
|
966
|
+
cleanupAutoUpdate?.();
|
|
967
|
+
cleanupAutoUpdate = null;
|
|
968
|
+
}
|
|
969
|
+
function startAutoUpdate() {
|
|
970
|
+
stopAutoUpdate();
|
|
971
|
+
const host = elRef.value;
|
|
972
|
+
const plane = target.value;
|
|
973
|
+
if (!host || !plane || !isSelected.value) {
|
|
974
|
+
projection.value = null;
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
cleanupAutoUpdate = autoUpdate(resolveTargetElement(host, options.selfTargetSelector), plane, update, {
|
|
978
|
+
ancestorScroll: options.plane.value === "root",
|
|
979
|
+
ancestorResize: true,
|
|
980
|
+
elementResize: true,
|
|
981
|
+
layoutShift: true,
|
|
982
|
+
animationFrame: false
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
watch([
|
|
986
|
+
elRef,
|
|
987
|
+
isSelected,
|
|
988
|
+
target
|
|
989
|
+
], () => {
|
|
990
|
+
if (!isSelected.value) {
|
|
991
|
+
stopAutoUpdate();
|
|
992
|
+
projection.value = null;
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
nextTick(startAutoUpdate);
|
|
996
|
+
}, {
|
|
997
|
+
immediate: true,
|
|
998
|
+
flush: "post"
|
|
999
|
+
});
|
|
1000
|
+
onBeforeUnmount(stopAutoUpdate);
|
|
1001
|
+
return {
|
|
1002
|
+
projection,
|
|
1003
|
+
target,
|
|
1004
|
+
update
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/composables/useToolbarPosition.ts
|
|
1009
|
+
function useToolbarPosition(referenceRef, floatingRef, isActive, options = {}) {
|
|
1010
|
+
const { gap = 8, interactionBoundary, targetSelector, selfTargetSelector, boundarySelector, padding = 8, interactionGeometry, interactionGeometryUpdate, placement = "left-start", orientation = "vertical" } = options;
|
|
1011
|
+
const position = ref({
|
|
1012
|
+
x: 0,
|
|
1013
|
+
y: 0,
|
|
1014
|
+
placement,
|
|
1015
|
+
orientation,
|
|
1016
|
+
strategy: "fixed",
|
|
1017
|
+
visible: false
|
|
1018
|
+
});
|
|
1019
|
+
let cleanupAutoUpdate = null;
|
|
1020
|
+
function resolveReference(host) {
|
|
1021
|
+
const selector = targetSelector ?? (host.dataset.dcLayerMode === "self" ? selfTargetSelector : void 0);
|
|
1022
|
+
if (!selector) return host;
|
|
1023
|
+
const candidate = host.querySelector(selector);
|
|
1024
|
+
if (!candidate) return host;
|
|
1025
|
+
const rect = candidate.getBoundingClientRect();
|
|
1026
|
+
return rect.width > 0 || rect.height > 0 ? candidate : host;
|
|
1027
|
+
}
|
|
1028
|
+
function isReferenceVisible(reference, boundary) {
|
|
1029
|
+
const rect = reference.getBoundingClientRect();
|
|
1030
|
+
const clip = boundary?.getBoundingClientRect() ?? {
|
|
1031
|
+
top: 0,
|
|
1032
|
+
right: window.innerWidth,
|
|
1033
|
+
bottom: window.innerHeight,
|
|
1034
|
+
left: 0
|
|
1035
|
+
};
|
|
1036
|
+
return rect.bottom > clip.top && rect.top < clip.bottom && rect.right > clip.left && rect.left < clip.right;
|
|
1037
|
+
}
|
|
1038
|
+
function resolveToolbarBoundary(host, reference) {
|
|
1039
|
+
if (!boundarySelector) return null;
|
|
1040
|
+
return host.closest(boundarySelector) ?? reference.closest(boundarySelector);
|
|
1041
|
+
}
|
|
1042
|
+
function applyPosition(next) {
|
|
1043
|
+
const current = position.value;
|
|
1044
|
+
if (current.x === next.x && current.y === next.y && current.placement === next.placement && current.orientation === next.orientation && current.strategy === next.strategy && current.visible === next.visible) return;
|
|
1045
|
+
position.value = next;
|
|
1046
|
+
}
|
|
1047
|
+
async function update() {
|
|
1048
|
+
const host = referenceRef.value;
|
|
1049
|
+
const floating = floatingRef.value;
|
|
1050
|
+
if (!host || !floating || !isActive.value) {
|
|
1051
|
+
applyPosition({
|
|
1052
|
+
...position.value,
|
|
1053
|
+
visible: false
|
|
1054
|
+
});
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const reference = resolveReference(host);
|
|
1058
|
+
interactionGeometryUpdate?.();
|
|
1059
|
+
const boundaryElement = interactionBoundary?.value ?? null;
|
|
1060
|
+
const interactionRect = interactionGeometry?.value.visibleRect;
|
|
1061
|
+
if (!(interactionGeometry ? interactionGeometry.value.visible : isReferenceVisible(reference, boundaryElement))) {
|
|
1062
|
+
applyPosition({
|
|
1063
|
+
...position.value,
|
|
1064
|
+
visible: false
|
|
1065
|
+
});
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const referenceRect = interactionRect ?? reference.getBoundingClientRect();
|
|
1069
|
+
const floatingRect = floating.getBoundingClientRect();
|
|
1070
|
+
const clipRect = boundaryElement?.getBoundingClientRect() ?? {
|
|
1071
|
+
top: 0,
|
|
1072
|
+
right: window.innerWidth,
|
|
1073
|
+
bottom: window.innerHeight,
|
|
1074
|
+
left: 0
|
|
1075
|
+
};
|
|
1076
|
+
const availableHeight = Math.max(0, clipRect.bottom - clipRect.top - padding * 2);
|
|
1077
|
+
const availableWidth = Math.max(0, clipRect.right - clipRect.left - padding * 2);
|
|
1078
|
+
const minY = clipRect.top + padding;
|
|
1079
|
+
const maxY = Math.max(minY, clipRect.bottom - padding - floatingRect.height);
|
|
1080
|
+
floating.style.maxHeight = `${availableHeight}px`;
|
|
1081
|
+
if (placement === "top-end") {
|
|
1082
|
+
floating.style.maxWidth = `${availableWidth}px`;
|
|
1083
|
+
const minX = clipRect.left + padding;
|
|
1084
|
+
const maxX = Math.max(minX, clipRect.right - padding - floatingRect.width);
|
|
1085
|
+
const topY = referenceRect.top - gap - floatingRect.height;
|
|
1086
|
+
const bottomY = referenceRect.bottom + gap;
|
|
1087
|
+
const resolvedPlacement = topY >= minY || !(bottomY <= maxY) ? "top-end" : "bottom-end";
|
|
1088
|
+
const requestedY = resolvedPlacement === "top-end" ? topY : bottomY;
|
|
1089
|
+
applyPosition({
|
|
1090
|
+
x: Math.min(Math.max(referenceRect.right - floatingRect.width, minX), maxX),
|
|
1091
|
+
y: Math.min(Math.max(requestedY, minY), maxY),
|
|
1092
|
+
placement: resolvedPlacement,
|
|
1093
|
+
orientation,
|
|
1094
|
+
strategy: "fixed",
|
|
1095
|
+
visible: true
|
|
1096
|
+
});
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
floating.style.maxWidth = "";
|
|
1100
|
+
applyPosition({
|
|
1101
|
+
x: (resolveToolbarBoundary(host, reference)?.getBoundingClientRect() ?? referenceRect).left - gap - floatingRect.width,
|
|
1102
|
+
y: Math.min(Math.max(referenceRect.top, minY), maxY),
|
|
1103
|
+
placement: "left-start",
|
|
1104
|
+
orientation,
|
|
1105
|
+
strategy: "fixed",
|
|
1106
|
+
visible: true
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
function stopAutoUpdate() {
|
|
1110
|
+
cleanupAutoUpdate?.();
|
|
1111
|
+
cleanupAutoUpdate = null;
|
|
1112
|
+
}
|
|
1113
|
+
function startAutoUpdate() {
|
|
1114
|
+
stopAutoUpdate();
|
|
1115
|
+
const host = referenceRef.value;
|
|
1116
|
+
const floating = floatingRef.value;
|
|
1117
|
+
if (!host || !floating || !isActive.value) return;
|
|
1118
|
+
cleanupAutoUpdate = autoUpdate(resolveReference(host), floating, update, {
|
|
1119
|
+
animationFrame: false,
|
|
1120
|
+
ancestorResize: true,
|
|
1121
|
+
ancestorScroll: true,
|
|
1122
|
+
elementResize: true,
|
|
1123
|
+
layoutShift: true
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
watch([
|
|
1127
|
+
referenceRef,
|
|
1128
|
+
floatingRef,
|
|
1129
|
+
isActive
|
|
1130
|
+
], () => {
|
|
1131
|
+
if (isActive.value) startAutoUpdate();
|
|
1132
|
+
else {
|
|
1133
|
+
stopAutoUpdate();
|
|
1134
|
+
applyPosition({
|
|
1135
|
+
...position.value,
|
|
1136
|
+
visible: false
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}, {
|
|
1140
|
+
immediate: true,
|
|
1141
|
+
flush: "post"
|
|
1142
|
+
});
|
|
1143
|
+
onBeforeUnmount(stopAutoUpdate);
|
|
1144
|
+
return {
|
|
1145
|
+
position,
|
|
1146
|
+
update
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/composables/useNodeState.ts
|
|
1151
|
+
/**
|
|
1152
|
+
* Computes reactive interaction state for a single schema node.
|
|
1153
|
+
*
|
|
1154
|
+
* @param getNodeId - Getter function returning the node ID (for reactivity safety)
|
|
1155
|
+
* @param ctx - The renderer context (from useRendererContext)
|
|
1156
|
+
*/
|
|
1157
|
+
function useNodeState(getNodeId, ctx) {
|
|
1158
|
+
const { engine, dragOverNodeId } = ctx;
|
|
1159
|
+
const isSelected = computed(() => engine.store.selectedNodeId.value === getNodeId());
|
|
1160
|
+
const isHovered = computed(() => engine.store.hoveredNodeId.value === getNodeId());
|
|
1161
|
+
const isDragOver = computed(() => dragOverNodeId.value === getNodeId());
|
|
1162
|
+
return {
|
|
1163
|
+
isSelected,
|
|
1164
|
+
isHovered,
|
|
1165
|
+
isDragOver,
|
|
1166
|
+
interactionClasses: computed(() => ({
|
|
1167
|
+
"dc-node--selected": isSelected.value,
|
|
1168
|
+
"dc-node--hovered": isHovered.value,
|
|
1169
|
+
"dc-node--drag-over": isDragOver.value
|
|
1170
|
+
}))
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
//#endregion
|
|
1174
|
+
//#region src/composables/useWidgetNode.ts
|
|
1175
|
+
/**
|
|
1176
|
+
* Composable that extracts all widget node state and event handling logic.
|
|
1177
|
+
* This is the primary composable for building custom node renderers.
|
|
1178
|
+
*
|
|
1179
|
+
* @param getNode - Getter for the current schema node
|
|
1180
|
+
* @param ctx - The renderer context (from useRendererContext)
|
|
1181
|
+
*/
|
|
1182
|
+
function useWidgetNode(getNode, ctx) {
|
|
1183
|
+
const { engine, componentMap, eventHooks } = ctx;
|
|
1184
|
+
const state = useNodeState(() => getNode().id, ctx);
|
|
1185
|
+
const meta = computed(() => engine.registry.getWidget(getNode().type));
|
|
1186
|
+
const resolvedComponent = computed(() => componentMap[getNode().type]);
|
|
1187
|
+
const layout = computed(() => resolveNodeLayout(getNode(), engine.registry, ctx.schema.value));
|
|
1188
|
+
const inSortScope = computed(() => layout.value.sortScope !== false);
|
|
1189
|
+
const isDragging = computed(() => engine.store.dragTarget.value?.sourceNodeId === getNode().id);
|
|
1190
|
+
const visible = computed(() => layout.value.visible);
|
|
1191
|
+
function readInstanceCtx() {
|
|
1192
|
+
return {
|
|
1193
|
+
node: getNode(),
|
|
1194
|
+
schema: ctx.schema.value
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function resolveMetaBehavior(field) {
|
|
1198
|
+
if (typeof field !== "function") return field !== false;
|
|
1199
|
+
return field(readInstanceCtx());
|
|
1200
|
+
}
|
|
1201
|
+
const useMask = computed(() => resolveMetaBehavior(meta.value?.mask));
|
|
1202
|
+
const selectable = computed(() => resolveMetaBehavior(meta.value?.selectable));
|
|
1203
|
+
const sortable = computed(() => resolveMetaBehavior(meta.value?.sortable));
|
|
1204
|
+
const draggable = computed(() => {
|
|
1205
|
+
if (!inSortScope.value || !sortable.value) return false;
|
|
1206
|
+
return resolveMetaBehavior(meta.value?.draggable);
|
|
1207
|
+
});
|
|
1208
|
+
const wrapperClasses = computed(() => [
|
|
1209
|
+
"dc-node",
|
|
1210
|
+
"dc-node--widget",
|
|
1211
|
+
{
|
|
1212
|
+
"dc-node--masked": useMask.value,
|
|
1213
|
+
"dc-node--unmasked": !useMask.value,
|
|
1214
|
+
"dc-node--non-selectable": !selectable.value,
|
|
1215
|
+
"dc-node--locked": inSortScope.value && !sortable.value,
|
|
1216
|
+
"dc-node--unsorted": !inSortScope.value,
|
|
1217
|
+
"dc-node--dragging": isDragging.value,
|
|
1218
|
+
"dc-node--hidden": !visible.value
|
|
1219
|
+
},
|
|
1220
|
+
state.interactionClasses.value
|
|
1221
|
+
]);
|
|
1222
|
+
const selectPending = { value: false };
|
|
1223
|
+
const handleSelect = (e) => {
|
|
1224
|
+
if (!selectable.value || selectPending.value) return;
|
|
1225
|
+
e.stopPropagation();
|
|
1226
|
+
const nodeId = getNode().id;
|
|
1227
|
+
runBeforeAfterHook(eventHooks.onBeforeSelect, {
|
|
1228
|
+
nodeId,
|
|
1229
|
+
event: e
|
|
1230
|
+
}, () => engine.store.selectNode(nodeId), eventHooks.onAfterSelect ? () => eventHooks.onAfterSelect?.({ nodeId }) : void 0, selectPending);
|
|
1231
|
+
};
|
|
1232
|
+
const handleMouseEnter = () => {
|
|
1233
|
+
const nodeId = getNode().id;
|
|
1234
|
+
if (engine.store.hoveredNodeId.value === nodeId) return;
|
|
1235
|
+
engine.store.hoverNode(nodeId);
|
|
1236
|
+
eventHooks.onHoverChange?.({ nodeId });
|
|
1237
|
+
};
|
|
1238
|
+
const handleMouseLeave = () => {
|
|
1239
|
+
if (engine.store.hoveredNodeId.value !== getNode().id) return;
|
|
1240
|
+
engine.store.hoverNode(null);
|
|
1241
|
+
eventHooks.onHoverChange?.({ nodeId: null });
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
state,
|
|
1245
|
+
resolvedComponent,
|
|
1246
|
+
meta,
|
|
1247
|
+
useMask,
|
|
1248
|
+
selectable,
|
|
1249
|
+
draggable,
|
|
1250
|
+
sortable,
|
|
1251
|
+
inSortScope,
|
|
1252
|
+
isDragging,
|
|
1253
|
+
visible,
|
|
1254
|
+
layout,
|
|
1255
|
+
wrapperClasses,
|
|
1256
|
+
handleSelect,
|
|
1257
|
+
handleMouseEnter,
|
|
1258
|
+
handleMouseLeave
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
//#endregion
|
|
1262
|
+
//#region src/node-interaction.ts
|
|
1263
|
+
function resolveNodeInteractionPresentation(owner) {
|
|
1264
|
+
return owner.kind === "container" ? {
|
|
1265
|
+
geometryMode: "node-box",
|
|
1266
|
+
selectionKind: "material-bounds",
|
|
1267
|
+
toolbarPlacement: "top-end",
|
|
1268
|
+
toolbarOrientation: "horizontal"
|
|
1269
|
+
} : {
|
|
1270
|
+
geometryMode: "root-band",
|
|
1271
|
+
selectionKind: "root-segment",
|
|
1272
|
+
toolbarPlacement: "left-start",
|
|
1273
|
+
toolbarOrientation: "vertical"
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
//#endregion
|
|
1277
|
+
//#region src/widget-runtime.ts
|
|
1278
|
+
const WIDGET_RUNTIME_CONTEXT_KEY = Symbol("dc-widget-runtime");
|
|
1279
|
+
function createWidgetRuntimeContext(getNode) {
|
|
1280
|
+
const ctx = useRendererContext();
|
|
1281
|
+
const nodeId = computed(() => getNode().id);
|
|
1282
|
+
const nodeType = computed(() => getNode().type);
|
|
1283
|
+
function updateProps(patch) {
|
|
1284
|
+
ctx.engine.execute({
|
|
1285
|
+
type: CommandType.UPDATE_PROPS,
|
|
1286
|
+
payload: {
|
|
1287
|
+
nodeId: nodeId.value,
|
|
1288
|
+
props: patch
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
function updateStyle(patch) {
|
|
1293
|
+
ctx.engine.execute({
|
|
1294
|
+
type: CommandType.UPDATE_PROPS,
|
|
1295
|
+
payload: {
|
|
1296
|
+
nodeId: nodeId.value,
|
|
1297
|
+
props: {},
|
|
1298
|
+
style: patch
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
return {
|
|
1303
|
+
nodeId,
|
|
1304
|
+
nodeType,
|
|
1305
|
+
updateProps,
|
|
1306
|
+
updateStyle,
|
|
1307
|
+
updateContainerStyle: (patch) => updateStyle({ container: patch }),
|
|
1308
|
+
updateContentStyle: (patch) => updateStyle({ content: patch })
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function useWidgetRuntime() {
|
|
1312
|
+
const ctx = inject(WIDGET_RUNTIME_CONTEXT_KEY);
|
|
1313
|
+
if (!ctx) throw new Error("[dragcraft/renderer] WidgetRuntimeContext not found. Ensure this component is rendered by WidgetRenderer.");
|
|
1314
|
+
return ctx;
|
|
1315
|
+
}
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/components/DefaultContainerFallback.ts
|
|
1318
|
+
var DefaultContainerFallback_default = defineComponent({
|
|
1319
|
+
name: "DcDefaultContainerFallback",
|
|
1320
|
+
props: { node: {
|
|
1321
|
+
type: Object,
|
|
1322
|
+
required: true
|
|
1323
|
+
} },
|
|
1324
|
+
setup(props) {
|
|
1325
|
+
return () => h("div", {
|
|
1326
|
+
"class": "dc-unresolved-container",
|
|
1327
|
+
"data-dc-component": "unresolved-container",
|
|
1328
|
+
"data-dc-unresolved-container": props.node.id
|
|
1329
|
+
}, Object.entries(props.node.container?.regions ?? {}).map(([regionId, nodes]) => {
|
|
1330
|
+
const owner = {
|
|
1331
|
+
kind: "container",
|
|
1332
|
+
containerId: props.node.id,
|
|
1333
|
+
regionId
|
|
1334
|
+
};
|
|
1335
|
+
return h("div", {
|
|
1336
|
+
"key": regionId,
|
|
1337
|
+
"class": "dc-container-region dc-container-region--unresolved",
|
|
1338
|
+
"data-dc-component": "container-region",
|
|
1339
|
+
"data-dc-state": "unresolved",
|
|
1340
|
+
"data-dc-container-id": props.node.id,
|
|
1341
|
+
"data-dc-container-region": regionId,
|
|
1342
|
+
"role": "group",
|
|
1343
|
+
"aria-label": regionId
|
|
1344
|
+
}, nodes.map((node) => h(WidgetRenderer_default, {
|
|
1345
|
+
key: node.id,
|
|
1346
|
+
node,
|
|
1347
|
+
owner
|
|
1348
|
+
})));
|
|
1349
|
+
}));
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
//#endregion
|
|
1353
|
+
//#region src/components/DefaultNodeHandle.ts
|
|
1354
|
+
/**
|
|
1355
|
+
* Default semantic selection handle for unmasked widgets and resolved containers.
|
|
1356
|
+
*/
|
|
1357
|
+
var DefaultNodeHandle_default = defineComponent({
|
|
1358
|
+
name: "DcDefaultNodeHandle",
|
|
1359
|
+
props: {
|
|
1360
|
+
nodeId: {
|
|
1361
|
+
type: String,
|
|
1362
|
+
required: true
|
|
1363
|
+
},
|
|
1364
|
+
nodeType: {
|
|
1365
|
+
type: String,
|
|
1366
|
+
required: true
|
|
1367
|
+
},
|
|
1368
|
+
owner: {
|
|
1369
|
+
type: Object,
|
|
1370
|
+
required: true
|
|
1371
|
+
},
|
|
1372
|
+
onSelect: {
|
|
1373
|
+
type: Function,
|
|
1374
|
+
required: true
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
setup(props) {
|
|
1378
|
+
const { t } = useI18n();
|
|
1379
|
+
return () => {
|
|
1380
|
+
const label = t("canvas.node-handle", "选中组件");
|
|
1381
|
+
return h("button", {
|
|
1382
|
+
"type": "button",
|
|
1383
|
+
"class": "dc-node__handle",
|
|
1384
|
+
"data-dc-component": "node-handle",
|
|
1385
|
+
"onClick": props.onSelect,
|
|
1386
|
+
"title": label,
|
|
1387
|
+
"aria-label": label
|
|
1388
|
+
}, [h("span", {
|
|
1389
|
+
"class": "dc-node__handle-surface",
|
|
1390
|
+
"data-dc-part": "surface"
|
|
1391
|
+
}, [h("span", {
|
|
1392
|
+
"class": "dc-node__handle-icon",
|
|
1393
|
+
"data-dc-part": "icon",
|
|
1394
|
+
"aria-hidden": "true"
|
|
1395
|
+
}, [h(IconComponent, { size: 12 })])])]);
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/components/DefaultNodeMask.ts
|
|
1401
|
+
/**
|
|
1402
|
+
* Default mask overlay component for widgets with mask=true.
|
|
1403
|
+
* Renders a transparent overlay that blocks widget interaction
|
|
1404
|
+
* and captures clicks for selection.
|
|
1405
|
+
*/
|
|
1406
|
+
var DefaultNodeMask_default = defineComponent({
|
|
1407
|
+
name: "DcDefaultNodeMask",
|
|
1408
|
+
props: {
|
|
1409
|
+
nodeId: {
|
|
1410
|
+
type: String,
|
|
1411
|
+
required: true
|
|
1412
|
+
},
|
|
1413
|
+
nodeType: {
|
|
1414
|
+
type: String,
|
|
1415
|
+
required: true
|
|
1416
|
+
},
|
|
1417
|
+
owner: {
|
|
1418
|
+
type: Object,
|
|
1419
|
+
required: true
|
|
1420
|
+
},
|
|
1421
|
+
onSelect: {
|
|
1422
|
+
type: Function,
|
|
1423
|
+
required: true
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
setup(props) {
|
|
1427
|
+
return () => h("div", {
|
|
1428
|
+
class: "dc-node__mask",
|
|
1429
|
+
onClick: props.onSelect
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
//#endregion
|
|
1434
|
+
//#region src/components/DefaultNodeSelection.ts
|
|
1435
|
+
var DefaultNodeSelection_default = defineComponent({
|
|
1436
|
+
name: "DcDefaultNodeSelection",
|
|
1437
|
+
props: {
|
|
1438
|
+
nodeId: {
|
|
1439
|
+
type: String,
|
|
1440
|
+
required: true
|
|
1441
|
+
},
|
|
1442
|
+
nodeType: {
|
|
1443
|
+
type: String,
|
|
1444
|
+
required: true
|
|
1445
|
+
},
|
|
1446
|
+
owner: {
|
|
1447
|
+
type: Object,
|
|
1448
|
+
required: true
|
|
1449
|
+
},
|
|
1450
|
+
projection: {
|
|
1451
|
+
type: Object,
|
|
1452
|
+
required: true
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
setup(props) {
|
|
1456
|
+
return () => h("div", {
|
|
1457
|
+
"class": "dc-node__selection-outline",
|
|
1458
|
+
"data-dc-component": "node-selection",
|
|
1459
|
+
"data-dc-state": props.projection.kind,
|
|
1460
|
+
"aria-hidden": "true"
|
|
1461
|
+
}, props.projection.kind === "root-segment" ? [
|
|
1462
|
+
h("span", {
|
|
1463
|
+
"class": "dc-node__selection-edge dc-node__selection-edge--block-start",
|
|
1464
|
+
"data-dc-part": "block-start-edge"
|
|
1465
|
+
}),
|
|
1466
|
+
h("span", {
|
|
1467
|
+
"class": "dc-node__selection-edge dc-node__selection-edge--inline-end",
|
|
1468
|
+
"data-dc-part": "inline-end-edge"
|
|
1469
|
+
}),
|
|
1470
|
+
h("span", {
|
|
1471
|
+
"class": "dc-node__selection-edge dc-node__selection-edge--block-end",
|
|
1472
|
+
"data-dc-part": "block-end-edge"
|
|
1473
|
+
}),
|
|
1474
|
+
h("span", {
|
|
1475
|
+
"class": "dc-node__selection-edge dc-node__selection-edge--inline-start",
|
|
1476
|
+
"data-dc-part": "inline-start-edge"
|
|
1477
|
+
})
|
|
1478
|
+
] : void 0);
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region src/components/DefaultNodeToolbar.ts
|
|
1483
|
+
/**
|
|
1484
|
+
* Default per-node floating toolbar component.
|
|
1485
|
+
* Renders actions based on the resolved action list from the action registry.
|
|
1486
|
+
* Supports both 'button' and 'drag-handle' action types.
|
|
1487
|
+
*
|
|
1488
|
+
* Positioning is owned by WidgetRenderer's measurable floating wrapper. The
|
|
1489
|
+
* resolved placement controls whether actions use a vertical or horizontal row.
|
|
1490
|
+
*/
|
|
1491
|
+
var DefaultNodeToolbar_default = defineComponent({
|
|
1492
|
+
name: "DcDefaultNodeToolbar",
|
|
1493
|
+
props: {
|
|
1494
|
+
nodeId: {
|
|
1495
|
+
type: String,
|
|
1496
|
+
required: true
|
|
1497
|
+
},
|
|
1498
|
+
nodeType: {
|
|
1499
|
+
type: String,
|
|
1500
|
+
required: true
|
|
1501
|
+
},
|
|
1502
|
+
owner: {
|
|
1503
|
+
type: Object,
|
|
1504
|
+
required: true
|
|
1505
|
+
},
|
|
1506
|
+
actions: {
|
|
1507
|
+
type: Array,
|
|
1508
|
+
required: true
|
|
1509
|
+
},
|
|
1510
|
+
state: {
|
|
1511
|
+
type: Object,
|
|
1512
|
+
required: true
|
|
1513
|
+
},
|
|
1514
|
+
toolbarPosition: {
|
|
1515
|
+
type: Object,
|
|
1516
|
+
default: void 0
|
|
1517
|
+
},
|
|
1518
|
+
onDragStart: {
|
|
1519
|
+
type: Function,
|
|
1520
|
+
required: true
|
|
1521
|
+
},
|
|
1522
|
+
onDragEnd: {
|
|
1523
|
+
type: Function,
|
|
1524
|
+
required: true
|
|
1525
|
+
}
|
|
1526
|
+
},
|
|
1527
|
+
setup(props) {
|
|
1528
|
+
return () => {
|
|
1529
|
+
const pos = props.toolbarPosition;
|
|
1530
|
+
const useFixed = pos != null;
|
|
1531
|
+
const actionVNodes = props.actions.map((action) => {
|
|
1532
|
+
if (action.type === "drag-handle") return h("div", {
|
|
1533
|
+
"class": [
|
|
1534
|
+
"dc-node__toolbar-btn",
|
|
1535
|
+
"dc-node__toolbar-btn--drag",
|
|
1536
|
+
{ "dc-node__toolbar-btn--disabled": action.disabled },
|
|
1537
|
+
action.className
|
|
1538
|
+
],
|
|
1539
|
+
"data-dc-part": "action",
|
|
1540
|
+
"data-dc-state": ["drag", action.disabled ? "disabled" : null].filter(Boolean).join(" "),
|
|
1541
|
+
"title": action.label,
|
|
1542
|
+
"aria-disabled": action.disabled ? "true" : void 0,
|
|
1543
|
+
"draggable": !action.disabled,
|
|
1544
|
+
"onDragstart": action.disabled ? void 0 : props.onDragStart,
|
|
1545
|
+
"onDragend": action.disabled ? void 0 : props.onDragEnd
|
|
1546
|
+
}, typeof action.icon === "string" ? action.icon : action.icon ? h(action.icon) : void 0);
|
|
1547
|
+
return h("button", {
|
|
1548
|
+
"type": "button",
|
|
1549
|
+
"class": ["dc-node__toolbar-btn", action.className],
|
|
1550
|
+
"data-dc-part": "action",
|
|
1551
|
+
"data-dc-state": action.key === "delete" ? "danger" : void 0,
|
|
1552
|
+
"title": action.label,
|
|
1553
|
+
"disabled": action.disabled,
|
|
1554
|
+
"onClick": action.handler
|
|
1555
|
+
}, typeof action.icon === "string" ? action.icon : action.icon ? h(action.icon) : void 0);
|
|
1556
|
+
});
|
|
1557
|
+
return h("div", {
|
|
1558
|
+
"class": ["dc-node__toolbar", {
|
|
1559
|
+
"dc-node__toolbar--floating": useFixed,
|
|
1560
|
+
[`dc-node__toolbar--${pos?.orientation ?? "vertical"}`]: true
|
|
1561
|
+
}],
|
|
1562
|
+
"data-dc-component": "node-toolbar",
|
|
1563
|
+
"data-dc-state": [useFixed ? "floating" : null, pos?.orientation ?? "vertical"].filter(Boolean).join(" "),
|
|
1564
|
+
"data-placement": pos?.placement,
|
|
1565
|
+
"data-orientation": pos?.orientation
|
|
1566
|
+
}, actionVNodes);
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
//#endregion
|
|
1571
|
+
//#region src/components/DefaultWidgetFallback.ts
|
|
1572
|
+
var DefaultWidgetFallback_default = defineComponent({
|
|
1573
|
+
name: "DcDefaultWidgetFallback",
|
|
1574
|
+
props: {
|
|
1575
|
+
nodeId: {
|
|
1576
|
+
type: String,
|
|
1577
|
+
required: true
|
|
1578
|
+
},
|
|
1579
|
+
nodeType: {
|
|
1580
|
+
type: String,
|
|
1581
|
+
default: "unknown"
|
|
1582
|
+
}
|
|
1583
|
+
},
|
|
1584
|
+
setup(props) {
|
|
1585
|
+
return () => h("div", {
|
|
1586
|
+
"class": "dc-widget-fallback",
|
|
1587
|
+
"data-dc-component": "widget-fallback"
|
|
1588
|
+
}, `Unknown widget: ${props.nodeType}`);
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
//#endregion
|
|
1592
|
+
//#region src/components/WidgetRenderer.ts
|
|
1593
|
+
const NODE_SURFACE_SELECTOR = "[data-dc-node-surface]";
|
|
1594
|
+
const TOOLBAR_BOUNDARY_SELECTOR = "[data-dc-toolbar-boundary]";
|
|
1595
|
+
const OVERLAY_BOUNDARY_SELECTOR = "[data-dc-overlay-boundary]";
|
|
1596
|
+
const CANVAS_INTERACTION_LAYER_SELECTOR = "[data-dc-canvas-interaction-layer]";
|
|
1597
|
+
const ContainerRuntimeProvider = defineComponent({
|
|
1598
|
+
name: "DcContainerRuntimeProvider",
|
|
1599
|
+
props: { runtime: {
|
|
1600
|
+
type: Object,
|
|
1601
|
+
required: true
|
|
1602
|
+
} },
|
|
1603
|
+
setup(props, { slots }) {
|
|
1604
|
+
provide(CONTAINER_RUNTIME_CONTEXT_KEY, props.runtime);
|
|
1605
|
+
return () => slots.default?.();
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
function resolveInteractionLayerTarget(host) {
|
|
1609
|
+
if (typeof document === "undefined") return "body";
|
|
1610
|
+
return host?.closest(".dc-canvas")?.querySelector(CANVAS_INTERACTION_LAYER_SELECTOR) ?? "body";
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* WidgetRenderer — thin orchestration layer.
|
|
1614
|
+
*
|
|
1615
|
+
* Delegates all logic to composables (useWidgetNode, useNodeActions, useNodeDrag)
|
|
1616
|
+
* and renders via configurable extension components (nodeMask, nodeHandle,
|
|
1617
|
+
* nodeToolbar, widgetFallback, nodeWrapper).
|
|
1618
|
+
*/
|
|
1619
|
+
var WidgetRenderer_default = defineComponent({
|
|
1620
|
+
name: "DcWidgetRenderer",
|
|
1621
|
+
props: {
|
|
1622
|
+
node: {
|
|
1623
|
+
type: Object,
|
|
1624
|
+
required: true
|
|
1625
|
+
},
|
|
1626
|
+
owner: {
|
|
1627
|
+
type: Object,
|
|
1628
|
+
default: () => ({ kind: "root" })
|
|
1629
|
+
},
|
|
1630
|
+
selectionPlane: {
|
|
1631
|
+
type: String,
|
|
1632
|
+
default: void 0
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
setup(props) {
|
|
1636
|
+
const ctx = useRendererContext();
|
|
1637
|
+
const { extensions } = ctx;
|
|
1638
|
+
provide(WIDGET_RUNTIME_CONTEXT_KEY, createWidgetRuntimeContext(() => props.node));
|
|
1639
|
+
const containerRuntime = createContainerRuntime(() => props.node, ctx);
|
|
1640
|
+
const widget = useWidgetNode(() => props.node, ctx);
|
|
1641
|
+
const { actions } = useNodeActions(() => props.node, ctx, () => props.owner);
|
|
1642
|
+
const drag = useNodeDrag(() => props.node, ctx);
|
|
1643
|
+
const interactionPresentation = resolveNodeInteractionPresentation(props.owner);
|
|
1644
|
+
const inheritedSelectionPlane = inject(NODE_SELECTION_PLANE_KEY, ref("content"));
|
|
1645
|
+
const subtreeSelectionPlane = computed(() => props.selectionPlane ?? inheritedSelectionPlane.value);
|
|
1646
|
+
const projectionPlane = computed(() => props.owner.kind === "root" ? "root" : subtreeSelectionPlane.value);
|
|
1647
|
+
provide(NODE_SELECTION_PLANE_KEY, subtreeSelectionPlane);
|
|
1648
|
+
const containerPlan = computed(() => props.node.container ? createContainerPlan(props.node, ctx.engine.registry) : null);
|
|
1649
|
+
const isResolvedContainer = computed(() => {
|
|
1650
|
+
const plan = containerPlan.value;
|
|
1651
|
+
if (plan?.ok !== true || !widget.resolvedComponent.value) return false;
|
|
1652
|
+
const declaredRegionIds = new Set(plan.plan.variant.regions.map((region) => region.id));
|
|
1653
|
+
return Object.keys(props.node.container?.regions ?? {}).every((regionId) => declaredRegionIds.has(regionId));
|
|
1654
|
+
});
|
|
1655
|
+
const isSelfPositionedLayer = computed(() => {
|
|
1656
|
+
if (props.owner.kind === "container") return false;
|
|
1657
|
+
const placement = widget.layout.value.placement;
|
|
1658
|
+
return placement.kind === "layer" && placement.mode === "self";
|
|
1659
|
+
});
|
|
1660
|
+
const usesBlockingMask = computed(() => widget.useMask.value && !isSelfPositionedLayer.value && !isResolvedContainer.value);
|
|
1661
|
+
const usesSelectionHandle = computed(() => !usesBlockingMask.value && widget.selectable.value && !isSelfPositionedLayer.value);
|
|
1662
|
+
const nodeElRef = ref(null);
|
|
1663
|
+
const toolbarElRef = ref(null);
|
|
1664
|
+
const handleAnchorElRef = ref(null);
|
|
1665
|
+
const isExternalHandleActive = computed(() => isResolvedContainer.value && usesSelectionHandle.value && !widget.state.isSelected.value);
|
|
1666
|
+
const { geometry: interactionGeometry, update: updateInteractionGeometry } = useNodeInteractionGeometry(nodeElRef, widget.state.isSelected, {
|
|
1667
|
+
mode: interactionPresentation.geometryMode,
|
|
1668
|
+
boundarySelector: OVERLAY_BOUNDARY_SELECTOR,
|
|
1669
|
+
selfTargetSelector: NODE_SURFACE_SELECTOR
|
|
1670
|
+
});
|
|
1671
|
+
const { projection: selectionProjection, target: selectionTarget } = useNodeSelectionProjection(nodeElRef, widget.state.isSelected, {
|
|
1672
|
+
kind: interactionPresentation.selectionKind,
|
|
1673
|
+
plane: projectionPlane,
|
|
1674
|
+
selfTargetSelector: NODE_SURFACE_SELECTOR
|
|
1675
|
+
});
|
|
1676
|
+
const { position: toolbarPosition } = useToolbarPosition(nodeElRef, toolbarElRef, widget.state.isSelected, {
|
|
1677
|
+
interactionBoundary: ctx.interactionBoundary,
|
|
1678
|
+
interactionGeometry,
|
|
1679
|
+
interactionGeometryUpdate: updateInteractionGeometry,
|
|
1680
|
+
selfTargetSelector: NODE_SURFACE_SELECTOR,
|
|
1681
|
+
boundarySelector: TOOLBAR_BOUNDARY_SELECTOR,
|
|
1682
|
+
placement: interactionPresentation.toolbarPlacement,
|
|
1683
|
+
orientation: interactionPresentation.toolbarOrientation
|
|
1684
|
+
});
|
|
1685
|
+
const { geometry: handleGeometry, update: updateHandleGeometry } = useNodeInteractionGeometry(nodeElRef, isExternalHandleActive, {
|
|
1686
|
+
mode: "node-box",
|
|
1687
|
+
boundarySelector: OVERLAY_BOUNDARY_SELECTOR
|
|
1688
|
+
});
|
|
1689
|
+
const { position: handlePosition } = useToolbarPosition(nodeElRef, handleAnchorElRef, isExternalHandleActive, {
|
|
1690
|
+
interactionBoundary: ctx.interactionBoundary,
|
|
1691
|
+
interactionGeometry: handleGeometry,
|
|
1692
|
+
interactionGeometryUpdate: updateHandleGeometry,
|
|
1693
|
+
boundarySelector: TOOLBAR_BOUNDARY_SELECTOR,
|
|
1694
|
+
placement: "left-start",
|
|
1695
|
+
orientation: "vertical"
|
|
1696
|
+
});
|
|
1697
|
+
function isDirectNodeHit(event) {
|
|
1698
|
+
const target = event.target;
|
|
1699
|
+
return target instanceof Element && target.closest("[data-node-id]") === event.currentTarget;
|
|
1700
|
+
}
|
|
1701
|
+
function handleDirectSelect(event) {
|
|
1702
|
+
if (isDirectNodeHit(event)) widget.handleSelect(event);
|
|
1703
|
+
}
|
|
1704
|
+
function handleMouseOver(event) {
|
|
1705
|
+
if (isDirectNodeHit(event)) widget.handleMouseEnter();
|
|
1706
|
+
}
|
|
1707
|
+
return () => {
|
|
1708
|
+
ctx.engine.store.schema.value;
|
|
1709
|
+
const node = props.node;
|
|
1710
|
+
const interactionLayerTarget = resolveInteractionLayerTarget(nodeElRef.value);
|
|
1711
|
+
const placement = widget.layout.value.placement;
|
|
1712
|
+
const isContainerOwned = props.owner.kind === "container";
|
|
1713
|
+
const ownerKind = isContainerOwned ? "container" : "root";
|
|
1714
|
+
const resolvedContainer = isResolvedContainer.value;
|
|
1715
|
+
const NodeMask = extensions.nodeMask ?? DefaultNodeMask_default;
|
|
1716
|
+
const NodeHandle = extensions.nodeHandle ?? DefaultNodeHandle_default;
|
|
1717
|
+
const NodeToolbar = extensions.nodeToolbar ?? DefaultNodeToolbar_default;
|
|
1718
|
+
const NodeSelection = extensions.nodeSelection ?? DefaultNodeSelection_default;
|
|
1719
|
+
const WidgetFallback = extensions.widgetFallback ?? DefaultWidgetFallback_default;
|
|
1720
|
+
const NodeWrapper = widget.meta.value?.wrapper ?? extensions.nodeWrapper;
|
|
1721
|
+
const widgetProps = { ...node.props };
|
|
1722
|
+
const wrapperStyle = normalizeStyleValueMap(node.style?.container);
|
|
1723
|
+
let contentStyle = normalizeStyleValueMap(node.style?.content);
|
|
1724
|
+
if (usesBlockingMask.value) {
|
|
1725
|
+
contentStyle = contentStyle ?? {};
|
|
1726
|
+
contentStyle.pointerEvents = "none";
|
|
1727
|
+
}
|
|
1728
|
+
let innerContent;
|
|
1729
|
+
if (node.container && !resolvedContainer) innerContent = h(DefaultContainerFallback_default, { node });
|
|
1730
|
+
else if (widget.resolvedComponent.value) {
|
|
1731
|
+
const material = h(widget.resolvedComponent.value, {
|
|
1732
|
+
...widgetProps,
|
|
1733
|
+
"style": contentStyle,
|
|
1734
|
+
"data-dc-node-surface": ""
|
|
1735
|
+
});
|
|
1736
|
+
innerContent = resolvedContainer ? h(ContainerRuntimeProvider, { runtime: containerRuntime }, { default: () => material }) : material;
|
|
1737
|
+
} else innerContent = h(WidgetFallback, {
|
|
1738
|
+
"nodeId": node.id,
|
|
1739
|
+
"nodeType": node.type,
|
|
1740
|
+
"data-dc-node-surface": ""
|
|
1741
|
+
});
|
|
1742
|
+
const wrapperChildren = [innerContent];
|
|
1743
|
+
if (selectionProjection.value && selectionTarget.value) {
|
|
1744
|
+
const projection = selectionProjection.value;
|
|
1745
|
+
wrapperChildren.push(h(Teleport, { to: selectionTarget.value }, [h("div", {
|
|
1746
|
+
"class": [
|
|
1747
|
+
"dc-node__selection-projection",
|
|
1748
|
+
`dc-node__selection-projection--${projection.kind}`,
|
|
1749
|
+
`dc-node__selection-projection--${ownerKind}-owned`
|
|
1750
|
+
],
|
|
1751
|
+
"data-node-id": node.id,
|
|
1752
|
+
"data-node-type": node.type,
|
|
1753
|
+
"data-dc-selection-plane": projection.plane,
|
|
1754
|
+
"style": {
|
|
1755
|
+
top: `${projection.bounds.top}px`,
|
|
1756
|
+
left: `${projection.bounds.left}px`,
|
|
1757
|
+
width: `${projection.bounds.width}px`,
|
|
1758
|
+
height: `${projection.bounds.height}px`
|
|
1759
|
+
}
|
|
1760
|
+
}, [h(NodeSelection, {
|
|
1761
|
+
nodeId: node.id,
|
|
1762
|
+
nodeType: node.type,
|
|
1763
|
+
owner: props.owner,
|
|
1764
|
+
projection
|
|
1765
|
+
})])]));
|
|
1766
|
+
}
|
|
1767
|
+
if (usesBlockingMask.value) wrapperChildren.push(h(NodeMask, {
|
|
1768
|
+
nodeId: node.id,
|
|
1769
|
+
nodeType: node.type,
|
|
1770
|
+
owner: props.owner,
|
|
1771
|
+
onSelect: widget.handleSelect
|
|
1772
|
+
}));
|
|
1773
|
+
if (usesSelectionHandle.value && !widget.state.isSelected.value) {
|
|
1774
|
+
const handleVNode = h(NodeHandle, {
|
|
1775
|
+
nodeId: node.id,
|
|
1776
|
+
nodeType: node.type,
|
|
1777
|
+
owner: props.owner,
|
|
1778
|
+
onSelect: widget.handleSelect
|
|
1779
|
+
});
|
|
1780
|
+
if (resolvedContainer) {
|
|
1781
|
+
const position = handlePosition.value;
|
|
1782
|
+
wrapperChildren.push(h(Teleport, { to: interactionLayerTarget }, [h("div", {
|
|
1783
|
+
"ref": handleAnchorElRef,
|
|
1784
|
+
"class": ["dc-node__handle-anchor", { "dc-node__handle-anchor--visible": position.visible }],
|
|
1785
|
+
"data-dc-component": "node-handle-anchor",
|
|
1786
|
+
"data-dc-state": position.visible ? "visible" : "hidden",
|
|
1787
|
+
"data-dc-node-handle-for": node.id,
|
|
1788
|
+
"style": {
|
|
1789
|
+
position: position.strategy,
|
|
1790
|
+
top: 0,
|
|
1791
|
+
left: 0,
|
|
1792
|
+
transform: `translate3d(${position.x}px, ${position.y}px, 0)`
|
|
1793
|
+
}
|
|
1794
|
+
}, [handleVNode])]));
|
|
1795
|
+
} else wrapperChildren.push(handleVNode);
|
|
1796
|
+
}
|
|
1797
|
+
if (widget.state.isSelected.value) {
|
|
1798
|
+
const toolbarVNode = h(NodeToolbar, {
|
|
1799
|
+
nodeId: node.id,
|
|
1800
|
+
nodeType: node.type,
|
|
1801
|
+
owner: props.owner,
|
|
1802
|
+
actions: actions.value,
|
|
1803
|
+
state: widget.state,
|
|
1804
|
+
toolbarPosition: toolbarPosition.value,
|
|
1805
|
+
onDragStart: drag.handleDragStart,
|
|
1806
|
+
onDragEnd: drag.handleDragEnd
|
|
1807
|
+
});
|
|
1808
|
+
wrapperChildren.push(h(Teleport, { to: interactionLayerTarget }, [h("div", {
|
|
1809
|
+
"ref": toolbarElRef,
|
|
1810
|
+
"class": "dc-node__toolbar-anchor",
|
|
1811
|
+
"data-placement": toolbarPosition.value.placement,
|
|
1812
|
+
"data-orientation": toolbarPosition.value.orientation,
|
|
1813
|
+
"style": {
|
|
1814
|
+
position: toolbarPosition.value.strategy,
|
|
1815
|
+
top: 0,
|
|
1816
|
+
left: 0,
|
|
1817
|
+
transform: `translate3d(${toolbarPosition.value.x}px, ${toolbarPosition.value.y}px, 0)`,
|
|
1818
|
+
visibility: toolbarPosition.value.visible ? "visible" : "hidden",
|
|
1819
|
+
pointerEvents: toolbarPosition.value.visible ? "auto" : "none"
|
|
1820
|
+
}
|
|
1821
|
+
}, [toolbarVNode])]));
|
|
1822
|
+
}
|
|
1823
|
+
const themeStates = [
|
|
1824
|
+
widget.useMask.value ? "masked" : "unmasked",
|
|
1825
|
+
!widget.selectable.value ? "non-selectable" : null,
|
|
1826
|
+
widget.inSortScope.value && !widget.sortable.value ? "locked" : null,
|
|
1827
|
+
!widget.inSortScope.value ? "unsorted" : null,
|
|
1828
|
+
widget.isDragging.value ? "dragging" : null,
|
|
1829
|
+
!widget.visible.value ? "hidden" : null,
|
|
1830
|
+
widget.state.isSelected.value ? "selected" : null,
|
|
1831
|
+
widget.state.isHovered.value ? "hovered" : null,
|
|
1832
|
+
widget.state.isDragOver.value ? "drag-over" : null,
|
|
1833
|
+
`${ownerKind}-owned`
|
|
1834
|
+
].filter(Boolean).join(" ");
|
|
1835
|
+
const coreWrapper = h("div", {
|
|
1836
|
+
"ref": nodeElRef,
|
|
1837
|
+
"class": [widget.wrapperClasses.value, `dc-node--${ownerKind}-owned`],
|
|
1838
|
+
"data-dc-component": "node",
|
|
1839
|
+
"data-dc-state": themeStates,
|
|
1840
|
+
"data-dc-node-owner": ownerKind,
|
|
1841
|
+
"style": wrapperStyle,
|
|
1842
|
+
"data-node-id": node.id,
|
|
1843
|
+
"data-node-type": node.type,
|
|
1844
|
+
"data-dc-layout-placement": isContainerOwned ? void 0 : placement.kind,
|
|
1845
|
+
"data-dc-layer-mode": !isContainerOwned && placement.kind === "layer" ? placement.mode : void 0,
|
|
1846
|
+
"data-dc-layout-region": isContainerOwned ? void 0 : widget.layout.value.region,
|
|
1847
|
+
"data-dc-sort-scope": isContainerOwned || widget.layout.value.sortScope === false ? void 0 : widget.layout.value.sortScope,
|
|
1848
|
+
"data-dc-visible": widget.visible.value ? void 0 : "false",
|
|
1849
|
+
"onMouseover": resolvedContainer ? void 0 : handleMouseOver,
|
|
1850
|
+
"onMouseleave": resolvedContainer ? void 0 : widget.handleMouseLeave,
|
|
1851
|
+
"onClick": isSelfPositionedLayer.value && widget.selectable.value ? widget.handleSelect : resolvedContainer && widget.selectable.value ? handleDirectSelect : void 0
|
|
1852
|
+
}, wrapperChildren);
|
|
1853
|
+
if (NodeWrapper) return h(NodeWrapper, {
|
|
1854
|
+
nodeId: node.id,
|
|
1855
|
+
nodeType: node.type,
|
|
1856
|
+
owner: props.owner,
|
|
1857
|
+
state: widget.state,
|
|
1858
|
+
meta: widget.meta.value
|
|
1859
|
+
}, { default: () => coreWrapper });
|
|
1860
|
+
return coreWrapper;
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
//#endregion
|
|
1865
|
+
//#region src/components/ContainerRegionOutlet.ts
|
|
1866
|
+
function isNestedRegionEvent(event, regionElement) {
|
|
1867
|
+
return event.target instanceof Element && event.target.closest("[data-dc-container-region]") !== regionElement;
|
|
1868
|
+
}
|
|
1869
|
+
var ContainerRegionOutlet_default = defineComponent({
|
|
1870
|
+
name: "DcContainerRegionOutlet",
|
|
1871
|
+
inheritAttrs: false,
|
|
1872
|
+
props: {
|
|
1873
|
+
regionId: {
|
|
1874
|
+
type: String,
|
|
1875
|
+
required: true
|
|
1876
|
+
},
|
|
1877
|
+
as: {
|
|
1878
|
+
type: [String, Object],
|
|
1879
|
+
default: "div"
|
|
1880
|
+
},
|
|
1881
|
+
resolveDropIndex: {
|
|
1882
|
+
type: Function,
|
|
1883
|
+
default: void 0
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
setup(props, { attrs }) {
|
|
1887
|
+
const ctx = useRendererContext();
|
|
1888
|
+
const runtime = useContainerRuntime();
|
|
1889
|
+
const definition = computed(() => runtime.regionDefinitions.value.find((item) => item.id === props.regionId));
|
|
1890
|
+
const regionNodes = computed(() => runtime.getRegionNodes(props.regionId));
|
|
1891
|
+
const containerMeta = computed(() => {
|
|
1892
|
+
const containerNode = ctx.schemaIndex.value.index.get(runtime.nodeId.value)?.node;
|
|
1893
|
+
return containerNode ? ctx.engine.registry.getWidget(containerNode.type) : void 0;
|
|
1894
|
+
});
|
|
1895
|
+
const isEmpty = computed(() => regionNodes.value.length === 0);
|
|
1896
|
+
const isActive = computed(() => {
|
|
1897
|
+
const destination = ctx.activeDestination.value;
|
|
1898
|
+
return destination?.kind === "container" && destination.containerId === runtime.nodeId.value && destination.regionId === props.regionId;
|
|
1899
|
+
});
|
|
1900
|
+
const isForbidden = computed(() => isActive.value && ctx.containerDropDecision.value?.allowed === false);
|
|
1901
|
+
function handleDragOver(event) {
|
|
1902
|
+
const regionElement = event.currentTarget;
|
|
1903
|
+
if (isNestedRegionEvent(event, regionElement)) return;
|
|
1904
|
+
event.preventDefault();
|
|
1905
|
+
event.stopPropagation();
|
|
1906
|
+
const resolver = props.resolveDropIndex ?? containerMeta.value?.containerAdapter?.resolveDropIndex;
|
|
1907
|
+
if (!resolver) {
|
|
1908
|
+
ctx.onContainerDragOver?.({
|
|
1909
|
+
event,
|
|
1910
|
+
containerId: runtime.nodeId.value,
|
|
1911
|
+
regionId: props.regionId,
|
|
1912
|
+
allowed: false,
|
|
1913
|
+
code: "CONTAINER_DROP_ADAPTER_MISSING"
|
|
1914
|
+
});
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
try {
|
|
1918
|
+
const nodes = regionNodes.value;
|
|
1919
|
+
const index = resolver({
|
|
1920
|
+
event,
|
|
1921
|
+
regionElement,
|
|
1922
|
+
itemElements: Array.from(regionElement.querySelectorAll(":scope > [data-node-id]")),
|
|
1923
|
+
nodes
|
|
1924
|
+
});
|
|
1925
|
+
if (index === null) {
|
|
1926
|
+
ctx.onContainerDragOver?.({
|
|
1927
|
+
event,
|
|
1928
|
+
containerId: runtime.nodeId.value,
|
|
1929
|
+
regionId: props.regionId,
|
|
1930
|
+
allowed: false,
|
|
1931
|
+
code: "CONTAINER_DROP_NO_TARGET"
|
|
1932
|
+
});
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (!Number.isInteger(index) || index < 0 || index > nodes.length) {
|
|
1936
|
+
ctx.onContainerDragOver?.({
|
|
1937
|
+
event,
|
|
1938
|
+
containerId: runtime.nodeId.value,
|
|
1939
|
+
regionId: props.regionId,
|
|
1940
|
+
allowed: false,
|
|
1941
|
+
code: "CONTAINER_DROP_ADAPTER_INVALID"
|
|
1942
|
+
});
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
ctx.onContainerDragOver?.({
|
|
1946
|
+
event,
|
|
1947
|
+
destination: {
|
|
1948
|
+
kind: "container",
|
|
1949
|
+
containerId: runtime.nodeId.value,
|
|
1950
|
+
regionId: props.regionId,
|
|
1951
|
+
index
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
ctx.onContainerDragOver?.({
|
|
1956
|
+
event,
|
|
1957
|
+
containerId: runtime.nodeId.value,
|
|
1958
|
+
regionId: props.regionId,
|
|
1959
|
+
allowed: false,
|
|
1960
|
+
code: "CONTAINER_DROP_ADAPTER_FAILED",
|
|
1961
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
function handleDragLeave(event) {
|
|
1966
|
+
const regionElement = event.currentTarget;
|
|
1967
|
+
if (isNestedRegionEvent(event, regionElement)) return;
|
|
1968
|
+
event.stopPropagation();
|
|
1969
|
+
ctx.onContainerDragLeave?.(event);
|
|
1970
|
+
}
|
|
1971
|
+
function handleDrop(event) {
|
|
1972
|
+
const regionElement = event.currentTarget;
|
|
1973
|
+
if (isNestedRegionEvent(event, regionElement)) return;
|
|
1974
|
+
event.preventDefault();
|
|
1975
|
+
event.stopPropagation();
|
|
1976
|
+
ctx.onContainerDrop?.(event);
|
|
1977
|
+
}
|
|
1978
|
+
return () => {
|
|
1979
|
+
const children = regionNodes.value.map((node) => h(WidgetRenderer_default, {
|
|
1980
|
+
key: node.id,
|
|
1981
|
+
node,
|
|
1982
|
+
owner: {
|
|
1983
|
+
kind: "container",
|
|
1984
|
+
containerId: runtime.nodeId.value,
|
|
1985
|
+
regionId: props.regionId
|
|
1986
|
+
}
|
|
1987
|
+
}));
|
|
1988
|
+
const DropIndicator = ctx.extensions.dropIndicator ?? DefaultDropIndicator_default;
|
|
1989
|
+
const EmptyState = ctx.extensions.emptyState ?? DefaultEmptyState_default;
|
|
1990
|
+
const ForbiddenOverlay = ctx.extensions.forbiddenOverlay ?? DefaultForbiddenOverlay_default;
|
|
1991
|
+
if (isEmpty.value) children.push(h(EmptyState, { isDragOver: isActive.value }));
|
|
1992
|
+
if (isActive.value && !isForbidden.value) {
|
|
1993
|
+
const index = ctx.activeDestination.value?.index;
|
|
1994
|
+
if (Number.isInteger(index) && index != null && index >= 0 && index <= regionNodes.value.length) children.splice(index, 0, h(DropIndicator, { key: "__container-drop-indicator__" }));
|
|
1995
|
+
}
|
|
1996
|
+
if (isForbidden.value) children.push(h(ForbiddenOverlay, {
|
|
1997
|
+
widgetType: ctx.engine.store.dragTarget.value?.widgetType ?? "",
|
|
1998
|
+
reason: ctx.containerDropDecision.value
|
|
1999
|
+
}));
|
|
2000
|
+
const themeStates = [
|
|
2001
|
+
isEmpty.value ? "empty" : null,
|
|
2002
|
+
isActive.value ? "active" : null,
|
|
2003
|
+
isForbidden.value ? "forbidden" : null
|
|
2004
|
+
].filter(Boolean).join(" ") || void 0;
|
|
2005
|
+
return h(props.as, mergeProps(attrs, {
|
|
2006
|
+
"class": ["dc-container-region", {
|
|
2007
|
+
"dc-container-region--empty": isEmpty.value,
|
|
2008
|
+
"dc-container-region--active": isActive.value,
|
|
2009
|
+
"dc-container-region--forbidden": isForbidden.value
|
|
2010
|
+
}],
|
|
2011
|
+
"data-dc-component": "container-region",
|
|
2012
|
+
"data-dc-state": themeStates,
|
|
2013
|
+
"data-dc-container-id": runtime.nodeId.value,
|
|
2014
|
+
"data-dc-container-region": props.regionId,
|
|
2015
|
+
"role": attrs.role ?? "group",
|
|
2016
|
+
"aria-label": attrs["aria-label"] ?? definition.value?.title ?? props.regionId,
|
|
2017
|
+
"aria-disabled": isForbidden.value ? "true" : void 0,
|
|
2018
|
+
"onDragover": handleDragOver,
|
|
2019
|
+
"onDragleave": handleDragLeave,
|
|
2020
|
+
"onDrop": handleDrop
|
|
2021
|
+
}), children);
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
//#endregion
|
|
2026
|
+
//#region src/components/DefaultContainerShell.ts
|
|
2027
|
+
const CHROME_MEASURE_SELECTOR = "[data-dc-chrome-position=\"fixed\"][data-dc-reserve-mode=\"measure\"][data-dc-avoid-content=\"true\"]";
|
|
2028
|
+
function sizeToCss(value) {
|
|
2029
|
+
return typeof value === "number" ? `${value}px` : value ?? "0px";
|
|
2030
|
+
}
|
|
2031
|
+
function edgeStyle(edge) {
|
|
2032
|
+
switch (edge) {
|
|
2033
|
+
case "block-start": return {
|
|
2034
|
+
top: "0px",
|
|
2035
|
+
right: "0px",
|
|
2036
|
+
left: "0px"
|
|
2037
|
+
};
|
|
2038
|
+
case "block-end": return {
|
|
2039
|
+
right: "0px",
|
|
2040
|
+
bottom: "0px",
|
|
2041
|
+
left: "0px"
|
|
2042
|
+
};
|
|
2043
|
+
case "inline-start": return {
|
|
2044
|
+
top: "0px",
|
|
2045
|
+
bottom: "0px",
|
|
2046
|
+
left: "0px"
|
|
2047
|
+
};
|
|
2048
|
+
case "inline-end": return {
|
|
2049
|
+
top: "0px",
|
|
2050
|
+
right: "0px",
|
|
2051
|
+
bottom: "0px"
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function chromeItemStyle(placement) {
|
|
2056
|
+
if (placement.position === "flow") return {
|
|
2057
|
+
position: "relative",
|
|
2058
|
+
pointerEvents: "auto"
|
|
2059
|
+
};
|
|
2060
|
+
if (placement.position === "fixed") return {
|
|
2061
|
+
position: "relative",
|
|
2062
|
+
zIndex: "20",
|
|
2063
|
+
pointerEvents: "auto"
|
|
2064
|
+
};
|
|
2065
|
+
return {
|
|
2066
|
+
position: "sticky",
|
|
2067
|
+
zIndex: "20",
|
|
2068
|
+
pointerEvents: "auto",
|
|
2069
|
+
...edgeStyle(placement.edge)
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function fixedChromeStackStyle(edge) {
|
|
2073
|
+
const style = {
|
|
2074
|
+
position: "absolute",
|
|
2075
|
+
display: "flex",
|
|
2076
|
+
pointerEvents: "none"
|
|
2077
|
+
};
|
|
2078
|
+
if (edge === "block-start" || edge === "block-end") {
|
|
2079
|
+
style.flexDirection = edge === "block-start" ? "column" : "column-reverse";
|
|
2080
|
+
style.left = "0px";
|
|
2081
|
+
style.right = "0px";
|
|
2082
|
+
style[edge === "block-start" ? "top" : "bottom"] = "0px";
|
|
2083
|
+
} else {
|
|
2084
|
+
style.flexDirection = edge === "inline-start" ? "row" : "row-reverse";
|
|
2085
|
+
style.top = "var(--dc-inset-block-start)";
|
|
2086
|
+
style.bottom = "var(--dc-inset-block-end)";
|
|
2087
|
+
style[edge === "inline-start" ? "left" : "right"] = "0px";
|
|
2088
|
+
}
|
|
2089
|
+
return style;
|
|
2090
|
+
}
|
|
2091
|
+
function layerItemStyle(placement) {
|
|
2092
|
+
if (placement.mode === "self") return {
|
|
2093
|
+
position: "absolute",
|
|
2094
|
+
inset: "0px",
|
|
2095
|
+
pointerEvents: "none"
|
|
2096
|
+
};
|
|
2097
|
+
const style = {
|
|
2098
|
+
position: "absolute",
|
|
2099
|
+
pointerEvents: "auto"
|
|
2100
|
+
};
|
|
2101
|
+
const block = placement.anchor.block ?? "end";
|
|
2102
|
+
const inline = placement.anchor.inline ?? "end";
|
|
2103
|
+
if (block === "start") style.top = sizeToCss(placement.offset?.blockStart ?? "calc(var(--dc-inset-block-start) + 16px)");
|
|
2104
|
+
else if (block === "center") style.top = "50%";
|
|
2105
|
+
else style.bottom = sizeToCss(placement.offset?.blockEnd ?? "calc(var(--dc-inset-block-end) + 16px)");
|
|
2106
|
+
if (inline === "start") style.left = sizeToCss(placement.offset?.inlineStart ?? "calc(var(--dc-inset-inline-start) + 16px)");
|
|
2107
|
+
else if (inline === "center") style.left = "50%";
|
|
2108
|
+
else style.right = sizeToCss(placement.offset?.inlineEnd ?? "calc(var(--dc-inset-inline-end) + 16px)");
|
|
2109
|
+
if (block === "center" && inline === "center") style.transform = "translate(-50%, -50%)";
|
|
2110
|
+
else if (block === "center") style.transform = "translateY(-50%)";
|
|
2111
|
+
else if (inline === "center") style.transform = "translateX(-50%)";
|
|
2112
|
+
return style;
|
|
2113
|
+
}
|
|
2114
|
+
function insetVariables(plan) {
|
|
2115
|
+
const sized = {
|
|
2116
|
+
"block-start": [],
|
|
2117
|
+
"block-end": [],
|
|
2118
|
+
"inline-start": [],
|
|
2119
|
+
"inline-end": []
|
|
2120
|
+
};
|
|
2121
|
+
const measuredFallback = {
|
|
2122
|
+
"block-start": [],
|
|
2123
|
+
"block-end": [],
|
|
2124
|
+
"inline-start": [],
|
|
2125
|
+
"inline-end": []
|
|
2126
|
+
};
|
|
2127
|
+
for (const entry of plan?.chrome ?? []) {
|
|
2128
|
+
const placement = entry.layout.placement;
|
|
2129
|
+
if (placement.position !== "fixed" || !placement.avoidContent) continue;
|
|
2130
|
+
if (placement.reserve.mode === "size") sized[placement.edge].push(sizeToCss(placement.reserve.size));
|
|
2131
|
+
else if (placement.reserve.mode === "measure" && placement.reserve.size !== void 0) measuredFallback[placement.edge].push(sizeToCss(placement.reserve.size));
|
|
2132
|
+
}
|
|
2133
|
+
const result = {};
|
|
2134
|
+
for (const [edge, sizes] of Object.entries(sized)) result[`--dc-sized-inset-${edge}`] = sizes.length > 1 ? `calc(${sizes.join(" + ")})` : sizes[0] ?? "0px";
|
|
2135
|
+
for (const [edge, sizes] of Object.entries(measuredFallback)) result[`--dc-measured-inset-${edge}`] = sizes.length > 1 ? `calc(${sizes.join(" + ")})` : sizes[0] ?? "0px";
|
|
2136
|
+
return result;
|
|
2137
|
+
}
|
|
2138
|
+
const DefaultContainerShell = defineComponent({
|
|
2139
|
+
name: "DcDefaultContainerShell",
|
|
2140
|
+
props: {
|
|
2141
|
+
isEmpty: {
|
|
2142
|
+
type: Boolean,
|
|
2143
|
+
default: false
|
|
2144
|
+
},
|
|
2145
|
+
regionVNodes: {
|
|
2146
|
+
type: Object,
|
|
2147
|
+
default: () => ({})
|
|
2148
|
+
},
|
|
2149
|
+
chromeVNodes: {
|
|
2150
|
+
type: Array,
|
|
2151
|
+
default: () => []
|
|
2152
|
+
},
|
|
2153
|
+
layerVNodes: {
|
|
2154
|
+
type: Object,
|
|
2155
|
+
default: () => ({})
|
|
2156
|
+
},
|
|
2157
|
+
forbiddenOverlayVNode: {
|
|
2158
|
+
type: Object,
|
|
2159
|
+
default: null
|
|
2160
|
+
},
|
|
2161
|
+
layoutPlan: {
|
|
2162
|
+
type: Object,
|
|
2163
|
+
default: void 0
|
|
2164
|
+
},
|
|
2165
|
+
surfaceStyle: {
|
|
2166
|
+
type: Object,
|
|
2167
|
+
default: void 0
|
|
2168
|
+
},
|
|
2169
|
+
selectionPresentation: {
|
|
2170
|
+
type: Object,
|
|
2171
|
+
default: void 0
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
setup(props, { slots }) {
|
|
2175
|
+
const shellRef = ref(null);
|
|
2176
|
+
let resizeObserver = null;
|
|
2177
|
+
function updateMeasuredInsets() {
|
|
2178
|
+
const shell = shellRef.value;
|
|
2179
|
+
if (!shell) return;
|
|
2180
|
+
const totals = {
|
|
2181
|
+
"block-start": 0,
|
|
2182
|
+
"block-end": 0,
|
|
2183
|
+
"inline-start": 0,
|
|
2184
|
+
"inline-end": 0
|
|
2185
|
+
};
|
|
2186
|
+
for (const item of Array.from(shell.querySelectorAll(CHROME_MEASURE_SELECTOR))) {
|
|
2187
|
+
const edge = item.dataset.dcChromeEdge;
|
|
2188
|
+
const rect = item.getBoundingClientRect();
|
|
2189
|
+
totals[edge] += edge === "block-start" || edge === "block-end" ? rect.height : rect.width;
|
|
2190
|
+
}
|
|
2191
|
+
for (const [edge, value] of Object.entries(totals)) shell.style.setProperty(`--dc-measured-inset-${edge}`, `${value}px`);
|
|
2192
|
+
}
|
|
2193
|
+
function refreshMeasuredInsets() {
|
|
2194
|
+
resizeObserver?.disconnect();
|
|
2195
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2196
|
+
resizeObserver = new ResizeObserver(updateMeasuredInsets);
|
|
2197
|
+
for (const item of Array.from(shellRef.value?.querySelectorAll(CHROME_MEASURE_SELECTOR) ?? [])) resizeObserver.observe(item);
|
|
2198
|
+
}
|
|
2199
|
+
nextTick(updateMeasuredInsets);
|
|
2200
|
+
}
|
|
2201
|
+
onMounted(refreshMeasuredInsets);
|
|
2202
|
+
onUpdated(refreshMeasuredInsets);
|
|
2203
|
+
onBeforeUnmount(() => resizeObserver?.disconnect());
|
|
2204
|
+
return () => {
|
|
2205
|
+
const additionalRegions = Object.entries(props.regionVNodes).filter(([region]) => region !== DEFAULT_LAYOUT_REGION).map(([region, nodes]) => h("div", {
|
|
2206
|
+
"key": region,
|
|
2207
|
+
"class": "dc-container-shell__region",
|
|
2208
|
+
"data-dc-layout-region": region
|
|
2209
|
+
}, nodes));
|
|
2210
|
+
const contentChrome = {
|
|
2211
|
+
"block-start": [],
|
|
2212
|
+
"block-end": [],
|
|
2213
|
+
"inline-start": [],
|
|
2214
|
+
"inline-end": []
|
|
2215
|
+
};
|
|
2216
|
+
const fixedChrome = {
|
|
2217
|
+
"block-start": {
|
|
2218
|
+
reserved: [],
|
|
2219
|
+
overlay: []
|
|
2220
|
+
},
|
|
2221
|
+
"block-end": {
|
|
2222
|
+
reserved: [],
|
|
2223
|
+
overlay: []
|
|
2224
|
+
},
|
|
2225
|
+
"inline-start": {
|
|
2226
|
+
reserved: [],
|
|
2227
|
+
overlay: []
|
|
2228
|
+
},
|
|
2229
|
+
"inline-end": {
|
|
2230
|
+
reserved: [],
|
|
2231
|
+
overlay: []
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
for (const [index, entry] of (props.layoutPlan?.chrome ?? []).entries()) {
|
|
2235
|
+
const placement = entry.layout.placement;
|
|
2236
|
+
const item = h("div", {
|
|
2237
|
+
"key": entry.node.id,
|
|
2238
|
+
"class": ["dc-container-shell__chrome-item", `dc-container-shell__chrome-item--${placement.position}`],
|
|
2239
|
+
"data-dc-chrome-edge": placement.edge,
|
|
2240
|
+
"data-dc-chrome-position": placement.position,
|
|
2241
|
+
"data-dc-reserve-mode": placement.reserve.mode,
|
|
2242
|
+
"data-dc-avoid-content": String(placement.avoidContent),
|
|
2243
|
+
"style": chromeItemStyle(placement)
|
|
2244
|
+
}, [props.chromeVNodes[index]]);
|
|
2245
|
+
if (placement.position === "fixed") {
|
|
2246
|
+
const stack = placement.avoidContent ? "reserved" : "overlay";
|
|
2247
|
+
fixedChrome[placement.edge][stack].push(item);
|
|
2248
|
+
} else contentChrome[placement.edge].push(item);
|
|
2249
|
+
}
|
|
2250
|
+
const fixedChromeStacks = Object.entries(fixedChrome).flatMap(([edge, groups]) => ["reserved", "overlay"].flatMap((stack) => {
|
|
2251
|
+
const items = groups[stack];
|
|
2252
|
+
return items.length > 0 ? [h("div", {
|
|
2253
|
+
"key": `${edge}:${stack}`,
|
|
2254
|
+
"class": [
|
|
2255
|
+
"dc-container-shell__chrome-stack",
|
|
2256
|
+
`dc-container-shell__chrome-stack--${edge}`,
|
|
2257
|
+
`dc-container-shell__chrome-stack--${stack}`
|
|
2258
|
+
],
|
|
2259
|
+
"data-dc-chrome-edge": edge,
|
|
2260
|
+
"data-dc-avoid-content-stack": String(stack === "reserved"),
|
|
2261
|
+
"style": fixedChromeStackStyle(edge)
|
|
2262
|
+
}, items)] : [];
|
|
2263
|
+
}));
|
|
2264
|
+
const layers = Array.from(props.layoutPlan?.layers.entries() ?? []).map(([layer, entries]) => {
|
|
2265
|
+
const vnodes = props.layerVNodes[layer] ?? [];
|
|
2266
|
+
return h("div", {
|
|
2267
|
+
"key": layer,
|
|
2268
|
+
"class": "dc-container-shell__layer",
|
|
2269
|
+
"data-dc-layer": layer,
|
|
2270
|
+
"style": {
|
|
2271
|
+
position: "absolute",
|
|
2272
|
+
inset: "0px",
|
|
2273
|
+
zIndex: "30",
|
|
2274
|
+
pointerEvents: "none"
|
|
2275
|
+
}
|
|
2276
|
+
}, entries.map((entry, index) => {
|
|
2277
|
+
const placement = entry.layout.placement;
|
|
2278
|
+
return h("div", {
|
|
2279
|
+
"key": entry.node.id,
|
|
2280
|
+
"class": ["dc-container-shell__layer-item", `dc-container-shell__layer-item--${placement.mode}`],
|
|
2281
|
+
"data-dc-layer-mode": placement.mode,
|
|
2282
|
+
"data-dc-layer-block": placement.anchor.block ?? "end",
|
|
2283
|
+
"data-dc-layer-inline": placement.anchor.inline ?? "end",
|
|
2284
|
+
"style": layerItemStyle(placement)
|
|
2285
|
+
}, [vnodes[index]]);
|
|
2286
|
+
}));
|
|
2287
|
+
});
|
|
2288
|
+
return h("div", {
|
|
2289
|
+
"ref": shellRef,
|
|
2290
|
+
"class": "dc-container-shell",
|
|
2291
|
+
"data-dc-component": "container-shell",
|
|
2292
|
+
"data-dc-state": props.isEmpty ? "empty" : void 0,
|
|
2293
|
+
"style": insetVariables(props.layoutPlan)
|
|
2294
|
+
}, [
|
|
2295
|
+
h("div", {
|
|
2296
|
+
class: "dc-container-shell__content",
|
|
2297
|
+
style: {
|
|
2298
|
+
position: "relative",
|
|
2299
|
+
boxSizing: "border-box"
|
|
2300
|
+
}
|
|
2301
|
+
}, [h("div", { class: "dc-container-shell__content-layout" }, [
|
|
2302
|
+
...contentChrome["block-start"],
|
|
2303
|
+
h("div", { class: "dc-container-shell__content-row" }, [
|
|
2304
|
+
contentChrome["inline-start"].length > 0 ? h("div", { class: "dc-container-shell__content-edge dc-container-shell__content-edge--inline-start" }, contentChrome["inline-start"]) : null,
|
|
2305
|
+
h("div", {
|
|
2306
|
+
class: "dc-container-shell__content-surface",
|
|
2307
|
+
style: props.surfaceStyle
|
|
2308
|
+
}, [...slots.default?.() ?? [], ...additionalRegions]),
|
|
2309
|
+
contentChrome["inline-end"].length > 0 ? h("div", { class: "dc-container-shell__content-edge dc-container-shell__content-edge--inline-end" }, contentChrome["inline-end"]) : null
|
|
2310
|
+
]),
|
|
2311
|
+
...contentChrome["block-end"],
|
|
2312
|
+
h("div", {
|
|
2313
|
+
"ref": (element) => {
|
|
2314
|
+
props.selectionPresentation?.registerPlane("content", element instanceof HTMLElement ? element : null);
|
|
2315
|
+
},
|
|
2316
|
+
"class": "dc-node-selection-plane dc-node-selection-plane--content",
|
|
2317
|
+
"data-dc-selection-plane": "content",
|
|
2318
|
+
"aria-hidden": "true"
|
|
2319
|
+
})
|
|
2320
|
+
])]),
|
|
2321
|
+
fixedChromeStacks.length > 0 ? h("div", {
|
|
2322
|
+
class: "dc-container-shell__chrome",
|
|
2323
|
+
style: {
|
|
2324
|
+
position: "absolute",
|
|
2325
|
+
inset: "0px",
|
|
2326
|
+
zIndex: "20",
|
|
2327
|
+
pointerEvents: "none"
|
|
2328
|
+
}
|
|
2329
|
+
}, fixedChromeStacks) : null,
|
|
2330
|
+
...layers,
|
|
2331
|
+
h("div", {
|
|
2332
|
+
"ref": (element) => {
|
|
2333
|
+
props.selectionPresentation?.registerPlane("root", element instanceof HTMLElement ? element : null);
|
|
2334
|
+
},
|
|
2335
|
+
"class": "dc-node-selection-plane dc-node-selection-plane--root",
|
|
2336
|
+
"data-dc-selection-plane": "root",
|
|
2337
|
+
"aria-hidden": "true"
|
|
2338
|
+
}),
|
|
2339
|
+
h("div", {
|
|
2340
|
+
"ref": (element) => {
|
|
2341
|
+
props.selectionPresentation?.registerPlane("viewport", element instanceof HTMLElement ? element : null);
|
|
2342
|
+
},
|
|
2343
|
+
"class": "dc-node-selection-plane dc-node-selection-plane--viewport",
|
|
2344
|
+
"data-dc-selection-plane": "viewport",
|
|
2345
|
+
"aria-hidden": "true"
|
|
2346
|
+
}),
|
|
2347
|
+
props.forbiddenOverlayVNode
|
|
2348
|
+
]);
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
const DefaultContainerShellWithOverlay = DefaultContainerShell;
|
|
2353
|
+
DefaultContainerShellWithOverlay.__dcHandlesForbiddenOverlay = true;
|
|
2354
|
+
//#endregion
|
|
2355
|
+
//#region src/components/RootRenderer.ts
|
|
2356
|
+
function handlesForbiddenOverlay(component) {
|
|
2357
|
+
return Boolean(component.__dcHandlesForbiddenOverlay);
|
|
2358
|
+
}
|
|
2359
|
+
function regionEntryIndex(plan, entry) {
|
|
2360
|
+
return (plan.regions.get(entry.layout.region ?? DEFAULT_LAYOUT_REGION) ?? []).findIndex((candidate) => candidate.node.id === entry.node.id);
|
|
2361
|
+
}
|
|
2362
|
+
function insertDropIndicator(regionVNodes, plan, schema, engine, destination, legacyIndex, indicator) {
|
|
2363
|
+
if (destination?.kind === "container") return;
|
|
2364
|
+
const sortScope = destination === void 0 ? DEFAULT_SORT_SCOPE : destination?.sortScope;
|
|
2365
|
+
if (!sortScope) return;
|
|
2366
|
+
const entries = plan.sortScopes.get(sortScope) ?? [];
|
|
2367
|
+
const requestedIndex = destination?.index ?? legacyIndex;
|
|
2368
|
+
const index = requestedIndex == null ? entries.length : Math.max(0, Math.min(requestedIndex, entries.length));
|
|
2369
|
+
const dragTarget = engine.store.dragTarget.value;
|
|
2370
|
+
const draggedEntry = dragTarget?.sourceNodeId ? plan.entries.find((entry) => entry.node.id === dragTarget.sourceNodeId) : void 0;
|
|
2371
|
+
const draggedLayout = !draggedEntry && dragTarget?.widgetType ? resolveNodeLayout({
|
|
2372
|
+
id: "__drop-indicator__",
|
|
2373
|
+
type: dragTarget.widgetType,
|
|
2374
|
+
props: {}
|
|
2375
|
+
}, engine.registry, schema) : void 0;
|
|
2376
|
+
const inferredRegion = draggedEntry?.layout.region ?? (draggedLayout?.placement.kind === "flow" ? draggedLayout.region : void 0);
|
|
2377
|
+
const adjacentEntry = index < entries.length ? entries[index] : entries.at(-1);
|
|
2378
|
+
const region = inferredRegion ?? adjacentEntry?.layout.region ?? DEFAULT_LAYOUT_REGION;
|
|
2379
|
+
const regionNodes = regionVNodes[region] ?? (regionVNodes[region] = []);
|
|
2380
|
+
const nextRegionEntry = entries.slice(index).find((entry) => (entry.layout.region ?? DEFAULT_LAYOUT_REGION) === region);
|
|
2381
|
+
const previousRegionEntry = entries.slice(0, index).findLast((entry) => (entry.layout.region ?? DEFAULT_LAYOUT_REGION) === region);
|
|
2382
|
+
if (!nextRegionEntry && !previousRegionEntry) {
|
|
2383
|
+
regionNodes.push(indicator);
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
const insertIndex = nextRegionEntry ? regionEntryIndex(plan, nextRegionEntry) : regionEntryIndex(plan, previousRegionEntry) + 1;
|
|
2387
|
+
regionNodes.splice(Math.max(0, insertIndex), 0, indicator);
|
|
2388
|
+
}
|
|
2389
|
+
var RootRenderer_default = defineComponent({
|
|
2390
|
+
name: "DcRootRenderer",
|
|
2391
|
+
props: {
|
|
2392
|
+
engine: {
|
|
2393
|
+
type: Object,
|
|
2394
|
+
required: true
|
|
2395
|
+
},
|
|
2396
|
+
componentMap: {
|
|
2397
|
+
type: Object,
|
|
2398
|
+
required: true
|
|
2399
|
+
},
|
|
2400
|
+
extensions: {
|
|
2401
|
+
type: Object,
|
|
2402
|
+
default: () => ({})
|
|
2403
|
+
},
|
|
2404
|
+
eventHooks: {
|
|
2405
|
+
type: Object,
|
|
2406
|
+
default: void 0
|
|
2407
|
+
},
|
|
2408
|
+
actionInterceptors: {
|
|
2409
|
+
type: Array,
|
|
2410
|
+
default: void 0
|
|
2411
|
+
},
|
|
2412
|
+
actionRegistry: {
|
|
2413
|
+
type: Object,
|
|
2414
|
+
default: void 0
|
|
2415
|
+
},
|
|
2416
|
+
dragOverNodeId: {
|
|
2417
|
+
type: Object,
|
|
2418
|
+
default: void 0
|
|
2419
|
+
},
|
|
2420
|
+
dragOverIndex: {
|
|
2421
|
+
type: Object,
|
|
2422
|
+
default: void 0
|
|
2423
|
+
},
|
|
2424
|
+
activeDestination: {
|
|
2425
|
+
type: Object,
|
|
2426
|
+
default: void 0
|
|
2427
|
+
},
|
|
2428
|
+
containerDropDecision: {
|
|
2429
|
+
type: Object,
|
|
2430
|
+
default: void 0
|
|
2431
|
+
},
|
|
2432
|
+
onContainerDragOver: {
|
|
2433
|
+
type: Function,
|
|
2434
|
+
default: void 0
|
|
2435
|
+
},
|
|
2436
|
+
onContainerDragLeave: {
|
|
2437
|
+
type: Function,
|
|
2438
|
+
default: void 0
|
|
2439
|
+
},
|
|
2440
|
+
onContainerDrop: {
|
|
2441
|
+
type: Function,
|
|
2442
|
+
default: void 0
|
|
2443
|
+
},
|
|
2444
|
+
interactionBoundary: {
|
|
2445
|
+
type: Object,
|
|
2446
|
+
default: void 0
|
|
2447
|
+
},
|
|
2448
|
+
isForbidden: {
|
|
2449
|
+
type: Object,
|
|
2450
|
+
default: void 0
|
|
2451
|
+
},
|
|
2452
|
+
forbiddenReason: {
|
|
2453
|
+
type: Object,
|
|
2454
|
+
default: void 0
|
|
2455
|
+
}
|
|
2456
|
+
},
|
|
2457
|
+
setup(props) {
|
|
2458
|
+
const ctx = createRendererContext({
|
|
2459
|
+
engine: props.engine,
|
|
2460
|
+
componentMap: props.componentMap,
|
|
2461
|
+
extensions: props.extensions,
|
|
2462
|
+
eventHooks: props.eventHooks,
|
|
2463
|
+
actionInterceptors: props.actionInterceptors,
|
|
2464
|
+
actionRegistry: props.actionRegistry,
|
|
2465
|
+
dragOverNodeId: props.dragOverNodeId,
|
|
2466
|
+
activeDestination: props.activeDestination,
|
|
2467
|
+
containerDropDecision: props.containerDropDecision,
|
|
2468
|
+
onContainerDragOver: props.onContainerDragOver,
|
|
2469
|
+
onContainerDragLeave: props.onContainerDragLeave,
|
|
2470
|
+
onContainerDrop: props.onContainerDrop,
|
|
2471
|
+
interactionBoundary: props.interactionBoundary
|
|
2472
|
+
});
|
|
2473
|
+
provide(RENDERER_CONTEXT_KEY, ctx);
|
|
2474
|
+
const selectionPresentation = createNodeSelectionPresentation();
|
|
2475
|
+
provide(NODE_SELECTION_PRESENTATION_KEY, selectionPresentation);
|
|
2476
|
+
const ContainerShell = computed(() => props.extensions?.containerShell ?? DefaultContainerShell);
|
|
2477
|
+
const ForbiddenOverlay = computed(() => props.extensions?.forbiddenOverlay ?? DefaultForbiddenOverlay_default);
|
|
2478
|
+
return () => {
|
|
2479
|
+
const schema = ctx.schema.value;
|
|
2480
|
+
const isDragOver = props.dragOverNodeId?.value === "root";
|
|
2481
|
+
const plan = ctx.layoutPlan.value;
|
|
2482
|
+
const DropIndicator = props.extensions?.dropIndicator ?? DefaultDropIndicator_default;
|
|
2483
|
+
const EmptyState = props.extensions?.emptyState ?? DefaultEmptyState_default;
|
|
2484
|
+
const regionVNodes = {};
|
|
2485
|
+
for (const [region, entries] of plan.regions) regionVNodes[region] = entries.map((entry) => h(WidgetRenderer_default, {
|
|
2486
|
+
"key": entry.node.id,
|
|
2487
|
+
"node": entry.node,
|
|
2488
|
+
"selectionPlane": "content",
|
|
2489
|
+
"data-dc-layout-region": entry.layout.region
|
|
2490
|
+
}));
|
|
2491
|
+
const chromeVNodes = plan.chrome.map((entry) => h(WidgetRenderer_default, {
|
|
2492
|
+
"key": entry.node.id,
|
|
2493
|
+
"node": entry.node,
|
|
2494
|
+
"selectionPlane": entry.layout.placement.kind === "chrome" && entry.layout.placement.position === "fixed" ? "viewport" : "content",
|
|
2495
|
+
"data-dc-layout-placement": "chrome"
|
|
2496
|
+
}));
|
|
2497
|
+
const layerVNodes = {};
|
|
2498
|
+
for (const [layer, entries] of plan.layers) layerVNodes[layer] = entries.map((entry) => h(WidgetRenderer_default, {
|
|
2499
|
+
"key": entry.node.id,
|
|
2500
|
+
"node": entry.node,
|
|
2501
|
+
"selectionPlane": "viewport",
|
|
2502
|
+
"data-dc-layout-placement": "layer"
|
|
2503
|
+
}));
|
|
2504
|
+
const isForbidden = props.isForbidden?.value ?? false;
|
|
2505
|
+
const createForbiddenOverlayVNode = () => h(ForbiddenOverlay.value, {
|
|
2506
|
+
key: "__forbidden__",
|
|
2507
|
+
widgetType: props.engine.store.dragTarget.value?.widgetType ?? "",
|
|
2508
|
+
reason: props.forbiddenReason?.value ?? null
|
|
2509
|
+
});
|
|
2510
|
+
const forbiddenOverlayVNode = isDragOver && isForbidden ? createForbiddenOverlayVNode() : null;
|
|
2511
|
+
if (isDragOver && !isForbidden) insertDropIndicator(regionVNodes, plan, schema, props.engine, props.activeDestination?.value, props.dragOverIndex?.value, h(DropIndicator, { key: "__drop-indicator__" }));
|
|
2512
|
+
const contentVNodes = regionVNodes[DEFAULT_LAYOUT_REGION] ?? [];
|
|
2513
|
+
const isEmpty = plan.entries.length === 0 && !isDragOver;
|
|
2514
|
+
const ContainerShellComponent = ContainerShell.value;
|
|
2515
|
+
const fallbackForbiddenOverlayVNode = forbiddenOverlayVNode && !handlesForbiddenOverlay(ContainerShellComponent) ? createForbiddenOverlayVNode() : null;
|
|
2516
|
+
return h("div", {
|
|
2517
|
+
"class": "dc-root-renderer",
|
|
2518
|
+
"data-dc-component": "root-renderer",
|
|
2519
|
+
"data-node-id": "root",
|
|
2520
|
+
"data-node-type": "root"
|
|
2521
|
+
}, [
|
|
2522
|
+
h(ContainerShellComponent, {
|
|
2523
|
+
class: { "dc-container-shell--empty": isEmpty },
|
|
2524
|
+
isEmpty,
|
|
2525
|
+
regionVNodes,
|
|
2526
|
+
chromeVNodes,
|
|
2527
|
+
layerVNodes,
|
|
2528
|
+
forbiddenOverlayVNode,
|
|
2529
|
+
layoutPlan: plan,
|
|
2530
|
+
surfaceStyle: normalizeStyleValueMap(schema.root.style?.surface),
|
|
2531
|
+
registry: props.engine.registry,
|
|
2532
|
+
selectionPresentation
|
|
2533
|
+
}, {
|
|
2534
|
+
default: () => {
|
|
2535
|
+
if (isEmpty) return [h(EmptyState, { isDragOver: false })];
|
|
2536
|
+
return contentVNodes;
|
|
2537
|
+
},
|
|
2538
|
+
...Object.fromEntries(Object.entries(regionVNodes).map(([region, vnodes]) => [region, () => vnodes]))
|
|
2539
|
+
}),
|
|
2540
|
+
h("div", {
|
|
2541
|
+
"ref": (element) => {
|
|
2542
|
+
selectionPresentation.registerFallback(element instanceof HTMLElement ? element : null);
|
|
2543
|
+
},
|
|
2544
|
+
"class": "dc-node-selection-plane dc-node-selection-plane--fallback",
|
|
2545
|
+
"data-dc-selection-plane": "fallback",
|
|
2546
|
+
"aria-hidden": "true"
|
|
2547
|
+
}),
|
|
2548
|
+
fallbackForbiddenOverlayVNode
|
|
2549
|
+
]);
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
});
|
|
2553
|
+
//#endregion
|
|
2554
|
+
//#region src/messages.ts
|
|
2555
|
+
const rendererMessages = {
|
|
2556
|
+
"zh-CN": {
|
|
2557
|
+
action: {
|
|
2558
|
+
"drag": "拖拽排序",
|
|
2559
|
+
"move-up": "上移",
|
|
2560
|
+
"move-down": "下移",
|
|
2561
|
+
"delete": "删除"
|
|
2562
|
+
},
|
|
2563
|
+
canvas: {
|
|
2564
|
+
"empty": "拖拽组件到这里",
|
|
2565
|
+
"node-handle": "选中组件"
|
|
2566
|
+
},
|
|
2567
|
+
forbidden: {
|
|
2568
|
+
default: "当前物料不满足创建条件,无法添加到画布",
|
|
2569
|
+
alreadyExists: "无法添加 — 该类型已存在"
|
|
2570
|
+
}
|
|
2571
|
+
},
|
|
2572
|
+
"en": {
|
|
2573
|
+
action: {
|
|
2574
|
+
"drag": "Drag to sort",
|
|
2575
|
+
"move-up": "Move up",
|
|
2576
|
+
"move-down": "Move down",
|
|
2577
|
+
"delete": "Delete"
|
|
2578
|
+
},
|
|
2579
|
+
canvas: {
|
|
2580
|
+
"empty": "Drag components here",
|
|
2581
|
+
"node-handle": "Select component"
|
|
2582
|
+
},
|
|
2583
|
+
forbidden: {
|
|
2584
|
+
default: "This component cannot be added to the canvas",
|
|
2585
|
+
alreadyExists: "Cannot add — this type already exists"
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
//#endregion
|
|
2590
|
+
export { ActionKey, CONTAINER_RUNTIME_CONTEXT_KEY, ContainerRegionOutlet_default as ContainerRegionOutlet, DefaultContainerFallback_default as DefaultContainerFallback, DefaultContainerShell, DefaultDropIndicator_default as DefaultDropIndicator, DefaultEmptyState_default as DefaultEmptyState, DefaultForbiddenOverlay_default as DefaultForbiddenOverlay, DefaultNodeHandle_default as DefaultNodeHandle, DefaultNodeMask_default as DefaultNodeMask, DefaultNodeSelection_default as DefaultNodeSelection, DefaultNodeToolbar_default as DefaultNodeToolbar, DefaultWidgetFallback_default as DefaultWidgetFallback, NODE_SELECTION_PLANE_KEY, NODE_SELECTION_PRESENTATION_KEY, RENDERER_CONTEXT_KEY, RootRenderer_default as RootRenderer, WIDGET_RUNTIME_CONTEXT_KEY, WidgetRenderer_default as WidgetRenderer, createConfirmActionInterceptor, createContainerRuntime, createDefaultActions, createDefaultEventHooks, createNodeActionRegistry, createNodeSelectionPresentation, createRendererContext, fireAfterHook, hideNativeDragImage, rendererMessages, resolveBeforeHook, resolveNodeInteractionPresentation, runActionPipeline, useContainerRuntime, useNodeActions, useNodeDrag, useNodeInteractionGeometry, useNodeSelectionPresentation, useNodeSelectionProjection, useNodeState, useRendererContext, useToolbarPosition, useWidgetNode, useWidgetRuntime };
|