@dragcraft/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2281 @@
1
+ import { readonly, ref, shallowRef } from "vue";
2
+ import { EventEmitter, cloneDeep, generateShortId } from "@dragcraft/utils";
3
+ //#region src/behavior.ts
4
+ /**
5
+ * Resolves a behavior field that may be a static boolean or a predicate function.
6
+ *
7
+ * - `undefined` → returns `defaultValue` (preserves opt-in defaults)
8
+ * - `boolean` → returns the boolean directly
9
+ * - `function` → invokes the function with the provided context
10
+ *
11
+ * @param field - The behavior field from WidgetMeta (may be undefined)
12
+ * @param ctx - The context to pass if field is a function
13
+ * @param defaultValue - Value when field is undefined (default: true)
14
+ */
15
+ function resolveBehavior(field, ctx, defaultValue = true) {
16
+ if (field === void 0) return defaultValue;
17
+ if (typeof field === "function") return field(ctx);
18
+ return field;
19
+ }
20
+ function normalizeCreatableDecision(value, defaultValue) {
21
+ if (value === void 0) return { allowed: defaultValue };
22
+ if (typeof value === "boolean") return { allowed: value };
23
+ return value;
24
+ }
25
+ function resolveCreatable(field, ctx, defaultValue = true) {
26
+ return normalizeCreatableDecision(typeof field === "function" ? field(ctx) : field, defaultValue);
27
+ }
28
+ //#endregion
29
+ //#region src/constants.ts
30
+ const CommandType = {
31
+ ADD_NODE: "ADD_NODE",
32
+ MOVE_NODE: "MOVE_NODE",
33
+ REMOVE_NODE: "REMOVE_NODE",
34
+ DUPLICATE_NODE: "DUPLICATE_NODE",
35
+ CHANGE_CONTAINER_VARIANT: "CHANGE_CONTAINER_VARIANT",
36
+ UPDATE_PROPS: "UPDATE_PROPS",
37
+ SET_GLOBAL_CONFIG: "SET_GLOBAL_CONFIG"
38
+ };
39
+ const EventName = {
40
+ SCHEMA_CHANGED: "schema:changed",
41
+ SELECTION_CHANGED: "selection:changed",
42
+ DRAG_ENTER: "drag:enter",
43
+ DRAG_OVER: "drag:over",
44
+ DRAG_LEAVE: "drag:leave",
45
+ DRAG_DROP: "drag:drop",
46
+ HISTORY_CHANGED: "history:changed",
47
+ NODE_ADDED: "node:added",
48
+ NODE_REMOVED: "node:removed",
49
+ NODE_DUPLICATED: "node:duplicated",
50
+ NODE_MOVED: "node:moved",
51
+ CONTAINER_VARIANT_CHANGED: "container:variant-changed",
52
+ NODE_UPDATED: "node:updated",
53
+ GLOBAL_CONFIG_CHANGED: "global-config:changed"
54
+ };
55
+ const DEFAULT_SCHEMA_VERSION = "1.0.0";
56
+ const DEFAULT_MAX_HISTORY_SIZE = 50;
57
+ //#endregion
58
+ //#region src/schema-utils.ts
59
+ function cloneSchema(schema) {
60
+ return cloneDeep(schema);
61
+ }
62
+ function deepFreeze(value) {
63
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
64
+ Object.freeze(value);
65
+ for (const child of Object.values(value)) deepFreeze(child);
66
+ }
67
+ return value;
68
+ }
69
+ //#endregion
70
+ //#region src/history-manager.ts
71
+ const ownedSnapshotPushers = /* @__PURE__ */ new WeakMap();
72
+ function pushOwnedHistorySnapshot(history, label, before) {
73
+ const pushOwned = ownedSnapshotPushers.get(history);
74
+ if (pushOwned) pushOwned(label, before);
75
+ else history.pushSnapshot(label, before);
76
+ }
77
+ function createHistoryManager(store, eventHub, maxSize = 50) {
78
+ const undoStack = [];
79
+ const redoStack = [];
80
+ const state = ref({
81
+ canUndo: false,
82
+ canRedo: false,
83
+ undoCount: 0,
84
+ redoCount: 0
85
+ });
86
+ let inTransaction = false;
87
+ let transactionLabel = "";
88
+ let transactionSnapshot = null;
89
+ function resetTransaction() {
90
+ inTransaction = false;
91
+ transactionLabel = "";
92
+ transactionSnapshot = null;
93
+ }
94
+ function trimUndoStack() {
95
+ if (undoStack.length > maxSize) undoStack.splice(0, undoStack.length - maxSize);
96
+ }
97
+ function pushUndo(label, snapshot) {
98
+ undoStack.push({
99
+ label,
100
+ snapshot
101
+ });
102
+ trimUndoStack();
103
+ redoStack.length = 0;
104
+ }
105
+ function emitChange() {
106
+ const nextState = {
107
+ canUndo: canUndo(),
108
+ canRedo: canRedo(),
109
+ undoCount: undoStack.length,
110
+ redoCount: redoStack.length
111
+ };
112
+ state.value = nextState;
113
+ eventHub.emit(EventName.HISTORY_CHANGED, nextState);
114
+ }
115
+ function pushOwnedSnapshot(label, before) {
116
+ if (inTransaction) return;
117
+ pushUndo(label, before);
118
+ emitChange();
119
+ }
120
+ function pushSnapshot(label, before) {
121
+ if (inTransaction) return;
122
+ pushOwnedSnapshot(label, deepFreeze(cloneSchema(before)));
123
+ }
124
+ function undo() {
125
+ if (!canUndo()) return;
126
+ const entry = undoStack.pop();
127
+ const currentSnapshot = store.getSnapshot();
128
+ redoStack.push({
129
+ label: entry.label,
130
+ snapshot: currentSnapshot
131
+ });
132
+ store.restoreSnapshot(entry.snapshot);
133
+ emitChange();
134
+ eventHub.emit(EventName.SCHEMA_CHANGED, store.getSnapshot());
135
+ }
136
+ function redo() {
137
+ if (!canRedo()) return;
138
+ const entry = redoStack.pop();
139
+ const currentSnapshot = store.getSnapshot();
140
+ undoStack.push({
141
+ label: entry.label,
142
+ snapshot: currentSnapshot
143
+ });
144
+ trimUndoStack();
145
+ store.restoreSnapshot(entry.snapshot);
146
+ emitChange();
147
+ eventHub.emit(EventName.SCHEMA_CHANGED, store.getSnapshot());
148
+ }
149
+ function canUndo() {
150
+ return undoStack.length > 0;
151
+ }
152
+ function canRedo() {
153
+ return redoStack.length > 0;
154
+ }
155
+ function beginTransaction(label) {
156
+ if (inTransaction) {
157
+ console.warn("[dragcraft/core] Transaction already in progress, ignoring beginTransaction");
158
+ return;
159
+ }
160
+ inTransaction = true;
161
+ transactionLabel = label ?? "transaction";
162
+ transactionSnapshot = store.getSnapshot();
163
+ }
164
+ function commitTransaction() {
165
+ if (!inTransaction) {
166
+ console.warn("[dragcraft/core] No transaction in progress, ignoring commitTransaction");
167
+ return;
168
+ }
169
+ if (transactionSnapshot && transactionSnapshot !== store.getSnapshot()) pushUndo(transactionLabel, transactionSnapshot);
170
+ resetTransaction();
171
+ emitChange();
172
+ }
173
+ function discardTransaction() {
174
+ if (!inTransaction) {
175
+ console.warn("[dragcraft/core] No transaction in progress, ignoring discardTransaction");
176
+ return;
177
+ }
178
+ const restored = transactionSnapshot !== null && transactionSnapshot !== store.getSnapshot();
179
+ if (restored) store.restoreSnapshot(transactionSnapshot);
180
+ resetTransaction();
181
+ emitChange();
182
+ if (restored) eventHub.emit(EventName.SCHEMA_CHANGED, store.getSnapshot());
183
+ }
184
+ function isInTransaction() {
185
+ return inTransaction;
186
+ }
187
+ function clear() {
188
+ undoStack.length = 0;
189
+ redoStack.length = 0;
190
+ resetTransaction();
191
+ emitChange();
192
+ }
193
+ const history = {
194
+ state,
195
+ pushSnapshot,
196
+ undo,
197
+ redo,
198
+ canUndo,
199
+ canRedo,
200
+ beginTransaction,
201
+ commitTransaction,
202
+ discardTransaction,
203
+ isInTransaction,
204
+ clear
205
+ };
206
+ ownedSnapshotPushers.set(history, pushOwnedSnapshot);
207
+ return history;
208
+ }
209
+ //#endregion
210
+ //#region src/command-bus.ts
211
+ const COMMAND_EVENT_MAP = {
212
+ [CommandType.ADD_NODE]: EventName.NODE_ADDED,
213
+ [CommandType.MOVE_NODE]: EventName.NODE_MOVED,
214
+ [CommandType.REMOVE_NODE]: EventName.NODE_REMOVED,
215
+ [CommandType.DUPLICATE_NODE]: EventName.NODE_DUPLICATED,
216
+ [CommandType.CHANGE_CONTAINER_VARIANT]: EventName.CONTAINER_VARIANT_CHANGED,
217
+ [CommandType.UPDATE_PROPS]: EventName.NODE_UPDATED,
218
+ [CommandType.SET_GLOBAL_CONFIG]: EventName.GLOBAL_CONFIG_CHANGED
219
+ };
220
+ function normalizeHandlerResult(result) {
221
+ if (result === false) return {
222
+ ok: false,
223
+ code: "COMMAND_REJECTED"
224
+ };
225
+ if (!result) return {
226
+ ok: true,
227
+ changed: true
228
+ };
229
+ if (!result.ok) return result;
230
+ return {
231
+ ...result,
232
+ changed: result.changed ?? true
233
+ };
234
+ }
235
+ function createCommandBus(store, registry, eventHub, history) {
236
+ const handlers = /* @__PURE__ */ new Map();
237
+ let executing = false;
238
+ const interactionStore = {
239
+ selectedNodeId: readonly(store.selectedNodeId),
240
+ hoveredNodeId: readonly(store.hoveredNodeId),
241
+ selectNode: store.selectNode,
242
+ hoverNode: store.hoverNode
243
+ };
244
+ function registerHandler(type, handler) {
245
+ handlers.set(type, handler);
246
+ }
247
+ function execute(command) {
248
+ if (executing) return {
249
+ ok: false,
250
+ code: "COMMAND_REENTRANT"
251
+ };
252
+ executing = true;
253
+ try {
254
+ const handler = handlers.get(command.type);
255
+ if (!handler) {
256
+ console.warn(`[dragcraft/core] No handler registered for command type: "${command.type}"`);
257
+ return {
258
+ ok: false,
259
+ code: "COMMAND_HANDLER_MISSING"
260
+ };
261
+ }
262
+ const beforeSnapshot = store.getSnapshot();
263
+ const draft = cloneSchema(beforeSnapshot);
264
+ const ctx = {
265
+ schema: beforeSnapshot,
266
+ draft,
267
+ store: interactionStore,
268
+ registry
269
+ };
270
+ let result;
271
+ try {
272
+ result = handler(ctx, command.payload);
273
+ } catch (error) {
274
+ console.error(`[dragcraft/core] Command "${command.type}" failed, discarding draft:`, error);
275
+ return {
276
+ ok: false,
277
+ code: "COMMAND_HANDLER_FAILED",
278
+ message: error instanceof Error ? error.message : String(error)
279
+ };
280
+ }
281
+ const normalized = normalizeHandlerResult(result);
282
+ if (!normalized.ok) return normalized;
283
+ if (!normalized.changed) return normalized;
284
+ const snapshot = store.commitSchema(draft);
285
+ pushOwnedHistorySnapshot(history, command.type, beforeSnapshot);
286
+ const specificEvent = COMMAND_EVENT_MAP[command.type];
287
+ if (specificEvent) eventHub.emit(specificEvent, normalized.eventPayload ?? command.payload);
288
+ eventHub.emit(EventName.SCHEMA_CHANGED, snapshot);
289
+ return normalized;
290
+ } finally {
291
+ executing = false;
292
+ }
293
+ }
294
+ return {
295
+ execute,
296
+ registerHandler
297
+ };
298
+ }
299
+ //#endregion
300
+ //#region src/schema-index.ts
301
+ function buildSchemaIndex(schema) {
302
+ const index = /* @__PURE__ */ new Map();
303
+ const diagnostics = [];
304
+ const add = (node, location) => {
305
+ if (node.id === schema.root.id) diagnostics.push({
306
+ code: "SCHEMA_NODE_ID_DUPLICATE",
307
+ severity: "error",
308
+ nodeId: node.id,
309
+ ...location.owner === "root" ? {} : {
310
+ ownerId: location.owner,
311
+ regionId: location.regionId
312
+ }
313
+ });
314
+ if (index.has(node.id)) diagnostics.push({
315
+ code: "SCHEMA_NODE_ID_DUPLICATE",
316
+ severity: "error",
317
+ nodeId: node.id
318
+ });
319
+ else index.set(node.id, {
320
+ node,
321
+ ...location
322
+ });
323
+ };
324
+ for (const [rootIndex, node] of (schema.root.children ?? []).entries()) {
325
+ add(node, {
326
+ owner: "root",
327
+ index: rootIndex,
328
+ depth: 1
329
+ });
330
+ for (const [regionId, children] of Object.entries(node.container?.regions ?? {})) for (const [childIndex, child] of children.entries()) {
331
+ add(child, {
332
+ owner: node.id,
333
+ regionId,
334
+ index: childIndex,
335
+ depth: 2
336
+ });
337
+ if (child.container) diagnostics.push({
338
+ code: "SCHEMA_CONTAINER_NESTED",
339
+ severity: "error",
340
+ nodeId: child.id,
341
+ ownerId: node.id,
342
+ regionId
343
+ });
344
+ }
345
+ }
346
+ return {
347
+ index,
348
+ diagnostics
349
+ };
350
+ }
351
+ function findIndexedNode(result, nodeId) {
352
+ return result.index.get(nodeId);
353
+ }
354
+ //#endregion
355
+ //#region src/schema-validation.ts
356
+ function isRecord$2(value) {
357
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
358
+ const prototype = Object.getPrototypeOf(value);
359
+ return prototype === Object.prototype || prototype === null;
360
+ }
361
+ function collectSchemaStructuralDiagnostics(input) {
362
+ const diagnostics = [];
363
+ if (!isRecord$2(input) || typeof input.version !== "string" || input.version.length === 0 || !Object.hasOwn(input, "root") || !input.root) {
364
+ diagnostics.push({
365
+ code: "SCHEMA_ENVELOPE_INVALID",
366
+ severity: "error"
367
+ });
368
+ return diagnostics;
369
+ }
370
+ if (!isRecord$2(input.globalConfig)) {
371
+ diagnostics.push({
372
+ code: "SCHEMA_GLOBAL_CONFIG_INVALID",
373
+ severity: "error",
374
+ path: "globalConfig"
375
+ });
376
+ return diagnostics;
377
+ }
378
+ const root = input.root;
379
+ if (!isRecord$2(root) || typeof root.id !== "string" || typeof root.type !== "string" || !isRecord$2(root.props)) {
380
+ diagnostics.push({
381
+ code: "SCHEMA_ROOT_INVALID",
382
+ severity: "error",
383
+ path: "root"
384
+ });
385
+ return diagnostics;
386
+ }
387
+ const inspectNode = (value, path, ownerId, regionId) => {
388
+ if (!isRecord$2(value) || typeof value.id !== "string" || typeof value.type !== "string" || !isRecord$2(value.props)) {
389
+ diagnostics.push({
390
+ code: "SCHEMA_NODE_INVALID",
391
+ severity: "error",
392
+ path,
393
+ nodeId: isRecord$2(value) && typeof value.id === "string" ? value.id : void 0,
394
+ ownerId,
395
+ regionId
396
+ });
397
+ return;
398
+ }
399
+ const nodeId = value.id;
400
+ if (value.container === void 0) return;
401
+ if (!isRecord$2(value.container) || typeof value.container.variant !== "string") {
402
+ diagnostics.push({
403
+ code: "CONTAINER_STATE_INVALID",
404
+ severity: "error",
405
+ nodeId,
406
+ path: `${path}.container`
407
+ });
408
+ return;
409
+ }
410
+ if (!isRecord$2(value.container.regions)) {
411
+ diagnostics.push({
412
+ code: "CONTAINER_REGIONS_INVALID",
413
+ severity: "error",
414
+ nodeId,
415
+ path: `${path}.container.regions`
416
+ });
417
+ return;
418
+ }
419
+ for (const [childRegionId, children] of Object.entries(value.container.regions)) {
420
+ if (!Array.isArray(children)) {
421
+ diagnostics.push({
422
+ code: "CONTAINER_REGION_CHILDREN_INVALID",
423
+ severity: "error",
424
+ nodeId,
425
+ regionId: childRegionId,
426
+ path: `${path}.container.regions.${childRegionId}`
427
+ });
428
+ continue;
429
+ }
430
+ children.forEach((child, index) => inspectNode(child, `${path}.container.regions.${childRegionId}.${index}`, nodeId, childRegionId));
431
+ }
432
+ };
433
+ if (root.children !== void 0 && !Array.isArray(root.children)) {
434
+ diagnostics.push({
435
+ code: "SCHEMA_CHILDREN_INVALID",
436
+ severity: "error",
437
+ path: "root.children"
438
+ });
439
+ return diagnostics;
440
+ }
441
+ for (const [index, node] of (root.children ?? []).entries()) inspectNode(node, `root.children.${index}`);
442
+ return diagnostics;
443
+ }
444
+ function validateSchema(input, registry) {
445
+ const schema = cloneSchema(input);
446
+ const structuralDiagnostics = collectSchemaStructuralDiagnostics(schema);
447
+ if (structuralDiagnostics.length > 0) return {
448
+ valid: false,
449
+ schema,
450
+ diagnostics: structuralDiagnostics
451
+ };
452
+ const diagnostics = [...buildSchemaIndex(schema).diagnostics];
453
+ for (const node of schema.root.children ?? []) {
454
+ for (const [regionId, children] of Object.entries(node.container?.regions ?? {})) for (const child of children) {
455
+ if (registry.getWidget(child.type)?.container && !child.container) diagnostics.push({
456
+ code: "SCHEMA_CONTAINER_NESTED",
457
+ severity: "error",
458
+ nodeId: child.id,
459
+ ownerId: node.id,
460
+ regionId
461
+ });
462
+ if (child.layout?.placement !== void 0 || child.layout?.order !== void 0) diagnostics.push({
463
+ code: "CONTAINER_CHILD_PAGE_LAYOUT_FORBIDDEN",
464
+ severity: "error",
465
+ nodeId: child.id,
466
+ ownerId: node.id,
467
+ regionId
468
+ });
469
+ }
470
+ const meta = registry.getWidget(node.type);
471
+ if (node.container && !meta) {
472
+ diagnostics.push({
473
+ code: "UNRESOLVED_CONTAINER_TYPE",
474
+ severity: "warning",
475
+ nodeId: node.id
476
+ });
477
+ continue;
478
+ }
479
+ if (node.container && !meta?.container) {
480
+ diagnostics.push({
481
+ code: "CONTAINER_CAPABILITY_MISMATCH",
482
+ severity: "error",
483
+ nodeId: node.id
484
+ });
485
+ continue;
486
+ }
487
+ if (!node.container && meta?.container) {
488
+ diagnostics.push({
489
+ code: "CONTAINER_STATE_MISSING",
490
+ severity: "error",
491
+ nodeId: node.id
492
+ });
493
+ continue;
494
+ }
495
+ if (!node.container || !meta?.container) continue;
496
+ const variant = meta.container.variants[node.container.variant];
497
+ if (!variant) {
498
+ diagnostics.push({
499
+ code: "CONTAINER_VARIANT_UNKNOWN",
500
+ severity: "error",
501
+ nodeId: node.id
502
+ });
503
+ continue;
504
+ }
505
+ const knownRegionIds = new Set(variant.regions.map((region) => region.id));
506
+ for (const regionId of Object.keys(node.container.regions)) if (!knownRegionIds.has(regionId)) diagnostics.push({
507
+ code: "CONTAINER_REGION_UNKNOWN",
508
+ severity: "error",
509
+ nodeId: node.id,
510
+ regionId
511
+ });
512
+ for (const region of variant.regions) node.container.regions[region.id] ??= [];
513
+ for (const region of variant.regions) {
514
+ const children = node.container.regions[region.id];
515
+ const { minItems = 0, maxItems = Number.POSITIVE_INFINITY } = region.constraints ?? {};
516
+ if (children.length < minItems) diagnostics.push({
517
+ code: "CONTAINER_REGION_MIN_ITEMS",
518
+ severity: "error",
519
+ nodeId: node.id,
520
+ regionId: region.id,
521
+ details: {
522
+ actual: children.length,
523
+ minItems
524
+ }
525
+ });
526
+ if (children.length > maxItems) diagnostics.push({
527
+ code: "CONTAINER_REGION_MAX_ITEMS",
528
+ severity: "error",
529
+ nodeId: node.id,
530
+ regionId: region.id,
531
+ details: {
532
+ actual: children.length,
533
+ maxItems
534
+ }
535
+ });
536
+ for (const child of children) {
537
+ if (region.constraints?.includeTypes && !region.constraints.includeTypes.includes(child.type)) diagnostics.push({
538
+ code: "CONTAINER_TYPE_NOT_INCLUDED",
539
+ severity: "error",
540
+ nodeId: child.id,
541
+ ownerId: node.id,
542
+ regionId: region.id
543
+ });
544
+ if (region.constraints?.excludeTypes?.includes(child.type)) diagnostics.push({
545
+ code: "CONTAINER_TYPE_EXCLUDED",
546
+ severity: "error",
547
+ nodeId: child.id,
548
+ ownerId: node.id,
549
+ regionId: region.id
550
+ });
551
+ }
552
+ }
553
+ }
554
+ return {
555
+ valid: diagnostics.every((item) => item.severity !== "error"),
556
+ schema,
557
+ diagnostics
558
+ };
559
+ }
560
+ //#endregion
561
+ //#region src/container-placement.ts
562
+ function placementFailureDetails(ctx) {
563
+ return {
564
+ nodeId: ctx.child.id,
565
+ containerId: ctx.callbackContext.container.id,
566
+ regionId: ctx.region.id
567
+ };
568
+ }
569
+ function resolvePlacementDecision(ctx) {
570
+ if (ctx.child.container || ctx.childHasContainerCapability) return {
571
+ allowed: false,
572
+ code: "CONTAINER_NESTING_FORBIDDEN"
573
+ };
574
+ const constraints = ctx.region.constraints ?? {};
575
+ if (constraints.includeTypes && !constraints.includeTypes.includes(ctx.child.type)) return {
576
+ allowed: false,
577
+ code: "CONTAINER_TYPE_NOT_INCLUDED"
578
+ };
579
+ if (constraints.excludeTypes?.includes(ctx.child.type)) return {
580
+ allowed: false,
581
+ code: "CONTAINER_TYPE_EXCLUDED"
582
+ };
583
+ if (ctx.targetCount + 1 > (constraints.maxItems ?? Number.POSITIVE_INFINITY)) return {
584
+ allowed: false,
585
+ code: "CONTAINER_REGION_MAX_ITEMS"
586
+ };
587
+ try {
588
+ const decision = cloneDeep(ctx.definition.canPlace?.(cloneDeep(ctx.callbackContext)) ?? { allowed: true });
589
+ if (!decision || typeof decision.allowed !== "boolean") return {
590
+ allowed: false,
591
+ code: "CONTAINER_PLACEMENT_PREDICATE_INVALID",
592
+ details: placementFailureDetails(ctx)
593
+ };
594
+ return decision;
595
+ } catch (error) {
596
+ return {
597
+ allowed: false,
598
+ code: "CONTAINER_PLACEMENT_PREDICATE_FAILED",
599
+ message: error instanceof Error ? error.message : String(error),
600
+ details: placementFailureDetails(ctx)
601
+ };
602
+ }
603
+ }
604
+ function isContainerState$1(value) {
605
+ if (!value || typeof value !== "object") return false;
606
+ const candidate = value;
607
+ return typeof candidate.variant === "string" && Boolean(candidate.regions) && typeof candidate.regions === "object" && !Array.isArray(candidate.regions) && Object.values(candidate.regions).every(Array.isArray);
608
+ }
609
+ function buildCandidateIndex(schema, candidateNode) {
610
+ return buildSchemaIndex({
611
+ version: schema.version,
612
+ globalConfig: {},
613
+ root: {
614
+ id: schema.root.id,
615
+ type: schema.root.type,
616
+ props: {},
617
+ children: [candidateNode]
618
+ }
619
+ });
620
+ }
621
+ function createContainerState(node, schema, registry, createNode) {
622
+ const definition = registry.getWidget(node.type)?.container;
623
+ if (!definition) return {
624
+ ok: false,
625
+ code: "CONTAINER_DEFINITION_MISSING"
626
+ };
627
+ const variant = definition.variants[definition.defaultVariant];
628
+ if (!variant) return {
629
+ ok: false,
630
+ code: "CONTAINER_DEFAULT_VARIANT_MISSING"
631
+ };
632
+ const emptyState = {
633
+ variant: definition.defaultVariant,
634
+ regions: Object.fromEntries(variant.regions.map((region) => [region.id, []]))
635
+ };
636
+ let state = emptyState;
637
+ try {
638
+ state = cloneDeep(definition.createInitialState ? definition.createInitialState({
639
+ containerNode: cloneDeep(node),
640
+ schema: cloneSchema(schema),
641
+ createNode
642
+ }) : emptyState);
643
+ } catch (error) {
644
+ return {
645
+ ok: false,
646
+ code: "CONTAINER_INITIAL_STATE_FAILED",
647
+ message: error instanceof Error ? error.message : String(error),
648
+ details: {
649
+ nodeId: node.id,
650
+ containerId: node.id
651
+ }
652
+ };
653
+ }
654
+ if (!isContainerState$1(state)) return {
655
+ ok: false,
656
+ code: "CONTAINER_INITIAL_STATE_INVALID",
657
+ details: {
658
+ nodeId: node.id,
659
+ containerId: node.id,
660
+ diagnostics: []
661
+ }
662
+ };
663
+ const candidateNode = cloneDeep(node);
664
+ candidateNode.container = state;
665
+ try {
666
+ const candidateIndex = buildCandidateIndex(schema, candidateNode);
667
+ const candidateNodeIds = new Set(candidateIndex.index.keys());
668
+ const candidateSchema = cloneSchema(schema);
669
+ candidateSchema.root.children ??= [];
670
+ candidateSchema.root.children.push(candidateNode);
671
+ const ownDiagnostics = validateSchema(candidateSchema, registry).diagnostics.filter((diagnostic) => diagnostic.nodeId === node.id || diagnostic.ownerId === node.id || diagnostic.nodeId !== void 0 && candidateNodeIds.has(diagnostic.nodeId));
672
+ if (ownDiagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
673
+ ok: false,
674
+ code: "CONTAINER_INITIAL_STATE_INVALID",
675
+ details: {
676
+ nodeId: node.id,
677
+ containerId: node.id,
678
+ diagnostics: ownDiagnostics
679
+ }
680
+ };
681
+ } catch (error) {
682
+ return {
683
+ ok: false,
684
+ code: "CONTAINER_INITIAL_STATE_INVALID",
685
+ message: error instanceof Error ? error.message : String(error),
686
+ details: {
687
+ nodeId: node.id,
688
+ containerId: node.id,
689
+ diagnostics: []
690
+ }
691
+ };
692
+ }
693
+ return {
694
+ ok: true,
695
+ state: cloneDeep(state)
696
+ };
697
+ }
698
+ function createRegisteredNode(registry, createId = generateShortId) {
699
+ return (type, overrides = {}) => {
700
+ const meta = registry.getWidget(type);
701
+ if (!meta) throw new Error(`Cannot initialize unregistered widget type: ${type}`);
702
+ const layout = overrides.layout !== void 0 ? cloneDeep(overrides.layout) : cloneDeep(meta.defaultLayout);
703
+ if (layout) {
704
+ delete layout.placement;
705
+ delete layout.order;
706
+ }
707
+ return {
708
+ id: createId(),
709
+ type,
710
+ props: {
711
+ ...cloneDeep(meta.defaultProps),
712
+ ...cloneDeep(overrides.props ?? {})
713
+ },
714
+ style: overrides.style !== void 0 ? cloneDeep(overrides.style) : cloneDeep(meta.defaultStyle),
715
+ layout
716
+ };
717
+ };
718
+ }
719
+ //#endregion
720
+ //#region src/helpers.ts
721
+ function cloneNodeSubtree(node, createId) {
722
+ const clone = cloneDeep(node);
723
+ clone.id = createId();
724
+ for (const children of Object.values(clone.container?.regions ?? {})) for (let index = 0; index < children.length; index++) children[index] = cloneNodeSubtree(children[index], createId);
725
+ return clone;
726
+ }
727
+ function collectSubtreeIds(node) {
728
+ const ids = /* @__PURE__ */ new Set([node.id]);
729
+ for (const children of Object.values(node.container?.regions ?? {})) for (const child of children) for (const id of collectSubtreeIds(child)) ids.add(id);
730
+ return ids;
731
+ }
732
+ /**
733
+ * Find a node by ID in the shallow ownership structure.
734
+ */
735
+ function findNodeById(root, id) {
736
+ if (root.id === id) return root;
737
+ return buildSchemaIndex({
738
+ version: "",
739
+ globalConfig: {},
740
+ root
741
+ }).index.get(id)?.node ?? null;
742
+ }
743
+ /**
744
+ * Find the parent node of a given node ID.
745
+ * Returns the owning root or container location, or null if not found.
746
+ */
747
+ function findParentNode(root, targetId) {
748
+ const indexed = buildSchemaIndex({
749
+ version: "",
750
+ globalConfig: {},
751
+ root
752
+ });
753
+ const location = indexed.index.get(targetId);
754
+ if (!location) return null;
755
+ if (location.owner === "root") return {
756
+ parent: root,
757
+ index: location.index
758
+ };
759
+ const parent = indexed.index.get(location.owner)?.node;
760
+ return parent && location.regionId ? {
761
+ parent,
762
+ regionId: location.regionId,
763
+ index: location.index
764
+ } : null;
765
+ }
766
+ /**
767
+ * Remove a node from the tree by ID.
768
+ * Returns the removed node or null if not found.
769
+ */
770
+ function removeNodeFromTree(root, nodeId) {
771
+ const result = findParentNode(root, nodeId);
772
+ if (!result || result.regionId) return null;
773
+ const { parent, index } = result;
774
+ return parent.children.splice(index, 1)[0];
775
+ }
776
+ /**
777
+ * Insert a node into a parent's children array at a given index.
778
+ * If index is undefined, appends to the end.
779
+ */
780
+ function insertNodeIntoTree(parent, node, index) {
781
+ if (!parent.children) parent.children = [];
782
+ if (index !== void 0 && index >= 0 && index <= parent.children.length) parent.children.splice(index, 0, node);
783
+ else parent.children.push(node);
784
+ }
785
+ /**
786
+ * Walk root and its direct children (one level), calling visitor for each node.
787
+ * If visitor returns false, stop traversal.
788
+ *
789
+ * Note: This does NOT recurse into grandchildren — the schema is a flat
790
+ * two-level structure (root → widgets).
791
+ */
792
+ function walkFlatChildren(root, visitor) {
793
+ if (visitor(root) === false) return;
794
+ if (root.children) {
795
+ for (const child of root.children) if (visitor(child) === false) return;
796
+ }
797
+ }
798
+ //#endregion
799
+ //#region src/layout.ts
800
+ const DEFAULT_LAYOUT_REGION = "content";
801
+ const DEFAULT_SORT_SCOPE = "content";
802
+ const DEFAULT_LAYER = "float";
803
+ function resolveFlowPlacement(placement) {
804
+ const region = placement?.region ?? "content";
805
+ return {
806
+ kind: "flow",
807
+ region,
808
+ sortScope: placement?.sortScope === void 0 ? region === "content" ? DEFAULT_SORT_SCOPE : false : placement.sortScope
809
+ };
810
+ }
811
+ function resolveChromePlacement(placement) {
812
+ return {
813
+ kind: "chrome",
814
+ edge: placement.edge,
815
+ position: placement.position ?? "fixed",
816
+ reserve: {
817
+ mode: placement.reserve?.mode ?? "measure",
818
+ size: placement.reserve?.size
819
+ },
820
+ avoidContent: placement.avoidContent ?? true
821
+ };
822
+ }
823
+ function resolveLayerPlacement(placement) {
824
+ const hasAnchor = placement.anchor !== void 0;
825
+ return {
826
+ kind: "layer",
827
+ layer: placement.layer ?? "float",
828
+ mode: placement.mode ?? (hasAnchor ? "framework" : "self"),
829
+ anchor: placement.anchor ?? {
830
+ block: "end",
831
+ inline: "end"
832
+ },
833
+ offset: placement.offset,
834
+ avoid: placement.avoid ?? ["safe-area", "chrome"]
835
+ };
836
+ }
837
+ function resolvePlacement(placement) {
838
+ if (!placement || placement.kind === "flow") return resolveFlowPlacement(placement);
839
+ if (placement.kind === "chrome") return resolveChromePlacement(placement);
840
+ return resolveLayerPlacement(placement);
841
+ }
842
+ function resolveNodeLayout(node, registry, schema) {
843
+ const layout = {
844
+ ...registry.getWidget(node.type)?.defaultLayout ?? {},
845
+ ...node.layout ?? {}
846
+ };
847
+ const placement = resolvePlacement(layout.placement);
848
+ const rawVisible = layout.visible ?? true;
849
+ const visible = typeof rawVisible === "function" ? schema !== void 0 && rawVisible({
850
+ node,
851
+ schema
852
+ }) : rawVisible;
853
+ return {
854
+ placement,
855
+ region: placement.kind === "flow" ? placement.region : void 0,
856
+ sortScope: placement.kind === "flow" ? placement.sortScope : false,
857
+ order: layout.order,
858
+ visible
859
+ };
860
+ }
861
+ function sortEntries(entries) {
862
+ entries.sort((a, b) => {
863
+ const orderA = a.layout.order ?? a.arrayIndex;
864
+ const orderB = b.layout.order ?? b.arrayIndex;
865
+ if (orderA !== orderB) return orderA - orderB;
866
+ return a.arrayIndex - b.arrayIndex;
867
+ });
868
+ }
869
+ function pushEntry(map, key, entry) {
870
+ const entries = map.get(key);
871
+ if (entries) entries.push(entry);
872
+ else map.set(key, [entry]);
873
+ }
874
+ function edgeOrder(edge) {
875
+ switch (edge) {
876
+ case "block-start": return 0;
877
+ case "inline-start": return 1;
878
+ case "inline-end": return 2;
879
+ case "block-end": return 3;
880
+ }
881
+ }
882
+ function createLayoutPlan(schema, registry) {
883
+ const children = schema.root.children ?? [];
884
+ const entries = [];
885
+ const regions = /* @__PURE__ */ new Map();
886
+ const chrome = [];
887
+ const layers = /* @__PURE__ */ new Map();
888
+ const sortScopes = /* @__PURE__ */ new Map();
889
+ for (let arrayIndex = 0; arrayIndex < children.length; arrayIndex++) {
890
+ const node = children[arrayIndex];
891
+ const layout = resolveNodeLayout(node, registry, schema);
892
+ const entry = {
893
+ node,
894
+ arrayIndex,
895
+ layout
896
+ };
897
+ entries.push(entry);
898
+ const placement = layout.placement;
899
+ if (placement.kind === "flow") {
900
+ pushEntry(regions, placement.region, entry);
901
+ if (placement.sortScope !== false) pushEntry(sortScopes, placement.sortScope, entry);
902
+ } else if (placement.kind === "chrome") chrome.push(entry);
903
+ else pushEntry(layers, placement.layer, entry);
904
+ }
905
+ sortEntries(entries);
906
+ for (const regionEntries of regions.values()) sortEntries(regionEntries);
907
+ for (const scopeEntries of sortScopes.values()) sortEntries(scopeEntries);
908
+ for (const layerEntries of layers.values()) sortEntries(layerEntries);
909
+ chrome.sort((a, b) => {
910
+ const placementA = a.layout.placement;
911
+ const placementB = b.layout.placement;
912
+ const edgeDelta = edgeOrder(placementA.edge) - edgeOrder(placementB.edge);
913
+ if (edgeDelta !== 0) return edgeDelta;
914
+ const orderA = a.layout.order ?? a.arrayIndex;
915
+ const orderB = b.layout.order ?? b.arrayIndex;
916
+ if (orderA !== orderB) return orderA - orderB;
917
+ return a.arrayIndex - b.arrayIndex;
918
+ });
919
+ return {
920
+ entries,
921
+ regions,
922
+ chrome,
923
+ layers,
924
+ sortScopes,
925
+ insets: { contributors: chrome.map((entry) => {
926
+ const placement = entry.layout.placement;
927
+ return placement.avoidContent ? {
928
+ edge: placement.edge,
929
+ sourceNodeId: entry.node.id,
930
+ reserve: placement.reserve
931
+ } : null;
932
+ }).filter((item) => item !== null) }
933
+ };
934
+ }
935
+ function getLayoutRegionEntries(plan, region = DEFAULT_LAYOUT_REGION) {
936
+ return plan.regions.get(region) ?? [];
937
+ }
938
+ function getSortScopeEntries(plan, sortScope = DEFAULT_SORT_SCOPE) {
939
+ return plan.sortScopes.get(sortScope) ?? [];
940
+ }
941
+ function getSortScopeNodes(plan, sortScope = DEFAULT_SORT_SCOPE) {
942
+ return getSortScopeEntries(plan, sortScope).map((entry) => entry.node);
943
+ }
944
+ function getSortableArrayIndexForInsert(scopeEntries, allChildren, visualIndex) {
945
+ if (scopeEntries.length === 0) return allChildren.length;
946
+ if (visualIndex <= 0) return scopeEntries[0].arrayIndex;
947
+ if (visualIndex >= scopeEntries.length) return scopeEntries[scopeEntries.length - 1].arrayIndex + 1;
948
+ return scopeEntries[visualIndex].arrayIndex;
949
+ }
950
+ function resolveNodeSource(schema, indexed, nodeId) {
951
+ const location = indexed.index.get(nodeId);
952
+ if (!location) return {
953
+ ok: false,
954
+ code: "NODE_NOT_FOUND"
955
+ };
956
+ if (location.owner === "root") return {
957
+ ok: true,
958
+ value: {
959
+ location,
960
+ children: schema.root.children ?? [],
961
+ index: location.index,
962
+ destination: {
963
+ kind: "root",
964
+ index: location.index
965
+ }
966
+ }
967
+ };
968
+ const owner = indexed.index.get(location.owner)?.node;
969
+ const regionId = location.regionId;
970
+ const children = regionId ? owner?.container?.regions[regionId] : void 0;
971
+ if (!owner || !regionId || !children) return {
972
+ ok: false,
973
+ code: "CONTAINER_OWNER_INVALID"
974
+ };
975
+ return {
976
+ ok: true,
977
+ value: {
978
+ location,
979
+ children,
980
+ index: location.index,
981
+ destination: {
982
+ kind: "container",
983
+ containerId: owner.id,
984
+ regionId,
985
+ index: location.index
986
+ }
987
+ }
988
+ };
989
+ }
990
+ function resolveDestination(schema, registry, destination) {
991
+ if (destination.kind === "root") {
992
+ schema.root.children ??= [];
993
+ return {
994
+ ok: true,
995
+ value: {
996
+ children: schema.root.children,
997
+ destination
998
+ }
999
+ };
1000
+ }
1001
+ const location = buildSchemaIndex(schema).index.get(destination.containerId);
1002
+ const container = location?.owner === "root" ? location.node : void 0;
1003
+ const definition = container && registry.getWidget(container.type)?.container;
1004
+ const variant = container?.container && definition?.variants[container.container.variant];
1005
+ const region = variant?.regions.find((item) => item.id === destination.regionId);
1006
+ const children = container?.container?.regions[destination.regionId];
1007
+ if (!container || !definition || !variant) return {
1008
+ ok: false,
1009
+ code: "CONTAINER_UNRESOLVED"
1010
+ };
1011
+ if (!region || !children) return {
1012
+ ok: false,
1013
+ code: "CONTAINER_REGION_UNKNOWN"
1014
+ };
1015
+ return {
1016
+ ok: true,
1017
+ value: {
1018
+ children,
1019
+ destination,
1020
+ container,
1021
+ definition,
1022
+ variant,
1023
+ region
1024
+ }
1025
+ };
1026
+ }
1027
+ function clampInsertIndex(index, length) {
1028
+ return Math.max(0, Math.min(index ?? length, length));
1029
+ }
1030
+ function stripPageLayout(node) {
1031
+ const clone = cloneDeep(node);
1032
+ if (clone.layout) {
1033
+ delete clone.layout.placement;
1034
+ delete clone.layout.order;
1035
+ }
1036
+ return clone;
1037
+ }
1038
+ //#endregion
1039
+ //#region src/sortable.ts
1040
+ /**
1041
+ * Collect the indices of all widgets whose `sortable` behavior resolves to false.
1042
+ * These widgets must stay at their current array index (absolute index locking).
1043
+ *
1044
+ * @param _children - The flat widget list (`root.children`)
1045
+ * @param registry - Widget meta registry for resolving behavior predicates
1046
+ * @param schema - Current designer schema (for predicate evaluation context)
1047
+ * @param sortScope - Sort scope to evaluate
1048
+ */
1049
+ function getLockedIndices(_children, registry, schema, sortScope = DEFAULT_SORT_SCOPE) {
1050
+ return getLockedIndicesFromEntries(getSortScopeEntries(createLayoutPlan(schema, registry), sortScope), registry, schema);
1051
+ }
1052
+ function getLockedIndicesFromEntries(scopeEntries, registry, schema) {
1053
+ return getLockedIndicesFromNodes(scopeEntries.map((entry) => entry.node), registry, schema);
1054
+ }
1055
+ function getLockedIndicesFromNodes(nodes, registry, schema) {
1056
+ const locked = /* @__PURE__ */ new Set();
1057
+ for (let i = 0; i < nodes.length; i++) {
1058
+ const node = nodes[i];
1059
+ const meta = registry.getWidget(node.type);
1060
+ if (!meta) continue;
1061
+ if (!resolveBehavior(meta.sortable, {
1062
+ node,
1063
+ schema
1064
+ })) locked.add(i);
1065
+ }
1066
+ return locked;
1067
+ }
1068
+ /**
1069
+ * Can a new node be inserted at `insertIndex` without shifting any locked widget?
1070
+ *
1071
+ * Inserting at index `i` shifts all widgets at index >= `i` rightward by 1.
1072
+ * If any locked widget has index >= `insertIndex`, it would be displaced.
1073
+ */
1074
+ function isInsertAllowed(insertIndex, lockedIndices) {
1075
+ for (const L of lockedIndices) if (L >= insertIndex) return false;
1076
+ return true;
1077
+ }
1078
+ /**
1079
+ * Can a node be moved from `srcIdx` to `targetIdx` (post-removal insertion index)
1080
+ * without altering any locked widget's absolute position?
1081
+ *
1082
+ * Mathematical analysis for each locked widget at index L (L !== srcIdx):
1083
+ * - srcIdx < L: Removal shifts L to L-1. Insertion must be at targetIdx <= L-1 to restore it.
1084
+ * - srcIdx > L: L is unaffected by removal. Insertion must satisfy targetIdx > L to keep it at L.
1085
+ */
1086
+ function isMoveAllowed(srcIdx, targetIdx, lockedIndices) {
1087
+ if (lockedIndices.has(srcIdx)) return false;
1088
+ for (const L of lockedIndices) {
1089
+ if (L === srcIdx) continue;
1090
+ if (srcIdx < L) {
1091
+ if (targetIdx > L - 1) return false;
1092
+ } else if (targetIdx <= L) return false;
1093
+ }
1094
+ return true;
1095
+ }
1096
+ /**
1097
+ * Can a node at `removeIndex` be removed without shifting any locked widget?
1098
+ *
1099
+ * Removing at index `i` shifts all widgets at index > `i` leftward by 1.
1100
+ * If any locked widget has index > `removeIndex`, it would be displaced.
1101
+ */
1102
+ function isRemoveAllowed(removeIndex, lockedIndices) {
1103
+ for (const L of lockedIndices) if (L > removeIndex) return false;
1104
+ return true;
1105
+ }
1106
+ /**
1107
+ * Compute the set of valid visual drop indices (0..children.length) for a drag operation.
1108
+ *
1109
+ * For new-widget drops (sourceNodeId === null): checks isInsertAllowed for each position.
1110
+ * For existing-widget moves: converts visual gap index to post-removal target index
1111
+ * and checks isMoveAllowed.
1112
+ *
1113
+ * @param children - The flat widget list
1114
+ * @param lockedIndices - Pre-computed locked indices from getLockedIndices()
1115
+ * @param sourceNodeId - ID of the dragged widget (null if dragging from material panel)
1116
+ */
1117
+ function getValidDropIndices(children, lockedIndices, sourceNodeId) {
1118
+ const valid = /* @__PURE__ */ new Set();
1119
+ const n = children.length;
1120
+ if (lockedIndices.size === 0) {
1121
+ for (let i = 0; i <= n; i++) valid.add(i);
1122
+ return valid;
1123
+ }
1124
+ if (sourceNodeId === null) {
1125
+ for (let i = 0; i <= n; i++) if (isInsertAllowed(i, lockedIndices)) valid.add(i);
1126
+ } else {
1127
+ const srcIdx = children.findIndex((c) => "node" in c ? c.node.id === sourceNodeId : c.id === sourceNodeId);
1128
+ if (srcIdx === -1) return valid;
1129
+ for (let visualIdx = 0; visualIdx <= n; visualIdx++) {
1130
+ let targetIdx = visualIdx;
1131
+ if (targetIdx > srcIdx) targetIdx = targetIdx - 1;
1132
+ if (targetIdx === srcIdx) {
1133
+ valid.add(visualIdx);
1134
+ continue;
1135
+ }
1136
+ if (isMoveAllowed(srcIdx, targetIdx, lockedIndices)) valid.add(visualIdx);
1137
+ }
1138
+ }
1139
+ return valid;
1140
+ }
1141
+ /**
1142
+ * Given a raw visual index and a set of valid indices, find the nearest valid one.
1143
+ * Returns null if no valid index exists.
1144
+ */
1145
+ function findNearestValidIndex(rawIndex, validIndices) {
1146
+ if (validIndices.size === 0) return null;
1147
+ if (validIndices.has(rawIndex)) return rawIndex;
1148
+ let best = null;
1149
+ let bestDist = Infinity;
1150
+ for (const v of validIndices) {
1151
+ const dist = Math.abs(v - rawIndex);
1152
+ if (dist < bestDist) {
1153
+ bestDist = dist;
1154
+ best = v;
1155
+ }
1156
+ }
1157
+ return best;
1158
+ }
1159
+ //#endregion
1160
+ //#region src/commands/add-node.ts
1161
+ function addNodeHandler(ctx, payload) {
1162
+ const { draft: rawSchema, registry } = ctx;
1163
+ const safeSchema = ctx.schema;
1164
+ const meta = registry.getWidget(payload.node.type);
1165
+ const destination = payload.destination ?? { kind: "root" };
1166
+ const createDecision = meta ? resolveCreatable(meta.creatable, {
1167
+ widgetType: payload.node.type,
1168
+ schema: ctx.schema
1169
+ }, true) : { allowed: true };
1170
+ if (!createDecision.allowed) {
1171
+ const reason = createDecision.message ?? createDecision.messageKey ?? createDecision.code;
1172
+ console.warn(`[dragcraft/core] ADD_NODE: blocked by creatable constraint for widget type "${payload.node.type}"${reason ? ` (${reason})` : ""}`);
1173
+ return {
1174
+ ok: false,
1175
+ code: createDecision.code ?? "NODE_NOT_CREATABLE",
1176
+ messageKey: createDecision.messageKey,
1177
+ message: createDecision.message
1178
+ };
1179
+ }
1180
+ const node = cloneDeep(payload.node);
1181
+ const candidateNodeIds = collectSubtreeIds(node);
1182
+ if (node.container && !meta) return {
1183
+ ok: false,
1184
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1185
+ };
1186
+ if (node.container && !meta?.container) return {
1187
+ ok: false,
1188
+ code: "CONTAINER_CAPABILITY_MISMATCH"
1189
+ };
1190
+ if (destination.kind === "container" && meta?.container) return {
1191
+ ok: false,
1192
+ code: "CONTAINER_NESTING_FORBIDDEN"
1193
+ };
1194
+ if (meta?.container && !node.container) {
1195
+ const initialized = createContainerState(node, safeSchema, registry, createRegisteredNode(registry));
1196
+ if (!initialized.ok) return initialized;
1197
+ node.container = initialized.state;
1198
+ }
1199
+ const idCandidate = cloneSchema(safeSchema);
1200
+ idCandidate.root.children ??= [];
1201
+ idCandidate.root.children.push(cloneDeep(node));
1202
+ const idDiagnostics = buildSchemaIndex(idCandidate).diagnostics.filter((diagnostic) => diagnostic.code === "SCHEMA_NODE_ID_DUPLICATE");
1203
+ if (idDiagnostics.length > 0) return {
1204
+ ok: false,
1205
+ code: "SCHEMA_NODE_ID_DUPLICATE",
1206
+ details: { diagnostics: idDiagnostics }
1207
+ };
1208
+ if (node.container) {
1209
+ const candidate = cloneSchema(safeSchema);
1210
+ candidate.root.children ??= [];
1211
+ candidate.root.children.push(cloneDeep(node));
1212
+ const candidateDiagnostics = validateSchema(candidate, registry).diagnostics.filter((diagnostic) => diagnostic.nodeId !== void 0 && candidateNodeIds.has(diagnostic.nodeId) || diagnostic.ownerId !== void 0 && candidateNodeIds.has(diagnostic.ownerId));
1213
+ if (candidateDiagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
1214
+ ok: false,
1215
+ code: "CONTAINER_STATE_INVALID",
1216
+ details: { diagnostics: candidateDiagnostics }
1217
+ };
1218
+ }
1219
+ if (destination.kind === "container") {
1220
+ const targetResult = resolveDestination(safeSchema, registry, destination);
1221
+ if (!targetResult.ok) return targetResult;
1222
+ const target = targetResult.value;
1223
+ if (!target.container || !target.definition || !target.variant || !target.region) return {
1224
+ ok: false,
1225
+ code: "CONTAINER_DESTINATION_REQUIRED"
1226
+ };
1227
+ const index = clampInsertIndex(destination.index, target.children.length);
1228
+ if (!isInsertAllowed(index, getLockedIndicesFromNodes(target.children, registry, safeSchema))) return {
1229
+ ok: false,
1230
+ code: "SORTABLE_LOCK_VIOLATION"
1231
+ };
1232
+ const decision = resolvePlacementDecision({
1233
+ definition: target.definition,
1234
+ region: target.region,
1235
+ child: node,
1236
+ childHasContainerCapability: Boolean(meta?.container),
1237
+ targetCount: target.children.length,
1238
+ callbackContext: {
1239
+ operation: "add",
1240
+ schema: safeSchema,
1241
+ container: target.container,
1242
+ variant: target.variant,
1243
+ region: target.region,
1244
+ child: node,
1245
+ targetIndex: index
1246
+ }
1247
+ });
1248
+ if (!decision.allowed) return {
1249
+ ok: false,
1250
+ code: decision.code ?? "CONTAINER_PLACEMENT_DENIED",
1251
+ messageKey: decision.messageKey,
1252
+ message: decision.message,
1253
+ details: decision.details
1254
+ };
1255
+ const candidate = cloneSchema(safeSchema);
1256
+ const candidateTarget = resolveDestination(candidate, registry, destination);
1257
+ if (!candidateTarget.ok) return candidateTarget;
1258
+ candidateTarget.value.children.splice(index, 0, stripPageLayout(cloneDeep(node)));
1259
+ const candidateDiagnostics = validateSchema(candidate, registry).diagnostics.filter((diagnostic) => diagnostic.nodeId !== void 0 && candidateNodeIds.has(diagnostic.nodeId) || diagnostic.ownerId !== void 0 && candidateNodeIds.has(diagnostic.ownerId));
1260
+ if (candidateDiagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
1261
+ ok: false,
1262
+ code: "SCHEMA_CANDIDATE_INVALID",
1263
+ details: { diagnostics: candidateDiagnostics }
1264
+ };
1265
+ const draftTarget = resolveDestination(rawSchema, registry, destination);
1266
+ if (!draftTarget.ok) return draftTarget;
1267
+ draftTarget.value.children.splice(index, 0, stripPageLayout(node));
1268
+ return {
1269
+ ok: true,
1270
+ eventPayload: {
1271
+ nodeId: node.id,
1272
+ destination: {
1273
+ ...destination,
1274
+ index
1275
+ }
1276
+ }
1277
+ };
1278
+ }
1279
+ rawSchema.root.children ??= [];
1280
+ const rootChildren = rawSchema.root.children;
1281
+ const nodeLayout = resolveNodeLayout(node, registry);
1282
+ const resolvedScope = destination.sortScope ?? (nodeLayout.sortScope === false ? void 0 : nodeLayout.sortScope);
1283
+ let resolvedArrayIndex = rootChildren.length;
1284
+ if (destination.index !== void 0 && resolvedScope !== void 0) {
1285
+ const scopeEntries = getSortScopeEntries(createLayoutPlan(safeSchema, registry), resolvedScope);
1286
+ const lockedIndices = getLockedIndicesFromEntries(scopeEntries, registry, safeSchema);
1287
+ if (!isInsertAllowed(destination.index, lockedIndices)) return {
1288
+ ok: false,
1289
+ code: "SORTABLE_LOCK_VIOLATION"
1290
+ };
1291
+ resolvedArrayIndex = getSortableArrayIndexForInsert(scopeEntries, rootChildren, destination.index);
1292
+ } else if (destination.index !== void 0) resolvedArrayIndex = clampInsertIndex(destination.index, rootChildren.length);
1293
+ const candidate = cloneSchema(safeSchema);
1294
+ candidate.root.children ??= [];
1295
+ candidate.root.children.splice(resolvedArrayIndex, 0, cloneDeep(node));
1296
+ const candidateDiagnostics = validateSchema(candidate, registry).diagnostics.filter((diagnostic) => diagnostic.nodeId !== void 0 && candidateNodeIds.has(diagnostic.nodeId) || diagnostic.ownerId !== void 0 && candidateNodeIds.has(diagnostic.ownerId));
1297
+ if (candidateDiagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
1298
+ ok: false,
1299
+ code: "SCHEMA_CANDIDATE_INVALID",
1300
+ details: { diagnostics: candidateDiagnostics }
1301
+ };
1302
+ rootChildren.splice(resolvedArrayIndex, 0, node);
1303
+ return {
1304
+ ok: true,
1305
+ eventPayload: {
1306
+ nodeId: node.id,
1307
+ destination: {
1308
+ kind: "root",
1309
+ sortScope: resolvedScope,
1310
+ index: resolvedArrayIndex
1311
+ }
1312
+ }
1313
+ };
1314
+ }
1315
+ //#endregion
1316
+ //#region src/commands/change-container-variant.ts
1317
+ function sameRegionIds(left, right) {
1318
+ const leftIds = left.regions.map((region) => region.id).sort();
1319
+ const rightIds = right.regions.map((region) => region.id).sort();
1320
+ return leftIds.length === rightIds.length && leftIds.every((id, index) => id === rightIds[index]);
1321
+ }
1322
+ function isPlainRecord$1(value) {
1323
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1324
+ const prototype = Object.getPrototypeOf(value);
1325
+ return prototype === Object.prototype || prototype === null;
1326
+ }
1327
+ function isSchemaNode(value) {
1328
+ return isPlainRecord$1(value) && typeof value.id === "string" && typeof value.type === "string" && isPlainRecord$1(value.props);
1329
+ }
1330
+ function isContainerState(value) {
1331
+ if (!isPlainRecord$1(value) || typeof value.variant !== "string" || !isPlainRecord$1(value.regions)) return false;
1332
+ return Object.values(value.regions).every((children) => Array.isArray(children) && children.every(isSchemaNode));
1333
+ }
1334
+ function isOptionalString(value) {
1335
+ return value === void 0 || typeof value === "string";
1336
+ }
1337
+ function invalidMigrationResult(containerId) {
1338
+ return {
1339
+ allowed: false,
1340
+ code: "CONTAINER_VARIANT_MIGRATION_INVALID"
1341
+ };
1342
+ }
1343
+ function safelyMigrateVariant(definition, ctx) {
1344
+ if (!definition.migrateVariant) return {
1345
+ allowed: false,
1346
+ code: "CONTAINER_VARIANT_MIGRATION_REQUIRED"
1347
+ };
1348
+ try {
1349
+ const result = cloneDeep(definition.migrateVariant(cloneDeep(ctx)));
1350
+ if (!isPlainRecord$1(result) || typeof result.allowed !== "boolean") return invalidMigrationResult(ctx.container.id);
1351
+ if (result.allowed) {
1352
+ if (!isContainerState(result.state)) return invalidMigrationResult(ctx.container.id);
1353
+ return {
1354
+ allowed: true,
1355
+ state: result.state
1356
+ };
1357
+ }
1358
+ if (!isOptionalString(result.code) || !isOptionalString(result.messageKey) || !isOptionalString(result.message) || result.details !== void 0 && !isPlainRecord$1(result.details)) return invalidMigrationResult(ctx.container.id);
1359
+ return {
1360
+ allowed: false,
1361
+ code: result.code,
1362
+ messageKey: result.messageKey,
1363
+ message: result.message,
1364
+ details: result.details
1365
+ };
1366
+ } catch (error) {
1367
+ return {
1368
+ allowed: false,
1369
+ code: "CONTAINER_VARIANT_MIGRATION_FAILED",
1370
+ message: error instanceof Error ? error.message : String(error),
1371
+ details: {
1372
+ nodeId: ctx.container.id,
1373
+ containerId: ctx.container.id,
1374
+ fromVariantId: ctx.fromVariantId,
1375
+ toVariantId: ctx.toVariantId
1376
+ }
1377
+ };
1378
+ }
1379
+ }
1380
+ function changeContainerVariantHandler(ctx, payload) {
1381
+ const rawSchema = ctx.draft;
1382
+ const indexed = buildSchemaIndex(rawSchema).index.get(payload.containerId);
1383
+ const definition = indexed && ctx.registry.getWidget(indexed.node.type)?.container;
1384
+ if (!indexed?.node.container || !definition) return {
1385
+ ok: false,
1386
+ code: "CONTAINER_UNRESOLVED"
1387
+ };
1388
+ const fromVariantId = indexed.node.container.variant;
1389
+ const from = definition.variants[fromVariantId];
1390
+ const to = definition.variants[payload.variant];
1391
+ if (!from || !to) return {
1392
+ ok: false,
1393
+ code: "CONTAINER_VARIANT_UNKNOWN"
1394
+ };
1395
+ const result = definition.migrateVariant ? safelyMigrateVariant(definition, {
1396
+ schema: rawSchema,
1397
+ container: indexed.node,
1398
+ fromVariantId,
1399
+ toVariantId: payload.variant,
1400
+ fromVariant: from,
1401
+ toVariant: to,
1402
+ state: indexed.node.container
1403
+ }) : sameRegionIds(from, to) ? {
1404
+ allowed: true,
1405
+ state: {
1406
+ ...cloneDeep(indexed.node.container),
1407
+ variant: payload.variant
1408
+ }
1409
+ } : safelyMigrateVariant(definition, {
1410
+ schema: rawSchema,
1411
+ container: indexed.node,
1412
+ fromVariantId,
1413
+ toVariantId: payload.variant,
1414
+ fromVariant: from,
1415
+ toVariant: to,
1416
+ state: indexed.node.container
1417
+ });
1418
+ if (!result.allowed) return {
1419
+ ok: false,
1420
+ code: result.code ?? "CONTAINER_VARIANT_MIGRATION_REJECTED",
1421
+ ...result.messageKey === void 0 ? {} : { messageKey: result.messageKey },
1422
+ ...result.message === void 0 ? {} : { message: result.message },
1423
+ ...result.details === void 0 ? {} : { details: result.details }
1424
+ };
1425
+ if (result.state.variant !== payload.variant) return {
1426
+ ok: false,
1427
+ code: "CONTAINER_VARIANT_MIGRATION_TARGET_MISMATCH"
1428
+ };
1429
+ const candidate = cloneSchema(rawSchema);
1430
+ const candidateNode = buildSchemaIndex(candidate).index.get(indexed.node.id)?.node;
1431
+ if (!candidateNode) return {
1432
+ ok: false,
1433
+ code: "CONTAINER_NOT_FOUND"
1434
+ };
1435
+ candidateNode.container = cloneDeep(result.state);
1436
+ const validation = validateSchema(candidate, ctx.registry);
1437
+ if (!validation.valid) return {
1438
+ ok: false,
1439
+ code: "CONTAINER_VARIANT_MIGRATION_INVALID",
1440
+ details: { diagnostics: validation.diagnostics }
1441
+ };
1442
+ const validatedState = buildSchemaIndex(validation.schema).index.get(indexed.node.id)?.node.container;
1443
+ if (!validatedState) return {
1444
+ ok: false,
1445
+ code: "CONTAINER_VARIANT_MIGRATION_INVALID"
1446
+ };
1447
+ indexed.node.container = cloneDeep(validatedState);
1448
+ return {
1449
+ ok: true,
1450
+ eventPayload: {
1451
+ containerId: indexed.node.id,
1452
+ fromVariant: fromVariantId,
1453
+ toVariant: payload.variant
1454
+ }
1455
+ };
1456
+ }
1457
+ //#endregion
1458
+ //#region src/commands/duplicate-node.ts
1459
+ function duplicateNodeHandler(ctx, payload) {
1460
+ const schema = ctx.schema;
1461
+ const indexed = buildSchemaIndex(schema);
1462
+ const sourceResult = resolveNodeSource(schema, indexed, payload.nodeId);
1463
+ if (!sourceResult.ok) return sourceResult;
1464
+ const source = sourceResult.value;
1465
+ const sourceNode = source.location.node;
1466
+ if (sourceNode.container && !ctx.registry.getWidget(sourceNode.type)?.container) return {
1467
+ ok: false,
1468
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1469
+ };
1470
+ if (source.destination.kind === "container") {
1471
+ const sourceDestination = source.destination;
1472
+ const owner = indexed.index.get(sourceDestination.containerId)?.node;
1473
+ const definition = owner && ctx.registry.getWidget(owner.type)?.container;
1474
+ const variant = owner?.container && definition?.variants[owner.container.variant];
1475
+ const region = variant?.regions.find((item) => item.id === sourceDestination.regionId);
1476
+ if (!definition || !variant || !region) return {
1477
+ ok: false,
1478
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1479
+ };
1480
+ }
1481
+ const clone = cloneNodeSubtree(sourceNode, generateShortId);
1482
+ let destination = {
1483
+ ...source.destination,
1484
+ index: source.index + 1
1485
+ };
1486
+ if (source.destination.kind === "root") {
1487
+ const sortScope = resolveNodeLayout(sourceNode, ctx.registry).sortScope;
1488
+ if (sortScope !== false) {
1489
+ const scopeIndex = getSortScopeEntries(createLayoutPlan(schema, ctx.registry), sortScope).findIndex((entry) => entry.node.id === sourceNode.id);
1490
+ if (scopeIndex !== -1) destination = {
1491
+ ...source.destination,
1492
+ index: scopeIndex + 1
1493
+ };
1494
+ }
1495
+ }
1496
+ const added = addNodeHandler(ctx, {
1497
+ node: clone,
1498
+ destination
1499
+ });
1500
+ if (!added) return {
1501
+ ok: false,
1502
+ code: "DUPLICATE_ADD_REJECTED"
1503
+ };
1504
+ if (!added.ok) return added;
1505
+ const addedEvent = added.eventPayload;
1506
+ return {
1507
+ ok: true,
1508
+ eventPayload: {
1509
+ sourceNodeId: payload.nodeId,
1510
+ nodeId: clone.id,
1511
+ destination: addedEvent?.destination ?? destination
1512
+ }
1513
+ };
1514
+ }
1515
+ //#endregion
1516
+ //#region src/commands/move-node.ts
1517
+ function moveNodeHandler(ctx, payload) {
1518
+ const { draft: rawSchema, registry } = ctx;
1519
+ const safeSchema = ctx.schema;
1520
+ const indexed = buildSchemaIndex(safeSchema);
1521
+ const sourceResult = resolveNodeSource(safeSchema, indexed, payload.nodeId);
1522
+ if (!sourceResult.ok) return sourceResult;
1523
+ const targetResult = resolveDestination(safeSchema, registry, payload.destination);
1524
+ if (!targetResult.ok) return targetResult;
1525
+ const source = sourceResult.value;
1526
+ const target = targetResult.value;
1527
+ const node = source.location.node;
1528
+ if (node.container && !registry.getWidget(node.type)?.container) return {
1529
+ ok: false,
1530
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1531
+ };
1532
+ const sourceMeta = registry.getWidget(node.type);
1533
+ const behaviorContext = {
1534
+ node,
1535
+ schema: safeSchema
1536
+ };
1537
+ if (!resolveBehavior(sourceMeta?.draggable, behaviorContext) || !resolveBehavior(sourceMeta?.sortable, behaviorContext)) return {
1538
+ ok: false,
1539
+ code: "NODE_NOT_MOVABLE"
1540
+ };
1541
+ if (source.destination.kind === "container") {
1542
+ const sourceRegionId = source.destination.regionId;
1543
+ const owner = indexed.index.get(source.destination.containerId)?.node;
1544
+ const definition = owner && registry.getWidget(owner.type)?.container;
1545
+ if (!definition) return {
1546
+ ok: false,
1547
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1548
+ };
1549
+ const variant = owner?.container && definition.variants[owner.container.variant];
1550
+ const region = variant?.regions.find((item) => item.id === sourceRegionId);
1551
+ if (!variant || !region) return {
1552
+ ok: false,
1553
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1554
+ };
1555
+ const sameRegion = target.children === source.children;
1556
+ const minItems = region.constraints?.minItems ?? 0;
1557
+ if (!sameRegion && source.children.length - 1 < minItems) return {
1558
+ ok: false,
1559
+ code: "CONTAINER_REGION_MIN_ITEMS"
1560
+ };
1561
+ }
1562
+ let requestedIndex = payload.destination.kind === "container" ? clampInsertIndex(payload.destination.index, target.children.length) : Math.max(0, payload.destination.index ?? Number.MAX_SAFE_INTEGER);
1563
+ const sameRegion = payload.destination.kind === "container" && source.destination.kind === "container" && target.children === source.children;
1564
+ if (sameRegion && source.index < requestedIndex) requestedIndex -= 1;
1565
+ if (sameRegion && source.index === requestedIndex) return {
1566
+ ok: true,
1567
+ changed: false
1568
+ };
1569
+ if (source.destination.kind === "container") {
1570
+ const sourceLocks = getLockedIndicesFromNodes(source.children, registry, safeSchema);
1571
+ if (sameRegion) {
1572
+ if (!isMoveAllowed(source.index, requestedIndex, sourceLocks)) return {
1573
+ ok: false,
1574
+ code: "SORTABLE_LOCK_VIOLATION"
1575
+ };
1576
+ } else if (!isRemoveAllowed(source.index, sourceLocks)) return {
1577
+ ok: false,
1578
+ code: "SORTABLE_LOCK_VIOLATION"
1579
+ };
1580
+ }
1581
+ if (payload.destination.kind === "container" && !sameRegion) {
1582
+ const targetLocks = getLockedIndicesFromNodes(target.children, registry, safeSchema);
1583
+ if (!isInsertAllowed(requestedIndex, targetLocks)) return {
1584
+ ok: false,
1585
+ code: "SORTABLE_LOCK_VIOLATION"
1586
+ };
1587
+ }
1588
+ if (target.container && target.definition && target.variant && target.region) {
1589
+ const targetCount = target.children.length - (target.children === source.children ? 1 : 0);
1590
+ const decision = resolvePlacementDecision({
1591
+ definition: target.definition,
1592
+ region: target.region,
1593
+ child: node,
1594
+ childHasContainerCapability: Boolean(sourceMeta?.container),
1595
+ targetCount,
1596
+ callbackContext: {
1597
+ operation: "move",
1598
+ schema: safeSchema,
1599
+ container: target.container,
1600
+ variant: target.variant,
1601
+ region: target.region,
1602
+ child: node,
1603
+ targetIndex: requestedIndex
1604
+ }
1605
+ });
1606
+ if (!decision.allowed) return {
1607
+ ok: false,
1608
+ code: decision.code ?? "CONTAINER_PLACEMENT_DENIED",
1609
+ messageKey: decision.messageKey,
1610
+ message: decision.message,
1611
+ details: decision.details
1612
+ };
1613
+ }
1614
+ const sourceScope = source.destination.kind === "root" ? resolveNodeLayout(node, registry).sortScope : void 0;
1615
+ const targetScope = payload.destination.kind === "root" ? payload.destination.sortScope ?? sourceScope ?? resolveNodeLayout(node, registry).sortScope : void 0;
1616
+ const targetUsesSortScope = typeof targetScope === "string";
1617
+ const targetEntriesBefore = payload.destination.kind === "root" && targetUsesSortScope ? getSortScopeEntries(createLayoutPlan(safeSchema, registry), targetScope) : [];
1618
+ if (payload.destination.kind === "root") requestedIndex = clampInsertIndex(payload.destination.index, targetUsesSortScope ? targetEntriesBefore.length : target.children.length);
1619
+ if (source.destination.kind === "root") {
1620
+ if (sourceScope === false && payload.destination.kind === "root") return {
1621
+ ok: false,
1622
+ code: "NODE_NOT_SORTABLE"
1623
+ };
1624
+ const sourceEntries = sourceScope === false ? [] : getSortScopeEntries(createLayoutPlan(safeSchema, registry), sourceScope);
1625
+ const sourceScopeIndex = sourceEntries.findIndex((entry) => entry.node.id === node.id);
1626
+ const sourceLocks = getLockedIndicesFromEntries(sourceEntries, registry, safeSchema);
1627
+ if (payload.destination.kind === "root" && targetScope === sourceScope) {
1628
+ if (sourceScopeIndex < requestedIndex) requestedIndex -= 1;
1629
+ if (sourceScopeIndex === requestedIndex) return {
1630
+ ok: true,
1631
+ changed: false
1632
+ };
1633
+ if (!isMoveAllowed(sourceScopeIndex, requestedIndex, sourceLocks)) return {
1634
+ ok: false,
1635
+ code: "SORTABLE_LOCK_VIOLATION"
1636
+ };
1637
+ } else if (sourceScope !== false && !isRemoveAllowed(sourceScopeIndex, sourceLocks)) return {
1638
+ ok: false,
1639
+ code: "SORTABLE_LOCK_VIOLATION"
1640
+ };
1641
+ }
1642
+ if (payload.destination.kind === "root" && targetUsesSortScope && targetScope !== sourceScope) {
1643
+ const targetLocks = getLockedIndicesFromEntries(targetEntriesBefore, registry, safeSchema);
1644
+ if (!isInsertAllowed(requestedIndex, targetLocks)) return {
1645
+ ok: false,
1646
+ code: "SORTABLE_LOCK_VIOLATION"
1647
+ };
1648
+ }
1649
+ const draftSourceResult = resolveNodeSource(rawSchema, buildSchemaIndex(rawSchema), payload.nodeId);
1650
+ if (!draftSourceResult.ok) return draftSourceResult;
1651
+ const draftTargetResult = resolveDestination(rawSchema, registry, payload.destination);
1652
+ if (!draftTargetResult.ok) return draftTargetResult;
1653
+ const draftSource = draftSourceResult.value;
1654
+ const draftTarget = draftTargetResult.value;
1655
+ const sameOwnerArray = draftTarget.children === draftSource.children;
1656
+ const sourceBefore = cloneDeep(draftSource.children);
1657
+ const targetBefore = sameOwnerArray ? null : cloneDeep(draftTarget.children);
1658
+ const [removed] = draftSource.children.splice(draftSource.index, 1);
1659
+ const inserted = payload.destination.kind === "container" ? stripPageLayout(removed) : cloneDeep(removed);
1660
+ const insertedIndex = payload.destination.kind === "root" ? targetUsesSortScope ? getSortableArrayIndexForInsert(getSortScopeEntries(createLayoutPlan(deepFreeze(cloneSchema(rawSchema)), registry), targetScope), draftTarget.children, requestedIndex) : clampInsertIndex(requestedIndex, draftTarget.children.length) : clampInsertIndex(requestedIndex, draftTarget.children.length);
1661
+ draftTarget.children.splice(insertedIndex, 0, inserted);
1662
+ const validation = validateSchema(rawSchema, registry);
1663
+ const movedNodeIds = collectSubtreeIds(inserted);
1664
+ const candidateDiagnostics = validation.diagnostics.filter((diagnostic) => diagnostic.nodeId !== void 0 && movedNodeIds.has(diagnostic.nodeId) || diagnostic.ownerId !== void 0 && movedNodeIds.has(diagnostic.ownerId));
1665
+ if (candidateDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
1666
+ draftSource.children.splice(0, draftSource.children.length, ...sourceBefore);
1667
+ if (targetBefore) draftTarget.children.splice(0, draftTarget.children.length, ...targetBefore);
1668
+ return {
1669
+ ok: false,
1670
+ code: "SCHEMA_CANDIDATE_INVALID",
1671
+ details: { diagnostics: candidateDiagnostics }
1672
+ };
1673
+ }
1674
+ return {
1675
+ ok: true,
1676
+ eventPayload: {
1677
+ nodeId: payload.nodeId,
1678
+ source: source.destination,
1679
+ destination: {
1680
+ ...payload.destination,
1681
+ index: insertedIndex
1682
+ }
1683
+ }
1684
+ };
1685
+ }
1686
+ //#endregion
1687
+ //#region src/commands/remove-node.ts
1688
+ function removeNodeHandler(ctx, payload) {
1689
+ const { store, registry } = ctx;
1690
+ const rawSchema = ctx.schema;
1691
+ if (payload.nodeId === rawSchema.root.id) {
1692
+ console.warn("[dragcraft/core] REMOVE_NODE: cannot remove root node");
1693
+ return false;
1694
+ }
1695
+ const indexed = buildSchemaIndex(rawSchema);
1696
+ const sourceResult = resolveNodeSource(rawSchema, indexed, payload.nodeId);
1697
+ if (!sourceResult.ok) {
1698
+ console.warn(`[dragcraft/core] REMOVE_NODE: node "${payload.nodeId}" not found`);
1699
+ return sourceResult;
1700
+ }
1701
+ const source = sourceResult.value;
1702
+ const node = source.location.node;
1703
+ const meta = registry.getWidget(node.type);
1704
+ if (!resolveBehavior(meta?.deletable, {
1705
+ node,
1706
+ schema: rawSchema
1707
+ })) return {
1708
+ ok: false,
1709
+ code: "NODE_NOT_DELETABLE"
1710
+ };
1711
+ if (node.container && !meta?.container) return {
1712
+ ok: false,
1713
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1714
+ };
1715
+ if (source.destination.kind === "container") {
1716
+ const sourceDestination = source.destination;
1717
+ const owner = indexed.index.get(sourceDestination.containerId)?.node;
1718
+ const definition = owner && registry.getWidget(owner.type)?.container;
1719
+ if (!definition) return {
1720
+ ok: false,
1721
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1722
+ };
1723
+ const variant = owner?.container && definition.variants[owner.container.variant];
1724
+ const region = variant?.regions.find((item) => item.id === sourceDestination.regionId);
1725
+ if (!variant || !region) return {
1726
+ ok: false,
1727
+ code: "UNRESOLVED_CONTAINER_READ_ONLY"
1728
+ };
1729
+ if (source.children.length - 1 < (region.constraints?.minItems ?? 0)) return {
1730
+ ok: false,
1731
+ code: "CONTAINER_REGION_MIN_ITEMS"
1732
+ };
1733
+ const lockedIndices = getLockedIndicesFromNodes(source.children, registry, rawSchema);
1734
+ if (!isRemoveAllowed(source.index, lockedIndices)) return {
1735
+ ok: false,
1736
+ code: "SORTABLE_LOCK_VIOLATION"
1737
+ };
1738
+ }
1739
+ if (source.destination.kind === "root") {
1740
+ const layout = node ? resolveNodeLayout(node, registry) : null;
1741
+ if (node && layout && layout.sortScope !== false) {
1742
+ const scopeEntries = getSortScopeEntries(createLayoutPlan(rawSchema, registry), layout.sortScope);
1743
+ const removeIndex = scopeEntries.findIndex((entry) => entry.node.id === payload.nodeId);
1744
+ if (removeIndex !== -1) {
1745
+ const lockedIndices = getLockedIndicesFromEntries(scopeEntries, registry, rawSchema);
1746
+ if (lockedIndices.size > 0 && !isRemoveAllowed(removeIndex, lockedIndices)) {
1747
+ console.warn(`[dragcraft/core] REMOVE_NODE: blocked by sortable constraint (removing index ${removeIndex} would shift locked widgets)`);
1748
+ return false;
1749
+ }
1750
+ }
1751
+ }
1752
+ }
1753
+ const removedIds = collectSubtreeIds(node);
1754
+ const draftIndex = buildSchemaIndex(ctx.draft);
1755
+ const draftSource = resolveNodeSource(ctx.draft, draftIndex, payload.nodeId);
1756
+ if (!draftSource.ok) return draftSource;
1757
+ draftSource.value.children.splice(draftSource.value.index, 1);
1758
+ if (store.selectedNodeId.value && removedIds.has(store.selectedNodeId.value)) store.selectNode(null);
1759
+ if (store.hoveredNodeId.value && removedIds.has(store.hoveredNodeId.value)) store.hoverNode(null);
1760
+ return {
1761
+ ok: true,
1762
+ eventPayload: {
1763
+ nodeId: payload.nodeId,
1764
+ source: source.destination
1765
+ }
1766
+ };
1767
+ }
1768
+ //#endregion
1769
+ //#region src/merge-record.ts
1770
+ const BLOCKED_RECORD_KEYS = /* @__PURE__ */ new Set([
1771
+ "__proto__",
1772
+ "prototype",
1773
+ "constructor"
1774
+ ]);
1775
+ function isPlainRecord(value) {
1776
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1777
+ const prototype = Object.getPrototypeOf(value);
1778
+ return prototype === Object.prototype || prototype === null;
1779
+ }
1780
+ function mergeRecord(target, patch) {
1781
+ let changed = false;
1782
+ for (const [key, value] of Object.entries(patch)) {
1783
+ if (BLOCKED_RECORD_KEYS.has(key)) continue;
1784
+ const current = target[key];
1785
+ if (Object.hasOwn(target, key) && isPlainRecord(current) && isPlainRecord(value)) changed = mergeRecord(current, value) || changed;
1786
+ else if (!Object.hasOwn(target, key) || !Object.is(current, value)) {
1787
+ target[key] = value;
1788
+ changed = true;
1789
+ }
1790
+ }
1791
+ return changed;
1792
+ }
1793
+ //#endregion
1794
+ //#region src/commands/set-global-config.ts
1795
+ function setGlobalConfigHandler(ctx, payload) {
1796
+ return {
1797
+ ok: true,
1798
+ changed: mergeRecord(ctx.draft.globalConfig, payload.config)
1799
+ };
1800
+ }
1801
+ //#endregion
1802
+ //#region src/commands/update-props.ts
1803
+ function updatePropsHandler(ctx, payload) {
1804
+ const rawSchema = ctx.draft;
1805
+ const node = findNodeById(rawSchema.root, payload.nodeId);
1806
+ if (!node) {
1807
+ console.warn(`[dragcraft/core] UPDATE_PROPS: node "${payload.nodeId}" not found`);
1808
+ return false;
1809
+ }
1810
+ let changed = mergeRecord(node.props, payload.props);
1811
+ if (payload.style) {
1812
+ const style = node.style ?? {};
1813
+ const styleChanged = mergeRecord(style, payload.style);
1814
+ if (!node.style && styleChanged) node.style = style;
1815
+ changed = styleChanged || changed;
1816
+ }
1817
+ return {
1818
+ ok: true,
1819
+ changed
1820
+ };
1821
+ }
1822
+ //#endregion
1823
+ //#region src/container-definition.ts
1824
+ const RESERVED_IDS = /* @__PURE__ */ new Set([
1825
+ "__proto__",
1826
+ "prototype",
1827
+ "constructor"
1828
+ ]);
1829
+ function isRecord$1(value) {
1830
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1831
+ const prototype = Object.getPrototypeOf(value);
1832
+ return prototype === Object.prototype || prototype === null;
1833
+ }
1834
+ function validateContainerDefinition(definition) {
1835
+ const errors = [];
1836
+ if (!isRecord$1(definition)) return {
1837
+ valid: false,
1838
+ errors: [{
1839
+ code: "CONTAINER_DEFINITION_INVALID",
1840
+ path: ""
1841
+ }]
1842
+ };
1843
+ const variants = definition.variants;
1844
+ if (!isRecord$1(variants)) return {
1845
+ valid: false,
1846
+ errors: [{
1847
+ code: "CONTAINER_VARIANTS_INVALID",
1848
+ path: "variants"
1849
+ }]
1850
+ };
1851
+ if (typeof definition.defaultVariant !== "string" || !Object.hasOwn(variants, definition.defaultVariant)) errors.push({
1852
+ code: "CONTAINER_DEFAULT_VARIANT_MISSING",
1853
+ path: "defaultVariant"
1854
+ });
1855
+ for (const [variantId, variantValue] of Object.entries(variants)) {
1856
+ const variantPath = `variants.${variantId}`;
1857
+ if (!variantId || RESERVED_IDS.has(variantId)) errors.push({
1858
+ code: "CONTAINER_VARIANT_ID_RESERVED",
1859
+ path: variantPath
1860
+ });
1861
+ if (!isRecord$1(variantValue)) {
1862
+ errors.push({
1863
+ code: "CONTAINER_VARIANT_INVALID",
1864
+ path: variantPath
1865
+ });
1866
+ continue;
1867
+ }
1868
+ if (!Array.isArray(variantValue.regions)) {
1869
+ errors.push({
1870
+ code: "CONTAINER_REGIONS_INVALID",
1871
+ path: `${variantPath}.regions`
1872
+ });
1873
+ continue;
1874
+ }
1875
+ const seen = /* @__PURE__ */ new Set();
1876
+ for (const [index, regionValue] of variantValue.regions.entries()) {
1877
+ const path = `${variantPath}.regions.${index}`;
1878
+ if (!isRecord$1(regionValue)) {
1879
+ errors.push({
1880
+ code: "CONTAINER_REGION_INVALID",
1881
+ path
1882
+ });
1883
+ continue;
1884
+ }
1885
+ const regionId = regionValue.id;
1886
+ if (typeof regionId !== "string" || !regionId || RESERVED_IDS.has(regionId)) errors.push({
1887
+ code: "CONTAINER_REGION_ID_RESERVED",
1888
+ path
1889
+ });
1890
+ if (typeof regionId === "string" && seen.has(regionId)) errors.push({
1891
+ code: "CONTAINER_REGION_ID_DUPLICATE",
1892
+ path
1893
+ });
1894
+ if (typeof regionId === "string") seen.add(regionId);
1895
+ const constraints = regionValue.constraints;
1896
+ if (constraints !== void 0 && !isRecord$1(constraints)) {
1897
+ errors.push({
1898
+ code: "CONTAINER_CONSTRAINTS_INVALID",
1899
+ path: `${path}.constraints`
1900
+ });
1901
+ continue;
1902
+ }
1903
+ const minItems = constraints?.minItems ?? 0;
1904
+ const maxItems = constraints?.maxItems ?? Number.POSITIVE_INFINITY;
1905
+ const maxItemsInvalid = constraints?.maxItems !== void 0 && (!Number.isInteger(maxItems) || maxItems < 0);
1906
+ if (!Number.isInteger(minItems) || minItems < 0 || maxItemsInvalid || minItems > maxItems) errors.push({
1907
+ code: "CONTAINER_CARDINALITY_INVALID",
1908
+ path: `${path}.constraints`
1909
+ });
1910
+ for (const listName of ["includeTypes", "excludeTypes"]) {
1911
+ const typeIds = constraints?.[listName];
1912
+ if (typeIds !== void 0 && (!Array.isArray(typeIds) || typeIds.some((typeId) => typeof typeId !== "string" || typeId.length === 0))) errors.push({
1913
+ code: "CONTAINER_TYPE_ID_INVALID",
1914
+ path: `${path}.constraints.${listName}`
1915
+ });
1916
+ }
1917
+ }
1918
+ }
1919
+ return {
1920
+ valid: errors.length === 0,
1921
+ errors
1922
+ };
1923
+ }
1924
+ //#endregion
1925
+ //#region src/container-plan.ts
1926
+ function createContainerPlan(node, registry) {
1927
+ const definition = registry.getWidget(node.type)?.container;
1928
+ if (!node.container || !definition) return {
1929
+ ok: false,
1930
+ code: "CONTAINER_UNRESOLVED",
1931
+ containerId: node.id
1932
+ };
1933
+ const variant = definition.variants[node.container.variant];
1934
+ if (!variant) return {
1935
+ ok: false,
1936
+ code: "CONTAINER_VARIANT_UNKNOWN",
1937
+ containerId: node.id
1938
+ };
1939
+ return {
1940
+ ok: true,
1941
+ plan: {
1942
+ containerId: node.id,
1943
+ variant,
1944
+ regions: variant.regions.map((region) => {
1945
+ const nodes = node.container.regions[region.id] ?? [];
1946
+ return {
1947
+ definition: region,
1948
+ nodes,
1949
+ isEmpty: nodes.length === 0
1950
+ };
1951
+ })
1952
+ }
1953
+ };
1954
+ }
1955
+ //#endregion
1956
+ //#region src/event-hub.ts
1957
+ var EventHub = class {
1958
+ emitter;
1959
+ constructor() {
1960
+ this.emitter = new EventEmitter();
1961
+ }
1962
+ on(event, listener) {
1963
+ this.emitter.on(event, listener);
1964
+ }
1965
+ off(event, listener) {
1966
+ this.emitter.off(event, listener);
1967
+ }
1968
+ emit(event, ...args) {
1969
+ this.emitter.emit(event, ...args);
1970
+ }
1971
+ once(event, listener) {
1972
+ this.emitter.once(event, listener);
1973
+ }
1974
+ clear() {
1975
+ this.emitter.clear();
1976
+ }
1977
+ };
1978
+ function createEventHub() {
1979
+ return new EventHub();
1980
+ }
1981
+ //#endregion
1982
+ //#region src/registry.ts
1983
+ function isRecord(value) {
1984
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1985
+ const prototype = Object.getPrototypeOf(value);
1986
+ return prototype === Object.prototype || prototype === null;
1987
+ }
1988
+ function createRegistry() {
1989
+ const widgets = /* @__PURE__ */ new Map();
1990
+ let globalConfigSchema;
1991
+ function registerWidget(meta) {
1992
+ if (!isRecord(meta)) {
1993
+ console.warn("[dragcraft/core] registerWidget: widget metadata must be a plain record");
1994
+ return;
1995
+ }
1996
+ if (!meta.type || typeof meta.type !== "string") {
1997
+ console.warn("[dragcraft/core] registerWidget: widget meta must have a non-empty \"type\" string");
1998
+ return;
1999
+ }
2000
+ if (!meta.title || typeof meta.title !== "string") {
2001
+ console.warn(`[dragcraft/core] registerWidget: widget "${meta.type}" must have a non-empty "title" string`);
2002
+ return;
2003
+ }
2004
+ if (meta.container !== void 0) {
2005
+ const validation = validateContainerDefinition(meta.container);
2006
+ if (!validation.valid) {
2007
+ const codes = validation.errors.map((error) => error.code).join(", ");
2008
+ console.warn(`[dragcraft/core] registerWidget: widget "${meta.type}" has an invalid container definition: ${codes}`);
2009
+ return;
2010
+ }
2011
+ }
2012
+ if (widgets.has(meta.type)) console.warn(`[dragcraft/core] Widget type "${meta.type}" is already registered, overwriting.`);
2013
+ widgets.set(meta.type, meta);
2014
+ }
2015
+ function registerGlobalConfigSchema(schema) {
2016
+ globalConfigSchema = schema;
2017
+ }
2018
+ function registerGlobalConfigFormSchema(schema) {
2019
+ globalConfigSchema = schema;
2020
+ }
2021
+ function getWidget(type) {
2022
+ return widgets.get(type);
2023
+ }
2024
+ function getGlobalConfigSchema() {
2025
+ return globalConfigSchema;
2026
+ }
2027
+ function getAllWidgets() {
2028
+ return Array.from(widgets.values());
2029
+ }
2030
+ return {
2031
+ registerWidget,
2032
+ registerGlobalConfigSchema,
2033
+ registerGlobalConfigFormSchema,
2034
+ getWidget,
2035
+ getGlobalConfigSchema,
2036
+ getAllWidgets
2037
+ };
2038
+ }
2039
+ //#endregion
2040
+ //#region src/schema-store.ts
2041
+ function createDefaultSchema() {
2042
+ return {
2043
+ version: DEFAULT_SCHEMA_VERSION,
2044
+ globalConfig: {},
2045
+ root: {
2046
+ id: "root",
2047
+ type: "root",
2048
+ props: {},
2049
+ children: []
2050
+ }
2051
+ };
2052
+ }
2053
+ function createSchemaStore(initialSchema, onSelectionChange) {
2054
+ const schema = shallowRef(deepFreeze(initialSchema ? cloneSchema(initialSchema) : createDefaultSchema()));
2055
+ const selectedNodeId = ref(null);
2056
+ const hoveredNodeId = ref(null);
2057
+ const dragTarget = ref(null);
2058
+ let schemaIndex = null;
2059
+ function getSchema() {
2060
+ return cloneSchema(schema.value);
2061
+ }
2062
+ function getSnapshot() {
2063
+ return schema.value;
2064
+ }
2065
+ function replaceSnapshot(snapshot) {
2066
+ schemaIndex = null;
2067
+ schema.value = snapshot;
2068
+ }
2069
+ function setSchema(newSchema) {
2070
+ replaceSnapshot(deepFreeze(cloneSchema(newSchema)));
2071
+ }
2072
+ function commitSchema(newSchema) {
2073
+ const snapshot = deepFreeze(newSchema);
2074
+ replaceSnapshot(snapshot);
2075
+ return snapshot;
2076
+ }
2077
+ function restoreSnapshot(snapshot) {
2078
+ replaceSnapshot(snapshot);
2079
+ }
2080
+ function selectNode(id) {
2081
+ selectedNodeId.value = id;
2082
+ onSelectionChange?.(id);
2083
+ }
2084
+ function hoverNode(id) {
2085
+ hoveredNodeId.value = id;
2086
+ }
2087
+ function setDragTarget(target) {
2088
+ dragTarget.value = target ? { ...target } : null;
2089
+ }
2090
+ function getNodeById(id) {
2091
+ if (schema.value.root.id === id) return schema.value.root;
2092
+ schemaIndex ??= buildSchemaIndex(schema.value);
2093
+ return schemaIndex.index.get(id)?.node ?? null;
2094
+ }
2095
+ return {
2096
+ schema,
2097
+ selectedNodeId,
2098
+ hoveredNodeId,
2099
+ dragTarget,
2100
+ getSchema,
2101
+ getSnapshot,
2102
+ setSchema,
2103
+ commitSchema,
2104
+ restoreSnapshot,
2105
+ selectNode,
2106
+ hoverNode,
2107
+ setDragTarget,
2108
+ getNodeById
2109
+ };
2110
+ }
2111
+ //#endregion
2112
+ //#region src/engine.ts
2113
+ function createEngine(options) {
2114
+ const maxHistorySize = options?.maxHistorySize ?? 50;
2115
+ const eventHub = createEventHub();
2116
+ const schemaStore = createSchemaStore(void 0, (id) => {
2117
+ eventHub.emit(EventName.SELECTION_CHANGED, id);
2118
+ });
2119
+ const store = {
2120
+ schema: readonly(schemaStore.schema),
2121
+ selectedNodeId: readonly(schemaStore.selectedNodeId),
2122
+ hoveredNodeId: readonly(schemaStore.hoveredNodeId),
2123
+ dragTarget: readonly(schemaStore.dragTarget),
2124
+ selectNode: schemaStore.selectNode,
2125
+ hoverNode: schemaStore.hoverNode,
2126
+ setDragTarget: schemaStore.setDragTarget
2127
+ };
2128
+ const registry = createRegistry();
2129
+ const history = createHistoryManager(schemaStore, eventHub, maxHistorySize);
2130
+ const commandBus = createCommandBus(schemaStore, registry, eventHub, history);
2131
+ const state = {
2132
+ getSchema: schemaStore.getSnapshot,
2133
+ getNodeById: (id) => {
2134
+ return schemaStore.getNodeById(id);
2135
+ },
2136
+ getSelectedNodeId: () => store.selectedNodeId.value,
2137
+ getHoveredNodeId: () => store.hoveredNodeId.value,
2138
+ getDragTarget: () => {
2139
+ const target = schemaStore.dragTarget.value;
2140
+ return target ? { ...target } : null;
2141
+ }
2142
+ };
2143
+ commandBus.registerHandler(CommandType.ADD_NODE, addNodeHandler);
2144
+ commandBus.registerHandler(CommandType.CHANGE_CONTAINER_VARIANT, changeContainerVariantHandler);
2145
+ commandBus.registerHandler(CommandType.DUPLICATE_NODE, duplicateNodeHandler);
2146
+ commandBus.registerHandler(CommandType.MOVE_NODE, moveNodeHandler);
2147
+ commandBus.registerHandler(CommandType.REMOVE_NODE, removeNodeHandler);
2148
+ commandBus.registerHandler(CommandType.UPDATE_PROPS, updatePropsHandler);
2149
+ commandBus.registerHandler(CommandType.SET_GLOBAL_CONFIG, setGlobalConfigHandler);
2150
+ function execute(command) {
2151
+ return commandBus.execute(command);
2152
+ }
2153
+ function registerHandler(type, handler) {
2154
+ commandBus.registerHandler(type, handler);
2155
+ }
2156
+ function registerWidget(meta) {
2157
+ registry.registerWidget(meta);
2158
+ }
2159
+ const migrations = [];
2160
+ function registerMigration(migration) {
2161
+ migrations.push(migration);
2162
+ }
2163
+ function migrateSchema(schema) {
2164
+ let current = schema;
2165
+ const applied = /* @__PURE__ */ new Set();
2166
+ let changed = true;
2167
+ while (changed) {
2168
+ changed = false;
2169
+ for (const m of migrations) {
2170
+ const key = `${m.fromVersion}->${m.toVersion}`;
2171
+ if (current.version === m.fromVersion && !applied.has(key)) {
2172
+ current = m.migrate(current);
2173
+ applied.add(key);
2174
+ changed = true;
2175
+ }
2176
+ }
2177
+ }
2178
+ return current;
2179
+ }
2180
+ function exportSchema() {
2181
+ return schemaStore.getSchema();
2182
+ }
2183
+ function importSchema(schema) {
2184
+ const structuralDiagnostics = collectSchemaStructuralDiagnostics(schema);
2185
+ if (structuralDiagnostics.length > 0) {
2186
+ console.warn("[dragcraft/core] importSchema: invalid schema, missing root or version");
2187
+ return {
2188
+ ok: false,
2189
+ diagnostics: structuralDiagnostics
2190
+ };
2191
+ }
2192
+ const validation = validateSchema(migrateSchema(cloneSchema(schema)), registry);
2193
+ if (!validation.valid) return {
2194
+ ok: false,
2195
+ diagnostics: validation.diagnostics
2196
+ };
2197
+ schemaStore.setSchema(validation.schema);
2198
+ history.clear();
2199
+ eventHub.emit(EventName.SCHEMA_CHANGED, schemaStore.getSnapshot());
2200
+ return {
2201
+ ok: true,
2202
+ diagnostics: validation.diagnostics
2203
+ };
2204
+ }
2205
+ function dispose() {
2206
+ eventHub.clear();
2207
+ history.clear();
2208
+ schemaStore.selectNode(null);
2209
+ schemaStore.hoverNode(null);
2210
+ schemaStore.setDragTarget(null);
2211
+ }
2212
+ return {
2213
+ store,
2214
+ state,
2215
+ commandBus,
2216
+ history,
2217
+ registry,
2218
+ eventHub,
2219
+ execute,
2220
+ registerHandler,
2221
+ registerWidget,
2222
+ registerMigration,
2223
+ migrateSchema,
2224
+ exportSchema,
2225
+ importSchema,
2226
+ dispose
2227
+ };
2228
+ }
2229
+ //#endregion
2230
+ //#region src/style.ts
2231
+ const LENGTH_STYLE_KEYS = /* @__PURE__ */ new Set([
2232
+ "bottom",
2233
+ "height",
2234
+ "left",
2235
+ "margin",
2236
+ "marginBlock",
2237
+ "marginBlockEnd",
2238
+ "marginBlockStart",
2239
+ "marginBottom",
2240
+ "marginInline",
2241
+ "marginInlineEnd",
2242
+ "marginInlineStart",
2243
+ "marginLeft",
2244
+ "marginRight",
2245
+ "marginTop",
2246
+ "maxHeight",
2247
+ "maxWidth",
2248
+ "minHeight",
2249
+ "minWidth",
2250
+ "padding",
2251
+ "paddingBlock",
2252
+ "paddingBlockEnd",
2253
+ "paddingBlockStart",
2254
+ "paddingBottom",
2255
+ "paddingInline",
2256
+ "paddingInlineEnd",
2257
+ "paddingInlineStart",
2258
+ "paddingLeft",
2259
+ "paddingRight",
2260
+ "paddingTop",
2261
+ "right",
2262
+ "top",
2263
+ "width"
2264
+ ]);
2265
+ function normalizeStyleValueMap(style) {
2266
+ if (!style) return void 0;
2267
+ const normalized = {};
2268
+ for (const [key, value] of Object.entries(style)) normalized[key] = typeof value === "number" && value !== 0 && LENGTH_STYLE_KEYS.has(key) ? `${value}px` : value;
2269
+ return normalized;
2270
+ }
2271
+ //#endregion
2272
+ //#region src/types.ts
2273
+ function commandFailure(code, reason = {}) {
2274
+ return {
2275
+ ok: false,
2276
+ code,
2277
+ ...reason
2278
+ };
2279
+ }
2280
+ //#endregion
2281
+ export { CommandType, DEFAULT_LAYER, DEFAULT_LAYOUT_REGION, DEFAULT_MAX_HISTORY_SIZE, DEFAULT_SCHEMA_VERSION, DEFAULT_SORT_SCOPE, EventHub, EventName, addNodeHandler, buildSchemaIndex, changeContainerVariantHandler, clampInsertIndex, cloneNodeSubtree, collectSubtreeIds, commandFailure, createCommandBus, createContainerPlan, createContainerState, createDefaultSchema, createEngine, createEventHub, createHistoryManager, createLayoutPlan, createRegisteredNode, createRegistry, duplicateNodeHandler, findIndexedNode, findNearestValidIndex, findNodeById, findParentNode, getLayoutRegionEntries, getLockedIndices, getLockedIndicesFromNodes, getSortScopeEntries, getSortScopeNodes, getSortableArrayIndexForInsert, getValidDropIndices, insertNodeIntoTree, isInsertAllowed, isMoveAllowed, isRemoveAllowed, moveNodeHandler, normalizeStyleValueMap, removeNodeFromTree, removeNodeHandler, resolveBehavior, resolveCreatable, resolveDestination, resolveNodeLayout, resolveNodeSource, resolvePlacementDecision, setGlobalConfigHandler, stripPageLayout, updatePropsHandler, validateContainerDefinition, validateSchema, walkFlatChildren };