@craftile/core 0.1.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/src/utils.ts ADDED
@@ -0,0 +1,7 @@
1
+ export const generateId = (): string => {
2
+ if (crypto.randomUUID) {
3
+ return crypto.randomUUID();
4
+ }
5
+
6
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
7
+ };
@@ -0,0 +1,6 @@
1
+ export { useBlocksEngine } from './useBlocksEngine';
2
+ export type {
3
+ UseBlocksEngineOptions,
4
+ UseBlocksEngineReturn,
5
+ UseBlocksEngineFromInstanceOptions,
6
+ } from './useBlocksEngine';
@@ -0,0 +1,307 @@
1
+ import { ref, onUnmounted, computed, toRaw, nextTick, type Ref } from 'vue';
2
+ import type { Block, Page, Region } from '@craftile/types';
3
+ import type { EngineConfig } from '../types';
4
+ import { Engine } from '../engine';
5
+
6
+ export interface UseBlocksEngineOptions extends EngineConfig {
7
+ autoSync?: boolean;
8
+ }
9
+
10
+ export interface UseBlocksEngineFromInstanceOptions {
11
+ autoSync?: boolean;
12
+ }
13
+
14
+ export interface UseBlocksEngineReturn {
15
+ engine: Engine;
16
+
17
+ page: Ref<Page>;
18
+ blocks: Ref<Record<string, Block>>;
19
+ regions: Ref<Region[]>;
20
+
21
+ rootLevelBlocks: Ref<Block[]>;
22
+
23
+ getBlockById: (id: string) => Block | undefined;
24
+ getBlocksByType: (type: string) => Block[];
25
+ getChildrenBlocks: (parentId: string) => Block[];
26
+
27
+ insertBlock: (
28
+ blockType: string,
29
+ options?: {
30
+ parentId?: string;
31
+ regionName?: string;
32
+ index?: number;
33
+ }
34
+ ) => string;
35
+ removeBlock: (blockId: string) => void;
36
+ moveBlock: (
37
+ blockId: string,
38
+ options: {
39
+ targetParentId?: string;
40
+ targetIndex?: number;
41
+ targetRegionName?: string;
42
+ }
43
+ ) => void;
44
+ setBlockProperty: (blockId: string, propertyKey: string, propertyValue: any) => void;
45
+ toggleBlock: (blockId: string, disabled?: boolean) => void;
46
+ duplicateBlock: (blockId: string) => string;
47
+
48
+ undo: () => boolean;
49
+ redo: () => boolean;
50
+ canUndo: Ref<boolean>;
51
+ canRedo: Ref<boolean>;
52
+
53
+ refresh: () => void;
54
+ destroy: () => void;
55
+ }
56
+
57
+ /**
58
+ * Vue composable for reactive integration with Blocks Engine
59
+ *
60
+ * @param options Configuration options for the engine and composable
61
+ * @returns Reactive interface to the blocks engine
62
+ */
63
+ export function useBlocksEngine(options?: UseBlocksEngineOptions): UseBlocksEngineReturn;
64
+
65
+ /**
66
+ * Vue composable for reactive integration with existing Blocks Engine
67
+ *
68
+ * @param engine Existing engine instance to wrap with reactive features
69
+ * @param options Options for the composable behavior
70
+ * @returns Reactive interface to the blocks engine
71
+ */
72
+ export function useBlocksEngine(engine: Engine, options?: UseBlocksEngineFromInstanceOptions): UseBlocksEngineReturn;
73
+
74
+ export function useBlocksEngine(
75
+ configOrEngine?: UseBlocksEngineOptions | Engine,
76
+ instanceOptions?: UseBlocksEngineFromInstanceOptions
77
+ ): UseBlocksEngineReturn {
78
+ const isEngineInstance = configOrEngine instanceof Engine;
79
+ let engine: Engine;
80
+ let autoSync: boolean;
81
+
82
+ if (isEngineInstance) {
83
+ engine = configOrEngine;
84
+ autoSync = instanceOptions?.autoSync ?? true;
85
+ } else {
86
+ const config = configOrEngine || {};
87
+ const { autoSync: configAutoSync = true, ...engineConfig } = config;
88
+ engine = new Engine(engineConfig);
89
+ autoSync = configAutoSync;
90
+ }
91
+
92
+ const pageState = ref<Page>({
93
+ blocks: {},
94
+ regions: [{ name: 'main', blocks: [] }],
95
+ });
96
+ const blocks = computed(() => pageState.value.blocks);
97
+ const regions = computed(() => pageState.value.regions);
98
+
99
+ const canUndo = ref(engine.canUndo());
100
+ const canRedo = ref(engine.canRedo());
101
+
102
+ const rootLevelBlocks = computed(() => {
103
+ const rootLevel: Block[] = [];
104
+
105
+ for (const region of regions.value!) {
106
+ for (const blockId of region.blocks) {
107
+ const block = blocks.value[blockId];
108
+
109
+ if (block) {
110
+ rootLevel.push(block);
111
+ }
112
+ }
113
+ }
114
+
115
+ return rootLevel;
116
+ });
117
+
118
+ const getBlockById = (id: string): Block | undefined => {
119
+ return blocks.value[id];
120
+ };
121
+
122
+ const getBlocksByType = (type: string): Block[] => {
123
+ return Object.values(blocks.value).filter((block: Block) => {
124
+ return block.type === type;
125
+ });
126
+ };
127
+
128
+ const getChildrenBlocks = (parentId: string): Block[] => {
129
+ const parent = blocks.value[parentId];
130
+
131
+ if (!parent) {
132
+ return [];
133
+ }
134
+
135
+ return parent.children.map((childId: string) => blocks.value[childId]).filter(Boolean) as Block[];
136
+ };
137
+
138
+ const syncStateFromEngine = () => {
139
+ const rawPage = toRaw(engine.getPage());
140
+ pageState.value = rawPage;
141
+
142
+ // Defer history state updates to next tick to ensure command is added to history first
143
+ nextTick(() => {
144
+ canUndo.value = engine.canUndo();
145
+ canRedo.value = engine.canRedo();
146
+ });
147
+ };
148
+
149
+ syncStateFromEngine();
150
+
151
+ const eventCleanups: Array<() => void> = [];
152
+
153
+ if (autoSync) {
154
+ const eventTypes = [
155
+ 'page:set',
156
+ 'block:insert',
157
+ 'block:remove',
158
+ 'block:move',
159
+ 'block:toggle',
160
+ 'block:duplicate',
161
+ 'block:property:set',
162
+ 'undo',
163
+ 'redo',
164
+ ] as const;
165
+
166
+ eventTypes.forEach((eventType) => {
167
+ const cleanup = engine.on(eventType, () => {
168
+ syncStateFromEngine();
169
+ });
170
+
171
+ eventCleanups.push(cleanup);
172
+ });
173
+ }
174
+
175
+ const insertBlock = (
176
+ blockType: string,
177
+ options?: {
178
+ parentId?: string;
179
+ regionName?: string;
180
+ index?: number;
181
+ }
182
+ ): string => {
183
+ const blockId = engine.insertBlock(blockType, options);
184
+
185
+ if (!autoSync) {
186
+ syncStateFromEngine();
187
+ }
188
+
189
+ return blockId;
190
+ };
191
+
192
+ const removeBlock = (blockId: string): void => {
193
+ engine.removeBlock(blockId);
194
+
195
+ if (!autoSync) {
196
+ syncStateFromEngine();
197
+ }
198
+ };
199
+
200
+ const moveBlock = (
201
+ blockId: string,
202
+ options: {
203
+ targetParentId?: string;
204
+ targetIndex?: number;
205
+ targetRegionName?: string;
206
+ }
207
+ ): void => {
208
+ engine.moveBlock(blockId, options);
209
+
210
+ if (!autoSync) {
211
+ syncStateFromEngine();
212
+ }
213
+ };
214
+
215
+ const setBlockProperty = (blockId: string, propertyKey: string, propertyValue: any): void => {
216
+ engine.setBlockProperty(blockId, propertyKey, propertyValue);
217
+
218
+ if (!autoSync) {
219
+ syncStateFromEngine();
220
+ }
221
+ };
222
+
223
+ const toggleBlock = (blockId: string, disabled?: boolean): void => {
224
+ engine.toggleBlock(blockId, disabled);
225
+
226
+ if (!autoSync) {
227
+ syncStateFromEngine();
228
+ }
229
+ };
230
+
231
+ const duplicateBlock = (blockId: string): string => {
232
+ const newBlockId = engine.duplicateBlock(blockId);
233
+
234
+ if (!autoSync) {
235
+ syncStateFromEngine();
236
+ }
237
+
238
+ return newBlockId;
239
+ };
240
+
241
+ const undo = (): boolean => {
242
+ const result = engine.undo();
243
+
244
+ if (!autoSync) {
245
+ syncStateFromEngine();
246
+ }
247
+
248
+ return result;
249
+ };
250
+
251
+ const redo = (): boolean => {
252
+ const result = engine.redo();
253
+
254
+ if (!autoSync) {
255
+ syncStateFromEngine();
256
+ }
257
+
258
+ return result;
259
+ };
260
+
261
+ const refresh = () => {
262
+ syncStateFromEngine();
263
+ };
264
+
265
+ const destroy = () => {
266
+ eventCleanups.forEach((cleanup) => cleanup());
267
+ eventCleanups.length = 0;
268
+ };
269
+
270
+ onUnmounted(() => {
271
+ destroy();
272
+ });
273
+
274
+ return {
275
+ // Reactive state
276
+ page: pageState,
277
+ blocks,
278
+ regions,
279
+
280
+ // Engine instance
281
+ engine,
282
+
283
+ // Computed helpers
284
+ rootLevelBlocks,
285
+ getBlockById,
286
+ getBlocksByType,
287
+ getChildrenBlocks,
288
+
289
+ // Command methods
290
+ insertBlock,
291
+ removeBlock,
292
+ moveBlock,
293
+ setBlockProperty,
294
+ toggleBlock,
295
+ duplicateBlock,
296
+
297
+ // History methods
298
+ undo,
299
+ redo,
300
+ canUndo,
301
+ canRedo,
302
+
303
+ // Utility methods
304
+ refresh,
305
+ destroy,
306
+ };
307
+ }