@craftile/core 1.3.0 → 1.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftile/core",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Framework-agnostic block editor engine for Craftile",
5
5
  "keywords": [
6
6
  "craftile",
@@ -26,8 +26,8 @@
26
26
  "src"
27
27
  ],
28
28
  "dependencies": {
29
- "@craftile/event-bus": "1.3.0",
30
- "@craftile/types": "1.3.0"
29
+ "@craftile/event-bus": "1.3.2",
30
+ "@craftile/types": "1.3.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "vitest": "^3.2.4",
@@ -13,16 +13,20 @@ export class DuplicateBlockCommand implements Command {
13
13
  private blockId: string;
14
14
 
15
15
  // State for reverting
16
- private duplicatedBlockId!: string;
16
+ private duplicatedBlockId: string;
17
17
  private duplicatedBlock?: Block;
18
18
  private parentId?: string | null;
19
19
  private insertIndex!: number;
20
20
  private regionId?: string | null;
21
21
  private emit: EngineEmitFn;
22
22
 
23
+ // Original descendant ID -> copy ID, reused on redo so the copy keeps its IDs
24
+ private cloneIds = new Map<string, string>();
25
+
23
26
  constructor(getPage: () => Page, options: DuplicateBlockOptions) {
24
27
  this.getPage = getPage;
25
28
  this.blockId = options.blockId;
29
+ this.duplicatedBlockId = generateId();
26
30
  this.emit = options.emit;
27
31
  }
28
32
 
@@ -33,8 +37,6 @@ export class DuplicateBlockCommand implements Command {
33
37
  throw new Error(`Block not found: ${this.blockId}`);
34
38
  }
35
39
 
36
- this.duplicatedBlockId = generateId();
37
-
38
40
  this.duplicatedBlock = this.cloneBlockWithNewIds(
39
41
  page,
40
42
  originalBlock,
@@ -62,6 +64,13 @@ export class DuplicateBlockCommand implements Command {
62
64
  }
63
65
 
64
66
  this.removeDuplicatedBlock(this.getPage());
67
+
68
+ this.emit('block:remove', {
69
+ blockId: this.duplicatedBlockId,
70
+ block: this.duplicatedBlock,
71
+ parentId: this.parentId || undefined,
72
+ regionId: this.regionId || undefined,
73
+ });
65
74
  }
66
75
 
67
76
  getBlockId(): string {
@@ -100,7 +109,11 @@ export class DuplicateBlockCommand implements Command {
100
109
  for (const childId of block.children) {
101
110
  const childBlock = page.blocks[childId];
102
111
  if (childBlock) {
103
- const newChildId = generateId();
112
+ let newChildId = this.cloneIds.get(childId);
113
+ if (!newChildId) {
114
+ newChildId = generateId();
115
+ this.cloneIds.set(childId, newChildId);
116
+ }
104
117
  this.cloneBlockWithNewIds(page, childBlock, newChildId, newId);
105
118
  clonedBlock.children.push(newChildId);
106
119
  }
@@ -31,6 +31,10 @@ export class InsertBlockFromPresetCommand implements Command {
31
31
  private emit: EngineEmitFn;
32
32
  private createdBlockIds: string[] = [];
33
33
 
34
+ // Child IDs in creation order, reused on redo so children keep their IDs
35
+ private childIds: string[] = [];
36
+ private childIdIndex = 0;
37
+
34
38
  constructor(getPage: () => Page, options: InsertBlockFromPresetOptions) {
35
39
  this.getPage = getPage;
36
40
  this.blockType = options.blockType;
@@ -75,6 +79,9 @@ export class InsertBlockFromPresetCommand implements Command {
75
79
 
76
80
  const blockSchema = this.blocksManager.get(this.blockType);
77
81
 
82
+ this.createdBlockIds = [];
83
+ this.childIdIndex = 0;
84
+
78
85
  if (this.presetData) {
79
86
  this.insertedBlock = {
80
87
  ...structuredClone(this.presetData),
@@ -185,7 +192,7 @@ export class InsertBlockFromPresetCommand implements Command {
185
192
  const childIds: string[] = [];
186
193
 
187
194
  for (const structure of structures) {
188
- const childId = generateId();
195
+ const childId = this.nextChildId();
189
196
  const childSchema = this.blocksManager.get(structure.type);
190
197
 
191
198
  if (!childSchema) {
@@ -220,6 +227,14 @@ export class InsertBlockFromPresetCommand implements Command {
220
227
  return childIds;
221
228
  }
222
229
 
230
+ private nextChildId(): string {
231
+ if (this.childIdIndex === this.childIds.length) {
232
+ this.childIds.push(generateId());
233
+ }
234
+
235
+ return this.childIds[this.childIdIndex++];
236
+ }
237
+
223
238
  private buildProperties(blockSchema?: BlockSchema, presetProperties?: Record<string, any>): Record<string, any> {
224
239
  const properties: Record<string, any> = {};
225
240
 
@@ -23,6 +23,8 @@ export class MoveBlockCommand implements Command {
23
23
  private originalParentId?: string;
24
24
  private originalIndex!: number;
25
25
  private originalRegionId?: string;
26
+ private resolvedIndex!: number;
27
+ private resolvedRegionId?: string;
26
28
 
27
29
  private emit: EngineEmitFn;
28
30
 
@@ -66,12 +68,14 @@ export class MoveBlockCommand implements Command {
66
68
  }
67
69
 
68
70
  this.moved = true;
71
+ this.resolvedIndex = target.index;
72
+ this.resolvedRegionId = target.kind === 'region' ? target.regionId : undefined;
69
73
 
70
74
  this.emit('block:move', {
71
75
  blockId: this.blockId,
72
76
  targetParentId: this.targetParentId,
73
- targetIndex: this.targetIndex,
74
- targetRegionId: target.kind === 'region' ? target.regionId : undefined,
77
+ targetIndex: this.resolvedIndex,
78
+ targetRegionId: this.resolvedRegionId,
75
79
  sourceParentId: this.originalParentId || null,
76
80
  sourceRegionId: this.originalRegionId || null,
77
81
  sourceIndex: this.originalIndex,
@@ -185,8 +189,8 @@ export class MoveBlockCommand implements Command {
185
189
  targetIndex: this.originalIndex,
186
190
  targetRegionId: this.originalRegionId,
187
191
  sourceParentId: this.targetParentId || null,
188
- sourceRegionId: this.targetRegionId || null,
189
- sourceIndex: this.targetIndex || 0,
192
+ sourceRegionId: this.resolvedRegionId || null,
193
+ sourceIndex: this.resolvedIndex,
190
194
  });
191
195
  }
192
196
 
@@ -11,6 +11,7 @@ export class RemoveBlockCommand implements Command {
11
11
  private getPage: () => Page;
12
12
  private blockId: string;
13
13
  private removedBlock?: Block;
14
+ private removedDescendants: Block[] = [];
14
15
  private originalIndex?: number;
15
16
  private originalParentId?: string;
16
17
  private regionId?: string;
@@ -49,6 +50,11 @@ export class RemoveBlockCommand implements Command {
49
50
  }
50
51
  }
51
52
 
53
+ this.removedDescendants = this.collectDescendants(page, this.removedBlock);
54
+ for (const descendant of this.removedDescendants) {
55
+ delete page.blocks[descendant.id];
56
+ }
57
+
52
58
  delete page.blocks[this.blockId];
53
59
 
54
60
  this.emit('block:remove', {
@@ -67,6 +73,9 @@ export class RemoveBlockCommand implements Command {
67
73
  const page = this.getPage();
68
74
 
69
75
  page.blocks[this.blockId] = this.removedBlock;
76
+ for (const descendant of this.removedDescendants) {
77
+ page.blocks[descendant.id] = descendant;
78
+ }
70
79
 
71
80
  if (this.originalParentId) {
72
81
  const parent = page.blocks[this.originalParentId];
@@ -96,4 +105,17 @@ export class RemoveBlockCommand implements Command {
96
105
  getRemovedBlock(): Block | undefined {
97
106
  return this.removedBlock;
98
107
  }
108
+
109
+ private collectDescendants(page: Page, block: Block): Block[] {
110
+ const descendants: Block[] = [];
111
+
112
+ for (const childId of block.children) {
113
+ const child = page.blocks[childId];
114
+ if (child) {
115
+ descendants.push(child, ...this.collectDescendants(page, child));
116
+ }
117
+ }
118
+
119
+ return descendants;
120
+ }
99
121
  }