@craftile/core 1.1.1 → 1.2.0
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 +3 -3
- package/src/blocks-manager.ts +6 -1
- package/src/commands/duplicate-block.ts +2 -5
- package/src/commands/insert-block-from-preset.ts +6 -8
- package/src/commands/insert-block.ts +6 -8
- package/src/commands/move-block.ts +7 -11
- package/src/commands/remove-block.ts +1 -0
- package/src/utils.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftile/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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.
|
|
30
|
-
"@craftile/types": "1.
|
|
29
|
+
"@craftile/event-bus": "1.2.0",
|
|
30
|
+
"@craftile/types": "1.2.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"vitest": "^3.2.4",
|
package/src/blocks-manager.ts
CHANGED
|
@@ -76,6 +76,7 @@ export class BlocksManager {
|
|
|
76
76
|
/**
|
|
77
77
|
* Validate if a block type can be a child of another type
|
|
78
78
|
* Private blocks can only be children if explicitly listed in parent's accepts array (no pattern matching)
|
|
79
|
+
* Patterns in the parent's rejects array remove matches from accepts, including private blocks
|
|
79
80
|
*/
|
|
80
81
|
canBeChild(childType: string, parentType: string): boolean {
|
|
81
82
|
const parentSchema = this.get(parentType);
|
|
@@ -84,6 +85,10 @@ export class BlocksManager {
|
|
|
84
85
|
return false;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
if (parentSchema.rejects?.some((pattern) => this.matchesPattern(childType, pattern))) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
const childSchema = this.get(childType);
|
|
88
93
|
const isPrivate = childSchema?.private === true;
|
|
89
94
|
|
|
@@ -104,7 +109,7 @@ export class BlocksManager {
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
|
-
* Check if a child type matches a pattern from the accepts list
|
|
112
|
+
* Check if a child type matches a pattern from the accepts or rejects list
|
|
108
113
|
* Supports glob-style wildcards: '*', '@theme/*', '*-button', '*type*', etc.
|
|
109
114
|
*/
|
|
110
115
|
private matchesPattern(childType: string, pattern: string): boolean {
|
|
@@ -108,9 +108,6 @@ export class DuplicateBlockCommand implements Command {
|
|
|
108
108
|
|
|
109
109
|
private determineInsertLocation(): void {
|
|
110
110
|
const originalBlock = this.page.blocks[this.blockId];
|
|
111
|
-
if (!originalBlock) {
|
|
112
|
-
throw new Error(`Block not found: ${this.blockId}`);
|
|
113
|
-
}
|
|
114
111
|
|
|
115
112
|
this.parentId = originalBlock.parentId || null;
|
|
116
113
|
|
|
@@ -148,7 +145,7 @@ export class DuplicateBlockCommand implements Command {
|
|
|
148
145
|
|
|
149
146
|
targetParent.children.splice(this.insertIndex, 0, this.duplicatedBlockId);
|
|
150
147
|
} else {
|
|
151
|
-
let targetRegion = this.page.regions.find((r) => r
|
|
148
|
+
let targetRegion = this.page.regions.find((r) => getRegionId(r) === this.regionId);
|
|
152
149
|
|
|
153
150
|
if (!targetRegion) {
|
|
154
151
|
targetRegion = { name: this.regionId || 'main', blocks: [] };
|
|
@@ -178,7 +175,7 @@ export class DuplicateBlockCommand implements Command {
|
|
|
178
175
|
} else {
|
|
179
176
|
// Remove from region
|
|
180
177
|
if (this.page.regions) {
|
|
181
|
-
const targetRegion = this.page.regions.find((r) => r
|
|
178
|
+
const targetRegion = this.page.regions.find((r) => getRegionId(r) === this.regionId);
|
|
182
179
|
if (targetRegion) {
|
|
183
180
|
const index = targetRegion.blocks.indexOf(this.duplicatedBlockId);
|
|
184
181
|
if (index !== -1) {
|
|
@@ -26,6 +26,7 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
26
26
|
private properties: Record<string, any>;
|
|
27
27
|
private insertedBlock?: Block;
|
|
28
28
|
private actualIndex?: number;
|
|
29
|
+
private resolvedRegionId?: string;
|
|
29
30
|
private blocksManager: BlocksManager;
|
|
30
31
|
private emit: EngineEmitFn;
|
|
31
32
|
private createdBlockIds: string[] = [];
|
|
@@ -69,6 +70,7 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
69
70
|
|
|
70
71
|
apply(): void {
|
|
71
72
|
const target = resolveInsertTarget(this.page, this.parentId, this.regionId, this.index);
|
|
73
|
+
this.resolvedRegionId = target.kind === 'region' ? target.regionId : undefined;
|
|
72
74
|
|
|
73
75
|
const blockSchema = this.blocksManager.get(this.blockType);
|
|
74
76
|
|
|
@@ -126,11 +128,7 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
126
128
|
this.insertedBlock.parentId = target.parent.id;
|
|
127
129
|
target.parent.children.splice(target.index, 0, this.blockId);
|
|
128
130
|
} else {
|
|
129
|
-
|
|
130
|
-
if (!region) {
|
|
131
|
-
region = { id: target.regionId, name: target.regionId, blocks: [] };
|
|
132
|
-
this.page.regions.push(region);
|
|
133
|
-
}
|
|
131
|
+
const region = this.page.regions.find((r) => getRegionId(r) === target.regionId)!;
|
|
134
132
|
region.blocks.splice(target.index, 0, this.blockId);
|
|
135
133
|
}
|
|
136
134
|
|
|
@@ -158,9 +156,8 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
158
156
|
if (parent && this.actualIndex !== undefined) {
|
|
159
157
|
parent.children.splice(this.actualIndex, 1);
|
|
160
158
|
}
|
|
161
|
-
} else {
|
|
162
|
-
const
|
|
163
|
-
const targetRegion = this.page.regions.find((r) => getRegionId(r) === targetRegionId);
|
|
159
|
+
} else if (this.resolvedRegionId) {
|
|
160
|
+
const targetRegion = this.page.regions.find((r) => getRegionId(r) === this.resolvedRegionId);
|
|
164
161
|
|
|
165
162
|
if (targetRegion && this.actualIndex !== undefined) {
|
|
166
163
|
targetRegion.blocks.splice(this.actualIndex, 1);
|
|
@@ -171,6 +168,7 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
171
168
|
blockId: this.blockId,
|
|
172
169
|
block: this.insertedBlock,
|
|
173
170
|
parentId: this.parentId,
|
|
171
|
+
regionId: this.resolvedRegionId,
|
|
174
172
|
});
|
|
175
173
|
}
|
|
176
174
|
|
|
@@ -23,6 +23,7 @@ export class InsertBlockCommand implements Command {
|
|
|
23
23
|
private blockSchema?: BlockSchema;
|
|
24
24
|
private insertedBlock?: Block;
|
|
25
25
|
private actualIndex?: number;
|
|
26
|
+
private resolvedRegionId?: string;
|
|
26
27
|
private emit: EngineEmitFn;
|
|
27
28
|
|
|
28
29
|
constructor(page: Page, options: InsertBlockOptions) {
|
|
@@ -40,6 +41,7 @@ export class InsertBlockCommand implements Command {
|
|
|
40
41
|
|
|
41
42
|
apply(): void {
|
|
42
43
|
const target = resolveInsertTarget(this.page, this.parentId, this.regionId, this.index);
|
|
44
|
+
this.resolvedRegionId = target.kind === 'region' ? target.regionId : undefined;
|
|
43
45
|
|
|
44
46
|
const blockName = this.blockSchema?.meta?.name || this.blockType;
|
|
45
47
|
|
|
@@ -59,11 +61,7 @@ export class InsertBlockCommand implements Command {
|
|
|
59
61
|
this.insertedBlock.parentId = target.parent.id;
|
|
60
62
|
target.parent.children.splice(target.index, 0, this.blockId);
|
|
61
63
|
} else {
|
|
62
|
-
|
|
63
|
-
if (!region) {
|
|
64
|
-
region = { id: target.regionId, name: target.regionId, blocks: [] };
|
|
65
|
-
this.page.regions.push(region);
|
|
66
|
-
}
|
|
64
|
+
const region = this.page.regions.find((r) => getRegionId(r) === target.regionId)!;
|
|
67
65
|
region.blocks.splice(target.index, 0, this.blockId);
|
|
68
66
|
}
|
|
69
67
|
|
|
@@ -88,9 +86,8 @@ export class InsertBlockCommand implements Command {
|
|
|
88
86
|
if (parent && this.actualIndex !== undefined) {
|
|
89
87
|
parent.children.splice(this.actualIndex, 1);
|
|
90
88
|
}
|
|
91
|
-
} else {
|
|
92
|
-
const
|
|
93
|
-
const targetRegion = this.page.regions.find((r) => getRegionId(r) === targetRegionId);
|
|
89
|
+
} else if (this.resolvedRegionId) {
|
|
90
|
+
const targetRegion = this.page.regions.find((r) => getRegionId(r) === this.resolvedRegionId);
|
|
94
91
|
|
|
95
92
|
if (targetRegion && this.actualIndex !== undefined) {
|
|
96
93
|
targetRegion.blocks.splice(this.actualIndex, 1);
|
|
@@ -101,6 +98,7 @@ export class InsertBlockCommand implements Command {
|
|
|
101
98
|
blockId: this.blockId,
|
|
102
99
|
block: this.insertedBlock,
|
|
103
100
|
parentId: this.parentId,
|
|
101
|
+
regionId: this.resolvedRegionId,
|
|
104
102
|
});
|
|
105
103
|
}
|
|
106
104
|
|
|
@@ -55,11 +55,7 @@ export class MoveBlockCommand implements Command {
|
|
|
55
55
|
target.parent.children.splice(target.index, 0, this.blockId);
|
|
56
56
|
} else {
|
|
57
57
|
this.blockToMove!.parentId = undefined;
|
|
58
|
-
|
|
59
|
-
if (!region) {
|
|
60
|
-
region = { id: target.regionId, name: target.regionId, blocks: [] };
|
|
61
|
-
this.page.regions.push(region);
|
|
62
|
-
}
|
|
58
|
+
const region = this.page.regions.find((r) => getRegionId(r) === target.regionId)!;
|
|
63
59
|
region.blocks.splice(target.index, 0, this.blockId);
|
|
64
60
|
}
|
|
65
61
|
|
|
@@ -67,7 +63,7 @@ export class MoveBlockCommand implements Command {
|
|
|
67
63
|
blockId: this.blockId,
|
|
68
64
|
targetParentId: this.targetParentId,
|
|
69
65
|
targetIndex: this.targetIndex,
|
|
70
|
-
targetRegionId:
|
|
66
|
+
targetRegionId: target.kind === 'region' ? target.regionId : undefined,
|
|
71
67
|
sourceParentId: this.originalParentId || null,
|
|
72
68
|
sourceRegionId: this.originalRegionId || null,
|
|
73
69
|
sourceIndex: this.originalIndex,
|
|
@@ -121,12 +117,12 @@ export class MoveBlockCommand implements Command {
|
|
|
121
117
|
|
|
122
118
|
const regionId = resolveRegionId(this.page, this.targetRegionId);
|
|
123
119
|
const existing = this.page.regions.find((r) => getRegionId(r) === regionId);
|
|
120
|
+
if (!existing) {
|
|
121
|
+
throw new Error(`Region not found: ${regionId}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
124
|
const sourceIsTarget = this.originalRegionId === regionId;
|
|
125
|
-
const prospective = existing
|
|
126
|
-
? sourceIsTarget
|
|
127
|
-
? existing.blocks.filter((id) => id !== this.blockId)
|
|
128
|
-
: existing.blocks
|
|
129
|
-
: [];
|
|
125
|
+
const prospective = sourceIsTarget ? existing.blocks.filter((id) => id !== this.blockId) : existing.blocks;
|
|
130
126
|
|
|
131
127
|
return { kind: 'region', regionId, index: clampIndex(prospective.length, this.targetIndex) };
|
|
132
128
|
}
|
package/src/utils.ts
CHANGED
|
@@ -60,10 +60,14 @@ export const resolveInsertTarget = (
|
|
|
60
60
|
|
|
61
61
|
const targetRegionId = resolveRegionId(page, regionId);
|
|
62
62
|
const existing = page.regions.find((r) => getRegionId(r) === targetRegionId);
|
|
63
|
+
if (!existing) {
|
|
64
|
+
throw new Error(`Region not found: ${targetRegionId}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
return {
|
|
64
68
|
kind: 'region',
|
|
65
69
|
regionId: targetRegionId,
|
|
66
|
-
index: clampIndex(existing
|
|
70
|
+
index: clampIndex(existing.blocks.length, index),
|
|
67
71
|
};
|
|
68
72
|
};
|
|
69
73
|
|