@templatical/core 0.9.1 → 0.10.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.js CHANGED
@@ -1,619 +1,503 @@
1
- // src/editor.ts
2
- import { createDefaultTemplateContent } from "@templatical/types";
3
- import {
4
- computed,
5
- reactive,
6
- readonly
7
- } from "@vue/reactivity";
1
+ import { createBlock, createDefaultTemplateContent, generateId } from "@templatical/types";
2
+ import { computed, reactive, readonly, ref, watch } from "@vue/reactivity";
3
+ //#region src/editor.ts
8
4
  function getColumnCount(layout) {
9
- if (layout === "1") return 1;
10
- if (layout === "3") return 3;
11
- return 2;
5
+ if (layout === "1") return 1;
6
+ if (layout === "3") return 3;
7
+ return 2;
12
8
  }
13
9
  function useEditor(options) {
14
- const state = reactive({
15
- content: options.content ?? createDefaultTemplateContent(
16
- options.defaultFontFamily,
17
- options.templateDefaults
18
- ),
19
- selectedBlockId: null,
20
- viewport: "desktop",
21
- darkMode: false,
22
- previewMode: false,
23
- isDirty: false,
24
- uiTheme: "auto"
25
- });
26
- const content = computed({
27
- get: () => state.content,
28
- set: (value) => {
29
- state.content = value;
30
- state.isDirty = true;
31
- }
32
- });
33
- const selectedBlock = computed(() => {
34
- if (!state.selectedBlockId) return null;
35
- return findBlockById(state.content.blocks, state.selectedBlockId);
36
- });
37
- function findBlockById(blocks, id) {
38
- for (const block of blocks) {
39
- if (block.id === id) return block;
40
- if (block.type === "section") {
41
- for (const column of block.children) {
42
- const found = findBlockById(column, id);
43
- if (found) return found;
44
- }
45
- }
46
- }
47
- return null;
48
- }
49
- function collectBlockIds(block, ids) {
50
- ids.add(block.id);
51
- if (block.type === "section") {
52
- for (const column of block.children) {
53
- for (const child of column) {
54
- collectBlockIds(child, ids);
55
- }
56
- }
57
- }
58
- }
59
- function findBlockParent(blocks, id, parent = { blocks }) {
60
- for (let i = 0; i < blocks.length; i++) {
61
- const block = blocks[i];
62
- if (block.id === id) return parent;
63
- if (block.type === "section") {
64
- for (let colIdx = 0; colIdx < block.children.length; colIdx++) {
65
- const result = findBlockParent(block.children[colIdx], id, {
66
- blocks: block.children[colIdx],
67
- sectionId: block.id,
68
- columnIndex: colIdx
69
- });
70
- if (result) return result;
71
- }
72
- }
73
- }
74
- return null;
75
- }
76
- function isBlockLocked(blockId) {
77
- return options.lockedBlocks?.value.has(blockId) ?? false;
78
- }
79
- function findBlockLocation(blockId) {
80
- const parent = findBlockParent(state.content.blocks, blockId);
81
- if (!parent) return null;
82
- const index = parent.blocks.findIndex((b) => b.id === blockId);
83
- if (index === -1) return null;
84
- return {
85
- targetSectionId: parent.sectionId,
86
- columnIndex: parent.columnIndex,
87
- index
88
- };
89
- }
90
- function setContent(newContent, markDirty2 = true) {
91
- state.content = newContent;
92
- if (markDirty2) {
93
- state.isDirty = true;
94
- }
95
- }
96
- function selectBlock(blockId) {
97
- if (blockId && isBlockLocked(blockId)) {
98
- return;
99
- }
100
- state.selectedBlockId = blockId;
101
- }
102
- function setViewport(viewport) {
103
- state.viewport = viewport;
104
- }
105
- function setDarkMode(darkMode) {
106
- state.darkMode = darkMode;
107
- }
108
- function setUiTheme(theme) {
109
- state.uiTheme = theme;
110
- }
111
- function setPreviewMode(previewMode) {
112
- state.previewMode = previewMode;
113
- if (previewMode) {
114
- state.selectedBlockId = null;
115
- }
116
- }
117
- function updateBlock(blockId, updates) {
118
- if (isBlockLocked(blockId)) {
119
- return;
120
- }
121
- const block = findBlockById(state.content.blocks, blockId);
122
- if (block) {
123
- Object.assign(block, updates);
124
- state.isDirty = true;
125
- }
126
- }
127
- function updateSettings(updates) {
128
- state.content.settings = { ...state.content.settings, ...updates };
129
- state.isDirty = true;
130
- }
131
- function addBlock(block, targetSectionId, columnIndex = 0, index) {
132
- if (targetSectionId) {
133
- if (isBlockLocked(targetSectionId)) {
134
- return;
135
- }
136
- const section = findBlockById(state.content.blocks, targetSectionId);
137
- if (section && section.type === "section") {
138
- if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {
139
- return;
140
- }
141
- section.children[columnIndex] = section.children[columnIndex] || [];
142
- const targetArray = section.children[columnIndex];
143
- if (index !== void 0 && index < targetArray.length) {
144
- targetArray.splice(index, 0, block);
145
- } else {
146
- targetArray.push(block);
147
- }
148
- }
149
- } else {
150
- if (index !== void 0 && index < state.content.blocks.length) {
151
- state.content.blocks.splice(index, 0, block);
152
- } else {
153
- state.content.blocks.push(block);
154
- }
155
- }
156
- state.isDirty = true;
157
- }
158
- function removeBlock(blockId) {
159
- if (isBlockLocked(blockId)) {
160
- return;
161
- }
162
- const parent = findBlockParent(state.content.blocks, blockId);
163
- if (parent) {
164
- const index = parent.blocks.findIndex((b) => b.id === blockId);
165
- if (index !== -1) {
166
- const [removed] = parent.blocks.splice(index, 1);
167
- if (state.selectedBlockId) {
168
- const removedIds = /* @__PURE__ */ new Set();
169
- collectBlockIds(removed, removedIds);
170
- if (removedIds.has(state.selectedBlockId)) {
171
- state.selectedBlockId = null;
172
- }
173
- }
174
- state.isDirty = true;
175
- }
176
- }
177
- }
178
- function moveBlock(blockId, newIndex, targetSectionId, columnIndex = 0) {
179
- if (isBlockLocked(blockId)) {
180
- return;
181
- }
182
- if (targetSectionId && isBlockLocked(targetSectionId)) {
183
- return;
184
- }
185
- const parent = findBlockParent(state.content.blocks, blockId);
186
- if (!parent) return;
187
- const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
188
- if (oldIndex === -1) return;
189
- let targetArray;
190
- if (targetSectionId) {
191
- const section = findBlockById(state.content.blocks, targetSectionId);
192
- if (!section || section.type !== "section") return;
193
- if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {
194
- return;
195
- }
196
- section.children[columnIndex] = section.children[columnIndex] || [];
197
- targetArray = section.children[columnIndex];
198
- } else {
199
- targetArray = state.content.blocks;
200
- }
201
- const [block] = parent.blocks.splice(oldIndex, 1);
202
- targetArray.splice(newIndex, 0, block);
203
- state.isDirty = true;
204
- }
205
- function markDirty() {
206
- state.isDirty = true;
207
- }
208
- return {
209
- state: readonly(state),
210
- content,
211
- selectedBlock,
212
- isBlockLocked,
213
- setContent,
214
- selectBlock,
215
- setViewport,
216
- setDarkMode,
217
- setUiTheme,
218
- setPreviewMode,
219
- updateBlock,
220
- updateSettings,
221
- addBlock,
222
- removeBlock,
223
- moveBlock,
224
- markDirty,
225
- findBlockLocation
226
- };
10
+ const state = reactive({
11
+ content: options.content ?? createDefaultTemplateContent(options.defaultFontFamily, options.templateDefaults),
12
+ selectedBlockId: null,
13
+ viewport: "desktop",
14
+ darkMode: false,
15
+ previewMode: false,
16
+ isDirty: false,
17
+ uiTheme: "auto"
18
+ });
19
+ const content = computed({
20
+ get: () => state.content,
21
+ set: (value) => {
22
+ state.content = value;
23
+ state.isDirty = true;
24
+ }
25
+ });
26
+ const selectedBlock = computed(() => {
27
+ if (!state.selectedBlockId) return null;
28
+ return findBlockById(state.content.blocks, state.selectedBlockId);
29
+ });
30
+ function findBlockById(blocks, id) {
31
+ for (const block of blocks) {
32
+ if (block.id === id) return block;
33
+ if (block.type === "section") for (const column of block.children) {
34
+ const found = findBlockById(column, id);
35
+ if (found) return found;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ function collectBlockIds(block, ids) {
41
+ ids.add(block.id);
42
+ if (block.type === "section") for (const column of block.children) for (const child of column) collectBlockIds(child, ids);
43
+ }
44
+ function findBlockParent(blocks, id, parent = { blocks }) {
45
+ for (let i = 0; i < blocks.length; i++) {
46
+ const block = blocks[i];
47
+ if (block.id === id) return parent;
48
+ if (block.type === "section") for (let colIdx = 0; colIdx < block.children.length; colIdx++) {
49
+ const result = findBlockParent(block.children[colIdx], id, {
50
+ blocks: block.children[colIdx],
51
+ sectionId: block.id,
52
+ columnIndex: colIdx
53
+ });
54
+ if (result) return result;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function isBlockLocked(blockId) {
60
+ return options.lockedBlocks?.value.has(blockId) ?? false;
61
+ }
62
+ function findBlockLocation(blockId) {
63
+ const parent = findBlockParent(state.content.blocks, blockId);
64
+ if (!parent) return null;
65
+ const index = parent.blocks.findIndex((b) => b.id === blockId);
66
+ if (index === -1) return null;
67
+ return {
68
+ targetSectionId: parent.sectionId,
69
+ columnIndex: parent.columnIndex,
70
+ index
71
+ };
72
+ }
73
+ function setContent(newContent, markDirty = true) {
74
+ state.content = newContent;
75
+ if (markDirty) state.isDirty = true;
76
+ }
77
+ function selectBlock(blockId) {
78
+ if (blockId && isBlockLocked(blockId)) return;
79
+ state.selectedBlockId = blockId;
80
+ }
81
+ function setViewport(viewport) {
82
+ state.viewport = viewport;
83
+ }
84
+ function setDarkMode(darkMode) {
85
+ state.darkMode = darkMode;
86
+ }
87
+ function setUiTheme(theme) {
88
+ state.uiTheme = theme;
89
+ }
90
+ function setPreviewMode(previewMode) {
91
+ state.previewMode = previewMode;
92
+ if (previewMode) state.selectedBlockId = null;
93
+ }
94
+ function updateBlock(blockId, updates) {
95
+ if (isBlockLocked(blockId)) return;
96
+ const block = findBlockById(state.content.blocks, blockId);
97
+ if (block) {
98
+ Object.assign(block, updates);
99
+ state.isDirty = true;
100
+ }
101
+ }
102
+ function updateSettings(updates) {
103
+ state.content.settings = {
104
+ ...state.content.settings,
105
+ ...updates
106
+ };
107
+ state.isDirty = true;
108
+ }
109
+ function addBlock(block, targetSectionId, columnIndex = 0, index) {
110
+ if (targetSectionId) {
111
+ if (isBlockLocked(targetSectionId)) return;
112
+ const section = findBlockById(state.content.blocks, targetSectionId);
113
+ if (section && section.type === "section") {
114
+ if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) return;
115
+ section.children[columnIndex] = section.children[columnIndex] || [];
116
+ const targetArray = section.children[columnIndex];
117
+ if (index !== void 0 && index < targetArray.length) targetArray.splice(index, 0, block);
118
+ else targetArray.push(block);
119
+ }
120
+ } else if (index !== void 0 && index < state.content.blocks.length) state.content.blocks.splice(index, 0, block);
121
+ else state.content.blocks.push(block);
122
+ state.isDirty = true;
123
+ }
124
+ function removeBlock(blockId) {
125
+ if (isBlockLocked(blockId)) return;
126
+ const parent = findBlockParent(state.content.blocks, blockId);
127
+ if (parent) {
128
+ const index = parent.blocks.findIndex((b) => b.id === blockId);
129
+ if (index !== -1) {
130
+ const [removed] = parent.blocks.splice(index, 1);
131
+ if (state.selectedBlockId) {
132
+ const removedIds = /* @__PURE__ */ new Set();
133
+ collectBlockIds(removed, removedIds);
134
+ if (removedIds.has(state.selectedBlockId)) state.selectedBlockId = null;
135
+ }
136
+ state.isDirty = true;
137
+ }
138
+ }
139
+ }
140
+ function moveBlock(blockId, newIndex, targetSectionId, columnIndex = 0) {
141
+ if (isBlockLocked(blockId)) return;
142
+ if (targetSectionId && isBlockLocked(targetSectionId)) return;
143
+ const parent = findBlockParent(state.content.blocks, blockId);
144
+ if (!parent) return;
145
+ const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
146
+ if (oldIndex === -1) return;
147
+ let targetArray;
148
+ if (targetSectionId) {
149
+ const section = findBlockById(state.content.blocks, targetSectionId);
150
+ if (!section || section.type !== "section") return;
151
+ if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) return;
152
+ section.children[columnIndex] = section.children[columnIndex] || [];
153
+ targetArray = section.children[columnIndex];
154
+ } else targetArray = state.content.blocks;
155
+ const [block] = parent.blocks.splice(oldIndex, 1);
156
+ targetArray.splice(newIndex, 0, block);
157
+ state.isDirty = true;
158
+ }
159
+ function markDirty() {
160
+ state.isDirty = true;
161
+ }
162
+ return {
163
+ state: readonly(state),
164
+ content,
165
+ selectedBlock,
166
+ isBlockLocked,
167
+ setContent,
168
+ selectBlock,
169
+ setViewport,
170
+ setDarkMode,
171
+ setUiTheme,
172
+ setPreviewMode,
173
+ updateBlock,
174
+ updateSettings,
175
+ addBlock,
176
+ removeBlock,
177
+ moveBlock,
178
+ markDirty,
179
+ findBlockLocation
180
+ };
227
181
  }
228
-
229
- // src/history.ts
230
- import { computed as computed2, ref } from "@vue/reactivity";
231
- var MAX_STACK_SIZE = 50;
232
- var DEBOUNCE_MS = 300;
233
- var NAVIGATE_IDLE_MS = 1500;
182
+ //#endregion
183
+ //#region src/history.ts
184
+ const MAX_STACK_SIZE = 50;
185
+ const DEBOUNCE_MS = 300;
186
+ const NAVIGATE_IDLE_MS = 1500;
234
187
  function useHistory(options) {
235
- const {
236
- content,
237
- setContent,
238
- isRemoteOperation,
239
- maxSize = MAX_STACK_SIZE
240
- } = options;
241
- const undoStack = ref([]);
242
- const redoStack = ref([]);
243
- const isNavigating = ref(false);
244
- let navigatingTimeoutId = null;
245
- let pendingDebounce = null;
246
- const canUndo = computed2(() => undoStack.value.length > 0);
247
- const canRedo = computed2(() => redoStack.value.length > 0);
248
- function cloneContent() {
249
- const seen = /* @__PURE__ */ new WeakSet();
250
- return JSON.parse(
251
- JSON.stringify(content.value, (_key, value) => {
252
- if (typeof value === "object" && value !== null) {
253
- if (seen.has(value)) return void 0;
254
- seen.add(value);
255
- }
256
- return value;
257
- })
258
- );
259
- }
260
- function pushToUndoStack(snapshot) {
261
- undoStack.value.push(snapshot);
262
- if (undoStack.value.length > maxSize) {
263
- undoStack.value.splice(0, undoStack.value.length - maxSize);
264
- }
265
- }
266
- function flushPendingDebounce() {
267
- if (pendingDebounce) {
268
- clearTimeout(pendingDebounce.timeoutId);
269
- pendingDebounce = null;
270
- }
271
- }
272
- function record() {
273
- if (isRemoteOperation?.()) {
274
- return;
275
- }
276
- flushPendingDebounce();
277
- pushToUndoStack(cloneContent());
278
- redoStack.value = [];
279
- }
280
- function recordDebounced(blockId) {
281
- if (isRemoteOperation?.()) {
282
- return;
283
- }
284
- if (pendingDebounce && pendingDebounce.blockId === blockId) {
285
- clearTimeout(pendingDebounce.timeoutId);
286
- pendingDebounce.timeoutId = setTimeout(() => {
287
- pendingDebounce = null;
288
- }, DEBOUNCE_MS);
289
- return;
290
- }
291
- flushPendingDebounce();
292
- pushToUndoStack(cloneContent());
293
- redoStack.value = [];
294
- pendingDebounce = {
295
- blockId,
296
- timeoutId: setTimeout(() => {
297
- pendingDebounce = null;
298
- }, DEBOUNCE_MS)
299
- };
300
- }
301
- function setNavigating() {
302
- isNavigating.value = true;
303
- if (navigatingTimeoutId) {
304
- clearTimeout(navigatingTimeoutId);
305
- }
306
- navigatingTimeoutId = setTimeout(() => {
307
- isNavigating.value = false;
308
- navigatingTimeoutId = null;
309
- }, NAVIGATE_IDLE_MS);
310
- }
311
- function undo() {
312
- if (undoStack.value.length === 0) {
313
- return;
314
- }
315
- flushPendingDebounce();
316
- const snapshot = undoStack.value.pop();
317
- redoStack.value.push(cloneContent());
318
- setContent(snapshot, true);
319
- setNavigating();
320
- }
321
- function redo() {
322
- if (redoStack.value.length === 0) {
323
- return;
324
- }
325
- flushPendingDebounce();
326
- const snapshot = redoStack.value.pop();
327
- undoStack.value.push(cloneContent());
328
- setContent(snapshot, true);
329
- setNavigating();
330
- }
331
- function clear() {
332
- undoStack.value = [];
333
- redoStack.value = [];
334
- flushPendingDebounce();
335
- }
336
- function destroy() {
337
- clear();
338
- if (navigatingTimeoutId) {
339
- clearTimeout(navigatingTimeoutId);
340
- navigatingTimeoutId = null;
341
- }
342
- }
343
- return {
344
- canUndo,
345
- canRedo,
346
- isNavigating,
347
- undo,
348
- redo,
349
- record,
350
- recordDebounced,
351
- clear,
352
- destroy
353
- };
188
+ const { content, setContent, isRemoteOperation, maxSize = MAX_STACK_SIZE } = options;
189
+ const undoStack = ref([]);
190
+ const redoStack = ref([]);
191
+ const isNavigating = ref(false);
192
+ let navigatingTimeoutId = null;
193
+ let pendingDebounce = null;
194
+ const canUndo = computed(() => undoStack.value.length > 0);
195
+ const canRedo = computed(() => redoStack.value.length > 0);
196
+ function cloneContent() {
197
+ const seen = /* @__PURE__ */ new WeakSet();
198
+ return JSON.parse(JSON.stringify(content.value, (_key, value) => {
199
+ if (typeof value === "object" && value !== null) {
200
+ if (seen.has(value)) return void 0;
201
+ seen.add(value);
202
+ }
203
+ return value;
204
+ }));
205
+ }
206
+ function pushToUndoStack(snapshot) {
207
+ undoStack.value.push(snapshot);
208
+ if (undoStack.value.length > maxSize) undoStack.value.splice(0, undoStack.value.length - maxSize);
209
+ }
210
+ function flushPendingDebounce() {
211
+ if (pendingDebounce) {
212
+ clearTimeout(pendingDebounce.timeoutId);
213
+ pendingDebounce = null;
214
+ }
215
+ }
216
+ function record() {
217
+ if (isRemoteOperation?.()) return;
218
+ flushPendingDebounce();
219
+ pushToUndoStack(cloneContent());
220
+ redoStack.value = [];
221
+ }
222
+ function recordDebounced(blockId) {
223
+ if (isRemoteOperation?.()) return;
224
+ if (pendingDebounce && pendingDebounce.blockId === blockId) {
225
+ clearTimeout(pendingDebounce.timeoutId);
226
+ pendingDebounce.timeoutId = setTimeout(() => {
227
+ pendingDebounce = null;
228
+ }, DEBOUNCE_MS);
229
+ return;
230
+ }
231
+ flushPendingDebounce();
232
+ pushToUndoStack(cloneContent());
233
+ redoStack.value = [];
234
+ pendingDebounce = {
235
+ blockId,
236
+ timeoutId: setTimeout(() => {
237
+ pendingDebounce = null;
238
+ }, DEBOUNCE_MS)
239
+ };
240
+ }
241
+ function setNavigating() {
242
+ isNavigating.value = true;
243
+ if (navigatingTimeoutId) clearTimeout(navigatingTimeoutId);
244
+ navigatingTimeoutId = setTimeout(() => {
245
+ isNavigating.value = false;
246
+ navigatingTimeoutId = null;
247
+ }, NAVIGATE_IDLE_MS);
248
+ }
249
+ function undo() {
250
+ if (undoStack.value.length === 0) return;
251
+ flushPendingDebounce();
252
+ const snapshot = undoStack.value.pop();
253
+ redoStack.value.push(cloneContent());
254
+ setContent(snapshot, true);
255
+ setNavigating();
256
+ }
257
+ function redo() {
258
+ if (redoStack.value.length === 0) return;
259
+ flushPendingDebounce();
260
+ const snapshot = redoStack.value.pop();
261
+ undoStack.value.push(cloneContent());
262
+ setContent(snapshot, true);
263
+ setNavigating();
264
+ }
265
+ function clear() {
266
+ undoStack.value = [];
267
+ redoStack.value = [];
268
+ flushPendingDebounce();
269
+ }
270
+ function destroy() {
271
+ clear();
272
+ if (navigatingTimeoutId) {
273
+ clearTimeout(navigatingTimeoutId);
274
+ navigatingTimeoutId = null;
275
+ }
276
+ }
277
+ return {
278
+ canUndo,
279
+ canRedo,
280
+ isNavigating,
281
+ undo,
282
+ redo,
283
+ record,
284
+ recordDebounced,
285
+ clear,
286
+ destroy
287
+ };
354
288
  }
355
-
356
- // src/block-actions.ts
357
- import { createBlock, generateId } from "@templatical/types";
289
+ //#endregion
290
+ //#region src/block-actions.ts
358
291
  function regenerateNestedIds(block) {
359
- if (block.type === "table") {
360
- block.rows = block.rows.map((row) => ({
361
- ...row,
362
- id: generateId(),
363
- cells: row.cells.map((cell) => ({ ...cell, id: generateId() }))
364
- }));
365
- } else if (block.type === "social") {
366
- block.icons = block.icons.map((icon) => ({ ...icon, id: generateId() }));
367
- } else if (block.type === "menu") {
368
- block.items = block.items.map((item) => ({ ...item, id: generateId() }));
369
- }
292
+ if (block.type === "table") block.rows = block.rows.map((row) => ({
293
+ ...row,
294
+ id: generateId(),
295
+ cells: row.cells.map((cell) => ({
296
+ ...cell,
297
+ id: generateId()
298
+ }))
299
+ }));
300
+ else if (block.type === "social") block.icons = block.icons.map((icon) => ({
301
+ ...icon,
302
+ id: generateId()
303
+ }));
304
+ else if (block.type === "menu") block.items = block.items.map((item) => ({
305
+ ...item,
306
+ id: generateId()
307
+ }));
370
308
  }
371
309
  function useBlockActions(options) {
372
- const { addBlock, removeBlock, updateBlock, selectBlock, findBlockLocation } = options;
373
- function createAndAddBlock(type, targetSectionId, columnIndex) {
374
- const block = createBlock(type, options.blockDefaults);
375
- addBlock(block, targetSectionId, columnIndex);
376
- selectBlock(block.id);
377
- return block;
378
- }
379
- function duplicateBlock(block, targetSectionId, columnIndex) {
380
- const cloned = JSON.parse(JSON.stringify(block));
381
- cloned.id = generateId();
382
- regenerateNestedIds(cloned);
383
- if (cloned.type === "section") {
384
- cloned.children = cloned.children.map(
385
- (column) => column.map((child) => {
386
- const clonedChild = JSON.parse(JSON.stringify(child));
387
- clonedChild.id = generateId();
388
- regenerateNestedIds(clonedChild);
389
- return clonedChild;
390
- })
391
- );
392
- }
393
- if (targetSectionId !== void 0 || columnIndex !== void 0) {
394
- addBlock(cloned, targetSectionId, columnIndex);
395
- } else {
396
- const sourceLocation = findBlockLocation?.(block.id) ?? null;
397
- if (sourceLocation) {
398
- addBlock(
399
- cloned,
400
- sourceLocation.targetSectionId,
401
- sourceLocation.columnIndex,
402
- sourceLocation.index + 1
403
- );
404
- } else {
405
- addBlock(cloned, targetSectionId, columnIndex);
406
- }
407
- }
408
- selectBlock(cloned.id);
409
- return cloned;
410
- }
411
- function deleteBlock(blockId) {
412
- removeBlock(blockId);
413
- }
414
- function updateBlockProperty(blockId, key, value) {
415
- updateBlock(blockId, { [key]: value });
416
- }
417
- return {
418
- createAndAddBlock,
419
- duplicateBlock,
420
- deleteBlock,
421
- updateBlockProperty
422
- };
310
+ const { addBlock, removeBlock, updateBlock, selectBlock, findBlockLocation } = options;
311
+ function createAndAddBlock(type, targetSectionId, columnIndex) {
312
+ const block = createBlock(type, options.blockDefaults);
313
+ addBlock(block, targetSectionId, columnIndex);
314
+ selectBlock(block.id);
315
+ return block;
316
+ }
317
+ function duplicateBlock(block, targetSectionId, columnIndex) {
318
+ const cloned = JSON.parse(JSON.stringify(block));
319
+ cloned.id = generateId();
320
+ regenerateNestedIds(cloned);
321
+ if (cloned.type === "section") cloned.children = cloned.children.map((column) => column.map((child) => {
322
+ const clonedChild = JSON.parse(JSON.stringify(child));
323
+ clonedChild.id = generateId();
324
+ regenerateNestedIds(clonedChild);
325
+ return clonedChild;
326
+ }));
327
+ if (targetSectionId !== void 0 || columnIndex !== void 0) addBlock(cloned, targetSectionId, columnIndex);
328
+ else {
329
+ const sourceLocation = findBlockLocation?.(block.id) ?? null;
330
+ if (sourceLocation) addBlock(cloned, sourceLocation.targetSectionId, sourceLocation.columnIndex, sourceLocation.index + 1);
331
+ else addBlock(cloned, targetSectionId, columnIndex);
332
+ }
333
+ selectBlock(cloned.id);
334
+ return cloned;
335
+ }
336
+ function deleteBlock(blockId) {
337
+ removeBlock(blockId);
338
+ }
339
+ function updateBlockProperty(blockId, key, value) {
340
+ updateBlock(blockId, { [key]: value });
341
+ }
342
+ return {
343
+ createAndAddBlock,
344
+ duplicateBlock,
345
+ deleteBlock,
346
+ updateBlockProperty
347
+ };
423
348
  }
424
-
425
- // src/auto-save.ts
426
- import { watch } from "@vue/reactivity";
349
+ //#endregion
350
+ //#region src/auto-save.ts
427
351
  function useAutoSave(options) {
428
- const {
429
- content,
430
- isDirty,
431
- onChange,
432
- debounce = 1e3,
433
- enabled = true
434
- } = options;
435
- let timeoutId = null;
436
- let paused = false;
437
- function isEnabled() {
438
- return typeof enabled === "function" ? enabled() : enabled;
439
- }
440
- function pause() {
441
- paused = true;
442
- cancel();
443
- }
444
- function resume() {
445
- paused = false;
446
- }
447
- function cancel() {
448
- if (timeoutId) {
449
- clearTimeout(timeoutId);
450
- timeoutId = null;
451
- }
452
- }
453
- function flush() {
454
- cancel();
455
- if (isDirty()) {
456
- onChange(JSON.parse(JSON.stringify(content.value)));
457
- }
458
- }
459
- function scheduleOnChange() {
460
- if (!isEnabled() || paused) return;
461
- cancel();
462
- timeoutId = setTimeout(() => {
463
- timeoutId = null;
464
- if (isEnabled() && !paused && isDirty()) {
465
- onChange(JSON.parse(JSON.stringify(content.value)));
466
- }
467
- }, debounce);
468
- }
469
- const stopWatch = watch(
470
- content,
471
- () => {
472
- if (isEnabled() && !paused && isDirty()) {
473
- scheduleOnChange();
474
- }
475
- },
476
- { deep: true }
477
- );
478
- function destroy() {
479
- stopWatch();
480
- cancel();
481
- }
482
- return {
483
- flush,
484
- cancel,
485
- pause,
486
- resume,
487
- destroy
488
- };
352
+ const { content, isDirty, onChange, debounce = 1e3, enabled = true } = options;
353
+ let timeoutId = null;
354
+ let paused = false;
355
+ function isEnabled() {
356
+ return typeof enabled === "function" ? enabled() : enabled;
357
+ }
358
+ function pause() {
359
+ paused = true;
360
+ cancel();
361
+ }
362
+ function resume() {
363
+ paused = false;
364
+ }
365
+ function cancel() {
366
+ if (timeoutId) {
367
+ clearTimeout(timeoutId);
368
+ timeoutId = null;
369
+ }
370
+ }
371
+ function flush() {
372
+ cancel();
373
+ if (isDirty()) onChange(JSON.parse(JSON.stringify(content.value)));
374
+ }
375
+ function scheduleOnChange() {
376
+ if (!isEnabled() || paused) return;
377
+ cancel();
378
+ timeoutId = setTimeout(() => {
379
+ timeoutId = null;
380
+ if (isEnabled() && !paused && isDirty()) onChange(JSON.parse(JSON.stringify(content.value)));
381
+ }, debounce);
382
+ }
383
+ const stopWatch = watch(content, () => {
384
+ if (isEnabled() && !paused && isDirty()) scheduleOnChange();
385
+ }, { deep: true });
386
+ function destroy() {
387
+ stopWatch();
388
+ cancel();
389
+ }
390
+ return {
391
+ flush,
392
+ cancel,
393
+ pause,
394
+ resume,
395
+ destroy
396
+ };
489
397
  }
490
-
491
- // src/condition-preview.ts
492
- import { computed as computed3, reactive as reactive2 } from "@vue/reactivity";
398
+ //#endregion
399
+ //#region src/condition-preview.ts
493
400
  function useConditionPreview(editor) {
494
- const hiddenBlockIds = reactive2(/* @__PURE__ */ new Set());
495
- const hasHiddenBlocks = computed3(() => hiddenBlockIds.size > 0);
496
- function isHidden(blockId) {
497
- return hiddenBlockIds.has(blockId);
498
- }
499
- function toggleBlock(blockId) {
500
- if (hiddenBlockIds.has(blockId)) {
501
- hiddenBlockIds.delete(blockId);
502
- } else {
503
- hiddenBlockIds.add(blockId);
504
- if (editor.state.selectedBlockId === blockId) {
505
- editor.selectBlock(null);
506
- }
507
- }
508
- }
509
- function reset() {
510
- hiddenBlockIds.clear();
511
- }
512
- return {
513
- isHidden,
514
- toggleBlock,
515
- reset,
516
- hasHiddenBlocks
517
- };
401
+ const hiddenBlockIds = reactive(/* @__PURE__ */ new Set());
402
+ const hasHiddenBlocks = computed(() => hiddenBlockIds.size > 0);
403
+ function isHidden(blockId) {
404
+ return hiddenBlockIds.has(blockId);
405
+ }
406
+ function toggleBlock(blockId) {
407
+ if (hiddenBlockIds.has(blockId)) hiddenBlockIds.delete(blockId);
408
+ else {
409
+ hiddenBlockIds.add(blockId);
410
+ if (editor.state.selectedBlockId === blockId) editor.selectBlock(null);
411
+ }
412
+ }
413
+ function reset() {
414
+ hiddenBlockIds.clear();
415
+ }
416
+ return {
417
+ isHidden,
418
+ toggleBlock,
419
+ reset,
420
+ hasHiddenBlocks
421
+ };
518
422
  }
519
-
520
- // src/data-source-fetch.ts
521
- import { computed as computed4, ref as ref2 } from "@vue/reactivity";
423
+ //#endregion
424
+ //#region src/data-source-fetch.ts
522
425
  function useDataSourceFetch(options) {
523
- const isFetching = ref2(false);
524
- const fetchError = ref2(false);
525
- const hasDataSource = computed4(() => !!options.definition.value?.dataSource);
526
- const needsFetch = computed4(
527
- () => hasDataSource.value && !options.block.value.dataSourceFetched
528
- );
529
- async function fetch() {
530
- const def = options.definition.value;
531
- if (!def?.dataSource) {
532
- return;
533
- }
534
- isFetching.value = true;
535
- fetchError.value = false;
536
- try {
537
- const result = await def.dataSource.onFetch({
538
- fieldValues: { ...options.block.value.fieldValues },
539
- blockId: options.block.value.id
540
- });
541
- if (result == null) {
542
- return;
543
- }
544
- const merged = { ...options.block.value.fieldValues };
545
- for (const key of Object.keys(merged)) {
546
- if (key in result) {
547
- merged[key] = result[key];
548
- }
549
- }
550
- options.onUpdate(merged, true);
551
- } catch (error) {
552
- console.warn("[Templatical] Data source fetch error:", error);
553
- fetchError.value = true;
554
- } finally {
555
- isFetching.value = false;
556
- }
557
- }
558
- return {
559
- isFetching,
560
- fetchError,
561
- fetch,
562
- hasDataSource,
563
- needsFetch
564
- };
426
+ const isFetching = ref(false);
427
+ const fetchError = ref(false);
428
+ const hasDataSource = computed(() => !!options.definition.value?.dataSource);
429
+ const needsFetch = computed(() => hasDataSource.value && !options.block.value.dataSourceFetched);
430
+ async function fetch() {
431
+ const def = options.definition.value;
432
+ if (!def?.dataSource) return;
433
+ isFetching.value = true;
434
+ fetchError.value = false;
435
+ try {
436
+ const result = await def.dataSource.onFetch({
437
+ fieldValues: { ...options.block.value.fieldValues },
438
+ blockId: options.block.value.id
439
+ });
440
+ if (result == null) return;
441
+ const merged = { ...options.block.value.fieldValues };
442
+ for (const key of Object.keys(merged)) if (key in result) merged[key] = result[key];
443
+ options.onUpdate(merged, true);
444
+ } catch (error) {
445
+ console.warn("[Templatical] Data source fetch error:", error);
446
+ fetchError.value = true;
447
+ } finally {
448
+ isFetching.value = false;
449
+ }
450
+ }
451
+ return {
452
+ isFetching,
453
+ fetchError,
454
+ fetch,
455
+ hasDataSource,
456
+ needsFetch
457
+ };
565
458
  }
566
-
567
- // src/history-interceptor.ts
459
+ //#endregion
460
+ //#region src/history-interceptor.ts
461
+ /**
462
+ * Wraps editor mutation methods to record history snapshots before each
463
+ * operation. Mutates the editor object in place.
464
+ *
465
+ * Must be applied **after** any collaboration broadcast wrapping so the
466
+ * call chain is: history.record() → broadcast → original mutation.
467
+ */
568
468
  function useHistoryInterceptor(editor, history) {
569
- const originalAddBlock = editor.addBlock;
570
- const originalRemoveBlock = editor.removeBlock;
571
- const originalMoveBlock = editor.moveBlock;
572
- const originalUpdateBlock = editor.updateBlock;
573
- const originalUpdateSettings = editor.updateSettings;
574
- editor.addBlock = (block, targetSectionId, columnIndex, index) => {
575
- if (targetSectionId && editor.isBlockLocked(targetSectionId)) {
576
- return;
577
- }
578
- history.record();
579
- originalAddBlock(block, targetSectionId, columnIndex, index);
580
- };
581
- editor.removeBlock = (blockId) => {
582
- if (editor.isBlockLocked(blockId)) {
583
- return;
584
- }
585
- history.record();
586
- originalRemoveBlock(blockId);
587
- };
588
- editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
589
- if (editor.isBlockLocked(blockId)) {
590
- return;
591
- }
592
- if (targetSectionId && editor.isBlockLocked(targetSectionId)) {
593
- return;
594
- }
595
- history.record();
596
- originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
597
- };
598
- editor.updateBlock = (blockId, updates) => {
599
- if (editor.isBlockLocked(blockId)) {
600
- return;
601
- }
602
- history.recordDebounced(blockId);
603
- originalUpdateBlock(blockId, updates);
604
- };
605
- editor.updateSettings = (updates) => {
606
- history.record();
607
- originalUpdateSettings(updates);
608
- };
469
+ const originalAddBlock = editor.addBlock;
470
+ const originalRemoveBlock = editor.removeBlock;
471
+ const originalMoveBlock = editor.moveBlock;
472
+ const originalUpdateBlock = editor.updateBlock;
473
+ const originalUpdateSettings = editor.updateSettings;
474
+ editor.addBlock = (block, targetSectionId, columnIndex, index) => {
475
+ if (targetSectionId && editor.isBlockLocked(targetSectionId)) return;
476
+ history.record();
477
+ originalAddBlock(block, targetSectionId, columnIndex, index);
478
+ };
479
+ editor.removeBlock = (blockId) => {
480
+ if (editor.isBlockLocked(blockId)) return;
481
+ history.record();
482
+ originalRemoveBlock(blockId);
483
+ };
484
+ editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
485
+ if (editor.isBlockLocked(blockId)) return;
486
+ if (targetSectionId && editor.isBlockLocked(targetSectionId)) return;
487
+ history.record();
488
+ originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
489
+ };
490
+ editor.updateBlock = (blockId, updates) => {
491
+ if (editor.isBlockLocked(blockId)) return;
492
+ history.recordDebounced(blockId);
493
+ originalUpdateBlock(blockId, updates);
494
+ };
495
+ editor.updateSettings = (updates) => {
496
+ history.record();
497
+ originalUpdateSettings(updates);
498
+ };
609
499
  }
610
- export {
611
- useAutoSave,
612
- useBlockActions,
613
- useConditionPreview,
614
- useDataSourceFetch,
615
- useEditor,
616
- useHistory,
617
- useHistoryInterceptor
618
- };
500
+ //#endregion
501
+ export { useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor };
502
+
619
503
  //# sourceMappingURL=index.js.map