@templatical/core 0.20.0 → 0.21.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/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { i as useEditor, n as UseEditorOptions, r as UseEditorReturn, t as EditorState } from "./editor-BIIsaIoN.js";
2
- import { Block, BlockDefaults, BlockType, CustomBlock, CustomBlockDefinition, TemplateContent } from "@templatical/types";
2
+ import { Block, BlockDefaults, BlockType, CustomBlock, CustomBlockDefinition, SavedBlock, SavedBlockPatch, SavedBlocksListParams, SavedBlocksProvider, TemplateContent } from "@templatical/types";
3
3
  import { ComputedRef, Ref } from "@vue/reactivity";
4
+ import { ComputedRef as ComputedRef$1, Ref as Ref$1 } from "vue";
4
5
  //#region src/history.d.ts
5
6
  interface UseHistoryOptions {
6
7
  content: Ref<TemplateContent>;
@@ -93,5 +94,95 @@ declare function useDataSourceFetch(options: {
93
94
  */
94
95
  declare function useHistoryInterceptor(editor: UseEditorReturn, history: UseHistoryReturn): void;
95
96
  //#endregion
96
- export { type EditorState, type UseAutoSaveOptions, type UseAutoSaveReturn, type UseBlockActionsOptions, type UseBlockActionsReturn, type UseConditionPreviewReturn, type UseEditorOptions, type UseEditorReturn, type UseHistoryOptions, type UseHistoryReturn, useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor };
97
+ //#region src/saved-blocks.d.ts
98
+ interface UseSavedBlocksOptions {
99
+ /**
100
+ * Storage backend. Supplied by the consumer via `init({ savedBlocks })`, or
101
+ * by Cloud's own adapter — this composable is transport-agnostic and never
102
+ * talks to a network itself.
103
+ */
104
+ provider: SavedBlocksProvider;
105
+ onError?: (error: Error) => void;
106
+ }
107
+ interface UseSavedBlocksReturn {
108
+ /** In-memory mirror of the provider's list, kept in sync after each call. */
109
+ savedBlocks: Ref$1<SavedBlock[]>;
110
+ isLoading: Ref$1<boolean>;
111
+ /**
112
+ * Distinct categories across the loaded entries, alphabetically sorted.
113
+ *
114
+ * Derived rather than stored: a category exists exactly as long as an entry
115
+ * carries it. Sorting here is not a violation of "the editor never re-sorts"
116
+ * — that rule protects the provider's ordering of *entries*, whereas these
117
+ * are strings with no given order.
118
+ */
119
+ categories: ComputedRef$1<string[]>;
120
+ /**
121
+ * Whether the provider supplied each mutation at all — `false` on the
122
+ * provider means the capability does not exist for this user, and the UI
123
+ * hides the corresponding affordance entirely.
124
+ *
125
+ * Constant for the provider's lifetime, but exposed as computed refs so
126
+ * consumers can bind them the same way as everything else here.
127
+ */
128
+ canCreate: ComputedRef$1<boolean>;
129
+ canUpdate: ComputedRef$1<boolean>;
130
+ canDelete: ComputedRef$1<boolean>;
131
+ /**
132
+ * Whether a *particular* entry may be edited / deleted: the capability must
133
+ * exist AND the entry must not opt out via `canUpdate` / `canDelete`.
134
+ *
135
+ * Use these rather than reading the flags directly, so every surface asks the
136
+ * question the same way and the "absent means allowed" default lives in one
137
+ * place.
138
+ */
139
+ canUpdateBlock: (block: SavedBlock) => boolean;
140
+ canDeleteBlock: (block: SavedBlock) => boolean;
141
+ /**
142
+ * Re-read from the provider. The editor always calls this bare and filters
143
+ * in memory; `params` exists for headless callers that want the provider to
144
+ * filter instead (see {@link SavedBlocksListParams}).
145
+ */
146
+ load: (params?: SavedBlocksListParams) => Promise<void>;
147
+ /** Rejects with an {@link SdkError} when the provider disabled `create`. */
148
+ create: (name: string, content: Block[], category?: string) => Promise<SavedBlock>;
149
+ /** Rejects when `update` is disabled or the entry opts out of editing. */
150
+ update: (id: string, patch: SavedBlockPatch) => Promise<SavedBlock>;
151
+ /** Rejects when `delete` is disabled or the entry opts out of deletion. */
152
+ remove: (id: string) => Promise<void>;
153
+ }
154
+ /**
155
+ * Reactive state over a {@link SavedBlocksProvider}.
156
+ *
157
+ * Owns the list, the loading flag, and the local mutations that keep the list
158
+ * consistent after each successful call, so a provider only has to implement
159
+ * four promise-returning methods. Errors are reported through `onError` and
160
+ * re-thrown, leaving the local list untouched on failure.
161
+ */
162
+ declare function useSavedBlocks(options: UseSavedBlocksOptions): UseSavedBlocksReturn;
163
+ //#endregion
164
+ //#region src/saved-blocks-local.d.ts
165
+ interface LocalStorageSavedBlocksOptions {
166
+ /**
167
+ * `localStorage` key holding the serialized array.
168
+ *
169
+ * @default "templatical:saved-blocks"
170
+ */
171
+ key?: string;
172
+ }
173
+ /**
174
+ * Browser-local {@link SavedBlocksProvider} backed by `localStorage`.
175
+ *
176
+ * Opt-in: pass it explicitly as `init({ savedBlocks: createLocalStorageSavedBlocksProvider() })`.
177
+ * The editor never falls back to it, so consumers without a provider keep the
178
+ * feature — and its UI — entirely off.
179
+ *
180
+ * Intended for demos, prototypes, and single-device use. Entries live in one
181
+ * browser profile only: they don't sync across devices or users, and clearing
182
+ * site data removes them. Back saved blocks with your own API for anything
183
+ * that needs to outlive a browser profile.
184
+ */
185
+ declare function createLocalStorageSavedBlocksProvider(options?: LocalStorageSavedBlocksOptions): SavedBlocksProvider;
186
+ //#endregion
187
+ export { type EditorState, type LocalStorageSavedBlocksOptions, type UseAutoSaveOptions, type UseAutoSaveReturn, type UseBlockActionsOptions, type UseBlockActionsReturn, type UseConditionPreviewReturn, type UseEditorOptions, type UseEditorReturn, type UseHistoryOptions, type UseHistoryReturn, type UseSavedBlocksOptions, type UseSavedBlocksReturn, createLocalStorageSavedBlocksProvider, useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor, useSavedBlocks };
97
188
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { createBlock, createDefaultTemplateContent, generateId, safeClone } from "@templatical/types";
1
+ import { SdkError, createBlock, createDefaultTemplateContent, generateId, safeClone } from "@templatical/types";
2
2
  import { computed, reactive, readonly, ref, watch } from "@vue/reactivity";
3
+ import { computed as computed$1, ref as ref$1 } from "vue";
3
4
  //#region src/editor.ts
4
5
  function getColumnCount(layout) {
5
6
  if (layout === "1") return 1;
@@ -493,6 +494,197 @@ function useHistoryInterceptor(editor, history) {
493
494
  };
494
495
  }
495
496
  //#endregion
496
- export { useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor };
497
+ //#region src/saved-blocks.ts
498
+ /**
499
+ * Reactive state over a {@link SavedBlocksProvider}.
500
+ *
501
+ * Owns the list, the loading flag, and the local mutations that keep the list
502
+ * consistent after each successful call, so a provider only has to implement
503
+ * four promise-returning methods. Errors are reported through `onError` and
504
+ * re-thrown, leaving the local list untouched on failure.
505
+ */
506
+ function useSavedBlocks(options) {
507
+ const { provider } = options;
508
+ const savedBlocks = ref$1([]);
509
+ const isLoading = ref$1(false);
510
+ const categories = computed$1(() => {
511
+ const seen = /* @__PURE__ */ new Set();
512
+ for (const block of savedBlocks.value) {
513
+ const category = block.category?.trim();
514
+ if (category) seen.add(category);
515
+ }
516
+ return [...seen].sort((a, b) => a.localeCompare(b));
517
+ });
518
+ const canCreate = computed$1(() => typeof provider.create === "function");
519
+ const canUpdate = computed$1(() => typeof provider.update === "function");
520
+ const canDelete = computed$1(() => typeof provider.delete === "function");
521
+ /** Absent flag means allowed — the flags exist only to forbid. */
522
+ function canUpdateBlock(block) {
523
+ return canUpdate.value && block.canUpdate !== false;
524
+ }
525
+ function canDeleteBlock(block) {
526
+ return canDelete.value && block.canDelete !== false;
527
+ }
528
+ /**
529
+ * The UI hides disabled actions, so reaching one of these means a programmatic
530
+ * caller went around it. Fail loudly rather than silently no-op: a resolved
531
+ * promise would read as "saved" to whoever awaited it.
532
+ */
533
+ function refuse(action, reason) {
534
+ throw new SdkError(`[Templatical] Saved blocks: ${action} is ${reason}. Check the capability before calling — the editor's own UI hides the action.`);
535
+ }
536
+ async function load(params) {
537
+ isLoading.value = true;
538
+ try {
539
+ savedBlocks.value = await provider.list(params);
540
+ } catch (error) {
541
+ options.onError?.(error);
542
+ throw error;
543
+ } finally {
544
+ isLoading.value = false;
545
+ }
546
+ }
547
+ async function create(name, content, category) {
548
+ const { create: providerCreate } = provider;
549
+ if (typeof providerCreate !== "function") refuse("create", "disabled by the provider");
550
+ try {
551
+ const created = await providerCreate(category ? {
552
+ name,
553
+ content,
554
+ category
555
+ } : {
556
+ name,
557
+ content
558
+ });
559
+ savedBlocks.value = [created, ...savedBlocks.value];
560
+ return created;
561
+ } catch (error) {
562
+ options.onError?.(error);
563
+ throw error;
564
+ }
565
+ }
566
+ async function update(id, patch) {
567
+ const { update: providerUpdate } = provider;
568
+ if (typeof providerUpdate !== "function") refuse("update", "disabled by the provider");
569
+ const local = savedBlocks.value.find((b) => b.id === id);
570
+ if (local && local.canUpdate === false) refuse("update", `not permitted for entry "${id}"`);
571
+ try {
572
+ const updated = await providerUpdate(id, patch);
573
+ savedBlocks.value = savedBlocks.value.map((b) => b.id === id ? updated : b);
574
+ return updated;
575
+ } catch (error) {
576
+ options.onError?.(error);
577
+ throw error;
578
+ }
579
+ }
580
+ async function remove(id) {
581
+ const { delete: providerDelete } = provider;
582
+ if (typeof providerDelete !== "function") refuse("delete", "disabled by the provider");
583
+ const local = savedBlocks.value.find((b) => b.id === id);
584
+ if (local && local.canDelete === false) refuse("delete", `not permitted for entry "${id}"`);
585
+ try {
586
+ await providerDelete(id);
587
+ savedBlocks.value = savedBlocks.value.filter((b) => b.id !== id);
588
+ } catch (error) {
589
+ options.onError?.(error);
590
+ throw error;
591
+ }
592
+ }
593
+ return {
594
+ savedBlocks,
595
+ isLoading,
596
+ categories,
597
+ canCreate,
598
+ canUpdate,
599
+ canDelete,
600
+ canUpdateBlock,
601
+ canDeleteBlock,
602
+ load,
603
+ create,
604
+ update,
605
+ remove
606
+ };
607
+ }
608
+ //#endregion
609
+ //#region src/saved-blocks-local.ts
610
+ const DEFAULT_KEY = "templatical:saved-blocks";
611
+ /**
612
+ * Browser-local {@link SavedBlocksProvider} backed by `localStorage`.
613
+ *
614
+ * Opt-in: pass it explicitly as `init({ savedBlocks: createLocalStorageSavedBlocksProvider() })`.
615
+ * The editor never falls back to it, so consumers without a provider keep the
616
+ * feature — and its UI — entirely off.
617
+ *
618
+ * Intended for demos, prototypes, and single-device use. Entries live in one
619
+ * browser profile only: they don't sync across devices or users, and clearing
620
+ * site data removes them. Back saved blocks with your own API for anything
621
+ * that needs to outlive a browser profile.
622
+ */
623
+ function createLocalStorageSavedBlocksProvider(options = {}) {
624
+ const storageKey = options.key ?? DEFAULT_KEY;
625
+ function getStorage() {
626
+ if (typeof localStorage === "undefined") throw new Error("[Templatical] createLocalStorageSavedBlocksProvider requires a browser environment with localStorage. Supply your own SavedBlocksProvider for server-side or non-browser use.");
627
+ return localStorage;
628
+ }
629
+ function readAll() {
630
+ const raw = getStorage().getItem(storageKey);
631
+ if (!raw) return [];
632
+ try {
633
+ const parsed = JSON.parse(raw);
634
+ return Array.isArray(parsed) ? parsed : [];
635
+ } catch {
636
+ return [];
637
+ }
638
+ }
639
+ function writeAll(blocks) {
640
+ getStorage().setItem(storageKey, JSON.stringify(blocks));
641
+ }
642
+ return {
643
+ async list(params) {
644
+ const all = readAll();
645
+ const search = params?.search?.trim().toLowerCase();
646
+ const category = params?.category?.trim();
647
+ return all.filter((b) => {
648
+ if (search && !b.name.toLowerCase().includes(search)) return false;
649
+ if (category && b.category !== category) return false;
650
+ return true;
651
+ });
652
+ },
653
+ async create(input) {
654
+ const now = (/* @__PURE__ */ new Date()).toISOString();
655
+ const created = {
656
+ id: generateId(),
657
+ name: input.name,
658
+ content: input.content,
659
+ ...input.category ? { category: input.category } : {},
660
+ createdAt: now,
661
+ updatedAt: now
662
+ };
663
+ writeAll([created, ...readAll()]);
664
+ return created;
665
+ },
666
+ async update(id, patch) {
667
+ const all = readAll();
668
+ const index = all.findIndex((b) => b.id === id);
669
+ if (index === -1) throw new Error(`[Templatical] Saved block not found: ${id}`);
670
+ const updated = {
671
+ ...all[index],
672
+ ...patch,
673
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
674
+ };
675
+ const next = [...all];
676
+ next[index] = updated;
677
+ writeAll(next);
678
+ return updated;
679
+ },
680
+ async delete(id) {
681
+ const all = readAll();
682
+ if (!all.some((b) => b.id === id)) throw new Error(`[Templatical] Saved block not found: ${id}`);
683
+ writeAll(all.filter((b) => b.id !== id));
684
+ }
685
+ };
686
+ }
687
+ //#endregion
688
+ export { createLocalStorageSavedBlocksProvider, useAutoSave, useBlockActions, useConditionPreview, useDataSourceFetch, useEditor, useHistory, useHistoryInterceptor, useSavedBlocks };
497
689
 
498
690
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/editor.ts","../src/history.ts","../src/block-actions.ts","../src/auto-save.ts","../src/condition-preview.ts","../src/data-source-fetch.ts","../src/history-interceptor.ts"],"sourcesContent":["import type {\n Block,\n ColumnLayout,\n TemplateContent,\n TemplateDefaults,\n TemplateSettings,\n UiTheme,\n ViewportSize,\n} from \"@templatical/types\";\nimport { createDefaultTemplateContent } from \"@templatical/types\";\n\nfunction getColumnCount(layout: ColumnLayout): number {\n if (layout === \"1\") return 1;\n if (layout === \"3\") return 3;\n return 2;\n}\nimport {\n computed,\n reactive,\n readonly,\n type DeepReadonly,\n type Ref,\n} from \"@vue/reactivity\";\n\nexport interface EditorState {\n content: TemplateContent;\n selectedBlockId: string | null;\n viewport: ViewportSize;\n darkMode: boolean;\n previewMode: boolean;\n isDirty: boolean;\n uiTheme: UiTheme;\n}\n\nexport interface UseEditorOptions {\n content: TemplateContent;\n defaultFontFamily?: string;\n templateDefaults?: TemplateDefaults;\n lockedBlocks?: Ref<Map<string, unknown>>;\n}\n\nexport interface UseEditorReturn {\n state: DeepReadonly<EditorState>;\n content: Ref<TemplateContent>;\n selectedBlock: Ref<Block | null>;\n setContent: (content: TemplateContent, markDirty?: boolean) => void;\n selectBlock: (blockId: string | null) => void;\n setViewport: (viewport: ViewportSize) => void;\n setDarkMode: (darkMode: boolean) => void;\n setPreviewMode: (previewMode: boolean) => void;\n setUiTheme: (theme: UiTheme) => void;\n updateBlock: (blockId: string, updates: Partial<Block>) => void;\n updateSettings: (updates: Partial<TemplateSettings>) => void;\n addBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n index?: number,\n ) => void;\n removeBlock: (blockId: string) => void;\n moveBlock: (\n blockId: string,\n newIndex: number,\n targetSectionId?: string,\n columnIndex?: number,\n ) => void;\n isBlockLocked: (blockId: string) => boolean;\n markDirty: () => void;\n findBlockLocation: (blockId: string) => {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null;\n}\n\nexport function useEditor(options: UseEditorOptions): UseEditorReturn {\n const state = reactive<EditorState>({\n content:\n options.content ??\n createDefaultTemplateContent(\n options.defaultFontFamily,\n options.templateDefaults,\n ),\n selectedBlockId: null,\n viewport: \"desktop\",\n darkMode: false,\n previewMode: false,\n isDirty: false,\n uiTheme: \"auto\",\n });\n\n const content = computed({\n get: () => state.content,\n set: (value: TemplateContent) => {\n state.content = value;\n state.isDirty = true;\n },\n });\n\n const selectedBlock = computed(() => {\n if (!state.selectedBlockId) return null;\n return findBlockById(state.content.blocks, state.selectedBlockId);\n });\n\n function findBlockById(blocks: Block[], id: string): Block | null {\n for (const block of blocks) {\n if (block.id === id) return block;\n if (block.type === \"section\") {\n for (const column of block.children) {\n const found = findBlockById(column, id);\n if (found) return found;\n }\n }\n }\n return null;\n }\n\n function collectBlockIds(block: Block, ids: Set<string>): void {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n collectBlockIds(child, ids);\n }\n }\n }\n }\n\n function findBlockParent(\n blocks: Block[],\n id: string,\n parent: {\n blocks: Block[];\n sectionId?: string;\n columnIndex?: number;\n } = { blocks },\n ): { blocks: Block[]; sectionId?: string; columnIndex?: number } | null {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n if (block.id === id) return parent;\n if (block.type === \"section\") {\n for (let colIdx = 0; colIdx < block.children.length; colIdx++) {\n const result = findBlockParent(block.children[colIdx], id, {\n blocks: block.children[colIdx],\n sectionId: block.id,\n columnIndex: colIdx,\n });\n if (result) return result;\n }\n }\n }\n return null;\n }\n\n function isBlockLocked(blockId: string): boolean {\n return options.lockedBlocks?.value.has(blockId) ?? false;\n }\n\n function findBlockLocation(blockId: string): {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null {\n const parent = findBlockParent(state.content.blocks, blockId);\n if (!parent) return null;\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n if (index === -1) return null;\n return {\n targetSectionId: parent.sectionId,\n columnIndex: parent.columnIndex,\n index,\n };\n }\n\n // TODO(collab): the lock checks in addBlock/moveBlock/removeBlock/updateBlock\n // are shallow — they only consider the directly-targeted block id. A section\n // can still be removed, moved, or have its `children` array rewritten while a\n // peer is editing one of its descendants, which silently disrupts that peer's\n // edit. Add a `hasLockedDescendant(blockId)` helper and gate section-level\n // operations on it (and on the parent of each affected child) so cascades\n // through the tree are also blocked.\n\n function setContent(newContent: TemplateContent, markDirty = true): void {\n state.content = newContent;\n if (markDirty) {\n state.isDirty = true;\n }\n }\n\n function selectBlock(blockId: string | null): void {\n if (blockId && isBlockLocked(blockId)) {\n return;\n }\n state.selectedBlockId = blockId;\n }\n\n function setViewport(viewport: ViewportSize): void {\n state.viewport = viewport;\n }\n\n function setDarkMode(darkMode: boolean): void {\n state.darkMode = darkMode;\n }\n\n function setUiTheme(theme: UiTheme): void {\n state.uiTheme = theme;\n }\n\n function setPreviewMode(previewMode: boolean): void {\n state.previewMode = previewMode;\n if (previewMode) {\n state.selectedBlockId = null;\n }\n }\n\n function updateBlock(blockId: string, updates: Partial<Block>): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n const block = findBlockById(state.content.blocks, blockId);\n if (block) {\n Object.assign(block, updates);\n state.isDirty = true;\n }\n }\n\n function updateSettings(updates: Partial<TemplateSettings>): void {\n state.content.settings = { ...state.content.settings, ...updates };\n state.isDirty = true;\n }\n\n function addBlock(\n block: Block,\n targetSectionId?: string,\n columnIndex = 0,\n index?: number,\n ): void {\n // Sections cannot be nested inside a column — MJML forbids `mj-section`\n // inside `mj-column`, so the renderer drops them on export (issue #292).\n // Reject the nest up front rather than lose the content silently later.\n if (targetSectionId && block.type === \"section\") {\n return;\n }\n if (targetSectionId) {\n if (isBlockLocked(targetSectionId)) {\n return;\n }\n const section = findBlockById(state.content.blocks, targetSectionId);\n if (section && section.type === \"section\") {\n if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {\n return;\n }\n section.children[columnIndex] = section.children[columnIndex] || [];\n const targetArray = section.children[columnIndex];\n if (index !== undefined && index < targetArray.length) {\n targetArray.splice(index, 0, block);\n } else {\n targetArray.push(block);\n }\n }\n } else {\n if (index !== undefined && index < state.content.blocks.length) {\n state.content.blocks.splice(index, 0, block);\n } else {\n state.content.blocks.push(block);\n }\n }\n state.isDirty = true;\n }\n\n function removeBlock(blockId: string): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n const parent = findBlockParent(state.content.blocks, blockId);\n if (parent) {\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n if (index !== -1) {\n const [removed] = parent.blocks.splice(index, 1);\n if (state.selectedBlockId) {\n const removedIds = new Set<string>();\n collectBlockIds(removed, removedIds);\n if (removedIds.has(state.selectedBlockId)) {\n state.selectedBlockId = null;\n }\n }\n state.isDirty = true;\n }\n }\n }\n\n function moveBlock(\n blockId: string,\n newIndex: number,\n targetSectionId?: string,\n columnIndex = 0,\n ): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n if (targetSectionId && isBlockLocked(targetSectionId)) {\n return;\n }\n\n const parent = findBlockParent(state.content.blocks, blockId);\n if (!parent) return;\n\n const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);\n if (oldIndex === -1) return;\n\n // Sections cannot be nested inside a column (issue #292) — refuse to move\n // a section into a section column; MJML would drop it on export.\n if (targetSectionId && parent.blocks[oldIndex].type === \"section\") {\n return;\n }\n\n // Resolve target before mutating the source — otherwise an invalid\n // targetSectionId leaves the block spliced-out and unrecoverable.\n let targetArray: Block[];\n if (targetSectionId) {\n const section = findBlockById(state.content.blocks, targetSectionId);\n if (!section || section.type !== \"section\") return;\n if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {\n return;\n }\n section.children[columnIndex] = section.children[columnIndex] || [];\n targetArray = section.children[columnIndex];\n } else {\n targetArray = state.content.blocks;\n }\n\n const [block] = parent.blocks.splice(oldIndex, 1);\n targetArray.splice(newIndex, 0, block);\n\n state.isDirty = true;\n }\n\n function markDirty(): void {\n state.isDirty = true;\n }\n\n return {\n state: readonly(state),\n content,\n selectedBlock,\n isBlockLocked,\n setContent,\n selectBlock,\n setViewport,\n setDarkMode,\n setUiTheme,\n setPreviewMode,\n updateBlock,\n updateSettings,\n addBlock,\n removeBlock,\n moveBlock,\n markDirty,\n findBlockLocation,\n };\n}\n","import { safeClone, type TemplateContent } from \"@templatical/types\";\nimport { computed, ref, type ComputedRef, type Ref } from \"@vue/reactivity\";\n\nexport interface UseHistoryOptions {\n content: Ref<TemplateContent>;\n setContent: (content: TemplateContent, markDirty?: boolean) => void;\n isRemoteOperation?: () => boolean;\n maxSize?: number;\n}\n\nexport interface UseHistoryReturn {\n canUndo: ComputedRef<boolean>;\n canRedo: ComputedRef<boolean>;\n isNavigating: Ref<boolean>;\n undo: () => void;\n redo: () => void;\n record: () => void;\n recordDebounced: (blockId: string) => void;\n clear: () => void;\n destroy: () => void;\n}\n\ninterface DebouncedSnapshot {\n blockId: string;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\nconst MAX_STACK_SIZE = 50;\nconst DEBOUNCE_MS = 300;\nconst NAVIGATE_IDLE_MS = 1500;\n\nexport function useHistory(options: UseHistoryOptions): UseHistoryReturn {\n const {\n content,\n setContent,\n isRemoteOperation,\n maxSize = MAX_STACK_SIZE,\n } = options;\n\n const undoStack = ref<TemplateContent[]>([]);\n const redoStack = ref<TemplateContent[]>([]);\n const isNavigating = ref(false);\n let navigatingTimeoutId: ReturnType<typeof setTimeout> | null = null;\n let pendingDebounce: DebouncedSnapshot | null = null;\n\n const canUndo = computed(() => undoStack.value.length > 0);\n const canRedo = computed(() => redoStack.value.length > 0);\n\n function cloneContent(): TemplateContent {\n // Cycle-safe deep clone (see `safeClone`). A naked JSON.stringify throws\n // `Converting circular structure to JSON` if a DOM element with a Sortable\n // expando back-ref ever sneaks into block data via a drag handler — which\n // would freeze the undo stack, since every subsequent mutation calls\n // `record()`. safeClone drops the back-ref instead of throwing.\n return safeClone(content.value);\n }\n\n function pushToUndoStack(snapshot: TemplateContent): void {\n undoStack.value.push(snapshot);\n if (undoStack.value.length > maxSize) {\n undoStack.value.splice(0, undoStack.value.length - maxSize);\n }\n }\n\n function flushPendingDebounce(): void {\n if (pendingDebounce) {\n clearTimeout(pendingDebounce.timeoutId);\n pendingDebounce = null;\n }\n }\n\n function record(): void {\n if (isRemoteOperation?.()) {\n return;\n }\n\n flushPendingDebounce();\n pushToUndoStack(cloneContent());\n redoStack.value = [];\n }\n\n function recordDebounced(blockId: string): void {\n if (isRemoteOperation?.()) {\n return;\n }\n\n if (pendingDebounce && pendingDebounce.blockId === blockId) {\n clearTimeout(pendingDebounce.timeoutId);\n pendingDebounce.timeoutId = setTimeout(() => {\n pendingDebounce = null;\n }, DEBOUNCE_MS);\n return;\n }\n\n flushPendingDebounce();\n\n pushToUndoStack(cloneContent());\n redoStack.value = [];\n\n pendingDebounce = {\n blockId,\n timeoutId: setTimeout(() => {\n pendingDebounce = null;\n }, DEBOUNCE_MS),\n };\n }\n\n function setNavigating(): void {\n isNavigating.value = true;\n if (navigatingTimeoutId) {\n clearTimeout(navigatingTimeoutId);\n }\n navigatingTimeoutId = setTimeout(() => {\n isNavigating.value = false;\n navigatingTimeoutId = null;\n }, NAVIGATE_IDLE_MS);\n }\n\n function undo(): void {\n if (undoStack.value.length === 0) {\n return;\n }\n\n flushPendingDebounce();\n\n const snapshot = undoStack.value.pop()!;\n redoStack.value.push(cloneContent());\n setContent(snapshot, true);\n setNavigating();\n }\n\n function redo(): void {\n if (redoStack.value.length === 0) {\n return;\n }\n\n flushPendingDebounce();\n\n const snapshot = redoStack.value.pop()!;\n undoStack.value.push(cloneContent());\n setContent(snapshot, true);\n setNavigating();\n }\n\n function clear(): void {\n undoStack.value = [];\n redoStack.value = [];\n flushPendingDebounce();\n }\n\n function destroy(): void {\n clear();\n if (navigatingTimeoutId) {\n clearTimeout(navigatingTimeoutId);\n navigatingTimeoutId = null;\n }\n }\n\n return {\n canUndo,\n canRedo,\n isNavigating,\n undo,\n redo,\n record,\n recordDebounced,\n clear,\n destroy,\n };\n}\n","import type { Block, BlockDefaults, BlockType } from \"@templatical/types\";\nimport { createBlock, generateId } from \"@templatical/types\";\n\nfunction regenerateNestedIds(block: Block): void {\n if (block.type === \"table\") {\n block.rows = block.rows.map((row) => ({\n ...row,\n id: generateId(),\n cells: row.cells.map((cell) => ({ ...cell, id: generateId() })),\n }));\n } else if (block.type === \"social\") {\n block.icons = block.icons.map((icon) => ({ ...icon, id: generateId() }));\n } else if (block.type === \"menu\") {\n block.items = block.items.map((item) => ({ ...item, id: generateId() }));\n }\n}\n\nexport interface UseBlockActionsOptions {\n addBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n index?: number,\n ) => void;\n removeBlock: (blockId: string) => void;\n updateBlock: (blockId: string, updates: Partial<Block>) => void;\n selectBlock: (blockId: string | null) => void;\n /** Locate a block in the tree — used by `duplicateBlock` to insert the\n * clone right after the source instead of appending to the end. */\n findBlockLocation?: (blockId: string) => {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null;\n blockDefaults?: BlockDefaults;\n}\n\nexport interface UseBlockActionsReturn {\n createAndAddBlock: (\n type: BlockType,\n targetSectionId?: string,\n columnIndex?: number,\n ) => Block;\n duplicateBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n ) => Block;\n deleteBlock: (blockId: string) => void;\n updateBlockProperty: <K extends keyof Block>(\n blockId: string,\n key: K,\n value: Block[K],\n ) => void;\n}\n\nexport function useBlockActions(\n options: UseBlockActionsOptions,\n): UseBlockActionsReturn {\n const { addBlock, removeBlock, updateBlock, selectBlock, findBlockLocation } =\n options;\n\n function createAndAddBlock(\n type: BlockType,\n targetSectionId?: string,\n columnIndex?: number,\n ): Block {\n const block = createBlock(type, options.blockDefaults);\n addBlock(block, targetSectionId, columnIndex);\n selectBlock(block.id);\n return block;\n }\n\n function duplicateBlock(\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n ): Block {\n const cloned = JSON.parse(JSON.stringify(block)) as Block;\n cloned.id = generateId();\n regenerateNestedIds(cloned);\n\n if (cloned.type === \"section\") {\n cloned.children = cloned.children.map((column) =>\n column.map((child) => {\n const clonedChild = JSON.parse(JSON.stringify(child)) as Block;\n clonedChild.id = generateId();\n regenerateNestedIds(clonedChild);\n return clonedChild;\n }),\n );\n }\n\n // Insert directly after the source block. Explicit target args win;\n // otherwise, resolve the source's location and bump index by 1. Falls\n // back to appending at the end if location is unknown.\n if (targetSectionId !== undefined || columnIndex !== undefined) {\n addBlock(cloned, targetSectionId, columnIndex);\n } else {\n const sourceLocation = findBlockLocation?.(block.id) ?? null;\n if (sourceLocation) {\n addBlock(\n cloned,\n sourceLocation.targetSectionId,\n sourceLocation.columnIndex,\n sourceLocation.index + 1,\n );\n } else {\n addBlock(cloned, targetSectionId, columnIndex);\n }\n }\n selectBlock(cloned.id);\n return cloned;\n }\n\n function deleteBlock(blockId: string): void {\n removeBlock(blockId);\n }\n\n function updateBlockProperty<K extends keyof Block>(\n blockId: string,\n key: K,\n value: Block[K],\n ): void {\n updateBlock(blockId, { [key]: value } as Partial<Block>);\n }\n\n return {\n createAndAddBlock,\n duplicateBlock,\n deleteBlock,\n updateBlockProperty,\n };\n}\n","import type { TemplateContent } from \"@templatical/types\";\nimport { watch, type Ref } from \"@vue/reactivity\";\n\nexport interface UseAutoSaveOptions {\n content: Ref<TemplateContent>;\n isDirty: () => boolean;\n onChange: (content: TemplateContent) => void;\n debounce?: number;\n enabled?: boolean | (() => boolean);\n}\n\nexport interface UseAutoSaveReturn {\n flush: () => void;\n cancel: () => void;\n pause: () => void;\n resume: () => void;\n destroy: () => void;\n}\n\nexport function useAutoSave(options: UseAutoSaveOptions): UseAutoSaveReturn {\n const {\n content,\n isDirty,\n onChange,\n debounce = 1000,\n enabled = true,\n } = options;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let paused = false;\n\n function isEnabled(): boolean {\n return typeof enabled === \"function\" ? enabled() : enabled;\n }\n\n function pause(): void {\n paused = true;\n cancel();\n }\n\n function resume(): void {\n paused = false;\n }\n\n function cancel(): void {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function flush(): void {\n cancel();\n if (isDirty()) {\n onChange(JSON.parse(JSON.stringify(content.value)));\n }\n }\n\n function scheduleOnChange(): void {\n if (!isEnabled() || paused) return;\n\n cancel();\n timeoutId = setTimeout(() => {\n timeoutId = null;\n if (isEnabled() && !paused && isDirty()) {\n onChange(JSON.parse(JSON.stringify(content.value)));\n }\n }, debounce);\n }\n\n const stopWatch = watch(\n content,\n () => {\n if (isEnabled() && !paused && isDirty()) {\n scheduleOnChange();\n }\n },\n { deep: true },\n );\n\n function destroy(): void {\n stopWatch();\n cancel();\n }\n\n return {\n flush,\n cancel,\n pause,\n resume,\n destroy,\n };\n}\n","import type { UseEditorReturn } from \"./editor\";\nimport { computed, reactive, type ComputedRef } from \"@vue/reactivity\";\n\nexport interface UseConditionPreviewReturn {\n isHidden: (blockId: string) => boolean;\n toggleBlock: (blockId: string) => void;\n reset: () => void;\n hasHiddenBlocks: ComputedRef<boolean>;\n}\n\nexport function useConditionPreview(\n editor: UseEditorReturn,\n): UseConditionPreviewReturn {\n const hiddenBlockIds = reactive(new Set<string>());\n\n const hasHiddenBlocks = computed(() => hiddenBlockIds.size > 0);\n\n function isHidden(blockId: string): boolean {\n return hiddenBlockIds.has(blockId);\n }\n\n function toggleBlock(blockId: string): void {\n if (hiddenBlockIds.has(blockId)) {\n hiddenBlockIds.delete(blockId);\n } else {\n hiddenBlockIds.add(blockId);\n\n if (editor.state.selectedBlockId === blockId) {\n editor.selectBlock(null);\n }\n }\n }\n\n function reset(): void {\n hiddenBlockIds.clear();\n }\n\n return {\n isHidden,\n toggleBlock,\n reset,\n hasHiddenBlocks,\n };\n}\n","import type { CustomBlock, CustomBlockDefinition } from \"@templatical/types\";\nimport type { ComputedRef, Ref } from \"@vue/reactivity\";\nimport { computed, ref } from \"@vue/reactivity\";\n\nexport function useDataSourceFetch(options: {\n definition: ComputedRef<CustomBlockDefinition | undefined>;\n block: ComputedRef<CustomBlock>;\n onUpdate: (fieldValues: Record<string, unknown>, fetched: boolean) => void;\n}): {\n isFetching: Ref<boolean>;\n fetchError: Ref<boolean>;\n fetch: () => Promise<void>;\n hasDataSource: ComputedRef<boolean>;\n needsFetch: ComputedRef<boolean>;\n} {\n const isFetching = ref(false);\n const fetchError = ref(false);\n\n const hasDataSource = computed(() => !!options.definition.value?.dataSource);\n\n const needsFetch = computed(\n () => hasDataSource.value && !options.block.value.dataSourceFetched,\n );\n\n async function fetch(): Promise<void> {\n const def = options.definition.value;\n if (!def?.dataSource) {\n return;\n }\n\n isFetching.value = true;\n fetchError.value = false;\n\n try {\n const result = await def.dataSource.onFetch({\n fieldValues: { ...options.block.value.fieldValues },\n blockId: options.block.value.id,\n });\n\n if (result == null) {\n return;\n }\n\n const merged = { ...options.block.value.fieldValues };\n for (const key of Object.keys(merged)) {\n if (key in result) {\n merged[key] = result[key];\n }\n }\n\n options.onUpdate(merged, true);\n } catch (error) {\n console.warn(\"[Templatical] Data source fetch error:\", error);\n fetchError.value = true;\n } finally {\n isFetching.value = false;\n }\n }\n\n return {\n isFetching,\n fetchError,\n fetch,\n hasDataSource,\n needsFetch,\n };\n}\n","import type { UseEditorReturn } from \"./editor\";\nimport type { UseHistoryReturn } from \"./history\";\n\n/**\n * Wraps editor mutation methods to record history snapshots before each\n * operation. Mutates the editor object in place.\n *\n * Must be applied **after** any collaboration broadcast wrapping so the\n * call chain is: history.record() → broadcast → original mutation.\n */\nexport function useHistoryInterceptor(\n editor: UseEditorReturn,\n history: UseHistoryReturn,\n): void {\n const originalAddBlock = editor.addBlock;\n const originalRemoveBlock = editor.removeBlock;\n const originalMoveBlock = editor.moveBlock;\n const originalUpdateBlock = editor.updateBlock;\n const originalUpdateSettings = editor.updateSettings;\n\n // Skip recording when the underlying op is a no-op (e.g., a peer-locked\n // block or section), otherwise the undo stack fills with snapshots that\n // are identical to current state and undo silently does nothing.\n editor.addBlock = (block, targetSectionId?, columnIndex?, index?) => {\n if (targetSectionId && editor.isBlockLocked(targetSectionId)) {\n return;\n }\n history.record();\n originalAddBlock(block, targetSectionId, columnIndex, index);\n };\n\n editor.removeBlock = (blockId) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n history.record();\n originalRemoveBlock(blockId);\n };\n\n editor.moveBlock = (blockId, newIndex, targetSectionId?, columnIndex?) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n if (targetSectionId && editor.isBlockLocked(targetSectionId)) {\n return;\n }\n history.record();\n originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);\n };\n\n editor.updateBlock = (blockId, updates) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n history.recordDebounced(blockId);\n originalUpdateBlock(blockId, updates);\n };\n\n editor.updateSettings = (updates) => {\n history.record();\n originalUpdateSettings(updates);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["ref","computed"],"sources":["../src/editor.ts","../src/history.ts","../src/block-actions.ts","../src/auto-save.ts","../src/condition-preview.ts","../src/data-source-fetch.ts","../src/history-interceptor.ts","../src/saved-blocks.ts","../src/saved-blocks-local.ts"],"sourcesContent":["import type {\n Block,\n ColumnLayout,\n TemplateContent,\n TemplateDefaults,\n TemplateSettings,\n UiTheme,\n ViewportSize,\n} from \"@templatical/types\";\nimport { createDefaultTemplateContent } from \"@templatical/types\";\n\nfunction getColumnCount(layout: ColumnLayout): number {\n if (layout === \"1\") return 1;\n if (layout === \"3\") return 3;\n return 2;\n}\nimport {\n computed,\n reactive,\n readonly,\n type DeepReadonly,\n type Ref,\n} from \"@vue/reactivity\";\n\nexport interface EditorState {\n content: TemplateContent;\n selectedBlockId: string | null;\n viewport: ViewportSize;\n darkMode: boolean;\n previewMode: boolean;\n isDirty: boolean;\n uiTheme: UiTheme;\n}\n\nexport interface UseEditorOptions {\n content: TemplateContent;\n defaultFontFamily?: string;\n templateDefaults?: TemplateDefaults;\n lockedBlocks?: Ref<Map<string, unknown>>;\n}\n\nexport interface UseEditorReturn {\n state: DeepReadonly<EditorState>;\n content: Ref<TemplateContent>;\n selectedBlock: Ref<Block | null>;\n setContent: (content: TemplateContent, markDirty?: boolean) => void;\n selectBlock: (blockId: string | null) => void;\n setViewport: (viewport: ViewportSize) => void;\n setDarkMode: (darkMode: boolean) => void;\n setPreviewMode: (previewMode: boolean) => void;\n setUiTheme: (theme: UiTheme) => void;\n updateBlock: (blockId: string, updates: Partial<Block>) => void;\n updateSettings: (updates: Partial<TemplateSettings>) => void;\n addBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n index?: number,\n ) => void;\n removeBlock: (blockId: string) => void;\n moveBlock: (\n blockId: string,\n newIndex: number,\n targetSectionId?: string,\n columnIndex?: number,\n ) => void;\n isBlockLocked: (blockId: string) => boolean;\n markDirty: () => void;\n findBlockLocation: (blockId: string) => {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null;\n}\n\nexport function useEditor(options: UseEditorOptions): UseEditorReturn {\n const state = reactive<EditorState>({\n content:\n options.content ??\n createDefaultTemplateContent(\n options.defaultFontFamily,\n options.templateDefaults,\n ),\n selectedBlockId: null,\n viewport: \"desktop\",\n darkMode: false,\n previewMode: false,\n isDirty: false,\n uiTheme: \"auto\",\n });\n\n const content = computed({\n get: () => state.content,\n set: (value: TemplateContent) => {\n state.content = value;\n state.isDirty = true;\n },\n });\n\n const selectedBlock = computed(() => {\n if (!state.selectedBlockId) return null;\n return findBlockById(state.content.blocks, state.selectedBlockId);\n });\n\n function findBlockById(blocks: Block[], id: string): Block | null {\n for (const block of blocks) {\n if (block.id === id) return block;\n if (block.type === \"section\") {\n for (const column of block.children) {\n const found = findBlockById(column, id);\n if (found) return found;\n }\n }\n }\n return null;\n }\n\n function collectBlockIds(block: Block, ids: Set<string>): void {\n ids.add(block.id);\n if (block.type === \"section\") {\n for (const column of block.children) {\n for (const child of column) {\n collectBlockIds(child, ids);\n }\n }\n }\n }\n\n function findBlockParent(\n blocks: Block[],\n id: string,\n parent: {\n blocks: Block[];\n sectionId?: string;\n columnIndex?: number;\n } = { blocks },\n ): { blocks: Block[]; sectionId?: string; columnIndex?: number } | null {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n if (block.id === id) return parent;\n if (block.type === \"section\") {\n for (let colIdx = 0; colIdx < block.children.length; colIdx++) {\n const result = findBlockParent(block.children[colIdx], id, {\n blocks: block.children[colIdx],\n sectionId: block.id,\n columnIndex: colIdx,\n });\n if (result) return result;\n }\n }\n }\n return null;\n }\n\n function isBlockLocked(blockId: string): boolean {\n return options.lockedBlocks?.value.has(blockId) ?? false;\n }\n\n function findBlockLocation(blockId: string): {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null {\n const parent = findBlockParent(state.content.blocks, blockId);\n if (!parent) return null;\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n if (index === -1) return null;\n return {\n targetSectionId: parent.sectionId,\n columnIndex: parent.columnIndex,\n index,\n };\n }\n\n // TODO(collab): the lock checks in addBlock/moveBlock/removeBlock/updateBlock\n // are shallow — they only consider the directly-targeted block id. A section\n // can still be removed, moved, or have its `children` array rewritten while a\n // peer is editing one of its descendants, which silently disrupts that peer's\n // edit. Add a `hasLockedDescendant(blockId)` helper and gate section-level\n // operations on it (and on the parent of each affected child) so cascades\n // through the tree are also blocked.\n\n function setContent(newContent: TemplateContent, markDirty = true): void {\n state.content = newContent;\n if (markDirty) {\n state.isDirty = true;\n }\n }\n\n function selectBlock(blockId: string | null): void {\n if (blockId && isBlockLocked(blockId)) {\n return;\n }\n state.selectedBlockId = blockId;\n }\n\n function setViewport(viewport: ViewportSize): void {\n state.viewport = viewport;\n }\n\n function setDarkMode(darkMode: boolean): void {\n state.darkMode = darkMode;\n }\n\n function setUiTheme(theme: UiTheme): void {\n state.uiTheme = theme;\n }\n\n function setPreviewMode(previewMode: boolean): void {\n state.previewMode = previewMode;\n if (previewMode) {\n state.selectedBlockId = null;\n }\n }\n\n function updateBlock(blockId: string, updates: Partial<Block>): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n const block = findBlockById(state.content.blocks, blockId);\n if (block) {\n Object.assign(block, updates);\n state.isDirty = true;\n }\n }\n\n function updateSettings(updates: Partial<TemplateSettings>): void {\n state.content.settings = { ...state.content.settings, ...updates };\n state.isDirty = true;\n }\n\n function addBlock(\n block: Block,\n targetSectionId?: string,\n columnIndex = 0,\n index?: number,\n ): void {\n // Sections cannot be nested inside a column — MJML forbids `mj-section`\n // inside `mj-column`, so the renderer drops them on export (issue #292).\n // Reject the nest up front rather than lose the content silently later.\n if (targetSectionId && block.type === \"section\") {\n return;\n }\n if (targetSectionId) {\n if (isBlockLocked(targetSectionId)) {\n return;\n }\n const section = findBlockById(state.content.blocks, targetSectionId);\n if (section && section.type === \"section\") {\n if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {\n return;\n }\n section.children[columnIndex] = section.children[columnIndex] || [];\n const targetArray = section.children[columnIndex];\n if (index !== undefined && index < targetArray.length) {\n targetArray.splice(index, 0, block);\n } else {\n targetArray.push(block);\n }\n }\n } else {\n if (index !== undefined && index < state.content.blocks.length) {\n state.content.blocks.splice(index, 0, block);\n } else {\n state.content.blocks.push(block);\n }\n }\n state.isDirty = true;\n }\n\n function removeBlock(blockId: string): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n const parent = findBlockParent(state.content.blocks, blockId);\n if (parent) {\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n if (index !== -1) {\n const [removed] = parent.blocks.splice(index, 1);\n if (state.selectedBlockId) {\n const removedIds = new Set<string>();\n collectBlockIds(removed, removedIds);\n if (removedIds.has(state.selectedBlockId)) {\n state.selectedBlockId = null;\n }\n }\n state.isDirty = true;\n }\n }\n }\n\n function moveBlock(\n blockId: string,\n newIndex: number,\n targetSectionId?: string,\n columnIndex = 0,\n ): void {\n if (isBlockLocked(blockId)) {\n return;\n }\n if (targetSectionId && isBlockLocked(targetSectionId)) {\n return;\n }\n\n const parent = findBlockParent(state.content.blocks, blockId);\n if (!parent) return;\n\n const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);\n if (oldIndex === -1) return;\n\n // Sections cannot be nested inside a column (issue #292) — refuse to move\n // a section into a section column; MJML would drop it on export.\n if (targetSectionId && parent.blocks[oldIndex].type === \"section\") {\n return;\n }\n\n // Resolve target before mutating the source — otherwise an invalid\n // targetSectionId leaves the block spliced-out and unrecoverable.\n let targetArray: Block[];\n if (targetSectionId) {\n const section = findBlockById(state.content.blocks, targetSectionId);\n if (!section || section.type !== \"section\") return;\n if (columnIndex < 0 || columnIndex >= getColumnCount(section.columns)) {\n return;\n }\n section.children[columnIndex] = section.children[columnIndex] || [];\n targetArray = section.children[columnIndex];\n } else {\n targetArray = state.content.blocks;\n }\n\n const [block] = parent.blocks.splice(oldIndex, 1);\n targetArray.splice(newIndex, 0, block);\n\n state.isDirty = true;\n }\n\n function markDirty(): void {\n state.isDirty = true;\n }\n\n return {\n state: readonly(state),\n content,\n selectedBlock,\n isBlockLocked,\n setContent,\n selectBlock,\n setViewport,\n setDarkMode,\n setUiTheme,\n setPreviewMode,\n updateBlock,\n updateSettings,\n addBlock,\n removeBlock,\n moveBlock,\n markDirty,\n findBlockLocation,\n };\n}\n","import { safeClone, type TemplateContent } from \"@templatical/types\";\nimport { computed, ref, type ComputedRef, type Ref } from \"@vue/reactivity\";\n\nexport interface UseHistoryOptions {\n content: Ref<TemplateContent>;\n setContent: (content: TemplateContent, markDirty?: boolean) => void;\n isRemoteOperation?: () => boolean;\n maxSize?: number;\n}\n\nexport interface UseHistoryReturn {\n canUndo: ComputedRef<boolean>;\n canRedo: ComputedRef<boolean>;\n isNavigating: Ref<boolean>;\n undo: () => void;\n redo: () => void;\n record: () => void;\n recordDebounced: (blockId: string) => void;\n clear: () => void;\n destroy: () => void;\n}\n\ninterface DebouncedSnapshot {\n blockId: string;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\nconst MAX_STACK_SIZE = 50;\nconst DEBOUNCE_MS = 300;\nconst NAVIGATE_IDLE_MS = 1500;\n\nexport function useHistory(options: UseHistoryOptions): UseHistoryReturn {\n const {\n content,\n setContent,\n isRemoteOperation,\n maxSize = MAX_STACK_SIZE,\n } = options;\n\n const undoStack = ref<TemplateContent[]>([]);\n const redoStack = ref<TemplateContent[]>([]);\n const isNavigating = ref(false);\n let navigatingTimeoutId: ReturnType<typeof setTimeout> | null = null;\n let pendingDebounce: DebouncedSnapshot | null = null;\n\n const canUndo = computed(() => undoStack.value.length > 0);\n const canRedo = computed(() => redoStack.value.length > 0);\n\n function cloneContent(): TemplateContent {\n // Cycle-safe deep clone (see `safeClone`). A naked JSON.stringify throws\n // `Converting circular structure to JSON` if a DOM element with a Sortable\n // expando back-ref ever sneaks into block data via a drag handler — which\n // would freeze the undo stack, since every subsequent mutation calls\n // `record()`. safeClone drops the back-ref instead of throwing.\n return safeClone(content.value);\n }\n\n function pushToUndoStack(snapshot: TemplateContent): void {\n undoStack.value.push(snapshot);\n if (undoStack.value.length > maxSize) {\n undoStack.value.splice(0, undoStack.value.length - maxSize);\n }\n }\n\n function flushPendingDebounce(): void {\n if (pendingDebounce) {\n clearTimeout(pendingDebounce.timeoutId);\n pendingDebounce = null;\n }\n }\n\n function record(): void {\n if (isRemoteOperation?.()) {\n return;\n }\n\n flushPendingDebounce();\n pushToUndoStack(cloneContent());\n redoStack.value = [];\n }\n\n function recordDebounced(blockId: string): void {\n if (isRemoteOperation?.()) {\n return;\n }\n\n if (pendingDebounce && pendingDebounce.blockId === blockId) {\n clearTimeout(pendingDebounce.timeoutId);\n pendingDebounce.timeoutId = setTimeout(() => {\n pendingDebounce = null;\n }, DEBOUNCE_MS);\n return;\n }\n\n flushPendingDebounce();\n\n pushToUndoStack(cloneContent());\n redoStack.value = [];\n\n pendingDebounce = {\n blockId,\n timeoutId: setTimeout(() => {\n pendingDebounce = null;\n }, DEBOUNCE_MS),\n };\n }\n\n function setNavigating(): void {\n isNavigating.value = true;\n if (navigatingTimeoutId) {\n clearTimeout(navigatingTimeoutId);\n }\n navigatingTimeoutId = setTimeout(() => {\n isNavigating.value = false;\n navigatingTimeoutId = null;\n }, NAVIGATE_IDLE_MS);\n }\n\n function undo(): void {\n if (undoStack.value.length === 0) {\n return;\n }\n\n flushPendingDebounce();\n\n const snapshot = undoStack.value.pop()!;\n redoStack.value.push(cloneContent());\n setContent(snapshot, true);\n setNavigating();\n }\n\n function redo(): void {\n if (redoStack.value.length === 0) {\n return;\n }\n\n flushPendingDebounce();\n\n const snapshot = redoStack.value.pop()!;\n undoStack.value.push(cloneContent());\n setContent(snapshot, true);\n setNavigating();\n }\n\n function clear(): void {\n undoStack.value = [];\n redoStack.value = [];\n flushPendingDebounce();\n }\n\n function destroy(): void {\n clear();\n if (navigatingTimeoutId) {\n clearTimeout(navigatingTimeoutId);\n navigatingTimeoutId = null;\n }\n }\n\n return {\n canUndo,\n canRedo,\n isNavigating,\n undo,\n redo,\n record,\n recordDebounced,\n clear,\n destroy,\n };\n}\n","import type { Block, BlockDefaults, BlockType } from \"@templatical/types\";\nimport { createBlock, generateId } from \"@templatical/types\";\n\nfunction regenerateNestedIds(block: Block): void {\n if (block.type === \"table\") {\n block.rows = block.rows.map((row) => ({\n ...row,\n id: generateId(),\n cells: row.cells.map((cell) => ({ ...cell, id: generateId() })),\n }));\n } else if (block.type === \"social\") {\n block.icons = block.icons.map((icon) => ({ ...icon, id: generateId() }));\n } else if (block.type === \"menu\") {\n block.items = block.items.map((item) => ({ ...item, id: generateId() }));\n }\n}\n\nexport interface UseBlockActionsOptions {\n addBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n index?: number,\n ) => void;\n removeBlock: (blockId: string) => void;\n updateBlock: (blockId: string, updates: Partial<Block>) => void;\n selectBlock: (blockId: string | null) => void;\n /** Locate a block in the tree — used by `duplicateBlock` to insert the\n * clone right after the source instead of appending to the end. */\n findBlockLocation?: (blockId: string) => {\n targetSectionId?: string;\n columnIndex?: number;\n index: number;\n } | null;\n blockDefaults?: BlockDefaults;\n}\n\nexport interface UseBlockActionsReturn {\n createAndAddBlock: (\n type: BlockType,\n targetSectionId?: string,\n columnIndex?: number,\n ) => Block;\n duplicateBlock: (\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n ) => Block;\n deleteBlock: (blockId: string) => void;\n updateBlockProperty: <K extends keyof Block>(\n blockId: string,\n key: K,\n value: Block[K],\n ) => void;\n}\n\nexport function useBlockActions(\n options: UseBlockActionsOptions,\n): UseBlockActionsReturn {\n const { addBlock, removeBlock, updateBlock, selectBlock, findBlockLocation } =\n options;\n\n function createAndAddBlock(\n type: BlockType,\n targetSectionId?: string,\n columnIndex?: number,\n ): Block {\n const block = createBlock(type, options.blockDefaults);\n addBlock(block, targetSectionId, columnIndex);\n selectBlock(block.id);\n return block;\n }\n\n function duplicateBlock(\n block: Block,\n targetSectionId?: string,\n columnIndex?: number,\n ): Block {\n const cloned = JSON.parse(JSON.stringify(block)) as Block;\n cloned.id = generateId();\n regenerateNestedIds(cloned);\n\n if (cloned.type === \"section\") {\n cloned.children = cloned.children.map((column) =>\n column.map((child) => {\n const clonedChild = JSON.parse(JSON.stringify(child)) as Block;\n clonedChild.id = generateId();\n regenerateNestedIds(clonedChild);\n return clonedChild;\n }),\n );\n }\n\n // Insert directly after the source block. Explicit target args win;\n // otherwise, resolve the source's location and bump index by 1. Falls\n // back to appending at the end if location is unknown.\n if (targetSectionId !== undefined || columnIndex !== undefined) {\n addBlock(cloned, targetSectionId, columnIndex);\n } else {\n const sourceLocation = findBlockLocation?.(block.id) ?? null;\n if (sourceLocation) {\n addBlock(\n cloned,\n sourceLocation.targetSectionId,\n sourceLocation.columnIndex,\n sourceLocation.index + 1,\n );\n } else {\n addBlock(cloned, targetSectionId, columnIndex);\n }\n }\n selectBlock(cloned.id);\n return cloned;\n }\n\n function deleteBlock(blockId: string): void {\n removeBlock(blockId);\n }\n\n function updateBlockProperty<K extends keyof Block>(\n blockId: string,\n key: K,\n value: Block[K],\n ): void {\n updateBlock(blockId, { [key]: value } as Partial<Block>);\n }\n\n return {\n createAndAddBlock,\n duplicateBlock,\n deleteBlock,\n updateBlockProperty,\n };\n}\n","import type { TemplateContent } from \"@templatical/types\";\nimport { watch, type Ref } from \"@vue/reactivity\";\n\nexport interface UseAutoSaveOptions {\n content: Ref<TemplateContent>;\n isDirty: () => boolean;\n onChange: (content: TemplateContent) => void;\n debounce?: number;\n enabled?: boolean | (() => boolean);\n}\n\nexport interface UseAutoSaveReturn {\n flush: () => void;\n cancel: () => void;\n pause: () => void;\n resume: () => void;\n destroy: () => void;\n}\n\nexport function useAutoSave(options: UseAutoSaveOptions): UseAutoSaveReturn {\n const {\n content,\n isDirty,\n onChange,\n debounce = 1000,\n enabled = true,\n } = options;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let paused = false;\n\n function isEnabled(): boolean {\n return typeof enabled === \"function\" ? enabled() : enabled;\n }\n\n function pause(): void {\n paused = true;\n cancel();\n }\n\n function resume(): void {\n paused = false;\n }\n\n function cancel(): void {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function flush(): void {\n cancel();\n if (isDirty()) {\n onChange(JSON.parse(JSON.stringify(content.value)));\n }\n }\n\n function scheduleOnChange(): void {\n if (!isEnabled() || paused) return;\n\n cancel();\n timeoutId = setTimeout(() => {\n timeoutId = null;\n if (isEnabled() && !paused && isDirty()) {\n onChange(JSON.parse(JSON.stringify(content.value)));\n }\n }, debounce);\n }\n\n const stopWatch = watch(\n content,\n () => {\n if (isEnabled() && !paused && isDirty()) {\n scheduleOnChange();\n }\n },\n { deep: true },\n );\n\n function destroy(): void {\n stopWatch();\n cancel();\n }\n\n return {\n flush,\n cancel,\n pause,\n resume,\n destroy,\n };\n}\n","import type { UseEditorReturn } from \"./editor\";\nimport { computed, reactive, type ComputedRef } from \"@vue/reactivity\";\n\nexport interface UseConditionPreviewReturn {\n isHidden: (blockId: string) => boolean;\n toggleBlock: (blockId: string) => void;\n reset: () => void;\n hasHiddenBlocks: ComputedRef<boolean>;\n}\n\nexport function useConditionPreview(\n editor: UseEditorReturn,\n): UseConditionPreviewReturn {\n const hiddenBlockIds = reactive(new Set<string>());\n\n const hasHiddenBlocks = computed(() => hiddenBlockIds.size > 0);\n\n function isHidden(blockId: string): boolean {\n return hiddenBlockIds.has(blockId);\n }\n\n function toggleBlock(blockId: string): void {\n if (hiddenBlockIds.has(blockId)) {\n hiddenBlockIds.delete(blockId);\n } else {\n hiddenBlockIds.add(blockId);\n\n if (editor.state.selectedBlockId === blockId) {\n editor.selectBlock(null);\n }\n }\n }\n\n function reset(): void {\n hiddenBlockIds.clear();\n }\n\n return {\n isHidden,\n toggleBlock,\n reset,\n hasHiddenBlocks,\n };\n}\n","import type { CustomBlock, CustomBlockDefinition } from \"@templatical/types\";\nimport type { ComputedRef, Ref } from \"@vue/reactivity\";\nimport { computed, ref } from \"@vue/reactivity\";\n\nexport function useDataSourceFetch(options: {\n definition: ComputedRef<CustomBlockDefinition | undefined>;\n block: ComputedRef<CustomBlock>;\n onUpdate: (fieldValues: Record<string, unknown>, fetched: boolean) => void;\n}): {\n isFetching: Ref<boolean>;\n fetchError: Ref<boolean>;\n fetch: () => Promise<void>;\n hasDataSource: ComputedRef<boolean>;\n needsFetch: ComputedRef<boolean>;\n} {\n const isFetching = ref(false);\n const fetchError = ref(false);\n\n const hasDataSource = computed(() => !!options.definition.value?.dataSource);\n\n const needsFetch = computed(\n () => hasDataSource.value && !options.block.value.dataSourceFetched,\n );\n\n async function fetch(): Promise<void> {\n const def = options.definition.value;\n if (!def?.dataSource) {\n return;\n }\n\n isFetching.value = true;\n fetchError.value = false;\n\n try {\n const result = await def.dataSource.onFetch({\n fieldValues: { ...options.block.value.fieldValues },\n blockId: options.block.value.id,\n });\n\n if (result == null) {\n return;\n }\n\n const merged = { ...options.block.value.fieldValues };\n for (const key of Object.keys(merged)) {\n if (key in result) {\n merged[key] = result[key];\n }\n }\n\n options.onUpdate(merged, true);\n } catch (error) {\n console.warn(\"[Templatical] Data source fetch error:\", error);\n fetchError.value = true;\n } finally {\n isFetching.value = false;\n }\n }\n\n return {\n isFetching,\n fetchError,\n fetch,\n hasDataSource,\n needsFetch,\n };\n}\n","import type { UseEditorReturn } from \"./editor\";\nimport type { UseHistoryReturn } from \"./history\";\n\n/**\n * Wraps editor mutation methods to record history snapshots before each\n * operation. Mutates the editor object in place.\n *\n * Must be applied **after** any collaboration broadcast wrapping so the\n * call chain is: history.record() → broadcast → original mutation.\n */\nexport function useHistoryInterceptor(\n editor: UseEditorReturn,\n history: UseHistoryReturn,\n): void {\n const originalAddBlock = editor.addBlock;\n const originalRemoveBlock = editor.removeBlock;\n const originalMoveBlock = editor.moveBlock;\n const originalUpdateBlock = editor.updateBlock;\n const originalUpdateSettings = editor.updateSettings;\n\n // Skip recording when the underlying op is a no-op (e.g., a peer-locked\n // block or section), otherwise the undo stack fills with snapshots that\n // are identical to current state and undo silently does nothing.\n editor.addBlock = (block, targetSectionId?, columnIndex?, index?) => {\n if (targetSectionId && editor.isBlockLocked(targetSectionId)) {\n return;\n }\n history.record();\n originalAddBlock(block, targetSectionId, columnIndex, index);\n };\n\n editor.removeBlock = (blockId) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n history.record();\n originalRemoveBlock(blockId);\n };\n\n editor.moveBlock = (blockId, newIndex, targetSectionId?, columnIndex?) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n if (targetSectionId && editor.isBlockLocked(targetSectionId)) {\n return;\n }\n history.record();\n originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);\n };\n\n editor.updateBlock = (blockId, updates) => {\n if (editor.isBlockLocked(blockId)) {\n return;\n }\n history.recordDebounced(blockId);\n originalUpdateBlock(blockId, updates);\n };\n\n editor.updateSettings = (updates) => {\n history.record();\n originalUpdateSettings(updates);\n };\n}\n","import type {\n Block,\n SavedBlock,\n SavedBlockPatch,\n SavedBlocksListParams,\n SavedBlocksProvider,\n} from \"@templatical/types\";\nimport { SdkError } from \"@templatical/types\";\nimport { computed, ref, type ComputedRef, type Ref } from \"vue\";\n\nexport interface UseSavedBlocksOptions {\n /**\n * Storage backend. Supplied by the consumer via `init({ savedBlocks })`, or\n * by Cloud's own adapter — this composable is transport-agnostic and never\n * talks to a network itself.\n */\n provider: SavedBlocksProvider;\n onError?: (error: Error) => void;\n}\n\nexport interface UseSavedBlocksReturn {\n /** In-memory mirror of the provider's list, kept in sync after each call. */\n savedBlocks: Ref<SavedBlock[]>;\n isLoading: Ref<boolean>;\n /**\n * Distinct categories across the loaded entries, alphabetically sorted.\n *\n * Derived rather than stored: a category exists exactly as long as an entry\n * carries it. Sorting here is not a violation of \"the editor never re-sorts\"\n * — that rule protects the provider's ordering of *entries*, whereas these\n * are strings with no given order.\n */\n categories: ComputedRef<string[]>;\n /**\n * Whether the provider supplied each mutation at all — `false` on the\n * provider means the capability does not exist for this user, and the UI\n * hides the corresponding affordance entirely.\n *\n * Constant for the provider's lifetime, but exposed as computed refs so\n * consumers can bind them the same way as everything else here.\n */\n canCreate: ComputedRef<boolean>;\n canUpdate: ComputedRef<boolean>;\n canDelete: ComputedRef<boolean>;\n /**\n * Whether a *particular* entry may be edited / deleted: the capability must\n * exist AND the entry must not opt out via `canUpdate` / `canDelete`.\n *\n * Use these rather than reading the flags directly, so every surface asks the\n * question the same way and the \"absent means allowed\" default lives in one\n * place.\n */\n canUpdateBlock: (block: SavedBlock) => boolean;\n canDeleteBlock: (block: SavedBlock) => boolean;\n /**\n * Re-read from the provider. The editor always calls this bare and filters\n * in memory; `params` exists for headless callers that want the provider to\n * filter instead (see {@link SavedBlocksListParams}).\n */\n load: (params?: SavedBlocksListParams) => Promise<void>;\n /** Rejects with an {@link SdkError} when the provider disabled `create`. */\n create: (\n name: string,\n content: Block[],\n category?: string,\n ) => Promise<SavedBlock>;\n /** Rejects when `update` is disabled or the entry opts out of editing. */\n update: (id: string, patch: SavedBlockPatch) => Promise<SavedBlock>;\n /** Rejects when `delete` is disabled or the entry opts out of deletion. */\n remove: (id: string) => Promise<void>;\n}\n\n/**\n * Reactive state over a {@link SavedBlocksProvider}.\n *\n * Owns the list, the loading flag, and the local mutations that keep the list\n * consistent after each successful call, so a provider only has to implement\n * four promise-returning methods. Errors are reported through `onError` and\n * re-thrown, leaving the local list untouched on failure.\n */\nexport function useSavedBlocks(\n options: UseSavedBlocksOptions,\n): UseSavedBlocksReturn {\n const { provider } = options;\n\n const savedBlocks = ref<SavedBlock[]>([]);\n const isLoading = ref(false);\n\n const categories = computed(() => {\n const seen = new Set<string>();\n for (const block of savedBlocks.value) {\n const category = block.category?.trim();\n if (category) seen.add(category);\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n });\n\n const canCreate = computed(() => typeof provider.create === \"function\");\n const canUpdate = computed(() => typeof provider.update === \"function\");\n const canDelete = computed(() => typeof provider.delete === \"function\");\n\n /** Absent flag means allowed — the flags exist only to forbid. */\n function canUpdateBlock(block: SavedBlock): boolean {\n return canUpdate.value && block.canUpdate !== false;\n }\n\n function canDeleteBlock(block: SavedBlock): boolean {\n return canDelete.value && block.canDelete !== false;\n }\n\n /**\n * The UI hides disabled actions, so reaching one of these means a programmatic\n * caller went around it. Fail loudly rather than silently no-op: a resolved\n * promise would read as \"saved\" to whoever awaited it.\n */\n function refuse(action: string, reason: string): never {\n throw new SdkError(\n `[Templatical] Saved blocks: ${action} is ${reason}. Check the capability before calling — the editor's own UI hides the action.`,\n );\n }\n\n async function load(params?: SavedBlocksListParams): Promise<void> {\n isLoading.value = true;\n try {\n savedBlocks.value = await provider.list(params);\n } catch (error) {\n options.onError?.(error as Error);\n throw error;\n } finally {\n isLoading.value = false;\n }\n }\n\n async function create(\n name: string,\n content: Block[],\n category?: string,\n ): Promise<SavedBlock> {\n const { create: providerCreate } = provider;\n if (typeof providerCreate !== \"function\") {\n refuse(\"create\", \"disabled by the provider\");\n }\n try {\n // Omit the key entirely when unset, rather than sending `undefined` —\n // a provider serialising the input to JSON would otherwise be handed an\n // explicit null-ish field it never asked for.\n const created = await providerCreate(\n category ? { name, content, category } : { name, content },\n );\n savedBlocks.value = [created, ...savedBlocks.value];\n return created;\n } catch (error) {\n options.onError?.(error as Error);\n throw error;\n }\n }\n\n async function update(\n id: string,\n patch: SavedBlockPatch,\n ): Promise<SavedBlock> {\n const { update: providerUpdate } = provider;\n if (typeof providerUpdate !== \"function\") {\n refuse(\"update\", \"disabled by the provider\");\n }\n // An id absent from the local list isn't refused — a headless caller may\n // legitimately patch something it never loaded. Only a loaded entry that\n // explicitly opted out is blocked.\n const local = savedBlocks.value.find((b) => b.id === id);\n if (local && local.canUpdate === false) {\n refuse(\"update\", `not permitted for entry \"${id}\"`);\n }\n try {\n const updated = await providerUpdate(id, patch);\n savedBlocks.value = savedBlocks.value.map((b) =>\n b.id === id ? updated : b,\n );\n return updated;\n } catch (error) {\n options.onError?.(error as Error);\n throw error;\n }\n }\n\n async function remove(id: string): Promise<void> {\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n refuse(\"delete\", \"disabled by the provider\");\n }\n const local = savedBlocks.value.find((b) => b.id === id);\n if (local && local.canDelete === false) {\n refuse(\"delete\", `not permitted for entry \"${id}\"`);\n }\n try {\n await providerDelete(id);\n savedBlocks.value = savedBlocks.value.filter((b) => b.id !== id);\n } catch (error) {\n options.onError?.(error as Error);\n throw error;\n }\n }\n\n return {\n savedBlocks,\n isLoading,\n categories,\n canCreate,\n canUpdate,\n canDelete,\n canUpdateBlock,\n canDeleteBlock,\n load,\n create,\n update,\n remove,\n };\n}\n","import type {\n SavedBlock,\n SavedBlockInput,\n SavedBlockPatch,\n SavedBlocksListParams,\n SavedBlocksProvider,\n} from \"@templatical/types\";\nimport { generateId } from \"@templatical/types\";\n\nexport interface LocalStorageSavedBlocksOptions {\n /**\n * `localStorage` key holding the serialized array.\n *\n * @default \"templatical:saved-blocks\"\n */\n key?: string;\n}\n\nconst DEFAULT_KEY = \"templatical:saved-blocks\";\n\n/**\n * Browser-local {@link SavedBlocksProvider} backed by `localStorage`.\n *\n * Opt-in: pass it explicitly as `init({ savedBlocks: createLocalStorageSavedBlocksProvider() })`.\n * The editor never falls back to it, so consumers without a provider keep the\n * feature — and its UI — entirely off.\n *\n * Intended for demos, prototypes, and single-device use. Entries live in one\n * browser profile only: they don't sync across devices or users, and clearing\n * site data removes them. Back saved blocks with your own API for anything\n * that needs to outlive a browser profile.\n */\nexport function createLocalStorageSavedBlocksProvider(\n options: LocalStorageSavedBlocksOptions = {},\n): SavedBlocksProvider {\n const storageKey = options.key ?? DEFAULT_KEY;\n\n function getStorage(): Storage {\n // Core targets a neutral platform, so `localStorage` isn't guaranteed —\n // fail with an actionable message rather than a bare ReferenceError.\n if (typeof localStorage === \"undefined\") {\n throw new Error(\n \"[Templatical] createLocalStorageSavedBlocksProvider requires a browser environment with localStorage. Supply your own SavedBlocksProvider for server-side or non-browser use.\",\n );\n }\n return localStorage;\n }\n\n function readAll(): SavedBlock[] {\n const raw = getStorage().getItem(storageKey);\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n // Tolerate a corrupted or hand-edited value rather than breaking the\n // editor: treat anything that isn't an array as \"no saved blocks\".\n return Array.isArray(parsed) ? (parsed as SavedBlock[]) : [];\n } catch {\n return [];\n }\n }\n\n function writeAll(blocks: SavedBlock[]): void {\n getStorage().setItem(storageKey, JSON.stringify(blocks));\n }\n\n // Every method is `async` so a synchronous failure (a missing\n // `localStorage`, a quota error from `setItem`) surfaces as a rejected\n // promise rather than a sync throw — callers of a `Promise`-returning\n // contract must be able to rely on `.catch()`.\n return {\n // The editor calls this bare and filters in memory; the params are honored\n // for headless callers that would rather the provider did the work.\n async list(params?: SavedBlocksListParams): Promise<SavedBlock[]> {\n const all = readAll();\n const search = params?.search?.trim().toLowerCase();\n const category = params?.category?.trim();\n return all.filter((b) => {\n if (search && !b.name.toLowerCase().includes(search)) return false;\n if (category && b.category !== category) return false;\n return true;\n });\n },\n\n // All three mutations are enabled: a browser-local store has no notion of\n // permissions, so there is nothing to forbid.\n async create(input: SavedBlockInput): Promise<SavedBlock> {\n const now = new Date().toISOString();\n const created: SavedBlock = {\n id: generateId(),\n name: input.name,\n content: input.content,\n ...(input.category ? { category: input.category } : {}),\n createdAt: now,\n updatedAt: now,\n };\n // Newest-first, matching the order the composable applies locally after\n // a create — so a reload preserves what the user just saw.\n writeAll([created, ...readAll()]);\n return created;\n },\n\n async update(id: string, patch: SavedBlockPatch): Promise<SavedBlock> {\n const all = readAll();\n const index = all.findIndex((b) => b.id === id);\n // Mirrors a REST 404 so provider behavior is consistent across adapters.\n if (index === -1) {\n throw new Error(`[Templatical] Saved block not found: ${id}`);\n }\n const updated: SavedBlock = {\n ...all[index],\n ...patch,\n updatedAt: new Date().toISOString(),\n };\n const next = [...all];\n next[index] = updated;\n writeAll(next);\n return updated;\n },\n\n async delete(id: string): Promise<void> {\n const all = readAll();\n if (!all.some((b) => b.id === id)) {\n throw new Error(`[Templatical] Saved block not found: ${id}`);\n }\n writeAll(all.filter((b) => b.id !== id));\n },\n };\n}\n"],"mappings":";;;;AAWA,SAAS,eAAe,QAA8B;CACpD,IAAI,WAAW,KAAK,OAAO;CAC3B,IAAI,WAAW,KAAK,OAAO;CAC3B,OAAO;AACT;AA4DA,SAAgB,UAAU,SAA4C;CACpE,MAAM,QAAQ,SAAsB;EAClC,SACE,QAAQ,WACR,6BACE,QAAQ,mBACR,QAAQ,gBACV;EACF,iBAAiB;EACjB,UAAU;EACV,UAAU;EACV,aAAa;EACb,SAAS;EACT,SAAS;CACX,CAAC;CAED,MAAM,UAAU,SAAS;EACvB,WAAW,MAAM;EACjB,MAAM,UAA2B;GAC/B,MAAM,UAAU;GAChB,MAAM,UAAU;EAClB;CACF,CAAC;CAED,MAAM,gBAAgB,eAAe;EACnC,IAAI,CAAC,MAAM,iBAAiB,OAAO;EACnC,OAAO,cAAc,MAAM,QAAQ,QAAQ,MAAM,eAAe;CAClE,CAAC;CAED,SAAS,cAAc,QAAiB,IAA0B;EAChE,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,MAAM,OAAO,IAAI,OAAO;GAC5B,IAAI,MAAM,SAAS,WACjB,KAAK,MAAM,UAAU,MAAM,UAAU;IACnC,MAAM,QAAQ,cAAc,QAAQ,EAAE;IACtC,IAAI,OAAO,OAAO;GACpB;EAEJ;EACA,OAAO;CACT;CAEA,SAAS,gBAAgB,OAAc,KAAwB;EAC7D,IAAI,IAAI,MAAM,EAAE;EAChB,IAAI,MAAM,SAAS,WACjB,KAAK,MAAM,UAAU,MAAM,UACzB,KAAK,MAAM,SAAS,QAClB,gBAAgB,OAAO,GAAG;CAIlC;CAEA,SAAS,gBACP,QACA,IACA,SAII,EAAE,OAAO,GACyD;EACtE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACtC,MAAM,QAAQ,OAAO;GACrB,IAAI,MAAM,OAAO,IAAI,OAAO;GAC5B,IAAI,MAAM,SAAS,WACjB,KAAK,IAAI,SAAS,GAAG,SAAS,MAAM,SAAS,QAAQ,UAAU;IAC7D,MAAM,SAAS,gBAAgB,MAAM,SAAS,SAAS,IAAI;KACzD,QAAQ,MAAM,SAAS;KACvB,WAAW,MAAM;KACjB,aAAa;IACf,CAAC;IACD,IAAI,QAAQ,OAAO;GACrB;EAEJ;EACA,OAAO;CACT;CAEA,SAAS,cAAc,SAA0B;EAC/C,OAAO,QAAQ,cAAc,MAAM,IAAI,OAAO,KAAK;CACrD;CAEA,SAAS,kBAAkB,SAIlB;EACP,MAAM,SAAS,gBAAgB,MAAM,QAAQ,QAAQ,OAAO;EAC5D,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,QAAQ,OAAO,OAAO,WAAW,MAAM,EAAE,OAAO,OAAO;EAC7D,IAAI,UAAU,IAAI,OAAO;EACzB,OAAO;GACL,iBAAiB,OAAO;GACxB,aAAa,OAAO;GACpB;EACF;CACF;CAUA,SAAS,WAAW,YAA6B,YAAY,MAAY;EACvE,MAAM,UAAU;EAChB,IAAI,WACF,MAAM,UAAU;CAEpB;CAEA,SAAS,YAAY,SAA8B;EACjD,IAAI,WAAW,cAAc,OAAO,GAClC;EAEF,MAAM,kBAAkB;CAC1B;CAEA,SAAS,YAAY,UAA8B;EACjD,MAAM,WAAW;CACnB;CAEA,SAAS,YAAY,UAAyB;EAC5C,MAAM,WAAW;CACnB;CAEA,SAAS,WAAW,OAAsB;EACxC,MAAM,UAAU;CAClB;CAEA,SAAS,eAAe,aAA4B;EAClD,MAAM,cAAc;EACpB,IAAI,aACF,MAAM,kBAAkB;CAE5B;CAEA,SAAS,YAAY,SAAiB,SAA+B;EACnE,IAAI,cAAc,OAAO,GACvB;EAEF,MAAM,QAAQ,cAAc,MAAM,QAAQ,QAAQ,OAAO;EACzD,IAAI,OAAO;GACT,OAAO,OAAO,OAAO,OAAO;GAC5B,MAAM,UAAU;EAClB;CACF;CAEA,SAAS,eAAe,SAA0C;EAChE,MAAM,QAAQ,WAAW;GAAE,GAAG,MAAM,QAAQ;GAAU,GAAG;EAAQ;EACjE,MAAM,UAAU;CAClB;CAEA,SAAS,SACP,OACA,iBACA,cAAc,GACd,OACM;EAIN,IAAI,mBAAmB,MAAM,SAAS,WACpC;EAEF,IAAI,iBAAiB;GACnB,IAAI,cAAc,eAAe,GAC/B;GAEF,MAAM,UAAU,cAAc,MAAM,QAAQ,QAAQ,eAAe;GACnE,IAAI,WAAW,QAAQ,SAAS,WAAW;IACzC,IAAI,cAAc,KAAK,eAAe,eAAe,QAAQ,OAAO,GAClE;IAEF,QAAQ,SAAS,eAAe,QAAQ,SAAS,gBAAgB,CAAC;IAClE,MAAM,cAAc,QAAQ,SAAS;IACrC,IAAI,UAAU,KAAA,KAAa,QAAQ,YAAY,QAC7C,YAAY,OAAO,OAAO,GAAG,KAAK;SAElC,YAAY,KAAK,KAAK;GAE1B;EACF,OACE,IAAI,UAAU,KAAA,KAAa,QAAQ,MAAM,QAAQ,OAAO,QACtD,MAAM,QAAQ,OAAO,OAAO,OAAO,GAAG,KAAK;OAE3C,MAAM,QAAQ,OAAO,KAAK,KAAK;EAGnC,MAAM,UAAU;CAClB;CAEA,SAAS,YAAY,SAAuB;EAC1C,IAAI,cAAc,OAAO,GACvB;EAEF,MAAM,SAAS,gBAAgB,MAAM,QAAQ,QAAQ,OAAO;EAC5D,IAAI,QAAQ;GACV,MAAM,QAAQ,OAAO,OAAO,WAAW,MAAM,EAAE,OAAO,OAAO;GAC7D,IAAI,UAAU,IAAI;IAChB,MAAM,CAAC,WAAW,OAAO,OAAO,OAAO,OAAO,CAAC;IAC/C,IAAI,MAAM,iBAAiB;KACzB,MAAM,6BAAa,IAAI,IAAY;KACnC,gBAAgB,SAAS,UAAU;KACnC,IAAI,WAAW,IAAI,MAAM,eAAe,GACtC,MAAM,kBAAkB;IAE5B;IACA,MAAM,UAAU;GAClB;EACF;CACF;CAEA,SAAS,UACP,SACA,UACA,iBACA,cAAc,GACR;EACN,IAAI,cAAc,OAAO,GACvB;EAEF,IAAI,mBAAmB,cAAc,eAAe,GAClD;EAGF,MAAM,SAAS,gBAAgB,MAAM,QAAQ,QAAQ,OAAO;EAC5D,IAAI,CAAC,QAAQ;EAEb,MAAM,WAAW,OAAO,OAAO,WAAW,MAAM,EAAE,OAAO,OAAO;EAChE,IAAI,aAAa,IAAI;EAIrB,IAAI,mBAAmB,OAAO,OAAO,SAAS,CAAC,SAAS,WACtD;EAKF,IAAI;EACJ,IAAI,iBAAiB;GACnB,MAAM,UAAU,cAAc,MAAM,QAAQ,QAAQ,eAAe;GACnE,IAAI,CAAC,WAAW,QAAQ,SAAS,WAAW;GAC5C,IAAI,cAAc,KAAK,eAAe,eAAe,QAAQ,OAAO,GAClE;GAEF,QAAQ,SAAS,eAAe,QAAQ,SAAS,gBAAgB,CAAC;GAClE,cAAc,QAAQ,SAAS;EACjC,OACE,cAAc,MAAM,QAAQ;EAG9B,MAAM,CAAC,SAAS,OAAO,OAAO,OAAO,UAAU,CAAC;EAChD,YAAY,OAAO,UAAU,GAAG,KAAK;EAErC,MAAM,UAAU;CAClB;CAEA,SAAS,YAAkB;EACzB,MAAM,UAAU;CAClB;CAEA,OAAO;EACL,OAAO,SAAS,KAAK;EACrB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;AC7UA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AACpB,MAAM,mBAAmB;AAEzB,SAAgB,WAAW,SAA8C;CACvE,MAAM,EACJ,SACA,YACA,mBACA,UAAU,mBACR;CAEJ,MAAM,YAAY,IAAuB,CAAC,CAAC;CAC3C,MAAM,YAAY,IAAuB,CAAC,CAAC;CAC3C,MAAM,eAAe,IAAI,KAAK;CAC9B,IAAI,sBAA4D;CAChE,IAAI,kBAA4C;CAEhD,MAAM,UAAU,eAAe,UAAU,MAAM,SAAS,CAAC;CACzD,MAAM,UAAU,eAAe,UAAU,MAAM,SAAS,CAAC;CAEzD,SAAS,eAAgC;EAMvC,OAAO,UAAU,QAAQ,KAAK;CAChC;CAEA,SAAS,gBAAgB,UAAiC;EACxD,UAAU,MAAM,KAAK,QAAQ;EAC7B,IAAI,UAAU,MAAM,SAAS,SAC3B,UAAU,MAAM,OAAO,GAAG,UAAU,MAAM,SAAS,OAAO;CAE9D;CAEA,SAAS,uBAA6B;EACpC,IAAI,iBAAiB;GACnB,aAAa,gBAAgB,SAAS;GACtC,kBAAkB;EACpB;CACF;CAEA,SAAS,SAAe;EACtB,IAAI,oBAAoB,GACtB;EAGF,qBAAqB;EACrB,gBAAgB,aAAa,CAAC;EAC9B,UAAU,QAAQ,CAAC;CACrB;CAEA,SAAS,gBAAgB,SAAuB;EAC9C,IAAI,oBAAoB,GACtB;EAGF,IAAI,mBAAmB,gBAAgB,YAAY,SAAS;GAC1D,aAAa,gBAAgB,SAAS;GACtC,gBAAgB,YAAY,iBAAiB;IAC3C,kBAAkB;GACpB,GAAG,WAAW;GACd;EACF;EAEA,qBAAqB;EAErB,gBAAgB,aAAa,CAAC;EAC9B,UAAU,QAAQ,CAAC;EAEnB,kBAAkB;GAChB;GACA,WAAW,iBAAiB;IAC1B,kBAAkB;GACpB,GAAG,WAAW;EAChB;CACF;CAEA,SAAS,gBAAsB;EAC7B,aAAa,QAAQ;EACrB,IAAI,qBACF,aAAa,mBAAmB;EAElC,sBAAsB,iBAAiB;GACrC,aAAa,QAAQ;GACrB,sBAAsB;EACxB,GAAG,gBAAgB;CACrB;CAEA,SAAS,OAAa;EACpB,IAAI,UAAU,MAAM,WAAW,GAC7B;EAGF,qBAAqB;EAErB,MAAM,WAAW,UAAU,MAAM,IAAI;EACrC,UAAU,MAAM,KAAK,aAAa,CAAC;EACnC,WAAW,UAAU,IAAI;EACzB,cAAc;CAChB;CAEA,SAAS,OAAa;EACpB,IAAI,UAAU,MAAM,WAAW,GAC7B;EAGF,qBAAqB;EAErB,MAAM,WAAW,UAAU,MAAM,IAAI;EACrC,UAAU,MAAM,KAAK,aAAa,CAAC;EACnC,WAAW,UAAU,IAAI;EACzB,cAAc;CAChB;CAEA,SAAS,QAAc;EACrB,UAAU,QAAQ,CAAC;EACnB,UAAU,QAAQ,CAAC;EACnB,qBAAqB;CACvB;CAEA,SAAS,UAAgB;EACvB,MAAM;EACN,IAAI,qBAAqB;GACvB,aAAa,mBAAmB;GAChC,sBAAsB;EACxB;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;ACtKA,SAAS,oBAAoB,OAAoB;CAC/C,IAAI,MAAM,SAAS,SACjB,MAAM,OAAO,MAAM,KAAK,KAAK,SAAS;EACpC,GAAG;EACH,IAAI,WAAW;EACf,OAAO,IAAI,MAAM,KAAK,UAAU;GAAE,GAAG;GAAM,IAAI,WAAW;EAAE,EAAE;CAChE,EAAE;MACG,IAAI,MAAM,SAAS,UACxB,MAAM,QAAQ,MAAM,MAAM,KAAK,UAAU;EAAE,GAAG;EAAM,IAAI,WAAW;CAAE,EAAE;MAClE,IAAI,MAAM,SAAS,QACxB,MAAM,QAAQ,MAAM,MAAM,KAAK,UAAU;EAAE,GAAG;EAAM,IAAI,WAAW;CAAE,EAAE;AAE3E;AAyCA,SAAgB,gBACd,SACuB;CACvB,MAAM,EAAE,UAAU,aAAa,aAAa,aAAa,sBACvD;CAEF,SAAS,kBACP,MACA,iBACA,aACO;EACP,MAAM,QAAQ,YAAY,MAAM,QAAQ,aAAa;EACrD,SAAS,OAAO,iBAAiB,WAAW;EAC5C,YAAY,MAAM,EAAE;EACpB,OAAO;CACT;CAEA,SAAS,eACP,OACA,iBACA,aACO;EACP,MAAM,SAAS,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;EAC/C,OAAO,KAAK,WAAW;EACvB,oBAAoB,MAAM;EAE1B,IAAI,OAAO,SAAS,WAClB,OAAO,WAAW,OAAO,SAAS,KAAK,WACrC,OAAO,KAAK,UAAU;GACpB,MAAM,cAAc,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;GACpD,YAAY,KAAK,WAAW;GAC5B,oBAAoB,WAAW;GAC/B,OAAO;EACT,CAAC,CACH;EAMF,IAAI,oBAAoB,KAAA,KAAa,gBAAgB,KAAA,GACnD,SAAS,QAAQ,iBAAiB,WAAW;OACxC;GACL,MAAM,iBAAiB,oBAAoB,MAAM,EAAE,KAAK;GACxD,IAAI,gBACF,SACE,QACA,eAAe,iBACf,eAAe,aACf,eAAe,QAAQ,CACzB;QAEA,SAAS,QAAQ,iBAAiB,WAAW;EAEjD;EACA,YAAY,OAAO,EAAE;EACrB,OAAO;CACT;CAEA,SAAS,YAAY,SAAuB;EAC1C,YAAY,OAAO;CACrB;CAEA,SAAS,oBACP,SACA,KACA,OACM;EACN,YAAY,SAAS,GAAG,MAAM,MAAM,CAAmB;CACzD;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF;;;AClHA,SAAgB,YAAY,SAAgD;CAC1E,MAAM,EACJ,SACA,SACA,UACA,WAAW,KACX,UAAU,SACR;CAEJ,IAAI,YAAkD;CACtD,IAAI,SAAS;CAEb,SAAS,YAAqB;EAC5B,OAAO,OAAO,YAAY,aAAa,QAAQ,IAAI;CACrD;CAEA,SAAS,QAAc;EACrB,SAAS;EACT,OAAO;CACT;CAEA,SAAS,SAAe;EACtB,SAAS;CACX;CAEA,SAAS,SAAe;EACtB,IAAI,WAAW;GACb,aAAa,SAAS;GACtB,YAAY;EACd;CACF;CAEA,SAAS,QAAc;EACrB,OAAO;EACP,IAAI,QAAQ,GACV,SAAS,KAAK,MAAM,KAAK,UAAU,QAAQ,KAAK,CAAC,CAAC;CAEtD;CAEA,SAAS,mBAAyB;EAChC,IAAI,CAAC,UAAU,KAAK,QAAQ;EAE5B,OAAO;EACP,YAAY,iBAAiB;GAC3B,YAAY;GACZ,IAAI,UAAU,KAAK,CAAC,UAAU,QAAQ,GACpC,SAAS,KAAK,MAAM,KAAK,UAAU,QAAQ,KAAK,CAAC,CAAC;EAEtD,GAAG,QAAQ;CACb;CAEA,MAAM,YAAY,MAChB,eACM;EACJ,IAAI,UAAU,KAAK,CAAC,UAAU,QAAQ,GACpC,iBAAiB;CAErB,GACA,EAAE,MAAM,KAAK,CACf;CAEA,SAAS,UAAgB;EACvB,UAAU;EACV,OAAO;CACT;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;CACF;AACF;;;AClFA,SAAgB,oBACd,QAC2B;CAC3B,MAAM,iBAAiB,yBAAS,IAAI,IAAY,CAAC;CAEjD,MAAM,kBAAkB,eAAe,eAAe,OAAO,CAAC;CAE9D,SAAS,SAAS,SAA0B;EAC1C,OAAO,eAAe,IAAI,OAAO;CACnC;CAEA,SAAS,YAAY,SAAuB;EAC1C,IAAI,eAAe,IAAI,OAAO,GAC5B,eAAe,OAAO,OAAO;OACxB;GACL,eAAe,IAAI,OAAO;GAE1B,IAAI,OAAO,MAAM,oBAAoB,SACnC,OAAO,YAAY,IAAI;EAE3B;CACF;CAEA,SAAS,QAAc;EACrB,eAAe,MAAM;CACvB;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF;;;ACvCA,SAAgB,mBAAmB,SAUjC;CACA,MAAM,aAAa,IAAI,KAAK;CAC5B,MAAM,aAAa,IAAI,KAAK;CAE5B,MAAM,gBAAgB,eAAe,CAAC,CAAC,QAAQ,WAAW,OAAO,UAAU;CAE3E,MAAM,aAAa,eACX,cAAc,SAAS,CAAC,QAAQ,MAAM,MAAM,iBACpD;CAEA,eAAe,QAAuB;EACpC,MAAM,MAAM,QAAQ,WAAW;EAC/B,IAAI,CAAC,KAAK,YACR;EAGF,WAAW,QAAQ;EACnB,WAAW,QAAQ;EAEnB,IAAI;GACF,MAAM,SAAS,MAAM,IAAI,WAAW,QAAQ;IAC1C,aAAa,EAAE,GAAG,QAAQ,MAAM,MAAM,YAAY;IAClD,SAAS,QAAQ,MAAM,MAAM;GAC/B,CAAC;GAED,IAAI,UAAU,MACZ;GAGF,MAAM,SAAS,EAAE,GAAG,QAAQ,MAAM,MAAM,YAAY;GACpD,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAClC,IAAI,OAAO,QACT,OAAO,OAAO,OAAO;GAIzB,QAAQ,SAAS,QAAQ,IAAI;EAC/B,SAAS,OAAO;GACd,QAAQ,KAAK,0CAA0C,KAAK;GAC5D,WAAW,QAAQ;EACrB,UAAU;GACR,WAAW,QAAQ;EACrB;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;;;;ACxDA,SAAgB,sBACd,QACA,SACM;CACN,MAAM,mBAAmB,OAAO;CAChC,MAAM,sBAAsB,OAAO;CACnC,MAAM,oBAAoB,OAAO;CACjC,MAAM,sBAAsB,OAAO;CACnC,MAAM,yBAAyB,OAAO;CAKtC,OAAO,YAAY,OAAO,iBAAkB,aAAc,UAAW;EACnE,IAAI,mBAAmB,OAAO,cAAc,eAAe,GACzD;EAEF,QAAQ,OAAO;EACf,iBAAiB,OAAO,iBAAiB,aAAa,KAAK;CAC7D;CAEA,OAAO,eAAe,YAAY;EAChC,IAAI,OAAO,cAAc,OAAO,GAC9B;EAEF,QAAQ,OAAO;EACf,oBAAoB,OAAO;CAC7B;CAEA,OAAO,aAAa,SAAS,UAAU,iBAAkB,gBAAiB;EACxE,IAAI,OAAO,cAAc,OAAO,GAC9B;EAEF,IAAI,mBAAmB,OAAO,cAAc,eAAe,GACzD;EAEF,QAAQ,OAAO;EACf,kBAAkB,SAAS,UAAU,iBAAiB,WAAW;CACnE;CAEA,OAAO,eAAe,SAAS,YAAY;EACzC,IAAI,OAAO,cAAc,OAAO,GAC9B;EAEF,QAAQ,gBAAgB,OAAO;EAC/B,oBAAoB,SAAS,OAAO;CACtC;CAEA,OAAO,kBAAkB,YAAY;EACnC,QAAQ,OAAO;EACf,uBAAuB,OAAO;CAChC;AACF;;;;;;;;;;;ACkBA,SAAgB,eACd,SACsB;CACtB,MAAM,EAAE,aAAa;CAErB,MAAM,cAAcA,MAAkB,CAAC,CAAC;CACxC,MAAM,YAAYA,MAAI,KAAK;CAE3B,MAAM,aAAaC,iBAAe;EAChC,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAK,MAAM,SAAS,YAAY,OAAO;GACrC,MAAM,WAAW,MAAM,UAAU,KAAK;GACtC,IAAI,UAAU,KAAK,IAAI,QAAQ;EACjC;EACA,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;CACpD,CAAC;CAED,MAAM,YAAYA,iBAAe,OAAO,SAAS,WAAW,UAAU;CACtE,MAAM,YAAYA,iBAAe,OAAO,SAAS,WAAW,UAAU;CACtE,MAAM,YAAYA,iBAAe,OAAO,SAAS,WAAW,UAAU;;CAGtE,SAAS,eAAe,OAA4B;EAClD,OAAO,UAAU,SAAS,MAAM,cAAc;CAChD;CAEA,SAAS,eAAe,OAA4B;EAClD,OAAO,UAAU,SAAS,MAAM,cAAc;CAChD;;;;;;CAOA,SAAS,OAAO,QAAgB,QAAuB;EACrD,MAAM,IAAI,SACR,+BAA+B,OAAO,MAAM,OAAO,8EACrD;CACF;CAEA,eAAe,KAAK,QAA+C;EACjE,UAAU,QAAQ;EAClB,IAAI;GACF,YAAY,QAAQ,MAAM,SAAS,KAAK,MAAM;EAChD,SAAS,OAAO;GACd,QAAQ,UAAU,KAAc;GAChC,MAAM;EACR,UAAU;GACR,UAAU,QAAQ;EACpB;CACF;CAEA,eAAe,OACb,MACA,SACA,UACqB;EACrB,MAAM,EAAE,QAAQ,mBAAmB;EACnC,IAAI,OAAO,mBAAmB,YAC5B,OAAO,UAAU,0BAA0B;EAE7C,IAAI;GAIF,MAAM,UAAU,MAAM,eACpB,WAAW;IAAE;IAAM;IAAS;GAAS,IAAI;IAAE;IAAM;GAAQ,CAC3D;GACA,YAAY,QAAQ,CAAC,SAAS,GAAG,YAAY,KAAK;GAClD,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,UAAU,KAAc;GAChC,MAAM;EACR;CACF;CAEA,eAAe,OACb,IACA,OACqB;EACrB,MAAM,EAAE,QAAQ,mBAAmB;EACnC,IAAI,OAAO,mBAAmB,YAC5B,OAAO,UAAU,0BAA0B;EAK7C,MAAM,QAAQ,YAAY,MAAM,MAAM,MAAM,EAAE,OAAO,EAAE;EACvD,IAAI,SAAS,MAAM,cAAc,OAC/B,OAAO,UAAU,4BAA4B,GAAG,EAAE;EAEpD,IAAI;GACF,MAAM,UAAU,MAAM,eAAe,IAAI,KAAK;GAC9C,YAAY,QAAQ,YAAY,MAAM,KAAK,MACzC,EAAE,OAAO,KAAK,UAAU,CAC1B;GACA,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,UAAU,KAAc;GAChC,MAAM;EACR;CACF;CAEA,eAAe,OAAO,IAA2B;EAC/C,MAAM,EAAE,QAAQ,mBAAmB;EACnC,IAAI,OAAO,mBAAmB,YAC5B,OAAO,UAAU,0BAA0B;EAE7C,MAAM,QAAQ,YAAY,MAAM,MAAM,MAAM,EAAE,OAAO,EAAE;EACvD,IAAI,SAAS,MAAM,cAAc,OAC/B,OAAO,UAAU,4BAA4B,GAAG,EAAE;EAEpD,IAAI;GACF,MAAM,eAAe,EAAE;GACvB,YAAY,QAAQ,YAAY,MAAM,QAAQ,MAAM,EAAE,OAAO,EAAE;EACjE,SAAS,OAAO;GACd,QAAQ,UAAU,KAAc;GAChC,MAAM;EACR;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;ACtMA,MAAM,cAAc;;;;;;;;;;;;;AAcpB,SAAgB,sCACd,UAA0C,CAAC,GACtB;CACrB,MAAM,aAAa,QAAQ,OAAO;CAElC,SAAS,aAAsB;EAG7B,IAAI,OAAO,iBAAiB,aAC1B,MAAM,IAAI,MACR,+KACF;EAEF,OAAO;CACT;CAEA,SAAS,UAAwB;EAC/B,MAAM,MAAM,WAAW,CAAC,CAAC,QAAQ,UAAU;EAC3C,IAAI,CAAC,KAAK,OAAO,CAAC;EAClB,IAAI;GACF,MAAM,SAAkB,KAAK,MAAM,GAAG;GAGtC,OAAO,MAAM,QAAQ,MAAM,IAAK,SAA0B,CAAC;EAC7D,QAAQ;GACN,OAAO,CAAC;EACV;CACF;CAEA,SAAS,SAAS,QAA4B;EAC5C,WAAW,CAAC,CAAC,QAAQ,YAAY,KAAK,UAAU,MAAM,CAAC;CACzD;CAMA,OAAO;EAGL,MAAM,KAAK,QAAuD;GAChE,MAAM,MAAM,QAAQ;GACpB,MAAM,SAAS,QAAQ,QAAQ,KAAK,CAAC,CAAC,YAAY;GAClD,MAAM,WAAW,QAAQ,UAAU,KAAK;GACxC,OAAO,IAAI,QAAQ,MAAM;IACvB,IAAI,UAAU,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,OAAO;IAC7D,IAAI,YAAY,EAAE,aAAa,UAAU,OAAO;IAChD,OAAO;GACT,CAAC;EACH;EAIA,MAAM,OAAO,OAA6C;GACxD,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GACnC,MAAM,UAAsB;IAC1B,IAAI,WAAW;IACf,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;IACrD,WAAW;IACX,WAAW;GACb;GAGA,SAAS,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC;GAChC,OAAO;EACT;EAEA,MAAM,OAAO,IAAY,OAA6C;GACpE,MAAM,MAAM,QAAQ;GACpB,MAAM,QAAQ,IAAI,WAAW,MAAM,EAAE,OAAO,EAAE;GAE9C,IAAI,UAAU,IACZ,MAAM,IAAI,MAAM,wCAAwC,IAAI;GAE9D,MAAM,UAAsB;IAC1B,GAAG,IAAI;IACP,GAAG;IACH,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GACpC;GACA,MAAM,OAAO,CAAC,GAAG,GAAG;GACpB,KAAK,SAAS;GACd,SAAS,IAAI;GACb,OAAO;EACT;EAEA,MAAM,OAAO,IAA2B;GACtC,MAAM,MAAM,QAAQ;GACpB,IAAI,CAAC,IAAI,MAAM,MAAM,EAAE,OAAO,EAAE,GAC9B,MAAM,IAAI,MAAM,wCAAwC,IAAI;GAE9D,SAAS,IAAI,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC;EACzC;CACF;AACF"}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@templatical/core",
3
3
  "description": "Framework-agnostic editor logic for Templatical email editor",
4
- "version": "0.20.0",
4
+ "version": "0.21.0",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
7
  "@vue/reactivity": "^3.5.40",
8
- "@templatical/types": "0.20.0"
8
+ "@templatical/types": "0.21.0"
9
9
  },
10
10
  "devDependencies": {
11
11
  "pusher-js": "^8.6.0",