@marlinjai/email-editor-core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3299 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AccordionBlockSchema: () => AccordionBlockSchema,
24
+ AccordionItemModel: () => AccordionItemModel,
25
+ BackgroundGradientSchema: () => BackgroundGradientSchema,
26
+ BlockModel: () => BlockModel,
27
+ BlockRegistryImpl: () => BlockRegistryImpl,
28
+ BlockSchema: () => BlockSchema,
29
+ BlockType: () => BlockType,
30
+ ButtonBlockSchema: () => ButtonBlockSchema,
31
+ CONTAINER_BLOCK_TYPES: () => CONTAINER_BLOCK_TYPES,
32
+ CURRENT_TEMPLATE_VERSION: () => CURRENT_TEMPLATE_VERSION,
33
+ CarouselBlockSchema: () => CarouselBlockSchema,
34
+ CarouselImageModel: () => CarouselImageModel,
35
+ ColumnModel: () => ColumnModel,
36
+ ColumnSchema: () => ColumnSchema,
37
+ CustomFontSchema: () => CustomFontSchema,
38
+ DividerBlockSchema: () => DividerBlockSchema,
39
+ EditorUIStore: () => EditorUIStore,
40
+ EmailTemplateSchema: () => EmailTemplateSchema,
41
+ EmailTemplateSchemaV1_0: () => EmailTemplateSchemaV1_0,
42
+ EmailTemplateSchemaV1_1: () => EmailTemplateSchemaV1_1,
43
+ ExtraAttributesSchema: () => ExtraAttributesSchema,
44
+ FontDefinitionModel: () => FontDefinitionModel,
45
+ FooterBlockSchema: () => FooterBlockSchema,
46
+ GradientStopSchema: () => GradientStopSchema,
47
+ HeaderBlockSchema: () => HeaderBlockSchema,
48
+ HeroBlockSchema: () => HeroBlockSchema,
49
+ HistoryManager: () => HistoryManager,
50
+ ImageBlockSchema: () => ImageBlockSchema,
51
+ LEAF_BLOCK_TYPES: () => LEAF_BLOCK_TYPES,
52
+ MJML_WRAPPER_DEFAULT_PADDING: () => MJML_WRAPPER_DEFAULT_PADDING,
53
+ MjmlHeadSchema: () => MjmlHeadSchema,
54
+ NavbarBlockSchema: () => NavbarBlockSchema,
55
+ NavbarLinkModel: () => NavbarLinkModel,
56
+ RawBlockSchema: () => RawBlockSchema,
57
+ RootStore: () => RootStore,
58
+ SUPPORTED_TEMPLATE_VERSIONS: () => SUPPORTED_TEMPLATE_VERSIONS,
59
+ SectionModel: () => SectionModel,
60
+ SectionSchema: () => SectionSchema,
61
+ SectionSchemaV1_0: () => SectionSchemaV1_0,
62
+ SelectionManager: () => SelectionManager,
63
+ SocialBlockSchema: () => SocialBlockSchema,
64
+ SocialLinkModel: () => SocialLinkModel,
65
+ SpacerBlockSchema: () => SpacerBlockSchema,
66
+ SpacingModel: () => SpacingModel,
67
+ SpacingSchema: () => SpacingSchema,
68
+ SubColumnModel: () => SubColumnModel,
69
+ TableBlockSchema: () => TableBlockSchema,
70
+ TemplateMetadataModel: () => TemplateMetadataModel,
71
+ TemplateMetadataSchema: () => TemplateMetadataSchema,
72
+ TemplateMigrationError: () => TemplateMigrationError,
73
+ TemplateModel: () => TemplateModel,
74
+ TextBlockSchema: () => TextBlockSchema,
75
+ ThemeColorModel: () => ThemeColorModel,
76
+ TopLevelItemModel: () => TopLevelItemModel,
77
+ TopLevelItemSchema: () => TopLevelItemSchema,
78
+ WrapperModel: () => WrapperModel,
79
+ WrapperSchema: () => WrapperSchema,
80
+ allSections: () => allSections,
81
+ buildGradientCSS: () => buildGradientCSS,
82
+ cloneSectionSnapshot: () => cloneSectionSnapshot,
83
+ createBlockRegistry: () => createBlockRegistry,
84
+ createColumn: () => createColumn,
85
+ createEmptyStore: () => createEmptyStore,
86
+ createHistoryManager: () => createHistoryManager,
87
+ createPrebuiltTemplateRegistry: () => createPrebuiltTemplateRegistry,
88
+ createRootStore: () => createRootStore,
89
+ createSection: () => createSection,
90
+ createSelectionManager: () => createSelectionManager,
91
+ createSubColumn: () => createSubColumn,
92
+ createTemplate: () => createTemplate,
93
+ createTemplateWithDefaultSection: () => createTemplateWithDefaultSection,
94
+ createWrapper: () => createWrapper,
95
+ isLeafBlockType: () => isLeafBlockType,
96
+ isTemplateMigrationError: () => isTemplateMigrationError,
97
+ isWrapper: () => isWrapper,
98
+ isWrapperInstance: () => isWrapperInstance,
99
+ migrateTemplate: () => migrateTemplate,
100
+ migrateV1_0ToV1_1: () => migrateV1_0ToV1_1,
101
+ validateTemplate: () => validateTemplate,
102
+ withTemplateId: () => withTemplateId
103
+ });
104
+ module.exports = __toCommonJS(index_exports);
105
+
106
+ // src/schema/gradient.ts
107
+ function buildGradientCSS(gradient) {
108
+ if (gradient.stops.length === 0) return void 0;
109
+ const stopList = gradient.stops.map((s) => `${s.color} ${s.position}%`).join(", ");
110
+ if (gradient.type === "radial") {
111
+ return `radial-gradient(circle, ${stopList})`;
112
+ }
113
+ return `linear-gradient(${gradient.angle}deg, ${stopList})`;
114
+ }
115
+
116
+ // src/schema/types.ts
117
+ function isWrapper(item) {
118
+ return item.type === "wrapper";
119
+ }
120
+ function allSections(items) {
121
+ return items.flatMap((item) => isWrapper(item) ? item.sections : [item]);
122
+ }
123
+
124
+ // src/schema/validation.ts
125
+ var import_zod = require("zod");
126
+ var SpacingSchema = import_zod.z.object({
127
+ top: import_zod.z.string().optional(),
128
+ right: import_zod.z.string().optional(),
129
+ bottom: import_zod.z.string().optional(),
130
+ left: import_zod.z.string().optional()
131
+ });
132
+ var GradientStopSchema = import_zod.z.object({
133
+ color: import_zod.z.string().min(1),
134
+ position: import_zod.z.number().min(0).max(100)
135
+ });
136
+ var BackgroundGradientSchema = import_zod.z.object({
137
+ type: import_zod.z.enum(["linear", "radial"]),
138
+ angle: import_zod.z.number().min(0).max(360),
139
+ stops: import_zod.z.array(GradientStopSchema).min(1)
140
+ });
141
+ var ExtraAttributesSchema = import_zod.z.record(import_zod.z.string().regex(/^[a-z][a-z0-9-]*$/), import_zod.z.string());
142
+ var MjmlHeadSchema = import_zod.z.object({
143
+ attributes: import_zod.z.string().optional(),
144
+ bodyAttributes: ExtraAttributesSchema.optional(),
145
+ headRaw: import_zod.z.string().optional()
146
+ });
147
+ var CustomFontSchema = import_zod.z.object({
148
+ name: import_zod.z.string(),
149
+ href: import_zod.z.string()
150
+ });
151
+ var TemplateMetadataSchema = import_zod.z.object({
152
+ name: import_zod.z.string().optional(),
153
+ subject: import_zod.z.string().optional(),
154
+ previewText: import_zod.z.string().optional(),
155
+ title: import_zod.z.string().optional(),
156
+ // Epoch milliseconds (what the editor's store emits) or an ISO 8601 string.
157
+ createdAt: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
158
+ updatedAt: import_zod.z.union([import_zod.z.string(), import_zod.z.number()]).optional(),
159
+ // Head component settings
160
+ fonts: import_zod.z.array(CustomFontSchema).optional(),
161
+ breakpoint: import_zod.z.string().optional(),
162
+ customCSS: import_zod.z.string().optional(),
163
+ inlineCSS: import_zod.z.string().optional(),
164
+ mjmlHead: MjmlHeadSchema.optional()
165
+ });
166
+ var TextBlockSchema = import_zod.z.object({
167
+ id: import_zod.z.string(),
168
+ type: import_zod.z.literal("text"),
169
+ hidden: import_zod.z.boolean().optional(),
170
+ extraAttributes: ExtraAttributesSchema.optional(),
171
+ content: import_zod.z.string(),
172
+ align: import_zod.z.enum(["left", "center", "right", "justify"]).optional(),
173
+ color: import_zod.z.string().optional(),
174
+ fontSize: import_zod.z.string().optional(),
175
+ fontFamily: import_zod.z.string().optional(),
176
+ padding: SpacingSchema.optional(),
177
+ lineHeight: import_zod.z.string().optional()
178
+ });
179
+ var ImageBlockSchema = import_zod.z.object({
180
+ id: import_zod.z.string(),
181
+ type: import_zod.z.literal("image"),
182
+ hidden: import_zod.z.boolean().optional(),
183
+ extraAttributes: ExtraAttributesSchema.optional(),
184
+ src: import_zod.z.string().min(1),
185
+ // Allow any non-empty string, not just URLs
186
+ alt: import_zod.z.string().optional(),
187
+ width: import_zod.z.string().optional(),
188
+ height: import_zod.z.string().optional(),
189
+ align: import_zod.z.enum(["left", "center", "right"]).optional(),
190
+ href: import_zod.z.string().optional(),
191
+ // Allow any string for link
192
+ padding: SpacingSchema.optional(),
193
+ borderRadius: import_zod.z.string().optional()
194
+ // Rounded corners
195
+ });
196
+ var ButtonBlockSchema = import_zod.z.object({
197
+ id: import_zod.z.string(),
198
+ type: import_zod.z.literal("button"),
199
+ hidden: import_zod.z.boolean().optional(),
200
+ extraAttributes: ExtraAttributesSchema.optional(),
201
+ label: import_zod.z.string().min(1),
202
+ href: import_zod.z.string().min(1),
203
+ // Allow any non-empty string
204
+ align: import_zod.z.enum(["left", "center", "right"]).optional(),
205
+ backgroundColor: import_zod.z.string().optional(),
206
+ color: import_zod.z.string().optional(),
207
+ borderRadius: import_zod.z.string().optional(),
208
+ border: import_zod.z.string().optional(),
209
+ // CSS border shorthand
210
+ padding: SpacingSchema.optional(),
211
+ innerPadding: import_zod.z.string().optional()
212
+ });
213
+ var DividerBlockSchema = import_zod.z.object({
214
+ id: import_zod.z.string(),
215
+ type: import_zod.z.literal("divider"),
216
+ hidden: import_zod.z.boolean().optional(),
217
+ extraAttributes: ExtraAttributesSchema.optional(),
218
+ borderColor: import_zod.z.string().optional(),
219
+ borderWidth: import_zod.z.string().optional(),
220
+ borderStyle: import_zod.z.enum(["solid", "dashed", "dotted"]).optional(),
221
+ width: import_zod.z.string().optional(),
222
+ // Width of the divider line
223
+ padding: SpacingSchema.optional()
224
+ });
225
+ var SpacerBlockSchema = import_zod.z.object({
226
+ id: import_zod.z.string(),
227
+ type: import_zod.z.literal("spacer"),
228
+ hidden: import_zod.z.boolean().optional(),
229
+ extraAttributes: ExtraAttributesSchema.optional(),
230
+ height: import_zod.z.string()
231
+ });
232
+ var HeaderBlockSchema = import_zod.z.object({
233
+ id: import_zod.z.string(),
234
+ type: import_zod.z.literal("header"),
235
+ hidden: import_zod.z.boolean().optional(),
236
+ extraAttributes: ExtraAttributesSchema.optional(),
237
+ locked: import_zod.z.literal(true)
238
+ });
239
+ var FooterBlockSchema = import_zod.z.object({
240
+ id: import_zod.z.string(),
241
+ type: import_zod.z.literal("footer"),
242
+ hidden: import_zod.z.boolean().optional(),
243
+ extraAttributes: ExtraAttributesSchema.optional(),
244
+ locked: import_zod.z.literal(true)
245
+ });
246
+ var SocialBlockSchema = import_zod.z.object({
247
+ id: import_zod.z.string(),
248
+ type: import_zod.z.literal("social"),
249
+ hidden: import_zod.z.boolean().optional(),
250
+ extraAttributes: ExtraAttributesSchema.optional(),
251
+ mode: import_zod.z.enum(["horizontal", "vertical"]).optional(),
252
+ align: import_zod.z.enum(["left", "center", "right"]).optional(),
253
+ iconSize: import_zod.z.string().optional(),
254
+ iconPadding: import_zod.z.string().optional(),
255
+ borderRadius: import_zod.z.string().optional(),
256
+ // For round icons
257
+ links: import_zod.z.array(
258
+ import_zod.z.object({
259
+ platform: import_zod.z.enum(["facebook", "twitter", "instagram", "linkedin", "youtube", "pinterest", "github"]),
260
+ url: import_zod.z.string(),
261
+ color: import_zod.z.string().optional()
262
+ // Per-icon color override (uses platform default if not set)
263
+ })
264
+ )
265
+ });
266
+ var HeroBlockSchema = import_zod.z.object({
267
+ id: import_zod.z.string(),
268
+ type: import_zod.z.literal("hero"),
269
+ hidden: import_zod.z.boolean().optional(),
270
+ extraAttributes: ExtraAttributesSchema.optional(),
271
+ backgroundImage: import_zod.z.string().min(1),
272
+ backgroundHeight: import_zod.z.string().optional(),
273
+ backgroundWidth: import_zod.z.string().optional(),
274
+ backgroundColor: import_zod.z.string().optional(),
275
+ verticalAlign: import_zod.z.enum(["top", "middle", "bottom"]).optional(),
276
+ mode: import_zod.z.enum(["fluid-height", "fixed-height"]).optional()
277
+ });
278
+ var AccordionBlockSchema = import_zod.z.object({
279
+ id: import_zod.z.string(),
280
+ type: import_zod.z.literal("accordion"),
281
+ hidden: import_zod.z.boolean().optional(),
282
+ extraAttributes: ExtraAttributesSchema.optional(),
283
+ items: import_zod.z.array(
284
+ import_zod.z.object({
285
+ title: import_zod.z.string(),
286
+ content: import_zod.z.string()
287
+ })
288
+ ),
289
+ iconPosition: import_zod.z.enum(["left", "right"]).optional(),
290
+ borderColor: import_zod.z.string().optional(),
291
+ fontFamily: import_zod.z.string().optional()
292
+ });
293
+ var RawBlockSchema = import_zod.z.object({
294
+ id: import_zod.z.string(),
295
+ type: import_zod.z.literal("raw"),
296
+ hidden: import_zod.z.boolean().optional(),
297
+ extraAttributes: ExtraAttributesSchema.optional(),
298
+ html: import_zod.z.string()
299
+ });
300
+ var NavbarBlockSchema = import_zod.z.object({
301
+ id: import_zod.z.string(),
302
+ type: import_zod.z.literal("navbar"),
303
+ hidden: import_zod.z.boolean().optional(),
304
+ extraAttributes: ExtraAttributesSchema.optional(),
305
+ links: import_zod.z.array(
306
+ import_zod.z.object({
307
+ label: import_zod.z.string(),
308
+ href: import_zod.z.string(),
309
+ color: import_zod.z.string().optional()
310
+ })
311
+ ),
312
+ hamburger: import_zod.z.boolean().optional(),
313
+ baseUrl: import_zod.z.string().optional(),
314
+ align: import_zod.z.enum(["left", "center", "right"]).optional(),
315
+ icoColor: import_zod.z.string().optional(),
316
+ padding: SpacingSchema.optional()
317
+ });
318
+ var CarouselBlockSchema = import_zod.z.object({
319
+ id: import_zod.z.string(),
320
+ type: import_zod.z.literal("carousel"),
321
+ hidden: import_zod.z.boolean().optional(),
322
+ extraAttributes: ExtraAttributesSchema.optional(),
323
+ images: import_zod.z.array(
324
+ import_zod.z.object({
325
+ src: import_zod.z.string(),
326
+ alt: import_zod.z.string().optional(),
327
+ href: import_zod.z.string().optional(),
328
+ thumbnailSrc: import_zod.z.string().optional()
329
+ })
330
+ ),
331
+ thumbnails: import_zod.z.enum(["visible", "hidden"]).optional(),
332
+ borderRadius: import_zod.z.string().optional(),
333
+ iconWidth: import_zod.z.string().optional(),
334
+ tbBorderRadius: import_zod.z.string().optional(),
335
+ padding: SpacingSchema.optional()
336
+ });
337
+ var TableBlockSchema = import_zod.z.object({
338
+ id: import_zod.z.string(),
339
+ type: import_zod.z.literal("table"),
340
+ hidden: import_zod.z.boolean().optional(),
341
+ extraAttributes: ExtraAttributesSchema.optional(),
342
+ headers: import_zod.z.array(import_zod.z.string()),
343
+ rows: import_zod.z.array(import_zod.z.array(import_zod.z.string())),
344
+ align: import_zod.z.enum(["left", "center", "right"]).optional(),
345
+ color: import_zod.z.string().optional(),
346
+ fontFamily: import_zod.z.string().optional(),
347
+ fontSize: import_zod.z.string().optional(),
348
+ cellpadding: import_zod.z.string().optional(),
349
+ cellspacing: import_zod.z.string().optional(),
350
+ border: import_zod.z.string().optional(),
351
+ padding: SpacingSchema.optional()
352
+ });
353
+ var BlockSchema = import_zod.z.discriminatedUnion("type", [
354
+ TextBlockSchema,
355
+ ImageBlockSchema,
356
+ ButtonBlockSchema,
357
+ DividerBlockSchema,
358
+ SpacerBlockSchema,
359
+ HeaderBlockSchema,
360
+ FooterBlockSchema,
361
+ SocialBlockSchema,
362
+ HeroBlockSchema,
363
+ AccordionBlockSchema,
364
+ RawBlockSchema,
365
+ NavbarBlockSchema,
366
+ CarouselBlockSchema,
367
+ TableBlockSchema
368
+ ]);
369
+ var ColumnSchema = import_zod.z.object({
370
+ id: import_zod.z.string(),
371
+ width: import_zod.z.number().min(0).max(100).optional(),
372
+ backgroundColor: import_zod.z.string().optional(),
373
+ backgroundGradient: BackgroundGradientSchema.optional(),
374
+ padding: SpacingSchema.optional(),
375
+ verticalAlign: import_zod.z.enum(["top", "middle", "bottom"]).optional(),
376
+ // Vertical content alignment
377
+ hidden: import_zod.z.boolean().optional(),
378
+ extraAttributes: ExtraAttributesSchema.optional(),
379
+ blocks: import_zod.z.array(BlockSchema)
380
+ });
381
+ var sectionFields = {
382
+ id: import_zod.z.string(),
383
+ type: import_zod.z.literal("section"),
384
+ backgroundColor: import_zod.z.string().optional(),
385
+ backgroundImage: import_zod.z.string().optional(),
386
+ backgroundPosition: import_zod.z.string().optional(),
387
+ backgroundRepeat: import_zod.z.enum(["repeat", "no-repeat"]).optional(),
388
+ backgroundSize: import_zod.z.string().optional(),
389
+ backgroundGradient: BackgroundGradientSchema.optional(),
390
+ padding: SpacingSchema.optional(),
391
+ noStack: import_zod.z.boolean().optional(),
392
+ fullWidth: import_zod.z.boolean().optional(),
393
+ hidden: import_zod.z.boolean().optional(),
394
+ extraAttributes: ExtraAttributesSchema.optional(),
395
+ bodyRaw: import_zod.z.boolean().optional(),
396
+ columns: import_zod.z.array(ColumnSchema).min(1)
397
+ };
398
+ var SectionSchema = import_zod.z.object({
399
+ ...sectionFields,
400
+ isWrapper: import_zod.z.undefined({ invalid_type_error: "isWrapper is a schema 1.0 field; a 1.1 document holds a wrapper instead" }).optional()
401
+ });
402
+ var SectionSchemaV1_0 = import_zod.z.object({
403
+ ...sectionFields,
404
+ isWrapper: import_zod.z.boolean().optional()
405
+ });
406
+ var WrapperSchema = import_zod.z.object({
407
+ id: import_zod.z.string(),
408
+ type: import_zod.z.literal("wrapper"),
409
+ hidden: import_zod.z.boolean().optional(),
410
+ backgroundColor: import_zod.z.string().optional(),
411
+ backgroundImage: import_zod.z.string().optional(),
412
+ backgroundGradient: BackgroundGradientSchema.optional(),
413
+ backgroundPosition: import_zod.z.string().optional(),
414
+ backgroundRepeat: import_zod.z.enum(["repeat", "no-repeat"]).optional(),
415
+ backgroundSize: import_zod.z.string().optional(),
416
+ border: import_zod.z.string().optional(),
417
+ borderTop: import_zod.z.string().optional(),
418
+ borderRight: import_zod.z.string().optional(),
419
+ borderBottom: import_zod.z.string().optional(),
420
+ borderLeft: import_zod.z.string().optional(),
421
+ borderRadius: import_zod.z.string().optional(),
422
+ padding: SpacingSchema.optional(),
423
+ fullWidth: import_zod.z.boolean().optional(),
424
+ cssClass: import_zod.z.string().optional(),
425
+ gap: import_zod.z.string().regex(/^[0-9]+(\.[0-9]+)?px$/, "gap is a length in px, e.g. 16px").optional(),
426
+ textAlign: import_zod.z.enum(["left", "center", "right"]).optional(),
427
+ extraAttributes: ExtraAttributesSchema.optional(),
428
+ sections: import_zod.z.array(SectionSchema)
429
+ });
430
+ var TopLevelItemSchema = import_zod.z.discriminatedUnion("type", [SectionSchema, WrapperSchema]);
431
+ var EmailTemplateSchemaV1_0 = import_zod.z.object({
432
+ id: import_zod.z.string().optional(),
433
+ version: import_zod.z.literal("1.0"),
434
+ metadata: TemplateMetadataSchema,
435
+ sections: import_zod.z.array(SectionSchemaV1_0)
436
+ });
437
+ var EmailTemplateSchemaV1_1 = import_zod.z.object({
438
+ id: import_zod.z.string().optional(),
439
+ version: import_zod.z.literal("1.1"),
440
+ metadata: TemplateMetadataSchema,
441
+ sections: import_zod.z.array(TopLevelItemSchema)
442
+ });
443
+ var EmailTemplateSchema = import_zod.z.discriminatedUnion("version", [EmailTemplateSchemaV1_0, EmailTemplateSchemaV1_1]);
444
+ function validateTemplate(template) {
445
+ return EmailTemplateSchema.safeParse(template);
446
+ }
447
+
448
+ // src/schema/migrate.ts
449
+ var import_nanoid = require("nanoid");
450
+ var CURRENT_TEMPLATE_VERSION = "1.1";
451
+ var SUPPORTED_TEMPLATE_VERSIONS = ["1.0", CURRENT_TEMPLATE_VERSION];
452
+ var TemplateMigrationError = class extends Error {
453
+ constructor(code, message, options = {}) {
454
+ super(message);
455
+ this.name = "TemplateMigrationError";
456
+ this.code = code;
457
+ this.version = options.version;
458
+ this.issues = options.issues ?? [];
459
+ }
460
+ };
461
+ function isTemplateMigrationError(error) {
462
+ return error instanceof TemplateMigrationError;
463
+ }
464
+ function parseVersion(version) {
465
+ const match = /^(\d+)\.(\d+)$/.exec(version);
466
+ if (!match) return null;
467
+ return [Number(match[1]), Number(match[2])];
468
+ }
469
+ function compareVersions(a, b) {
470
+ return a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1];
471
+ }
472
+ function migrateTemplate(doc) {
473
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
474
+ throw new TemplateMigrationError(
475
+ "INVALID_INPUT",
476
+ `Expected a template document object, received ${doc === null ? "null" : Array.isArray(doc) ? "an array" : typeof doc}.`
477
+ );
478
+ }
479
+ const version = doc.version;
480
+ if (typeof version !== "string" || version.length === 0) {
481
+ throw new TemplateMigrationError(
482
+ "MISSING_VERSION",
483
+ 'The template document has no "version" field, so its schema cannot be determined.'
484
+ );
485
+ }
486
+ const parsed = parseVersion(version);
487
+ if (!parsed) {
488
+ throw new TemplateMigrationError(
489
+ "UNSUPPORTED_VERSION",
490
+ `Template schema version "${version}" is not of the form "major.minor".`,
491
+ { version }
492
+ );
493
+ }
494
+ const current = parseVersion(CURRENT_TEMPLATE_VERSION);
495
+ if (compareVersions(parsed, current) > 0) {
496
+ throw new TemplateMigrationError(
497
+ "NEWER_VERSION",
498
+ `Template schema version "${version}" is newer than this editor supports (${CURRENT_TEMPLATE_VERSION}). Upgrade the @marlinjai/email-editor packages to open it.`,
499
+ { version }
500
+ );
501
+ }
502
+ if (!SUPPORTED_TEMPLATE_VERSIONS.includes(version)) {
503
+ throw new TemplateMigrationError(
504
+ "UNSUPPORTED_VERSION",
505
+ `Template schema version "${version}" is not supported and has no migration to ${CURRENT_TEMPLATE_VERSION}.`,
506
+ { version }
507
+ );
508
+ }
509
+ let next = doc;
510
+ if (version === "1.0") {
511
+ assertValid(EmailTemplateSchemaV1_0, next, version);
512
+ next = migrateV1_0ToV1_1(next);
513
+ }
514
+ assertValid(EmailTemplateSchemaV1_1, next, version);
515
+ return next;
516
+ }
517
+ function assertValid(schema, doc, version) {
518
+ const result = schema.safeParse(doc);
519
+ if (result.success) return;
520
+ const issues = result.error.issues.map((issue) => ({ path: issue.path, message: issue.message }));
521
+ const first = issues[0];
522
+ throw new TemplateMigrationError(
523
+ "INVALID_DOCUMENT",
524
+ `Template document does not match schema version ${version}: ${first ? `${first.path.join(".") || "(root)"}: ${first.message}` : "unknown validation error"}${issues.length > 1 ? ` (and ${issues.length - 1} more)` : ""}.`,
525
+ { version, issues }
526
+ );
527
+ }
528
+ function migrateV1_0ToV1_1(doc) {
529
+ if (!doc.sections.some((s) => s.isWrapper === true && !s.bodyRaw)) {
530
+ return { ...doc, version: "1.1", sections: doc.sections.map(stripIsWrapper) };
531
+ }
532
+ const ids = /* @__PURE__ */ new Set();
533
+ for (const s of doc.sections) {
534
+ ids.add(s.id);
535
+ for (const c of s.columns ?? []) {
536
+ ids.add(c.id);
537
+ for (const b of c.blocks ?? []) ids.add(b.id);
538
+ }
539
+ }
540
+ const freshId = (base) => {
541
+ let candidate = `${base}-inner`;
542
+ for (let n = 2; ids.has(candidate); n++) candidate = `${base}-inner-${n}`;
543
+ ids.add(candidate);
544
+ return candidate;
545
+ };
546
+ const sections = doc.sections.map((s) => {
547
+ if (s.isWrapper !== true || s.bodyRaw) return stripIsWrapper(s);
548
+ const { isWrapper: _flag, noStack: _noStack, ...rest } = s;
549
+ const wrapper = { id: s.id, type: "wrapper", sections: [{ id: freshId(s.id), type: "section", columns: s.columns }] };
550
+ if (rest.hidden !== void 0) wrapper.hidden = rest.hidden;
551
+ if (rest.backgroundColor !== void 0) wrapper.backgroundColor = rest.backgroundColor;
552
+ if (rest.backgroundImage !== void 0) wrapper.backgroundImage = rest.backgroundImage;
553
+ if (rest.backgroundGradient !== void 0) wrapper.backgroundGradient = rest.backgroundGradient;
554
+ if (rest.backgroundPosition !== void 0) wrapper.backgroundPosition = rest.backgroundPosition;
555
+ if (rest.backgroundRepeat !== void 0) wrapper.backgroundRepeat = rest.backgroundRepeat;
556
+ if (rest.backgroundSize !== void 0) wrapper.backgroundSize = rest.backgroundSize;
557
+ if (rest.fullWidth !== void 0) wrapper.fullWidth = rest.fullWidth;
558
+ if (rest.padding !== void 0) wrapper.padding = rest.padding;
559
+ if (rest.extraAttributes !== void 0) wrapper.extraAttributes = rest.extraAttributes;
560
+ return wrapper;
561
+ });
562
+ return { ...doc, version: "1.1", sections };
563
+ }
564
+ function stripIsWrapper(s) {
565
+ if (!("isWrapper" in s)) return s;
566
+ const { isWrapper: _flag, ...rest } = s;
567
+ return rest;
568
+ }
569
+ function withTemplateId(doc) {
570
+ if (typeof doc.id === "string" && doc.id.length > 0) return doc;
571
+ return { ...doc, id: (0, import_nanoid.nanoid)() };
572
+ }
573
+
574
+ // src/registry/BlockRegistry.ts
575
+ var BlockRegistryImpl = class {
576
+ constructor() {
577
+ this.definitions = /* @__PURE__ */ new Map();
578
+ }
579
+ /**
580
+ * Register a new block definition
581
+ */
582
+ register(definition) {
583
+ if (this.definitions.has(definition.type)) {
584
+ console.warn(`Block type "${definition.type}" is already registered. Overwriting.`);
585
+ }
586
+ this.definitions.set(definition.type, definition);
587
+ }
588
+ /**
589
+ * Unregister a block definition
590
+ */
591
+ unregister(type) {
592
+ this.definitions.delete(type);
593
+ }
594
+ /**
595
+ * Get a block definition by type
596
+ */
597
+ get(type) {
598
+ return this.definitions.get(type);
599
+ }
600
+ /**
601
+ * Get all registered block definitions
602
+ */
603
+ getAll() {
604
+ return Array.from(this.definitions.values());
605
+ }
606
+ /**
607
+ * Get blocks by category
608
+ */
609
+ getByCategory(category) {
610
+ return this.getAll().filter((def) => def.category === category);
611
+ }
612
+ /**
613
+ * Check if a block type is registered
614
+ */
615
+ has(type) {
616
+ return this.definitions.has(type);
617
+ }
618
+ };
619
+ function createBlockRegistry() {
620
+ return new BlockRegistryImpl();
621
+ }
622
+
623
+ // src/store/mst/models/BlockModel.ts
624
+ var import_mobx_state_tree = require("mobx-state-tree");
625
+
626
+ // src/store/mst/models/spacingSnapshot.ts
627
+ var SIDES = [
628
+ ["top", "paddingTop"],
629
+ ["right", "paddingRight"],
630
+ ["bottom", "paddingBottom"],
631
+ ["left", "paddingLeft"]
632
+ ];
633
+ function paddingIn(snapshot) {
634
+ if (!snapshot || typeof snapshot !== "object") return snapshot;
635
+ const { padding, ...rest } = snapshot;
636
+ if (padding === void 0) return snapshot;
637
+ const flat = rest;
638
+ if (padding && typeof padding === "object") {
639
+ for (const [side, field] of SIDES) {
640
+ const value = padding[side];
641
+ if (flat[field] === void 0 && typeof value === "string" && value !== "") flat[field] = value;
642
+ }
643
+ }
644
+ return flat;
645
+ }
646
+ function paddingOut(snapshot) {
647
+ if (!snapshot || typeof snapshot !== "object") return snapshot;
648
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft, ...rest } = snapshot;
649
+ const values = {
650
+ top: paddingTop,
651
+ right: paddingRight,
652
+ bottom: paddingBottom,
653
+ left: paddingLeft
654
+ };
655
+ const padding = {};
656
+ for (const [side] of SIDES) {
657
+ const value = values[side];
658
+ if (typeof value === "string" && value !== "") padding[side] = value;
659
+ }
660
+ return Object.keys(padding).length > 0 ? { ...rest, padding } : rest;
661
+ }
662
+
663
+ // src/store/mst/models/filledDefaults.ts
664
+ var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
665
+ function fillDefaults(snapshot, defaults) {
666
+ if (!snapshot || typeof snapshot !== "object") return snapshot;
667
+ const s = { ...snapshot };
668
+ const filled = { ...s.filled ?? {} };
669
+ for (const [key, make] of Object.entries(defaults)) {
670
+ if (s[key] !== void 0) continue;
671
+ const value = make();
672
+ s[key] = value;
673
+ filled[key] = value;
674
+ }
675
+ if (Object.keys(filled).length > 0) s.filled = filled;
676
+ return s;
677
+ }
678
+ function dropFilled(snapshot) {
679
+ if (!snapshot || typeof snapshot !== "object") return snapshot;
680
+ const { filled, ...rest } = snapshot;
681
+ if (filled) {
682
+ for (const [key, value] of Object.entries(filled)) {
683
+ if (same(rest[key], value)) delete rest[key];
684
+ }
685
+ }
686
+ for (const key of Object.keys(rest)) if (rest[key] === void 0) delete rest[key];
687
+ return rest;
688
+ }
689
+ var empty = () => [];
690
+ var no = () => false;
691
+ var BLOCK_DEFAULTS = {
692
+ hidden: no,
693
+ content: () => "",
694
+ links: empty,
695
+ items: empty,
696
+ navLinks: empty,
697
+ hamburger: no,
698
+ images: empty,
699
+ headers: empty,
700
+ rows: empty,
701
+ locked: no
702
+ };
703
+ var COLUMN_DEFAULTS = {
704
+ width: () => 100,
705
+ hidden: no,
706
+ subColumns: empty
707
+ };
708
+ var SECTION_DEFAULTS = {
709
+ type: () => "section",
710
+ fullWidth: no,
711
+ noStack: no,
712
+ hidden: no
713
+ };
714
+ var WRAPPER_DEFAULTS = {
715
+ hidden: no,
716
+ fullWidth: no
717
+ };
718
+ function fillColumnWidths(section) {
719
+ if (!section || typeof section !== "object") return section;
720
+ const s = section;
721
+ if (!Array.isArray(s.columns) || s.columns.length === 0) return section;
722
+ if (s.columns.every((c) => c?.width !== void 0)) return section;
723
+ const share = 100 / s.columns.length;
724
+ return {
725
+ ...s,
726
+ columns: s.columns.map((c) => {
727
+ const col = c;
728
+ if (!col || typeof col !== "object" || col.width !== void 0) return c;
729
+ return { ...col, width: share, filled: { ...col.filled ?? {}, width: share } };
730
+ })
731
+ };
732
+ }
733
+
734
+ // src/store/mst/models/BlockModel.ts
735
+ var BlockType = /* @__PURE__ */ ((BlockType4) => {
736
+ BlockType4["TEXT"] = "text";
737
+ BlockType4["IMAGE"] = "image";
738
+ BlockType4["BUTTON"] = "button";
739
+ BlockType4["DIVIDER"] = "divider";
740
+ BlockType4["SPACER"] = "spacer";
741
+ BlockType4["SOCIAL"] = "social";
742
+ BlockType4["HERO"] = "hero";
743
+ BlockType4["ACCORDION"] = "accordion";
744
+ BlockType4["RAW"] = "raw";
745
+ BlockType4["NAVBAR"] = "navbar";
746
+ BlockType4["CAROUSEL"] = "carousel";
747
+ BlockType4["TABLE"] = "table";
748
+ BlockType4["HEADER"] = "header";
749
+ BlockType4["FOOTER"] = "footer";
750
+ return BlockType4;
751
+ })(BlockType || {});
752
+ var SocialLinkModel = import_mobx_state_tree.types.model("SocialLink", {
753
+ platform: import_mobx_state_tree.types.string,
754
+ url: import_mobx_state_tree.types.string,
755
+ color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
756
+ });
757
+ var NavbarLinkModel = import_mobx_state_tree.types.model("NavbarLink", {
758
+ href: import_mobx_state_tree.types.string,
759
+ label: import_mobx_state_tree.types.string,
760
+ color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
761
+ });
762
+ var CarouselImageModel = import_mobx_state_tree.types.model("CarouselImage", {
763
+ src: import_mobx_state_tree.types.string,
764
+ alt: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
765
+ href: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
766
+ thumbnailSrc: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
767
+ });
768
+ var AccordionItemModel = import_mobx_state_tree.types.model("AccordionItem", {
769
+ title: import_mobx_state_tree.types.string,
770
+ content: import_mobx_state_tree.types.string
771
+ });
772
+ var SpacingModel = import_mobx_state_tree.types.model("Spacing", {
773
+ top: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
774
+ right: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
775
+ bottom: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
776
+ left: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string)
777
+ });
778
+ var BlockModelBase = import_mobx_state_tree.types.model("Block", {
779
+ id: import_mobx_state_tree.types.identifier,
780
+ type: import_mobx_state_tree.types.enumeration("BlockType", Object.values(BlockType)),
781
+ hidden: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
782
+ // === Common Text Properties ===
783
+ content: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.string, ""),
784
+ color: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
785
+ backgroundColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
786
+ fontSize: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
787
+ fontFamily: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
788
+ align: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["left", "center", "right", "justify"])),
789
+ lineHeight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
790
+ // === Spacing ===
791
+ paddingTop: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
792
+ paddingRight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
793
+ paddingBottom: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
794
+ paddingLeft: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
795
+ // === Image Properties ===
796
+ src: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
797
+ alt: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
798
+ width: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
799
+ height: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
800
+ // === Button Properties ===
801
+ href: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
802
+ label: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
803
+ borderRadius: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
804
+ border: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
805
+ innerPadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
806
+ // === Divider Properties ===
807
+ borderColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
808
+ borderWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
809
+ borderStyle: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["solid", "dashed", "dotted"])),
810
+ // === Spacer Properties ===
811
+ // (uses height from image properties)
812
+ // === Social Properties ===
813
+ links: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(SocialLinkModel), []),
814
+ iconSize: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
815
+ iconPadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
816
+ mode: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["horizontal", "vertical", "fixed-height", "fluid-height"])),
817
+ // === Hero Properties ===
818
+ backgroundImage: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
819
+ backgroundHeight: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
820
+ backgroundWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
821
+ verticalAlign: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["top", "middle", "bottom"])),
822
+ // === Accordion Properties ===
823
+ items: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(AccordionItemModel), []),
824
+ iconPosition: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["left", "right"])),
825
+ // === Raw Properties ===
826
+ html: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
827
+ // === Navbar Properties ===
828
+ navLinks: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(NavbarLinkModel), []),
829
+ hamburger: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
830
+ baseUrl: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
831
+ icoColor: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
832
+ // === Carousel Properties ===
833
+ images: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(CarouselImageModel), []),
834
+ thumbnails: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.enumeration(["visible", "hidden"])),
835
+ iconWidth: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
836
+ tbBorderRadius: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
837
+ // === Table Properties ===
838
+ headers: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(import_mobx_state_tree.types.string), []),
839
+ rows: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.array(import_mobx_state_tree.types.array(import_mobx_state_tree.types.string)), []),
840
+ cellpadding: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
841
+ cellspacing: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.string),
842
+ // === Locked Blocks (Header/Footer) ===
843
+ locked: import_mobx_state_tree.types.optional(import_mobx_state_tree.types.boolean, false),
844
+ // === MJML attributes the inspector has no control for (kept from an import) ===
845
+ extraAttributes: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.frozen()),
846
+ /** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
847
+ filled: import_mobx_state_tree.types.maybe(import_mobx_state_tree.types.frozen())
848
+ }).actions((self) => ({
849
+ /**
850
+ * Update any style property by name
851
+ */
852
+ updateStyle(property, value) {
853
+ if (property in self) {
854
+ self[property] = value;
855
+ }
856
+ },
857
+ /**
858
+ * Update multiple properties at once
859
+ */
860
+ updateProperties(updates) {
861
+ Object.entries(updates).forEach(([key, value]) => {
862
+ if (key in self) {
863
+ self[key] = value;
864
+ }
865
+ });
866
+ },
867
+ /**
868
+ * Set block content (for text blocks)
869
+ */
870
+ setContent(content) {
871
+ self.content = content;
872
+ },
873
+ /**
874
+ * Toggle block visibility
875
+ */
876
+ toggleHidden() {
877
+ self.hidden = !self.hidden;
878
+ },
879
+ /**
880
+ * Set padding values
881
+ */
882
+ setPadding(padding) {
883
+ if (padding.top !== void 0) self.paddingTop = padding.top;
884
+ if (padding.right !== void 0) self.paddingRight = padding.right;
885
+ if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
886
+ if (padding.left !== void 0) self.paddingLeft = padding.left;
887
+ },
888
+ /**
889
+ * Add a social link
890
+ */
891
+ addSocialLink(platform, url, color) {
892
+ self.links.push(SocialLinkModel.create({ platform, url, color }));
893
+ },
894
+ /**
895
+ * Remove a social link by index
896
+ */
897
+ removeSocialLink(index) {
898
+ self.links.splice(index, 1);
899
+ },
900
+ /**
901
+ * Add a navbar link
902
+ */
903
+ addNavbarLink(href, label, color) {
904
+ self.navLinks.push(NavbarLinkModel.create({ href, label, color }));
905
+ },
906
+ /**
907
+ * Remove a navbar link by index
908
+ */
909
+ removeNavbarLink(index) {
910
+ self.navLinks.splice(index, 1);
911
+ },
912
+ /**
913
+ * Add a carousel image
914
+ */
915
+ addCarouselImage(src, alt, href, thumbnailSrc) {
916
+ self.images.push(CarouselImageModel.create({ src, alt, href, thumbnailSrc }));
917
+ },
918
+ /**
919
+ * Remove a carousel image by index
920
+ */
921
+ removeCarouselImage(index) {
922
+ self.images.splice(index, 1);
923
+ },
924
+ /**
925
+ * Add an accordion item
926
+ */
927
+ addAccordionItem(title, content) {
928
+ self.items.push(AccordionItemModel.create({ title, content }));
929
+ },
930
+ /**
931
+ * Remove an accordion item by index
932
+ */
933
+ removeAccordionItem(index) {
934
+ self.items.splice(index, 1);
935
+ },
936
+ /**
937
+ * Add a table row
938
+ */
939
+ addTableRow(cells) {
940
+ self.rows.push(cells);
941
+ },
942
+ /**
943
+ * Remove a table row by index
944
+ */
945
+ removeTableRow(index) {
946
+ self.rows.splice(index, 1);
947
+ }
948
+ })).views((self) => ({
949
+ /**
950
+ * Computed style object for React rendering
951
+ */
952
+ get computedStyle() {
953
+ const style = {};
954
+ if (self.color) style.color = self.color;
955
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
956
+ if (self.fontSize) style.fontSize = self.fontSize;
957
+ if (self.fontFamily) style.fontFamily = self.fontFamily;
958
+ if (self.align) style.textAlign = self.align;
959
+ if (self.lineHeight) style.lineHeight = self.lineHeight;
960
+ if (self.paddingTop) style.paddingTop = self.paddingTop;
961
+ if (self.paddingRight) style.paddingRight = self.paddingRight;
962
+ if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
963
+ if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
964
+ if (self.borderRadius) style.borderRadius = self.borderRadius;
965
+ if (self.border) style.border = self.border;
966
+ return style;
967
+ },
968
+ /**
969
+ * Get padding as a single object
970
+ */
971
+ get padding() {
972
+ return {
973
+ top: self.paddingTop || void 0,
974
+ right: self.paddingRight || void 0,
975
+ bottom: self.paddingBottom || void 0,
976
+ left: self.paddingLeft || void 0
977
+ };
978
+ },
979
+ /**
980
+ * Get padding as a CSS string (e.g., "10px 20px 10px 20px")
981
+ */
982
+ get paddingString() {
983
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
984
+ if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
985
+ return void 0;
986
+ }
987
+ return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
988
+ },
989
+ /**
990
+ * MJML attributes for export
991
+ */
992
+ get mjmlAttributes() {
993
+ const attrs = {};
994
+ if (self.color) attrs.color = self.color;
995
+ if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
996
+ if (self.fontSize) attrs["font-size"] = self.fontSize;
997
+ if (self.fontFamily) attrs["font-family"] = self.fontFamily;
998
+ if (self.align) attrs.align = self.align;
999
+ if (self.lineHeight) attrs["line-height"] = self.lineHeight;
1000
+ const paddingParts = [];
1001
+ if (self.paddingTop) paddingParts.push(`top:${self.paddingTop}`);
1002
+ if (self.paddingRight) paddingParts.push(`right:${self.paddingRight}`);
1003
+ if (self.paddingBottom) paddingParts.push(`bottom:${self.paddingBottom}`);
1004
+ if (self.paddingLeft) paddingParts.push(`left:${self.paddingLeft}`);
1005
+ if (paddingParts.length > 0) {
1006
+ const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
1007
+ attrs.padding = padding;
1008
+ }
1009
+ if (self.src) attrs.src = self.src;
1010
+ if (self.alt) attrs.alt = self.alt;
1011
+ if (self.width) attrs.width = self.width;
1012
+ if (self.height) attrs.height = self.height;
1013
+ if (self.href) attrs.href = self.href;
1014
+ if (self.borderRadius) attrs["border-radius"] = self.borderRadius;
1015
+ if (self.border) attrs.border = self.border;
1016
+ if (self.innerPadding) attrs["inner-padding"] = self.innerPadding;
1017
+ if (self.borderColor) attrs["border-color"] = self.borderColor;
1018
+ if (self.borderWidth) attrs["border-width"] = self.borderWidth;
1019
+ if (self.borderStyle) attrs["border-style"] = self.borderStyle;
1020
+ return attrs;
1021
+ },
1022
+ /**
1023
+ * Check if this is a text-based block
1024
+ */
1025
+ get isTextBlock() {
1026
+ return self.type === "text" /* TEXT */;
1027
+ },
1028
+ /**
1029
+ * Check if this is an image block
1030
+ */
1031
+ get isImageBlock() {
1032
+ return self.type === "image" /* IMAGE */;
1033
+ },
1034
+ /**
1035
+ * Check if this is a button block
1036
+ */
1037
+ get isButtonBlock() {
1038
+ return self.type === "button" /* BUTTON */;
1039
+ },
1040
+ /**
1041
+ * Check if this block is locked (header/footer)
1042
+ */
1043
+ get isLocked() {
1044
+ return self.locked || self.type === "header" /* HEADER */ || self.type === "footer" /* FOOTER */;
1045
+ },
1046
+ /**
1047
+ * Get display name for layers panel
1048
+ */
1049
+ get displayName() {
1050
+ switch (self.type) {
1051
+ case "text" /* TEXT */:
1052
+ const text = self.content.replace(/<[^>]*>/g, "").trim();
1053
+ return text.length > 20 ? text.substring(0, 20) + "..." : text || "Text";
1054
+ case "image" /* IMAGE */:
1055
+ return self.alt || "Image";
1056
+ case "button" /* BUTTON */:
1057
+ return self.label || "Button";
1058
+ case "divider" /* DIVIDER */:
1059
+ return "Divider";
1060
+ case "spacer" /* SPACER */:
1061
+ return `Spacer (${self.height || "20px"})`;
1062
+ case "social" /* SOCIAL */:
1063
+ return "Social Links";
1064
+ case "hero" /* HERO */:
1065
+ return "Hero";
1066
+ case "accordion" /* ACCORDION */:
1067
+ return "Accordion";
1068
+ case "raw" /* RAW */:
1069
+ return "Custom HTML";
1070
+ case "navbar" /* NAVBAR */:
1071
+ return "Navigation";
1072
+ case "carousel" /* CAROUSEL */:
1073
+ return "Carousel";
1074
+ case "table" /* TABLE */:
1075
+ return "Table";
1076
+ case "header" /* HEADER */:
1077
+ return "Header";
1078
+ case "footer" /* FOOTER */:
1079
+ return "Footer";
1080
+ default:
1081
+ return "Block";
1082
+ }
1083
+ }
1084
+ }));
1085
+ var BlockModel = BlockModelBase.preProcessSnapshot((raw) => {
1086
+ let snapshot = paddingIn(raw);
1087
+ if (snapshot && snapshot.type === "navbar") {
1088
+ const { links, navLinks, ...rest } = snapshot;
1089
+ const stored = navLinks && navLinks.length > 0 ? navLinks : links ?? [];
1090
+ snapshot = { ...rest, navLinks: stored };
1091
+ }
1092
+ return fillDefaults(snapshot, BLOCK_DEFAULTS);
1093
+ }).postProcessSnapshot((raw) => {
1094
+ const snapshot = paddingOut(dropFilled(raw));
1095
+ if (snapshot.type !== "navbar") return snapshot;
1096
+ const { navLinks, links: _socialLinks, ...rest } = snapshot;
1097
+ return { ...rest, links: navLinks ?? [] };
1098
+ });
1099
+
1100
+ // src/registry/blockCategories.ts
1101
+ var LEAF_BLOCK_TYPES = [
1102
+ "text" /* TEXT */,
1103
+ "image" /* IMAGE */,
1104
+ "button" /* BUTTON */,
1105
+ "divider" /* DIVIDER */,
1106
+ "spacer" /* SPACER */,
1107
+ "social" /* SOCIAL */
1108
+ ];
1109
+ var CONTAINER_BLOCK_TYPES = [
1110
+ "hero" /* HERO */,
1111
+ "accordion" /* ACCORDION */,
1112
+ "raw" /* RAW */,
1113
+ "navbar" /* NAVBAR */,
1114
+ "carousel" /* CAROUSEL */,
1115
+ "table" /* TABLE */,
1116
+ "header" /* HEADER */,
1117
+ "footer" /* FOOTER */
1118
+ ];
1119
+ function isLeafBlockType(t) {
1120
+ return LEAF_BLOCK_TYPES.includes(t);
1121
+ }
1122
+
1123
+ // src/history/HistoryManager.ts
1124
+ var import_immer = require("immer");
1125
+ var HistoryManager = class {
1126
+ constructor(initialState, maxHistorySize = 50) {
1127
+ this.history = [];
1128
+ this.currentIndex = -1;
1129
+ this.maxHistorySize = maxHistorySize;
1130
+ this.history.push(initialState);
1131
+ this.currentIndex = 0;
1132
+ }
1133
+ /**
1134
+ * Get current state
1135
+ */
1136
+ getCurrentState() {
1137
+ return this.history[this.currentIndex];
1138
+ }
1139
+ /**
1140
+ * Update state with a producer function (Immer)
1141
+ * Adds new state to history
1142
+ */
1143
+ updateState(producer) {
1144
+ const newState = (0, import_immer.produce)(this.getCurrentState(), producer);
1145
+ if (this.currentIndex < this.history.length - 1) {
1146
+ this.history = this.history.slice(0, this.currentIndex + 1);
1147
+ }
1148
+ this.history.push(newState);
1149
+ if (this.history.length > this.maxHistorySize) {
1150
+ this.history = this.history.slice(-this.maxHistorySize);
1151
+ this.currentIndex = this.history.length - 1;
1152
+ } else {
1153
+ this.currentIndex++;
1154
+ }
1155
+ return newState;
1156
+ }
1157
+ /**
1158
+ * Replace current state without affecting history
1159
+ */
1160
+ replaceState(newState) {
1161
+ this.history[this.currentIndex] = newState;
1162
+ }
1163
+ /**
1164
+ * Undo to previous state
1165
+ */
1166
+ undo() {
1167
+ if (!this.canUndo()) {
1168
+ return null;
1169
+ }
1170
+ this.currentIndex--;
1171
+ return this.getCurrentState();
1172
+ }
1173
+ /**
1174
+ * Redo to next state
1175
+ */
1176
+ redo() {
1177
+ if (!this.canRedo()) {
1178
+ return null;
1179
+ }
1180
+ this.currentIndex++;
1181
+ return this.getCurrentState();
1182
+ }
1183
+ /**
1184
+ * Check if undo is available
1185
+ */
1186
+ canUndo() {
1187
+ return this.currentIndex > 0;
1188
+ }
1189
+ /**
1190
+ * Check if redo is available
1191
+ */
1192
+ canRedo() {
1193
+ return this.currentIndex < this.history.length - 1;
1194
+ }
1195
+ /**
1196
+ * Clear all history and set new initial state
1197
+ */
1198
+ reset(initialState) {
1199
+ this.history = [initialState];
1200
+ this.currentIndex = 0;
1201
+ }
1202
+ /**
1203
+ * Get history size
1204
+ */
1205
+ getHistorySize() {
1206
+ return this.history.length;
1207
+ }
1208
+ /**
1209
+ * Get current index
1210
+ */
1211
+ getCurrentIndex() {
1212
+ return this.currentIndex;
1213
+ }
1214
+ };
1215
+ function createHistoryManager(initialState, maxHistorySize = 50) {
1216
+ return new HistoryManager(initialState, maxHistorySize);
1217
+ }
1218
+
1219
+ // src/selection/SelectionManager.ts
1220
+ var SelectionManager = class {
1221
+ constructor() {
1222
+ this.state = {
1223
+ blockId: null,
1224
+ sectionId: null
1225
+ };
1226
+ this.listeners = [];
1227
+ }
1228
+ /**
1229
+ * Get current selection
1230
+ */
1231
+ getSelection() {
1232
+ return { ...this.state };
1233
+ }
1234
+ /**
1235
+ * Select a block
1236
+ */
1237
+ selectBlock(blockId, sectionId) {
1238
+ this.state = { blockId, sectionId };
1239
+ this.notify();
1240
+ }
1241
+ /**
1242
+ * Select a section
1243
+ */
1244
+ selectSection(sectionId) {
1245
+ this.state = { blockId: null, sectionId };
1246
+ this.notify();
1247
+ }
1248
+ /**
1249
+ * Clear selection
1250
+ */
1251
+ clearSelection() {
1252
+ this.state = { blockId: null, sectionId: null };
1253
+ this.notify();
1254
+ }
1255
+ /**
1256
+ * Subscribe to selection changes
1257
+ */
1258
+ subscribe(listener) {
1259
+ this.listeners.push(listener);
1260
+ return () => {
1261
+ this.listeners = this.listeners.filter((l) => l !== listener);
1262
+ };
1263
+ }
1264
+ /**
1265
+ * Notify all listeners
1266
+ */
1267
+ notify() {
1268
+ this.listeners.forEach((listener) => listener(this.state));
1269
+ }
1270
+ };
1271
+ function createSelectionManager() {
1272
+ return new SelectionManager();
1273
+ }
1274
+
1275
+ // src/templates/registry.ts
1276
+ var PrebuiltTemplateRegistryImpl = class {
1277
+ constructor() {
1278
+ this.templates = /* @__PURE__ */ new Map();
1279
+ }
1280
+ /**
1281
+ * Register a pre-built template
1282
+ */
1283
+ register(template) {
1284
+ this.templates.set(template.id, template);
1285
+ }
1286
+ /**
1287
+ * Get a template by ID
1288
+ */
1289
+ get(id) {
1290
+ return this.templates.get(id);
1291
+ }
1292
+ /**
1293
+ * Get all registered templates
1294
+ */
1295
+ getAll() {
1296
+ return Array.from(this.templates.values());
1297
+ }
1298
+ /**
1299
+ * Get templates filtered by category
1300
+ */
1301
+ getByCategory(category) {
1302
+ return this.getAll().filter((t) => t.category === category);
1303
+ }
1304
+ };
1305
+ function createPrebuiltTemplateRegistry() {
1306
+ return new PrebuiltTemplateRegistryImpl();
1307
+ }
1308
+
1309
+ // src/store/mst/models/ColumnModel.ts
1310
+ var import_mobx_state_tree3 = require("mobx-state-tree");
1311
+ var import_nanoid3 = require("nanoid");
1312
+
1313
+ // src/store/mst/models/SubColumnModel.ts
1314
+ var import_mobx_state_tree2 = require("mobx-state-tree");
1315
+ var import_nanoid2 = require("nanoid");
1316
+ var SubColumnModel = import_mobx_state_tree2.types.model("SubColumn", {
1317
+ id: import_mobx_state_tree2.types.identifier,
1318
+ width: import_mobx_state_tree2.types.optional(import_mobx_state_tree2.types.number, 50),
1319
+ backgroundColor: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
1320
+ backgroundGradient: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.frozen()),
1321
+ verticalAlign: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.enumeration(["top", "middle", "bottom"])),
1322
+ paddingTop: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
1323
+ paddingRight: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
1324
+ paddingBottom: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
1325
+ paddingLeft: import_mobx_state_tree2.types.maybe(import_mobx_state_tree2.types.string),
1326
+ blocks: import_mobx_state_tree2.types.array(BlockModel)
1327
+ }).actions((self) => ({
1328
+ addBlock(block, index) {
1329
+ const type = block.type;
1330
+ if (!isLeafBlockType(type)) {
1331
+ throw new Error(`SubColumn only accepts leaf blocks; got "${type}"`);
1332
+ }
1333
+ const blockToAdd = (0, import_mobx_state_tree2.isStateTreeNode)(block) ? (0, import_mobx_state_tree2.detach)(block) : BlockModel.create(block);
1334
+ if (index !== void 0 && index >= 0 && index <= self.blocks.length) {
1335
+ self.blocks.splice(index, 0, blockToAdd);
1336
+ } else {
1337
+ self.blocks.push(blockToAdd);
1338
+ }
1339
+ return blockToAdd;
1340
+ },
1341
+ removeBlock(blockId) {
1342
+ const block = self.blocks.find((b) => b.id === blockId);
1343
+ if (block) {
1344
+ (0, import_mobx_state_tree2.destroy)(block);
1345
+ return true;
1346
+ }
1347
+ return false;
1348
+ },
1349
+ moveBlock(fromIndex, toIndex) {
1350
+ if (fromIndex < 0 || fromIndex >= self.blocks.length) return false;
1351
+ if (toIndex < 0 || toIndex > self.blocks.length) return false;
1352
+ if (fromIndex === toIndex) return false;
1353
+ const [block] = self.blocks.splice(fromIndex, 1);
1354
+ const adj = toIndex > fromIndex ? toIndex - 1 : toIndex;
1355
+ self.blocks.splice(adj, 0, block);
1356
+ return true;
1357
+ },
1358
+ detachBlock(blockId) {
1359
+ const block = self.blocks.find((b) => b.id === blockId);
1360
+ return block ? (0, import_mobx_state_tree2.detach)(block) : void 0;
1361
+ },
1362
+ setWidth(width) {
1363
+ self.width = Math.max(0, Math.min(100, width));
1364
+ },
1365
+ setPadding(p) {
1366
+ if (p.top !== void 0) self.paddingTop = p.top;
1367
+ if (p.right !== void 0) self.paddingRight = p.right;
1368
+ if (p.bottom !== void 0) self.paddingBottom = p.bottom;
1369
+ if (p.left !== void 0) self.paddingLeft = p.left;
1370
+ },
1371
+ updateProperties(updates) {
1372
+ Object.entries(updates).forEach(([k, v]) => {
1373
+ if (k in self) self[k] = v;
1374
+ });
1375
+ },
1376
+ clearBlocks() {
1377
+ self.blocks.forEach((b) => (0, import_mobx_state_tree2.destroy)(b));
1378
+ self.blocks.clear();
1379
+ }
1380
+ })).views((self) => ({
1381
+ get isEmpty() {
1382
+ return self.blocks.length === 0;
1383
+ },
1384
+ get computedStyle() {
1385
+ const style = { width: `${self.width}%` };
1386
+ if (self.backgroundGradient) {
1387
+ const css = buildGradientCSS(self.backgroundGradient);
1388
+ if (css) style.backgroundImage = css;
1389
+ }
1390
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
1391
+ if (self.verticalAlign) style.verticalAlign = self.verticalAlign;
1392
+ if (self.paddingTop) style.paddingTop = self.paddingTop;
1393
+ if (self.paddingRight) style.paddingRight = self.paddingRight;
1394
+ if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
1395
+ if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
1396
+ return style;
1397
+ }
1398
+ }));
1399
+ function createSubColumn(opts = {}) {
1400
+ return {
1401
+ id: opts.id ?? (0, import_nanoid2.nanoid)(),
1402
+ width: opts.width ?? 50,
1403
+ blocks: opts.blocks ?? []
1404
+ };
1405
+ }
1406
+
1407
+ // src/store/mst/models/ColumnModel.ts
1408
+ var ColumnModelBase = import_mobx_state_tree3.types.model("Column", {
1409
+ id: import_mobx_state_tree3.types.identifier,
1410
+ width: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.number, 100),
1411
+ // percentage (e.g., 50 for 50%)
1412
+ backgroundColor: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
1413
+ backgroundGradient: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen()),
1414
+ verticalAlign: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.enumeration(["top", "middle", "bottom"])),
1415
+ paddingTop: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
1416
+ paddingRight: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
1417
+ paddingBottom: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
1418
+ paddingLeft: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.string),
1419
+ hidden: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.boolean, false),
1420
+ blocks: import_mobx_state_tree3.types.array(BlockModel),
1421
+ /**
1422
+ * Optional sub-columns for depth-2 nesting.
1423
+ * Invariant: blocks XOR subColumns. Both can be empty, but never both populated.
1424
+ *
1425
+ * `types.late` is used to break TypeScript's deep inference, which otherwise
1426
+ * makes the inferred RootStore type exceed the compiler's serialization limit.
1427
+ */
1428
+ subColumns: import_mobx_state_tree3.types.optional(import_mobx_state_tree3.types.array(import_mobx_state_tree3.types.late(() => SubColumnModel)), []),
1429
+ /** MJML attributes the inspector has no control for (kept from an import). */
1430
+ extraAttributes: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen()),
1431
+ /** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
1432
+ filled: import_mobx_state_tree3.types.maybe(import_mobx_state_tree3.types.frozen())
1433
+ }).preProcessSnapshot((snapshot) => {
1434
+ if (snapshot && Array.isArray(snapshot.blocks) && snapshot.blocks.length > 0 && Array.isArray(snapshot.subColumns) && snapshot.subColumns.length > 0) {
1435
+ throw new Error(
1436
+ "Invalid Column snapshot: cannot have both blocks and subColumns populated"
1437
+ );
1438
+ }
1439
+ return snapshot;
1440
+ }).actions((self) => ({
1441
+ /**
1442
+ * Add a block to this column
1443
+ */
1444
+ addBlock(block, index) {
1445
+ if (self.subColumns.length > 0) {
1446
+ throw new Error("Cannot add block to a group column; merge sub-columns first");
1447
+ }
1448
+ const blockToAdd = (0, import_mobx_state_tree3.isStateTreeNode)(block) ? (0, import_mobx_state_tree3.detach)(block) : BlockModel.create(block);
1449
+ if (index !== void 0 && index >= 0 && index <= self.blocks.length) {
1450
+ self.blocks.splice(index, 0, blockToAdd);
1451
+ } else {
1452
+ self.blocks.push(blockToAdd);
1453
+ }
1454
+ return blockToAdd;
1455
+ },
1456
+ /**
1457
+ * Remove a block by ID
1458
+ */
1459
+ removeBlock(blockId) {
1460
+ const block = self.blocks.find((b) => b.id === blockId);
1461
+ if (block) {
1462
+ (0, import_mobx_state_tree3.destroy)(block);
1463
+ return true;
1464
+ }
1465
+ return false;
1466
+ },
1467
+ /**
1468
+ * Move a block within this column
1469
+ */
1470
+ moveBlock(fromIndex, toIndex) {
1471
+ if (fromIndex < 0 || fromIndex >= self.blocks.length) return false;
1472
+ if (toIndex < 0 || toIndex > self.blocks.length) return false;
1473
+ if (fromIndex === toIndex) return false;
1474
+ const [block] = self.blocks.splice(fromIndex, 1);
1475
+ const adjustedToIndex = toIndex > fromIndex ? toIndex - 1 : toIndex;
1476
+ self.blocks.splice(adjustedToIndex, 0, block);
1477
+ return true;
1478
+ },
1479
+ /**
1480
+ * Detach a block (remove without destroying, for moving to another column)
1481
+ */
1482
+ detachBlock(blockId) {
1483
+ const block = self.blocks.find((b) => b.id === blockId);
1484
+ if (block) {
1485
+ return (0, import_mobx_state_tree3.detach)(block);
1486
+ }
1487
+ return void 0;
1488
+ },
1489
+ /**
1490
+ * Update column properties
1491
+ */
1492
+ updateProperties(updates) {
1493
+ Object.entries(updates).forEach(([key, value]) => {
1494
+ if (key in self) {
1495
+ self[key] = value;
1496
+ }
1497
+ });
1498
+ },
1499
+ /**
1500
+ * Set padding
1501
+ */
1502
+ setPadding(padding) {
1503
+ if (padding.top !== void 0) self.paddingTop = padding.top;
1504
+ if (padding.right !== void 0) self.paddingRight = padding.right;
1505
+ if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
1506
+ if (padding.left !== void 0) self.paddingLeft = padding.left;
1507
+ },
1508
+ /**
1509
+ * Toggle column visibility
1510
+ */
1511
+ toggleHidden() {
1512
+ self.hidden = !self.hidden;
1513
+ },
1514
+ /**
1515
+ * Set column width
1516
+ */
1517
+ setWidth(width) {
1518
+ self.width = Math.max(0, Math.min(100, width));
1519
+ },
1520
+ /**
1521
+ * Clear all blocks
1522
+ */
1523
+ clearBlocks() {
1524
+ self.blocks.forEach((block) => (0, import_mobx_state_tree3.destroy)(block));
1525
+ self.blocks.clear();
1526
+ },
1527
+ /**
1528
+ * Convert this leaf column into a group of N sub-columns (2-4).
1529
+ * Existing blocks migrate into the first sub-column.
1530
+ */
1531
+ splitIntoSubColumns(count) {
1532
+ if (count < 2 || count > 4) {
1533
+ throw new Error(`splitIntoSubColumns requires count in [2..4], got ${count}`);
1534
+ }
1535
+ const firstBlocks = self.blocks.map((b) => (0, import_mobx_state_tree3.detach)(b));
1536
+ self.blocks.clear();
1537
+ self.subColumns.clear();
1538
+ const base = Math.floor(100 / count * 100) / 100;
1539
+ for (let i = 0; i < count; i++) {
1540
+ const isLast = i === count - 1;
1541
+ const w = isLast ? Math.round((100 - base * (count - 1)) * 100) / 100 : base;
1542
+ self.subColumns.push(
1543
+ SubColumnModel.create(createSubColumn({
1544
+ width: w,
1545
+ blocks: i === 0 ? firstBlocks : []
1546
+ }))
1547
+ );
1548
+ }
1549
+ },
1550
+ /**
1551
+ * Merge all sub-column blocks back into self.blocks (concatenated in order).
1552
+ */
1553
+ mergeSubColumns() {
1554
+ const collected = [];
1555
+ for (const sc of self.subColumns) {
1556
+ while (sc.blocks.length > 0) {
1557
+ const b = (0, import_mobx_state_tree3.detach)(sc.blocks[0]);
1558
+ collected.push(b);
1559
+ }
1560
+ }
1561
+ self.subColumns.clear();
1562
+ for (const b of collected) {
1563
+ self.blocks.push(b);
1564
+ }
1565
+ }
1566
+ })).views((self) => ({
1567
+ /**
1568
+ * Whether this column is a leaf (holds blocks) or a group (holds sub-columns).
1569
+ */
1570
+ get kind() {
1571
+ return self.subColumns.length > 0 ? "group" : "leaf";
1572
+ },
1573
+ /**
1574
+ * Find a block by ID
1575
+ */
1576
+ getBlockById(blockId) {
1577
+ return self.blocks.find((b) => b.id === blockId);
1578
+ },
1579
+ /**
1580
+ * Get block index
1581
+ */
1582
+ getBlockIndex(blockId) {
1583
+ return self.blocks.findIndex((b) => b.id === blockId);
1584
+ },
1585
+ /**
1586
+ * Get visible blocks only
1587
+ */
1588
+ get visibleBlocks() {
1589
+ return self.blocks.filter((b) => !b.hidden);
1590
+ },
1591
+ /**
1592
+ * Count of blocks
1593
+ */
1594
+ get blockCount() {
1595
+ return self.blocks.length;
1596
+ },
1597
+ /**
1598
+ * Check if column is empty
1599
+ */
1600
+ get isEmpty() {
1601
+ return self.blocks.length === 0;
1602
+ },
1603
+ /**
1604
+ * Get padding as object
1605
+ */
1606
+ get padding() {
1607
+ return {
1608
+ top: self.paddingTop || void 0,
1609
+ right: self.paddingRight || void 0,
1610
+ bottom: self.paddingBottom || void 0,
1611
+ left: self.paddingLeft || void 0
1612
+ };
1613
+ },
1614
+ /**
1615
+ * Get padding as CSS string
1616
+ */
1617
+ get paddingString() {
1618
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
1619
+ if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
1620
+ return void 0;
1621
+ }
1622
+ return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
1623
+ },
1624
+ /**
1625
+ * Computed style for rendering
1626
+ */
1627
+ get computedStyle() {
1628
+ const style = {
1629
+ width: `${self.width}%`
1630
+ };
1631
+ if (self.backgroundGradient) {
1632
+ const css = buildGradientCSS(self.backgroundGradient);
1633
+ if (css) style.backgroundImage = css;
1634
+ }
1635
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
1636
+ if (self.verticalAlign) style.verticalAlign = self.verticalAlign;
1637
+ if (self.paddingTop) style.paddingTop = self.paddingTop;
1638
+ if (self.paddingRight) style.paddingRight = self.paddingRight;
1639
+ if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
1640
+ if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
1641
+ return style;
1642
+ },
1643
+ /**
1644
+ * MJML attributes for export
1645
+ */
1646
+ get mjmlAttributes() {
1647
+ const attrs = {};
1648
+ if (self.width !== 100) attrs.width = `${self.width}%`;
1649
+ if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
1650
+ if (self.verticalAlign) attrs["vertical-align"] = self.verticalAlign;
1651
+ const hasPadding = self.paddingTop || self.paddingRight || self.paddingBottom || self.paddingLeft;
1652
+ if (hasPadding) {
1653
+ const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
1654
+ attrs.padding = padding;
1655
+ }
1656
+ return attrs;
1657
+ }
1658
+ }));
1659
+ var ColumnModel = ColumnModelBase.preProcessSnapshot(
1660
+ (snapshot) => fillDefaults(paddingIn(snapshot), COLUMN_DEFAULTS)
1661
+ ).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
1662
+ function createColumn(options = {}) {
1663
+ return {
1664
+ id: options.id || (0, import_nanoid3.nanoid)(),
1665
+ width: options.width || 100,
1666
+ blocks: options.blocks || []
1667
+ };
1668
+ }
1669
+
1670
+ // src/store/mst/models/SectionModel.ts
1671
+ var import_mobx_state_tree4 = require("mobx-state-tree");
1672
+ var import_nanoid4 = require("nanoid");
1673
+ var SectionModelBase = import_mobx_state_tree4.types.model("Section", {
1674
+ id: import_mobx_state_tree4.types.identifier,
1675
+ type: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.literal("section"), "section"),
1676
+ // Background
1677
+ backgroundColor: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1678
+ backgroundImage: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1679
+ backgroundGradient: import_mobx_state_tree4.types.maybe(
1680
+ import_mobx_state_tree4.types.model("BackgroundGradient", {
1681
+ type: import_mobx_state_tree4.types.enumeration(["linear", "radial"]),
1682
+ angle: import_mobx_state_tree4.types.number,
1683
+ stops: import_mobx_state_tree4.types.array(
1684
+ import_mobx_state_tree4.types.model("GradientStop", {
1685
+ color: import_mobx_state_tree4.types.string,
1686
+ position: import_mobx_state_tree4.types.number
1687
+ })
1688
+ )
1689
+ })
1690
+ ),
1691
+ backgroundPosition: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1692
+ backgroundRepeat: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.enumeration(["repeat", "no-repeat"])),
1693
+ backgroundSize: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1694
+ // Layout
1695
+ fullWidth: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
1696
+ noStack: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
1697
+ // mj-group behavior
1698
+ // Visibility
1699
+ hidden: import_mobx_state_tree4.types.optional(import_mobx_state_tree4.types.boolean, false),
1700
+ // Padding
1701
+ paddingTop: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1702
+ paddingRight: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1703
+ paddingBottom: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1704
+ paddingLeft: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.string),
1705
+ // Columns
1706
+ columns: import_mobx_state_tree4.types.array(ColumnModel),
1707
+ /**
1708
+ * Emit the section's raw blocks straight into its parent (mj-body or
1709
+ * mj-wrapper) instead of wrapping them in an mj-section (set by the MJML
1710
+ * import for markup that sat there). Ignored once the section holds any
1711
+ * other block.
1712
+ */
1713
+ bodyRaw: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.boolean),
1714
+ /** MJML attributes the inspector has no control for (kept from an import). */
1715
+ extraAttributes: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.frozen()),
1716
+ /** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
1717
+ filled: import_mobx_state_tree4.types.maybe(import_mobx_state_tree4.types.frozen())
1718
+ }).actions((self) => ({
1719
+ /**
1720
+ * Add a column to this section
1721
+ */
1722
+ addColumn(column, index) {
1723
+ const columnToAdd = ColumnModel.is(column) ? (0, import_mobx_state_tree4.detach)(column) : ColumnModel.create(column);
1724
+ if (index !== void 0 && index >= 0 && index <= self.columns.length) {
1725
+ self.columns.splice(index, 0, columnToAdd);
1726
+ } else {
1727
+ self.columns.push(columnToAdd);
1728
+ }
1729
+ this.rebalanceColumnWidths();
1730
+ return columnToAdd;
1731
+ },
1732
+ /**
1733
+ * Remove a column by ID
1734
+ */
1735
+ removeColumn(columnId) {
1736
+ const column = self.columns.find((c) => c.id === columnId);
1737
+ if (column && self.columns.length > 1) {
1738
+ (0, import_mobx_state_tree4.destroy)(column);
1739
+ this.rebalanceColumnWidths();
1740
+ return true;
1741
+ }
1742
+ return false;
1743
+ },
1744
+ /**
1745
+ * Set the number of columns (1-4)
1746
+ */
1747
+ setColumnCount(count) {
1748
+ const currentCount = self.columns.length;
1749
+ if (count === currentCount) return;
1750
+ if (count > currentCount) {
1751
+ for (let i = currentCount; i < count; i++) {
1752
+ self.columns.push(ColumnModel.create(createColumn()));
1753
+ }
1754
+ } else {
1755
+ const blocksToMove = [];
1756
+ for (let i = count; i < currentCount; i++) {
1757
+ const column = self.columns[i];
1758
+ column.blocks.forEach((block) => {
1759
+ blocksToMove.push((0, import_mobx_state_tree4.detach)(block));
1760
+ });
1761
+ }
1762
+ while (self.columns.length > count) {
1763
+ const column = self.columns[self.columns.length - 1];
1764
+ (0, import_mobx_state_tree4.destroy)(column);
1765
+ }
1766
+ blocksToMove.forEach((block) => {
1767
+ self.columns[0].blocks.push(block);
1768
+ });
1769
+ }
1770
+ this.rebalanceColumnWidths();
1771
+ },
1772
+ /**
1773
+ * Rebalance column widths to equal distribution
1774
+ */
1775
+ rebalanceColumnWidths() {
1776
+ const columnCount = self.columns.length;
1777
+ if (columnCount === 0) return;
1778
+ const equalWidth = Math.floor(100 / columnCount);
1779
+ self.columns.forEach((column, index) => {
1780
+ if (index === columnCount - 1) {
1781
+ column.setWidth(100 - equalWidth * (columnCount - 1));
1782
+ } else {
1783
+ column.setWidth(equalWidth);
1784
+ }
1785
+ });
1786
+ },
1787
+ /**
1788
+ * Update section properties
1789
+ */
1790
+ updateProperties(updates) {
1791
+ Object.entries(updates).forEach(([key, value]) => {
1792
+ if (key in self) {
1793
+ self[key] = value;
1794
+ }
1795
+ });
1796
+ },
1797
+ /**
1798
+ * Set padding
1799
+ */
1800
+ setPadding(padding) {
1801
+ if (padding.top !== void 0) self.paddingTop = padding.top;
1802
+ if (padding.right !== void 0) self.paddingRight = padding.right;
1803
+ if (padding.bottom !== void 0) self.paddingBottom = padding.bottom;
1804
+ if (padding.left !== void 0) self.paddingLeft = padding.left;
1805
+ },
1806
+ /**
1807
+ * Toggle section visibility
1808
+ */
1809
+ toggleHidden() {
1810
+ self.hidden = !self.hidden;
1811
+ },
1812
+ /**
1813
+ * Toggle full width
1814
+ */
1815
+ toggleFullWidth() {
1816
+ self.fullWidth = !self.fullWidth;
1817
+ },
1818
+ /**
1819
+ * Toggle no-stack (mj-group)
1820
+ */
1821
+ toggleNoStack() {
1822
+ self.noStack = !self.noStack;
1823
+ },
1824
+ /**
1825
+ * Move a block from one column to another within this section
1826
+ */
1827
+ moveBlockBetweenColumns(blockId, sourceColumnId, targetColumnId, targetIndex) {
1828
+ const sourceColumn = self.columns.find((c) => c.id === sourceColumnId);
1829
+ const targetColumn = self.columns.find((c) => c.id === targetColumnId);
1830
+ if (!sourceColumn || !targetColumn) return false;
1831
+ const block = sourceColumn.detachBlock(blockId);
1832
+ if (!block) return false;
1833
+ targetColumn.addBlock(block, targetIndex);
1834
+ return true;
1835
+ }
1836
+ })).views((self) => ({
1837
+ /**
1838
+ * Find a column by ID
1839
+ */
1840
+ getColumnById(columnId) {
1841
+ return self.columns.find((c) => c.id === columnId);
1842
+ },
1843
+ /**
1844
+ * Find a block by ID (searches all columns)
1845
+ */
1846
+ findBlockById(blockId) {
1847
+ for (const column of self.columns) {
1848
+ const block = column.getBlockById(blockId);
1849
+ if (block) return block;
1850
+ }
1851
+ return void 0;
1852
+ },
1853
+ /**
1854
+ * Find which column contains a block
1855
+ */
1856
+ findColumnByBlockId(blockId) {
1857
+ for (const column of self.columns) {
1858
+ if (column.getBlockById(blockId)) {
1859
+ return column;
1860
+ }
1861
+ }
1862
+ return void 0;
1863
+ },
1864
+ /**
1865
+ * Get column index
1866
+ */
1867
+ getColumnIndex(columnId) {
1868
+ return self.columns.findIndex((c) => c.id === columnId);
1869
+ },
1870
+ /**
1871
+ * Get visible columns
1872
+ */
1873
+ get visibleColumns() {
1874
+ return self.columns.filter((c) => !c.hidden);
1875
+ },
1876
+ /**
1877
+ * Column count
1878
+ */
1879
+ get columnCount() {
1880
+ return self.columns.length;
1881
+ },
1882
+ /**
1883
+ * Total block count across all columns
1884
+ */
1885
+ get totalBlockCount() {
1886
+ return self.columns.reduce((sum, col) => sum + col.blockCount, 0);
1887
+ },
1888
+ /**
1889
+ * Check if section is empty (all columns are empty)
1890
+ */
1891
+ get isEmpty() {
1892
+ return self.columns.every((c) => c.isEmpty);
1893
+ },
1894
+ /**
1895
+ * Get padding as object
1896
+ */
1897
+ get padding() {
1898
+ return {
1899
+ top: self.paddingTop || void 0,
1900
+ right: self.paddingRight || void 0,
1901
+ bottom: self.paddingBottom || void 0,
1902
+ left: self.paddingLeft || void 0
1903
+ };
1904
+ },
1905
+ /**
1906
+ * Get padding as CSS string
1907
+ */
1908
+ get paddingString() {
1909
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
1910
+ if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) {
1911
+ return void 0;
1912
+ }
1913
+ return `${paddingTop || "0"} ${paddingRight || "0"} ${paddingBottom || "0"} ${paddingLeft || "0"}`;
1914
+ },
1915
+ /**
1916
+ * Computed style for rendering
1917
+ */
1918
+ get computedStyle() {
1919
+ const style = {};
1920
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
1921
+ const gradientCSS = self.backgroundGradient ? buildGradientCSS(self.backgroundGradient) : void 0;
1922
+ if (gradientCSS) {
1923
+ style.backgroundImage = gradientCSS;
1924
+ } else if (self.backgroundImage) {
1925
+ style.backgroundImage = `url(${self.backgroundImage})`;
1926
+ if (self.backgroundPosition) style.backgroundPosition = self.backgroundPosition;
1927
+ if (self.backgroundRepeat) style.backgroundRepeat = self.backgroundRepeat;
1928
+ if (self.backgroundSize) style.backgroundSize = self.backgroundSize;
1929
+ }
1930
+ if (self.paddingTop) style.paddingTop = self.paddingTop;
1931
+ if (self.paddingRight) style.paddingRight = self.paddingRight;
1932
+ if (self.paddingBottom) style.paddingBottom = self.paddingBottom;
1933
+ if (self.paddingLeft) style.paddingLeft = self.paddingLeft;
1934
+ return style;
1935
+ },
1936
+ /**
1937
+ * MJML attributes for export
1938
+ */
1939
+ get mjmlAttributes() {
1940
+ const attrs = {};
1941
+ if (self.backgroundColor) attrs["background-color"] = self.backgroundColor;
1942
+ if (self.backgroundImage) attrs["background-url"] = self.backgroundImage;
1943
+ if (self.backgroundPosition) attrs["background-position"] = self.backgroundPosition;
1944
+ if (self.backgroundRepeat) attrs["background-repeat"] = self.backgroundRepeat;
1945
+ if (self.backgroundSize) attrs["background-size"] = self.backgroundSize;
1946
+ if (self.fullWidth) attrs["full-width"] = "full-width";
1947
+ const hasPadding = self.paddingTop || self.paddingRight || self.paddingBottom || self.paddingLeft;
1948
+ if (hasPadding) {
1949
+ const padding = `${self.paddingTop || "0"} ${self.paddingRight || "0"} ${self.paddingBottom || "0"} ${self.paddingLeft || "0"}`;
1950
+ attrs.padding = padding;
1951
+ }
1952
+ return attrs;
1953
+ },
1954
+ /**
1955
+ * How wide the section's visible columns are together, in percent, as MJML
1956
+ * lays them out: each column's width, where a column opened without one
1957
+ * holds MJML's share (100 / columns, whatever its siblings say). Over 100,
1958
+ * the last column wraps below the others on desktop.
1959
+ */
1960
+ get columnWidthTotal() {
1961
+ const total = self.columns.filter((c) => !c.hidden).reduce((sum, c) => sum + c.width, 0);
1962
+ return Math.round(total * 100) / 100;
1963
+ },
1964
+ /** Whether the columns overflow the section ({@link columnWidthTotal} over 100). */
1965
+ get columnsOverflow() {
1966
+ return this.columnWidthTotal > 100;
1967
+ },
1968
+ /**
1969
+ * Display name for layers panel
1970
+ */
1971
+ get displayName() {
1972
+ const count = self.columns.length;
1973
+ if (count === 1) {
1974
+ return "Full Width Section";
1975
+ }
1976
+ return `${count}-Column Section`;
1977
+ }
1978
+ }));
1979
+ var SectionModel = SectionModelBase.preProcessSnapshot(
1980
+ (snapshot) => fillDefaults(fillColumnWidths(paddingIn(snapshot)), SECTION_DEFAULTS)
1981
+ ).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
1982
+ function createSection(options = {}) {
1983
+ const columnCount = options.columnCount || 1;
1984
+ const columnWidth = Math.floor(100 / columnCount);
1985
+ const columns = [];
1986
+ for (let i = 0; i < columnCount; i++) {
1987
+ columns.push(createColumn({
1988
+ width: i === columnCount - 1 ? 100 - columnWidth * (columnCount - 1) : columnWidth
1989
+ }));
1990
+ }
1991
+ return {
1992
+ id: options.id || (0, import_nanoid4.nanoid)(),
1993
+ type: "section",
1994
+ backgroundColor: options.backgroundColor,
1995
+ columns
1996
+ };
1997
+ }
1998
+
1999
+ // src/store/mst/models/WrapperModel.ts
2000
+ var import_mobx_state_tree5 = require("mobx-state-tree");
2001
+ var import_nanoid5 = require("nanoid");
2002
+ var MJML_WRAPPER_DEFAULT_PADDING = { top: "20px", right: "0px", bottom: "20px", left: "0px" };
2003
+ var WrapperModelBase = import_mobx_state_tree5.types.model("Wrapper", {
2004
+ id: import_mobx_state_tree5.types.identifier,
2005
+ type: import_mobx_state_tree5.types.literal("wrapper"),
2006
+ hidden: import_mobx_state_tree5.types.optional(import_mobx_state_tree5.types.boolean, false),
2007
+ // Background
2008
+ backgroundColor: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2009
+ backgroundImage: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2010
+ backgroundGradient: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen()),
2011
+ backgroundPosition: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2012
+ backgroundRepeat: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.enumeration(["repeat", "no-repeat"])),
2013
+ backgroundSize: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2014
+ // Border
2015
+ border: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2016
+ borderTop: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2017
+ borderRight: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2018
+ borderBottom: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2019
+ borderLeft: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2020
+ borderRadius: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2021
+ // Padding (the schema's `padding` object, flat in the store; see `spacingSnapshot.ts`)
2022
+ paddingTop: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2023
+ paddingRight: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2024
+ paddingBottom: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2025
+ paddingLeft: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2026
+ // Layout
2027
+ fullWidth: import_mobx_state_tree5.types.optional(import_mobx_state_tree5.types.boolean, false),
2028
+ cssClass: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2029
+ gap: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.string),
2030
+ textAlign: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.enumeration(["left", "center", "right"])),
2031
+ sections: import_mobx_state_tree5.types.array(SectionModel),
2032
+ /** MJML attributes the inspector has no control for (kept from an import). */
2033
+ extraAttributes: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen()),
2034
+ /** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
2035
+ filled: import_mobx_state_tree5.types.maybe(import_mobx_state_tree5.types.frozen())
2036
+ }).actions((self) => ({
2037
+ /** Set any of the inspector's fields; `undefined` clears one. */
2038
+ updateProperties(updates) {
2039
+ for (const [key, value] of Object.entries(updates)) {
2040
+ if (key in self) self[key] = value;
2041
+ }
2042
+ },
2043
+ toggleHidden() {
2044
+ self.hidden = !self.hidden;
2045
+ },
2046
+ toggleFullWidth() {
2047
+ self.fullWidth = !self.fullWidth;
2048
+ }
2049
+ })).views((self) => ({
2050
+ get isEmpty() {
2051
+ return self.sections.length === 0 || self.sections.every((s) => s.isEmpty);
2052
+ },
2053
+ get displayName() {
2054
+ const n = self.sections.length;
2055
+ return n === 0 ? "Container (empty)" : `Container, ${n} ${n === 1 ? "section" : "sections"}`;
2056
+ },
2057
+ /**
2058
+ * The padding the mail gets: MJML's `20px 0` when none is set. As soon as
2059
+ * one side is set, the compiler writes all four and an unset side is 0.
2060
+ */
2061
+ get effectivePadding() {
2062
+ const { paddingTop, paddingRight, paddingBottom, paddingLeft } = self;
2063
+ if (!paddingTop && !paddingRight && !paddingBottom && !paddingLeft) return { ...MJML_WRAPPER_DEFAULT_PADDING };
2064
+ return { top: paddingTop || "0px", right: paddingRight || "0px", bottom: paddingBottom || "0px", left: paddingLeft || "0px" };
2065
+ },
2066
+ /** The wrapper's box on the canvas, styled as MJML renders it. */
2067
+ get computedStyle() {
2068
+ const style = {};
2069
+ const gradientCSS = self.backgroundGradient ? buildGradientCSS(self.backgroundGradient) : void 0;
2070
+ if (gradientCSS) {
2071
+ style.backgroundColor = self.backgroundGradient?.stops[0]?.color;
2072
+ style.backgroundImage = gradientCSS;
2073
+ } else {
2074
+ if (self.backgroundColor) style.backgroundColor = self.backgroundColor;
2075
+ if (self.backgroundImage) {
2076
+ style.backgroundImage = `url(${self.backgroundImage})`;
2077
+ style.backgroundPosition = self.backgroundPosition || "top center";
2078
+ style.backgroundSize = self.backgroundSize || "auto";
2079
+ style.backgroundRepeat = self.backgroundRepeat || "repeat";
2080
+ }
2081
+ }
2082
+ if (self.border) style.border = self.border;
2083
+ if (self.borderTop) style.borderTop = self.borderTop;
2084
+ if (self.borderRight) style.borderRight = self.borderRight;
2085
+ if (self.borderBottom) style.borderBottom = self.borderBottom;
2086
+ if (self.borderLeft) style.borderLeft = self.borderLeft;
2087
+ if (self.borderRadius) {
2088
+ style.borderRadius = self.borderRadius;
2089
+ style.overflow = "hidden";
2090
+ }
2091
+ const p = this.effectivePadding;
2092
+ style.paddingTop = p.top;
2093
+ style.paddingRight = p.right;
2094
+ style.paddingBottom = p.bottom;
2095
+ style.paddingLeft = p.left;
2096
+ if (self.textAlign) style.textAlign = self.textAlign;
2097
+ return style;
2098
+ }
2099
+ }));
2100
+ var WrapperModel = WrapperModelBase.preProcessSnapshot(
2101
+ (snapshot) => fillDefaults(paddingIn(snapshot), WRAPPER_DEFAULTS)
2102
+ ).postProcessSnapshot((snapshot) => paddingOut(dropFilled(snapshot)));
2103
+ function createWrapper(options = {}) {
2104
+ return {
2105
+ id: options.id || (0, import_nanoid5.nanoid)(),
2106
+ type: "wrapper",
2107
+ sections: options.sections ?? [createSection()]
2108
+ };
2109
+ }
2110
+
2111
+ // src/store/mst/models/TemplateModel.ts
2112
+ var import_mobx_state_tree6 = require("mobx-state-tree");
2113
+ var import_nanoid6 = require("nanoid");
2114
+ var TopLevelItemModel = import_mobx_state_tree6.types.union(
2115
+ {
2116
+ dispatcher: (snapshot) => snapshot?.type === "wrapper" ? WrapperModel : SectionModel
2117
+ },
2118
+ SectionModel,
2119
+ WrapperModel
2120
+ );
2121
+ function isWrapperInstance(item) {
2122
+ return item?.type === "wrapper";
2123
+ }
2124
+ function cloneSectionSnapshot(snapshot) {
2125
+ const cloneBlocks = (blocks) => (blocks ?? []).map((b) => ({ ...b, id: (0, import_nanoid6.nanoid)() }));
2126
+ return {
2127
+ ...snapshot,
2128
+ id: (0, import_nanoid6.nanoid)(),
2129
+ columns: (snapshot.columns ?? []).map((col) => ({
2130
+ ...col,
2131
+ id: (0, import_nanoid6.nanoid)(),
2132
+ blocks: cloneBlocks(col.blocks),
2133
+ ...col.subColumns ? { subColumns: col.subColumns.map((sc) => ({ ...sc, id: (0, import_nanoid6.nanoid)(), blocks: cloneBlocks(sc.blocks) })) } : {}
2134
+ }))
2135
+ };
2136
+ }
2137
+ var FontDefinitionModel = import_mobx_state_tree6.types.model("FontDefinition", {
2138
+ name: import_mobx_state_tree6.types.string,
2139
+ href: import_mobx_state_tree6.types.string
2140
+ });
2141
+ var ThemeColorModel = import_mobx_state_tree6.types.model("ThemeColor", {
2142
+ name: import_mobx_state_tree6.types.string,
2143
+ value: import_mobx_state_tree6.types.string
2144
+ }).actions((self) => ({
2145
+ setValue(value) {
2146
+ self.value = value;
2147
+ },
2148
+ setName(name) {
2149
+ self.name = name;
2150
+ }
2151
+ }));
2152
+ var DEFAULT_THEME_COLORS = [
2153
+ { name: "Primary", value: "#944923" },
2154
+ { name: "Secondary", value: "#ffffff" },
2155
+ { name: "Text", value: "#333333" },
2156
+ { name: "Background", value: "#f5f5f5" }
2157
+ ];
2158
+ var METADATA_DEFAULTS = {
2159
+ title: () => "Untitled Template",
2160
+ subject: () => "",
2161
+ previewText: () => "",
2162
+ createdAt: () => Date.now(),
2163
+ updatedAt: () => Date.now(),
2164
+ fonts: () => [],
2165
+ themeColors: () => DEFAULT_THEME_COLORS.map((c) => ({ ...c }))
2166
+ };
2167
+ var TemplateMetadataModel = import_mobx_state_tree6.types.model("TemplateMetadata", {
2168
+ title: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, "Untitled Template"),
2169
+ subject: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, ""),
2170
+ previewText: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, ""),
2171
+ createdAt: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.Date, () => /* @__PURE__ */ new Date()),
2172
+ updatedAt: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.Date, () => /* @__PURE__ */ new Date()),
2173
+ fonts: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.array(FontDefinitionModel), []),
2174
+ themeColors: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.array(ThemeColorModel), DEFAULT_THEME_COLORS),
2175
+ breakpoint: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
2176
+ customCSS: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
2177
+ inlineCSS: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.string),
2178
+ /** The MJML head and body settings of an imported document (see `MjmlHead` in the schema). */
2179
+ mjmlHead: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen()),
2180
+ /** Defaults the store filled when it opened the node; they go back out only if changed (see `filledDefaults.ts`). */
2181
+ filled: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen()),
2182
+ /** Dates a stored document wrote as ISO strings, so they go back out as written. */
2183
+ dateStrings: import_mobx_state_tree6.types.maybe(import_mobx_state_tree6.types.frozen())
2184
+ }).actions((self) => ({
2185
+ update(updates) {
2186
+ Object.entries(updates).forEach(([key, value]) => {
2187
+ if (key in self && value !== void 0) {
2188
+ self[key] = value;
2189
+ }
2190
+ });
2191
+ self.updatedAt = /* @__PURE__ */ new Date();
2192
+ },
2193
+ addFont(name, href) {
2194
+ self.fonts.push(FontDefinitionModel.create({ name, href }));
2195
+ self.updatedAt = /* @__PURE__ */ new Date();
2196
+ },
2197
+ removeFont(name) {
2198
+ const index = self.fonts.findIndex((f) => f.name === name);
2199
+ if (index !== -1) {
2200
+ self.fonts.splice(index, 1);
2201
+ self.updatedAt = /* @__PURE__ */ new Date();
2202
+ }
2203
+ },
2204
+ addThemeColor(name, value) {
2205
+ self.themeColors.push(ThemeColorModel.create({ name, value }));
2206
+ self.updatedAt = /* @__PURE__ */ new Date();
2207
+ },
2208
+ updateThemeColor(name, value) {
2209
+ const color = self.themeColors.find((c) => c.name === name);
2210
+ if (color) {
2211
+ color.setValue(value);
2212
+ self.updatedAt = /* @__PURE__ */ new Date();
2213
+ }
2214
+ },
2215
+ removeThemeColor(name) {
2216
+ const index = self.themeColors.findIndex((c) => c.name === name);
2217
+ if (index !== -1) {
2218
+ self.themeColors.splice(index, 1);
2219
+ self.updatedAt = /* @__PURE__ */ new Date();
2220
+ }
2221
+ },
2222
+ touch() {
2223
+ self.updatedAt = /* @__PURE__ */ new Date();
2224
+ }
2225
+ })).preProcessSnapshot((snapshot) => {
2226
+ if (!snapshot) return snapshot;
2227
+ const dateStrings = {};
2228
+ const toDate = (key, value) => {
2229
+ if (typeof value !== "string") return value;
2230
+ const ms = Date.parse(value);
2231
+ if (Number.isNaN(ms)) return void 0;
2232
+ dateStrings[key] = value;
2233
+ return ms;
2234
+ };
2235
+ const parsed = {
2236
+ ...snapshot,
2237
+ createdAt: toDate("createdAt", snapshot.createdAt),
2238
+ updatedAt: toDate("updatedAt", snapshot.updatedAt),
2239
+ ...Object.keys(dateStrings).length > 0 ? { dateStrings } : {}
2240
+ };
2241
+ return fillDefaults(parsed, METADATA_DEFAULTS);
2242
+ }).postProcessSnapshot((snapshot) => {
2243
+ const { dateStrings, ...rest } = dropFilled(snapshot);
2244
+ const out = rest;
2245
+ for (const [key, written] of Object.entries(dateStrings ?? {})) {
2246
+ if (out[key] === Date.parse(written)) out[key] = written;
2247
+ }
2248
+ return out;
2249
+ });
2250
+ var TemplateModel = import_mobx_state_tree6.types.model("Template", {
2251
+ id: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, () => (0, import_nanoid6.nanoid)()),
2252
+ version: import_mobx_state_tree6.types.optional(import_mobx_state_tree6.types.string, CURRENT_TEMPLATE_VERSION),
2253
+ metadata: import_mobx_state_tree6.types.optional(TemplateMetadataModel, {}),
2254
+ /** The top level, in order: sections and wrappers (see `TopLevelItemModel`). */
2255
+ sections: import_mobx_state_tree6.types.array(TopLevelItemModel)
2256
+ }).preProcessSnapshot((snapshot) => {
2257
+ if (!snapshot || snapshot.version !== "1.0" || !Array.isArray(snapshot.sections)) return snapshot;
2258
+ return migrateV1_0ToV1_1(snapshot);
2259
+ }).views((self) => ({
2260
+ /** Every section in document order, the ones inside wrappers included. */
2261
+ get allSections() {
2262
+ const out = [];
2263
+ for (const item of self.sections) {
2264
+ if (isWrapperInstance(item)) out.push(...item.sections);
2265
+ else out.push(item);
2266
+ }
2267
+ return out;
2268
+ },
2269
+ /** The top-level wrappers, in order. */
2270
+ get wrappers() {
2271
+ return self.sections.filter(isWrapperInstance);
2272
+ }
2273
+ })).views((self) => ({
2274
+ getSectionById(sectionId) {
2275
+ return self.allSections.find((s) => s.id === sectionId);
2276
+ },
2277
+ getWrapperById(wrapperId) {
2278
+ return self.wrappers.find((w) => w.id === wrapperId);
2279
+ },
2280
+ /** The wrapper a section sits in; undefined for a top-level section (or an unknown id). */
2281
+ findWrapperBySectionId(sectionId) {
2282
+ return self.wrappers.find((w) => w.sections.some((s) => s.id === sectionId));
2283
+ },
2284
+ /** The index of a top-level item (section or wrapper); -1 when it is not at the top level. */
2285
+ getSectionIndex(itemId) {
2286
+ return self.sections.findIndex((s) => s.id === itemId);
2287
+ }
2288
+ })).actions((self) => {
2289
+ const locate = (sectionId) => {
2290
+ const top = self.sections.findIndex((s) => s.id === sectionId && !isWrapperInstance(s));
2291
+ if (top !== -1) return { list: self.sections, index: top };
2292
+ for (const wrapper of self.wrappers) {
2293
+ const index = wrapper.sections.findIndex((s) => s.id === sectionId);
2294
+ if (index !== -1) return { list: wrapper.sections, index, wrapper };
2295
+ }
2296
+ return void 0;
2297
+ };
2298
+ return {
2299
+ /**
2300
+ * Add a section at the top level, or into a wrapper when `wrapperId`
2301
+ * names one. `index` is the position in that list; the end when left out.
2302
+ */
2303
+ addSection(section, index, wrapperId) {
2304
+ const sectionToAdd = SectionModel.create(section);
2305
+ const wrapper = wrapperId ? self.getWrapperById(wrapperId) : void 0;
2306
+ if (wrapperId && !wrapper) throw new Error(`No wrapper with id "${wrapperId}"`);
2307
+ const list = wrapper ? wrapper.sections : self.sections;
2308
+ if (index !== void 0 && index >= 0 && index <= list.length) list.splice(index, 0, sectionToAdd);
2309
+ else list.push(sectionToAdd);
2310
+ self.metadata.touch();
2311
+ return sectionToAdd;
2312
+ },
2313
+ /** Add a wrapper at the top level (by default around one empty section). */
2314
+ addWrapper(wrapper = createWrapper(), index) {
2315
+ const wrapperToAdd = WrapperModel.create(wrapper);
2316
+ if (index !== void 0 && index >= 0 && index <= self.sections.length) self.sections.splice(index, 0, wrapperToAdd);
2317
+ else self.sections.push(wrapperToAdd);
2318
+ self.metadata.touch();
2319
+ return wrapperToAdd;
2320
+ },
2321
+ /** Remove a section, wherever it is (a wrapper that loses its last section stays, empty). */
2322
+ removeSection(sectionId) {
2323
+ const at = locate(sectionId);
2324
+ if (!at) return false;
2325
+ (0, import_mobx_state_tree6.destroy)(at.list[at.index]);
2326
+ self.metadata.touch();
2327
+ return true;
2328
+ },
2329
+ /**
2330
+ * Move a top-level item (a section or a wrapper) to another position
2331
+ * of the top level.
2332
+ */
2333
+ moveSection(itemId, toIndex) {
2334
+ const fromIndex = self.sections.findIndex((s) => s.id === itemId);
2335
+ if (fromIndex === -1 || toIndex < 0 || toIndex >= self.sections.length) return false;
2336
+ if (fromIndex === toIndex) return false;
2337
+ const item = (0, import_mobx_state_tree6.detach)(self.sections[fromIndex]);
2338
+ self.sections.splice(toIndex, 0, item);
2339
+ self.metadata.touch();
2340
+ return true;
2341
+ },
2342
+ /**
2343
+ * Move a section into a wrapper, out of one, between two, or within one.
2344
+ * `wrapperId: null` targets the top level. `index` is the position in
2345
+ * the target list as it is after the section left its old place.
2346
+ */
2347
+ moveSectionTo(sectionId, target) {
2348
+ const at = locate(sectionId);
2349
+ if (!at) return false;
2350
+ const wrapper = target.wrapperId ? self.getWrapperById(target.wrapperId) : void 0;
2351
+ if (target.wrapperId && !wrapper) return false;
2352
+ const sameList = (at.wrapper?.id ?? null) === (target.wrapperId ?? null);
2353
+ if (sameList && at.index === target.index) return false;
2354
+ const section = (0, import_mobx_state_tree6.detach)(at.list[at.index]);
2355
+ const list = wrapper ? wrapper.sections : self.sections;
2356
+ const index = Math.max(0, Math.min(target.index, list.length));
2357
+ list.splice(index, 0, section);
2358
+ self.metadata.touch();
2359
+ return true;
2360
+ },
2361
+ /** Put a top-level section into a new wrapper, in its place. Returns the wrapper. */
2362
+ wrapSection(sectionId) {
2363
+ const index = self.sections.findIndex((s) => s.id === sectionId && !isWrapperInstance(s));
2364
+ if (index === -1) return void 0;
2365
+ const section = (0, import_mobx_state_tree6.detach)(self.sections[index]);
2366
+ const wrapper = WrapperModel.create({ id: (0, import_nanoid6.nanoid)(), type: "wrapper", sections: [] });
2367
+ self.sections.splice(index, 0, wrapper);
2368
+ wrapper.sections.push(section);
2369
+ self.metadata.touch();
2370
+ return wrapper;
2371
+ },
2372
+ /** Take a wrapper's sections out to the top level, in its place, and remove the wrapper. Returns the sections' ids. */
2373
+ unwrap(wrapperId) {
2374
+ const index = self.sections.findIndex((s) => s.id === wrapperId && isWrapperInstance(s));
2375
+ if (index === -1) return [];
2376
+ const wrapper = self.sections[index];
2377
+ const sections = wrapper.sections.slice().map((s) => (0, import_mobx_state_tree6.detach)(s));
2378
+ (0, import_mobx_state_tree6.destroy)(wrapper);
2379
+ self.sections.splice(index, 0, ...sections);
2380
+ self.metadata.touch();
2381
+ return sections.map((s) => s.id);
2382
+ },
2383
+ /**
2384
+ * Remove a wrapper. With `keepSections`, its sections stay, in its place
2385
+ * (the same as {@link unwrap}); otherwise they go with it.
2386
+ */
2387
+ removeWrapper(wrapperId, options) {
2388
+ const wrapper = self.getWrapperById(wrapperId);
2389
+ if (!wrapper) return false;
2390
+ if (options.keepSections) {
2391
+ this.unwrap(wrapperId);
2392
+ } else {
2393
+ (0, import_mobx_state_tree6.destroy)(wrapper);
2394
+ self.metadata.touch();
2395
+ }
2396
+ return true;
2397
+ },
2398
+ /** Duplicate a section right after itself, in the same wrapper (or at the top level). */
2399
+ duplicateSection(sectionId) {
2400
+ const at = locate(sectionId);
2401
+ if (!at) return void 0;
2402
+ const newSection = SectionModel.create(cloneSectionSnapshot((0, import_mobx_state_tree6.getSnapshot)(at.list[at.index])));
2403
+ at.list.splice(at.index + 1, 0, newSection);
2404
+ self.metadata.touch();
2405
+ return newSection;
2406
+ },
2407
+ /** Duplicate a wrapper and everything inside it, right after itself. */
2408
+ duplicateWrapper(wrapperId) {
2409
+ const index = self.sections.findIndex((s) => s.id === wrapperId && isWrapperInstance(s));
2410
+ if (index === -1) return void 0;
2411
+ const snapshot = (0, import_mobx_state_tree6.getSnapshot)(self.sections[index]);
2412
+ const copy = WrapperModel.create({
2413
+ ...snapshot,
2414
+ id: (0, import_nanoid6.nanoid)(),
2415
+ sections: snapshot.sections.map((s) => cloneSectionSnapshot(s))
2416
+ });
2417
+ self.sections.splice(index + 1, 0, copy);
2418
+ self.metadata.touch();
2419
+ return copy;
2420
+ },
2421
+ insertBlock(columnId, block, index) {
2422
+ for (const section of self.allSections) {
2423
+ const column = section.getColumnById(columnId);
2424
+ if (column) {
2425
+ self.metadata.touch();
2426
+ const newBlock = BlockModel.create(block);
2427
+ if (index !== void 0 && index >= 0 && index <= column.blocks.length) {
2428
+ column.blocks.splice(index, 0, newBlock);
2429
+ } else {
2430
+ column.blocks.push(newBlock);
2431
+ }
2432
+ return newBlock;
2433
+ }
2434
+ }
2435
+ return void 0;
2436
+ },
2437
+ moveBlock(blockId, targetColumnId, targetIndex) {
2438
+ let sourceColumn;
2439
+ let block;
2440
+ for (const section of self.allSections) {
2441
+ const col = section.findColumnByBlockId(blockId);
2442
+ if (col) {
2443
+ sourceColumn = col;
2444
+ block = col.getBlockById(blockId);
2445
+ break;
2446
+ }
2447
+ }
2448
+ if (!sourceColumn || !block) return false;
2449
+ let targetColumn;
2450
+ for (const section of self.allSections) {
2451
+ const col = section.getColumnById(targetColumnId);
2452
+ if (col) {
2453
+ targetColumn = col;
2454
+ break;
2455
+ }
2456
+ }
2457
+ if (!targetColumn) return false;
2458
+ if (sourceColumn.id === targetColumn.id) {
2459
+ const fromIndex = sourceColumn.getBlockIndex(blockId);
2460
+ sourceColumn.moveBlock(fromIndex, targetIndex);
2461
+ } else {
2462
+ const detachedBlock = sourceColumn.detachBlock(blockId);
2463
+ if (detachedBlock) {
2464
+ if (targetIndex >= 0 && targetIndex <= targetColumn.blocks.length) {
2465
+ targetColumn.blocks.splice(targetIndex, 0, detachedBlock);
2466
+ } else {
2467
+ targetColumn.blocks.push(detachedBlock);
2468
+ }
2469
+ }
2470
+ }
2471
+ self.metadata.touch();
2472
+ return true;
2473
+ },
2474
+ deleteBlock(blockId) {
2475
+ for (const section of self.allSections) {
2476
+ for (const column of section.columns) {
2477
+ if (column.removeBlock(blockId)) {
2478
+ self.metadata.touch();
2479
+ return true;
2480
+ }
2481
+ }
2482
+ }
2483
+ return false;
2484
+ },
2485
+ updateMetadata(updates) {
2486
+ self.metadata.update(updates);
2487
+ },
2488
+ clear() {
2489
+ self.sections.forEach((s) => (0, import_mobx_state_tree6.destroy)(s));
2490
+ self.sections.clear();
2491
+ self.metadata.touch();
2492
+ }
2493
+ };
2494
+ }).views((self) => ({
2495
+ findColumnById(columnId) {
2496
+ for (const section of self.allSections) {
2497
+ const column = section.getColumnById(columnId);
2498
+ if (column) return column;
2499
+ }
2500
+ return void 0;
2501
+ },
2502
+ findBlockById(blockId) {
2503
+ for (const section of self.allSections) {
2504
+ const block = section.findBlockById(blockId);
2505
+ if (block) return block;
2506
+ }
2507
+ return void 0;
2508
+ },
2509
+ findSectionByBlockId(blockId) {
2510
+ for (const section of self.allSections) {
2511
+ if (section.findBlockById(blockId)) {
2512
+ return section;
2513
+ }
2514
+ }
2515
+ return void 0;
2516
+ },
2517
+ findColumnByBlockId(blockId) {
2518
+ for (const section of self.allSections) {
2519
+ const column = section.findColumnByBlockId(blockId);
2520
+ if (column) return column;
2521
+ }
2522
+ return void 0;
2523
+ },
2524
+ /** Visible top-level items (a hidden wrapper hides everything inside it). */
2525
+ get visibleSections() {
2526
+ return self.sections.filter((s) => !s.hidden);
2527
+ },
2528
+ /** How many sections the document has, the ones inside wrappers included. */
2529
+ get sectionCount() {
2530
+ return self.allSections.length;
2531
+ },
2532
+ get totalBlockCount() {
2533
+ return self.allSections.reduce((sum, section) => sum + section.totalBlockCount, 0);
2534
+ },
2535
+ get isEmpty() {
2536
+ return self.allSections.every((s) => s.isEmpty);
2537
+ },
2538
+ getBlocksByType(type) {
2539
+ const blocks = [];
2540
+ for (const section of self.allSections) {
2541
+ for (const column of section.columns) {
2542
+ for (const block of column.blocks) {
2543
+ if (block.type === type) {
2544
+ blocks.push(block);
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ return blocks;
2550
+ },
2551
+ get allBlocks() {
2552
+ const blocks = [];
2553
+ for (const section of self.allSections) {
2554
+ for (const column of section.columns) {
2555
+ blocks.push(...column.blocks);
2556
+ }
2557
+ }
2558
+ return blocks;
2559
+ }
2560
+ }));
2561
+ function createTemplate(options = {}) {
2562
+ return {
2563
+ id: options.id || (0, import_nanoid6.nanoid)(),
2564
+ version: CURRENT_TEMPLATE_VERSION,
2565
+ metadata: {
2566
+ title: options.title || "Untitled Template",
2567
+ createdAt: /* @__PURE__ */ new Date(),
2568
+ updatedAt: /* @__PURE__ */ new Date()
2569
+ },
2570
+ sections: options.sections || []
2571
+ };
2572
+ }
2573
+ function createTemplateWithDefaultSection(options = {}) {
2574
+ return {
2575
+ id: options.id || (0, import_nanoid6.nanoid)(),
2576
+ version: CURRENT_TEMPLATE_VERSION,
2577
+ metadata: {
2578
+ title: options.title || "Untitled Template",
2579
+ createdAt: /* @__PURE__ */ new Date(),
2580
+ updatedAt: /* @__PURE__ */ new Date()
2581
+ },
2582
+ sections: [createSection()]
2583
+ };
2584
+ }
2585
+
2586
+ // src/store/mst/EditorUIStore.ts
2587
+ var import_mobx_state_tree7 = require("mobx-state-tree");
2588
+ var EditorUIStore = import_mobx_state_tree7.types.model("EditorUI", {
2589
+ // === Selection State ===
2590
+ selectedBlockId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
2591
+ selectedSectionId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
2592
+ selectedColumnId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
2593
+ selectedSubColumnId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
2594
+ /** A wrapper (container around sections), one level out from sections. */
2595
+ selectedWrapperId: import_mobx_state_tree7.types.maybe(import_mobx_state_tree7.types.string),
2596
+ // === Panel State ===
2597
+ activeTab: import_mobx_state_tree7.types.optional(
2598
+ import_mobx_state_tree7.types.enumeration(["elements", "layout", "layers", "settings", "prebuilt", "saved"]),
2599
+ "elements"
2600
+ ),
2601
+ showLeftPanel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.boolean, true),
2602
+ showRightPanel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.boolean, true),
2603
+ // === View Mode ===
2604
+ previewDevice: import_mobx_state_tree7.types.optional(
2605
+ import_mobx_state_tree7.types.enumeration(["desktop", "mobile"]),
2606
+ "desktop"
2607
+ ),
2608
+ zoomLevel: import_mobx_state_tree7.types.optional(import_mobx_state_tree7.types.number, 1)
2609
+ }).volatile(() => ({
2610
+ // === Transient State (not persisted) ===
2611
+ hoverBlockId: void 0,
2612
+ hoverSectionId: void 0,
2613
+ hoverColumnId: void 0,
2614
+ hoverSubColumnId: void 0,
2615
+ hoverWrapperId: void 0,
2616
+ /** The wrapper whose delete the editor is asking about (keep its sections, or delete everything). */
2617
+ pendingWrapperDeleteId: void 0,
2618
+ // Drag state
2619
+ isDragging: false,
2620
+ dragData: void 0,
2621
+ dropIntent: void 0,
2622
+ // Resize state
2623
+ isResizing: false,
2624
+ resizeState: void 0,
2625
+ // Inline editing state
2626
+ isInlineEditing: false,
2627
+ inlineEditBlockId: void 0
2628
+ })).actions((self) => ({
2629
+ // === Selection Actions ===
2630
+ /**
2631
+ * Select a block
2632
+ */
2633
+ selectBlock(blockId) {
2634
+ self.selectedBlockId = blockId || void 0;
2635
+ self.selectedSectionId = void 0;
2636
+ self.selectedColumnId = void 0;
2637
+ self.selectedSubColumnId = void 0;
2638
+ self.selectedWrapperId = void 0;
2639
+ },
2640
+ /**
2641
+ * Select a section
2642
+ */
2643
+ selectSection(sectionId) {
2644
+ self.selectedSectionId = sectionId || void 0;
2645
+ self.selectedBlockId = void 0;
2646
+ self.selectedColumnId = void 0;
2647
+ self.selectedSubColumnId = void 0;
2648
+ self.selectedWrapperId = void 0;
2649
+ },
2650
+ /**
2651
+ * Select a column
2652
+ */
2653
+ selectColumn(columnId) {
2654
+ self.selectedColumnId = columnId || void 0;
2655
+ self.selectedBlockId = void 0;
2656
+ self.selectedSectionId = void 0;
2657
+ self.selectedSubColumnId = void 0;
2658
+ self.selectedWrapperId = void 0;
2659
+ },
2660
+ /**
2661
+ * Select a sub-column (depth-2 nested column).
2662
+ * Mutually exclusive with the other selection levels.
2663
+ */
2664
+ selectSubColumn(subColumnId) {
2665
+ self.selectedSubColumnId = subColumnId || void 0;
2666
+ self.selectedBlockId = void 0;
2667
+ self.selectedSectionId = void 0;
2668
+ self.selectedColumnId = void 0;
2669
+ self.selectedWrapperId = void 0;
2670
+ },
2671
+ /**
2672
+ * Select a wrapper (the container around sections).
2673
+ * Mutually exclusive with the other selection levels.
2674
+ */
2675
+ selectWrapper(wrapperId) {
2676
+ self.selectedWrapperId = wrapperId || void 0;
2677
+ self.selectedBlockId = void 0;
2678
+ self.selectedSectionId = void 0;
2679
+ self.selectedColumnId = void 0;
2680
+ self.selectedSubColumnId = void 0;
2681
+ },
2682
+ /**
2683
+ * Clear all selection
2684
+ */
2685
+ clearSelection() {
2686
+ self.selectedBlockId = void 0;
2687
+ self.selectedSectionId = void 0;
2688
+ self.selectedColumnId = void 0;
2689
+ self.selectedSubColumnId = void 0;
2690
+ self.selectedWrapperId = void 0;
2691
+ },
2692
+ // === Hover Actions ===
2693
+ /**
2694
+ * Set hover block
2695
+ */
2696
+ setHoverBlock(blockId) {
2697
+ self.hoverBlockId = blockId;
2698
+ },
2699
+ /**
2700
+ * Set hover section
2701
+ */
2702
+ setHoverSection(sectionId) {
2703
+ self.hoverSectionId = sectionId;
2704
+ },
2705
+ /**
2706
+ * Set hover column
2707
+ */
2708
+ setHoverColumn(columnId) {
2709
+ self.hoverColumnId = columnId;
2710
+ },
2711
+ /**
2712
+ * Set hover sub-column (depth-2 nested)
2713
+ */
2714
+ setHoverSubColumn(subColumnId) {
2715
+ self.hoverSubColumnId = subColumnId;
2716
+ },
2717
+ /**
2718
+ * Set hover wrapper
2719
+ */
2720
+ setHoverWrapper(wrapperId) {
2721
+ self.hoverWrapperId = wrapperId;
2722
+ },
2723
+ // === Wrapper delete dialog ===
2724
+ /** Ask whether to keep a wrapper's sections or delete everything (the editor shows its own dialog). */
2725
+ requestWrapperDelete(wrapperId) {
2726
+ self.pendingWrapperDeleteId = wrapperId;
2727
+ },
2728
+ cancelWrapperDelete() {
2729
+ self.pendingWrapperDeleteId = void 0;
2730
+ },
2731
+ /**
2732
+ * Clear all hover states
2733
+ */
2734
+ clearHover() {
2735
+ self.hoverBlockId = void 0;
2736
+ self.hoverSectionId = void 0;
2737
+ self.hoverColumnId = void 0;
2738
+ self.hoverSubColumnId = void 0;
2739
+ self.hoverWrapperId = void 0;
2740
+ },
2741
+ // === Drag Actions ===
2742
+ /**
2743
+ * Start a drag operation for an existing block
2744
+ */
2745
+ startBlockDrag(blockId, sourceColumnId, sourceIndex) {
2746
+ self.isDragging = true;
2747
+ self.dragData = {
2748
+ blockId,
2749
+ sourceColumnId,
2750
+ sourceIndex,
2751
+ isNewBlock: false
2752
+ };
2753
+ },
2754
+ /**
2755
+ * Start a drag operation for a new block from the toolbar
2756
+ */
2757
+ startNewBlockDrag(blockType) {
2758
+ self.isDragging = true;
2759
+ self.dragData = {
2760
+ blockType,
2761
+ isNewBlock: true
2762
+ };
2763
+ },
2764
+ /**
2765
+ * Update the drop intent during drag
2766
+ */
2767
+ setDropIntent(intent) {
2768
+ self.dropIntent = intent;
2769
+ },
2770
+ /**
2771
+ * End the drag operation
2772
+ */
2773
+ endDrag() {
2774
+ self.isDragging = false;
2775
+ self.dragData = void 0;
2776
+ self.dropIntent = void 0;
2777
+ },
2778
+ // === Resize Actions ===
2779
+ /**
2780
+ * Start a resize operation
2781
+ */
2782
+ startResize(targetId, handleType, originalValue) {
2783
+ self.isResizing = true;
2784
+ self.resizeState = {
2785
+ targetId,
2786
+ handleType,
2787
+ originalValue,
2788
+ previewValue: originalValue
2789
+ };
2790
+ },
2791
+ /**
2792
+ * Update resize preview value
2793
+ */
2794
+ updateResizePreview(value) {
2795
+ if (self.resizeState) {
2796
+ self.resizeState.previewValue = value;
2797
+ }
2798
+ },
2799
+ /**
2800
+ * End resize operation (commit or cancel)
2801
+ */
2802
+ endResize() {
2803
+ self.isResizing = false;
2804
+ self.resizeState = void 0;
2805
+ },
2806
+ // === Inline Editing Actions ===
2807
+ /**
2808
+ * Start inline editing for a block
2809
+ */
2810
+ startInlineEdit(blockId) {
2811
+ self.isInlineEditing = true;
2812
+ self.inlineEditBlockId = blockId;
2813
+ self.selectedBlockId = blockId;
2814
+ },
2815
+ /**
2816
+ * End inline editing
2817
+ */
2818
+ endInlineEdit() {
2819
+ self.isInlineEditing = false;
2820
+ self.inlineEditBlockId = void 0;
2821
+ },
2822
+ // === Panel Actions ===
2823
+ /**
2824
+ * Set the active sidebar tab
2825
+ */
2826
+ setActiveTab(tab) {
2827
+ self.activeTab = tab;
2828
+ },
2829
+ /**
2830
+ * Toggle left panel visibility
2831
+ */
2832
+ toggleLeftPanel() {
2833
+ self.showLeftPanel = !self.showLeftPanel;
2834
+ },
2835
+ /**
2836
+ * Toggle right panel visibility
2837
+ */
2838
+ toggleRightPanel() {
2839
+ self.showRightPanel = !self.showRightPanel;
2840
+ },
2841
+ /**
2842
+ * Set left panel visibility
2843
+ */
2844
+ setLeftPanelVisible(visible) {
2845
+ self.showLeftPanel = visible;
2846
+ },
2847
+ /**
2848
+ * Set right panel visibility
2849
+ */
2850
+ setRightPanelVisible(visible) {
2851
+ self.showRightPanel = visible;
2852
+ },
2853
+ // === View Actions ===
2854
+ /**
2855
+ * Set preview device
2856
+ */
2857
+ setPreviewDevice(device) {
2858
+ self.previewDevice = device;
2859
+ },
2860
+ /**
2861
+ * Toggle preview device between desktop and mobile
2862
+ */
2863
+ togglePreviewDevice() {
2864
+ self.previewDevice = self.previewDevice === "desktop" ? "mobile" : "desktop";
2865
+ },
2866
+ /**
2867
+ * Set zoom level
2868
+ */
2869
+ setZoomLevel(zoom) {
2870
+ self.zoomLevel = Math.max(0.25, Math.min(2, zoom));
2871
+ },
2872
+ /**
2873
+ * Zoom in
2874
+ */
2875
+ zoomIn() {
2876
+ self.zoomLevel = Math.min(2, self.zoomLevel + 0.1);
2877
+ },
2878
+ /**
2879
+ * Zoom out
2880
+ */
2881
+ zoomOut() {
2882
+ self.zoomLevel = Math.max(0.25, self.zoomLevel - 0.1);
2883
+ },
2884
+ /**
2885
+ * Reset zoom to 100%
2886
+ */
2887
+ resetZoom() {
2888
+ self.zoomLevel = 1;
2889
+ }
2890
+ })).views((self) => ({
2891
+ /**
2892
+ * Get the current selection type
2893
+ */
2894
+ get selectionType() {
2895
+ if (self.selectedBlockId) return "block";
2896
+ if (self.selectedSectionId) return "section";
2897
+ if (self.selectedColumnId) return "column";
2898
+ if (self.selectedSubColumnId) return "subColumn";
2899
+ if (self.selectedWrapperId) return "wrapper";
2900
+ return null;
2901
+ },
2902
+ /**
2903
+ * Check if anything is selected
2904
+ */
2905
+ get hasSelection() {
2906
+ return !!(self.selectedBlockId || self.selectedSectionId || self.selectedColumnId || self.selectedSubColumnId || self.selectedWrapperId);
2907
+ },
2908
+ /**
2909
+ * Check if a specific block is selected
2910
+ */
2911
+ isBlockSelected(blockId) {
2912
+ return self.selectedBlockId === blockId;
2913
+ },
2914
+ /**
2915
+ * Check if a specific section is selected
2916
+ */
2917
+ isSectionSelected(sectionId) {
2918
+ return self.selectedSectionId === sectionId;
2919
+ },
2920
+ /**
2921
+ * Check if a specific column is selected
2922
+ */
2923
+ isColumnSelected(columnId) {
2924
+ return self.selectedColumnId === columnId;
2925
+ },
2926
+ /**
2927
+ * Check if a specific wrapper is selected
2928
+ */
2929
+ isWrapperSelected(wrapperId) {
2930
+ return self.selectedWrapperId === wrapperId;
2931
+ },
2932
+ /**
2933
+ * Check if a specific block is hovered
2934
+ */
2935
+ isBlockHovered(blockId) {
2936
+ return self.hoverBlockId === blockId;
2937
+ },
2938
+ /**
2939
+ * Check if we're dragging an existing block
2940
+ */
2941
+ get isDraggingExistingBlock() {
2942
+ return self.isDragging && !!self.dragData && !self.dragData.isNewBlock;
2943
+ },
2944
+ /**
2945
+ * Check if we're dragging a new block from toolbar
2946
+ */
2947
+ get isDraggingNewBlock() {
2948
+ return self.isDragging && !!self.dragData && !!self.dragData.isNewBlock;
2949
+ },
2950
+ /**
2951
+ * Get the currently dragged block type (for new blocks)
2952
+ */
2953
+ get draggedBlockType() {
2954
+ return self.dragData?.blockType;
2955
+ },
2956
+ /**
2957
+ * Get the currently dragged block ID (for existing blocks)
2958
+ */
2959
+ get draggedBlockId() {
2960
+ return self.dragData?.blockId;
2961
+ },
2962
+ /**
2963
+ * Get preview width based on device
2964
+ */
2965
+ get previewWidth() {
2966
+ return self.previewDevice === "desktop" ? 600 : 375;
2967
+ },
2968
+ /**
2969
+ * Check if in mobile preview mode
2970
+ */
2971
+ get isMobilePreview() {
2972
+ return self.previewDevice === "mobile";
2973
+ },
2974
+ /**
2975
+ * Check if in desktop preview mode
2976
+ */
2977
+ get isDesktopPreview() {
2978
+ return self.previewDevice === "desktop";
2979
+ },
2980
+ /**
2981
+ * Get zoom percentage
2982
+ */
2983
+ get zoomPercentage() {
2984
+ return Math.round(self.zoomLevel * 100);
2985
+ }
2986
+ }));
2987
+
2988
+ // src/store/mst/RootStore.ts
2989
+ var import_mobx_state_tree8 = require("mobx-state-tree");
2990
+ var RootStore = import_mobx_state_tree8.types.model("RootStore", {
2991
+ template: TemplateModel,
2992
+ editorUI: EditorUIStore
2993
+ }).volatile(() => ({
2994
+ // History state for undo/redo. Typed as `unknown` to keep the inferred
2995
+ // RootStore type small enough for TypeScript's serialization limit;
2996
+ // history snapshots are roundtripped through MST validation anyway.
2997
+ history: [],
2998
+ historyIndex: -1,
2999
+ maxHistory: 50,
3000
+ isUndoRedo: false,
3001
+ /**
3002
+ * The snapshot undo or redo just applied. The snapshot listener runs after
3003
+ * the outermost action (undo itself) has finished, so it must recognise
3004
+ * this one and not record it as a new step: that would cut off the redo
3005
+ * future.
3006
+ */
3007
+ appliedFromHistory: void 0
3008
+ })).actions((self) => ({
3009
+ /**
3010
+ * Initialize history with current state
3011
+ */
3012
+ initHistory() {
3013
+ const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
3014
+ self.history = [snapshot];
3015
+ self.historyIndex = 0;
3016
+ },
3017
+ /**
3018
+ * Record current state to history (called after mutations)
3019
+ */
3020
+ recordHistory() {
3021
+ if (self.isUndoRedo) return;
3022
+ const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
3023
+ if (self.appliedFromHistory !== void 0) {
3024
+ const applied = self.appliedFromHistory;
3025
+ self.appliedFromHistory = void 0;
3026
+ if (JSON.stringify(applied) === JSON.stringify(snapshot)) return;
3027
+ }
3028
+ if (self.historyIndex < self.history.length - 1) {
3029
+ self.history = self.history.slice(0, self.historyIndex + 1);
3030
+ }
3031
+ self.history.push(snapshot);
3032
+ if (self.history.length > self.maxHistory) {
3033
+ self.history = self.history.slice(self.history.length - self.maxHistory);
3034
+ }
3035
+ self.historyIndex = self.history.length - 1;
3036
+ },
3037
+ /**
3038
+ * Undo to previous state
3039
+ */
3040
+ undo() {
3041
+ if (self.historyIndex <= 0) return false;
3042
+ self.isUndoRedo = true;
3043
+ self.historyIndex--;
3044
+ (0, import_mobx_state_tree8.applySnapshot)(self.template, self.history[self.historyIndex]);
3045
+ self.appliedFromHistory = (0, import_mobx_state_tree8.getSnapshot)(self.template);
3046
+ self.isUndoRedo = false;
3047
+ this.validateSelection();
3048
+ return true;
3049
+ },
3050
+ /**
3051
+ * Redo to next state
3052
+ */
3053
+ redo() {
3054
+ if (self.historyIndex >= self.history.length - 1) return false;
3055
+ self.isUndoRedo = true;
3056
+ self.historyIndex++;
3057
+ (0, import_mobx_state_tree8.applySnapshot)(self.template, self.history[self.historyIndex]);
3058
+ self.appliedFromHistory = (0, import_mobx_state_tree8.getSnapshot)(self.template);
3059
+ self.isUndoRedo = false;
3060
+ this.validateSelection();
3061
+ return true;
3062
+ },
3063
+ /**
3064
+ * Clear history
3065
+ */
3066
+ clearHistory() {
3067
+ const snapshot = (0, import_mobx_state_tree8.getSnapshot)(self.template);
3068
+ self.history = [snapshot];
3069
+ self.historyIndex = 0;
3070
+ },
3071
+ /**
3072
+ * Validate and clear selection if items no longer exist
3073
+ */
3074
+ validateSelection() {
3075
+ if (self.editorUI.selectedBlockId && !self.template.findBlockById(self.editorUI.selectedBlockId)) {
3076
+ self.editorUI.clearSelection();
3077
+ }
3078
+ if (self.editorUI.selectedSectionId && !self.template.getSectionById(self.editorUI.selectedSectionId)) {
3079
+ self.editorUI.clearSelection();
3080
+ }
3081
+ if (self.editorUI.selectedColumnId && !self.template.findColumnById(self.editorUI.selectedColumnId)) {
3082
+ self.editorUI.clearSelection();
3083
+ }
3084
+ if (self.editorUI.selectedWrapperId && !self.template.getWrapperById(self.editorUI.selectedWrapperId)) {
3085
+ self.editorUI.clearSelection();
3086
+ }
3087
+ if (self.editorUI.selectedSubColumnId && !this.findSubColumn(self.editorUI.selectedSubColumnId)) {
3088
+ self.editorUI.clearSelection();
3089
+ }
3090
+ if (self.editorUI.pendingWrapperDeleteId && !self.template.getWrapperById(self.editorUI.pendingWrapperDeleteId)) {
3091
+ self.editorUI.cancelWrapperDelete();
3092
+ }
3093
+ },
3094
+ findSubColumn(id) {
3095
+ for (const section of self.template.allSections) {
3096
+ for (const column of section.columns) {
3097
+ const sc = (column.subColumns ?? []).find((s) => s.id === id);
3098
+ if (sc) return sc;
3099
+ }
3100
+ }
3101
+ return void 0;
3102
+ },
3103
+ /**
3104
+ * Load a new template
3105
+ */
3106
+ loadTemplate(templateData) {
3107
+ (0, import_mobx_state_tree8.applySnapshot)(self.template, templateData);
3108
+ self.editorUI.clearSelection();
3109
+ this.clearHistory();
3110
+ },
3111
+ /**
3112
+ * Reset to a new empty template
3113
+ */
3114
+ resetTemplate(title) {
3115
+ const newTemplate = createTemplateWithDefaultSection({ title });
3116
+ (0, import_mobx_state_tree8.applySnapshot)(self.template, newTemplate);
3117
+ self.editorUI.clearSelection();
3118
+ this.clearHistory();
3119
+ }
3120
+ })).views((self) => ({
3121
+ /**
3122
+ * Check if undo is available
3123
+ */
3124
+ get canUndo() {
3125
+ return self.historyIndex > 0;
3126
+ },
3127
+ /**
3128
+ * Check if redo is available
3129
+ */
3130
+ get canRedo() {
3131
+ return self.historyIndex < self.history.length - 1;
3132
+ },
3133
+ /**
3134
+ * Get the currently selected block. Explicit return types here
3135
+ * (and on the other selectors below) keep the inferred RootStore
3136
+ * type small enough for TypeScript's serialization limit.
3137
+ */
3138
+ get selectedBlock() {
3139
+ if (!self.editorUI.selectedBlockId) return void 0;
3140
+ return self.template.findBlockById(self.editorUI.selectedBlockId);
3141
+ },
3142
+ /**
3143
+ * Get the currently selected section
3144
+ */
3145
+ get selectedSection() {
3146
+ if (!self.editorUI.selectedSectionId) return void 0;
3147
+ return self.template.getSectionById(self.editorUI.selectedSectionId);
3148
+ },
3149
+ /**
3150
+ * Get the currently selected column
3151
+ */
3152
+ get selectedColumn() {
3153
+ if (!self.editorUI.selectedColumnId) return void 0;
3154
+ return self.template.findColumnById(self.editorUI.selectedColumnId);
3155
+ },
3156
+ /**
3157
+ * Get the currently selected sub-column (depth-2 nested).
3158
+ */
3159
+ get selectedSubColumn() {
3160
+ const id = self.editorUI.selectedSubColumnId;
3161
+ if (!id) return void 0;
3162
+ for (const section of self.template.allSections) {
3163
+ for (const column of section.columns) {
3164
+ const sc = (column.subColumns ?? []).find((s) => s.id === id);
3165
+ if (sc) return sc;
3166
+ }
3167
+ }
3168
+ return void 0;
3169
+ },
3170
+ /**
3171
+ * Get the currently selected wrapper
3172
+ */
3173
+ get selectedWrapper() {
3174
+ if (!self.editorUI.selectedWrapperId) return void 0;
3175
+ return self.template.getWrapperById(self.editorUI.selectedWrapperId);
3176
+ },
3177
+ /**
3178
+ * Get history size info
3179
+ */
3180
+ get historyInfo() {
3181
+ return {
3182
+ current: self.historyIndex + 1,
3183
+ total: self.history.length,
3184
+ canUndo: self.historyIndex > 0,
3185
+ canRedo: self.historyIndex < self.history.length - 1
3186
+ };
3187
+ }
3188
+ }));
3189
+ function createRootStore(options = {}) {
3190
+ const { template: initialTemplate, onChange, onChangeDebounce = 300 } = options;
3191
+ const store = RootStore.create({
3192
+ template: initialTemplate || createTemplateWithDefaultSection(),
3193
+ editorUI: {}
3194
+ });
3195
+ store.initHistory();
3196
+ if (onChange) {
3197
+ let timeoutId;
3198
+ (0, import_mobx_state_tree8.onSnapshot)(store.template, (snapshot) => {
3199
+ store.recordHistory();
3200
+ if (timeoutId) clearTimeout(timeoutId);
3201
+ timeoutId = setTimeout(() => {
3202
+ onChange(snapshot);
3203
+ }, onChangeDebounce);
3204
+ });
3205
+ } else {
3206
+ (0, import_mobx_state_tree8.onSnapshot)(store.template, () => {
3207
+ store.recordHistory();
3208
+ });
3209
+ }
3210
+ return store;
3211
+ }
3212
+ function createEmptyStore() {
3213
+ return createRootStore({
3214
+ template: createTemplate()
3215
+ });
3216
+ }
3217
+ // Annotate the CommonJS export names for ESM import in node:
3218
+ 0 && (module.exports = {
3219
+ AccordionBlockSchema,
3220
+ AccordionItemModel,
3221
+ BackgroundGradientSchema,
3222
+ BlockModel,
3223
+ BlockRegistryImpl,
3224
+ BlockSchema,
3225
+ BlockType,
3226
+ ButtonBlockSchema,
3227
+ CONTAINER_BLOCK_TYPES,
3228
+ CURRENT_TEMPLATE_VERSION,
3229
+ CarouselBlockSchema,
3230
+ CarouselImageModel,
3231
+ ColumnModel,
3232
+ ColumnSchema,
3233
+ CustomFontSchema,
3234
+ DividerBlockSchema,
3235
+ EditorUIStore,
3236
+ EmailTemplateSchema,
3237
+ EmailTemplateSchemaV1_0,
3238
+ EmailTemplateSchemaV1_1,
3239
+ ExtraAttributesSchema,
3240
+ FontDefinitionModel,
3241
+ FooterBlockSchema,
3242
+ GradientStopSchema,
3243
+ HeaderBlockSchema,
3244
+ HeroBlockSchema,
3245
+ HistoryManager,
3246
+ ImageBlockSchema,
3247
+ LEAF_BLOCK_TYPES,
3248
+ MJML_WRAPPER_DEFAULT_PADDING,
3249
+ MjmlHeadSchema,
3250
+ NavbarBlockSchema,
3251
+ NavbarLinkModel,
3252
+ RawBlockSchema,
3253
+ RootStore,
3254
+ SUPPORTED_TEMPLATE_VERSIONS,
3255
+ SectionModel,
3256
+ SectionSchema,
3257
+ SectionSchemaV1_0,
3258
+ SelectionManager,
3259
+ SocialBlockSchema,
3260
+ SocialLinkModel,
3261
+ SpacerBlockSchema,
3262
+ SpacingModel,
3263
+ SpacingSchema,
3264
+ SubColumnModel,
3265
+ TableBlockSchema,
3266
+ TemplateMetadataModel,
3267
+ TemplateMetadataSchema,
3268
+ TemplateMigrationError,
3269
+ TemplateModel,
3270
+ TextBlockSchema,
3271
+ ThemeColorModel,
3272
+ TopLevelItemModel,
3273
+ TopLevelItemSchema,
3274
+ WrapperModel,
3275
+ WrapperSchema,
3276
+ allSections,
3277
+ buildGradientCSS,
3278
+ cloneSectionSnapshot,
3279
+ createBlockRegistry,
3280
+ createColumn,
3281
+ createEmptyStore,
3282
+ createHistoryManager,
3283
+ createPrebuiltTemplateRegistry,
3284
+ createRootStore,
3285
+ createSection,
3286
+ createSelectionManager,
3287
+ createSubColumn,
3288
+ createTemplate,
3289
+ createTemplateWithDefaultSection,
3290
+ createWrapper,
3291
+ isLeafBlockType,
3292
+ isTemplateMigrationError,
3293
+ isWrapper,
3294
+ isWrapperInstance,
3295
+ migrateTemplate,
3296
+ migrateV1_0ToV1_1,
3297
+ validateTemplate,
3298
+ withTemplateId
3299
+ });