@emabuild/core 0.1.5 → 0.3.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.
Files changed (73) hide show
  1. package/dist/canvas/column-renderer.d.ts.map +1 -1
  2. package/dist/canvas/content-renderer.d.ts +6 -3
  3. package/dist/canvas/content-renderer.d.ts.map +1 -1
  4. package/dist/canvas/editor-canvas.d.ts +11 -0
  5. package/dist/canvas/editor-canvas.d.ts.map +1 -1
  6. package/dist/canvas/inline-toolbar.d.ts +8 -0
  7. package/dist/canvas/inline-toolbar.d.ts.map +1 -1
  8. package/dist/canvas/row-renderer.d.ts +2 -0
  9. package/dist/canvas/row-renderer.d.ts.map +1 -1
  10. package/dist/compat/unlayer-adapter.d.ts +33 -0
  11. package/dist/compat/unlayer-adapter.d.ts.map +1 -0
  12. package/dist/dnd/drag-manager.d.ts +1 -0
  13. package/dist/dnd/drag-manager.d.ts.map +1 -1
  14. package/dist/dnd/drag-state.d.ts +10 -3
  15. package/dist/dnd/drag-state.d.ts.map +1 -1
  16. package/dist/{form-tool-CduLiZgt.js → form-tool-C7760Hvm.js} +12 -11
  17. package/dist/form-tool-C7760Hvm.js.map +1 -0
  18. package/dist/{html-tool-D4ay2h-U.js → html-tool-4zZO2hqE.js} +3 -3
  19. package/dist/{html-tool-D4ay2h-U.js.map → html-tool-4zZO2hqE.js.map} +1 -1
  20. package/dist/index-zy5NbC2E.js +4303 -0
  21. package/dist/index-zy5NbC2E.js.map +1 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -5
  25. package/dist/mail-editor.d.ts +13 -2
  26. package/dist/mail-editor.d.ts.map +1 -1
  27. package/dist/{menu-tool-KvGDbaYD.js → menu-tool-Cu5D_VYs.js} +12 -11
  28. package/dist/menu-tool-Cu5D_VYs.js.map +1 -0
  29. package/dist/properties/property-panel.d.ts.map +1 -1
  30. package/dist/properties/widgets/index.d.ts +1 -0
  31. package/dist/properties/widgets/index.d.ts.map +1 -1
  32. package/dist/properties/widgets/number-unit-widget.d.ts +10 -0
  33. package/dist/properties/widgets/number-unit-widget.d.ts.map +1 -0
  34. package/dist/register-elements.d.ts.map +1 -1
  35. package/dist/sidebar/a11y-checker.d.ts +27 -0
  36. package/dist/sidebar/a11y-checker.d.ts.map +1 -0
  37. package/dist/sidebar/body-settings.d.ts +2 -0
  38. package/dist/sidebar/body-settings.d.ts.map +1 -1
  39. package/dist/sidebar/editor-sidebar.d.ts +5 -2
  40. package/dist/sidebar/editor-sidebar.d.ts.map +1 -1
  41. package/dist/{social-tool-B8Jg2yE-.js → social-tool-kPuP-4n6.js} +13 -12
  42. package/dist/social-tool-kPuP-4n6.js.map +1 -0
  43. package/dist/state/editor-store.d.ts +30 -28
  44. package/dist/state/editor-store.d.ts.map +1 -1
  45. package/dist/{table-tool-BzpD08dq.js → table-tool-CcWFvTSc.js} +14 -13
  46. package/dist/table-tool-CcWFvTSc.js.map +1 -0
  47. package/dist/timer-tool-CG1oul_Z.js +55 -0
  48. package/dist/timer-tool-CG1oul_Z.js.map +1 -0
  49. package/dist/tools/built-in/button-tool.d.ts.map +1 -1
  50. package/dist/tools/built-in/divider-tool.d.ts.map +1 -1
  51. package/dist/tools/built-in/heading-tool.d.ts.map +1 -1
  52. package/dist/tools/built-in/image-tool.d.ts.map +1 -1
  53. package/dist/tools/built-in/paragraph-tool.d.ts.map +1 -1
  54. package/dist/tools/built-in/shared-options.d.ts +105 -0
  55. package/dist/tools/built-in/shared-options.d.ts.map +1 -0
  56. package/dist/tools/built-in/text-tool.d.ts.map +1 -1
  57. package/dist/tools/helpers/index.d.ts +1 -1
  58. package/dist/tools/helpers/index.d.ts.map +1 -1
  59. package/dist/tools/helpers/value-extractor.d.ts +9 -0
  60. package/dist/tools/helpers/value-extractor.d.ts.map +1 -1
  61. package/dist/tools/tool-registry.d.ts.map +1 -1
  62. package/dist/{video-tool-CdGVmZxz.js → video-tool-CttMka8Z.js} +7 -6
  63. package/dist/video-tool-CttMka8Z.js.map +1 -0
  64. package/package.json +13 -26
  65. package/dist/form-tool-CduLiZgt.js.map +0 -1
  66. package/dist/index-BoKIXJKv.js +0 -3001
  67. package/dist/index-BoKIXJKv.js.map +0 -1
  68. package/dist/menu-tool-KvGDbaYD.js.map +0 -1
  69. package/dist/social-tool-B8Jg2yE-.js.map +0 -1
  70. package/dist/table-tool-BzpD08dq.js.map +0 -1
  71. package/dist/timer-tool-Ck1ERDW-.js +0 -54
  72. package/dist/timer-tool-Ck1ERDW-.js.map +0 -1
  73. package/dist/video-tool-CdGVmZxz.js.map +0 -1
@@ -1,3001 +0,0 @@
1
- import { html as p, LitElement as $, css as k } from "lit";
2
- import { property as b, customElement as C, state as pt } from "lit/decorators.js";
3
- import { unsafeHTML as U } from "lit/directives/unsafe-html.js";
4
- import { styleMap as ht } from "lit/directives/style-map.js";
5
- function ut() {
6
- const e = {};
7
- return {
8
- getCounters() {
9
- return { ...e };
10
- },
11
- setCounters(t) {
12
- for (const o of Object.keys(e))
13
- delete e[o];
14
- Object.assign(e, t);
15
- },
16
- next(t) {
17
- const o = e[t] ?? 0;
18
- return e[t] = o + 1, e[t];
19
- }
20
- };
21
- }
22
- class gt {
23
- constructor() {
24
- this.listeners = /* @__PURE__ */ new Map();
25
- }
26
- /** Register a listener for an event */
27
- on(t, o) {
28
- this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(o);
29
- }
30
- /** Remove a specific listener */
31
- off(t, o) {
32
- this.listeners.get(t)?.delete(o);
33
- }
34
- /** Emit an event with a payload. Errors in listeners are caught and logged. */
35
- emit(t, o) {
36
- this.listeners.get(t)?.forEach((n) => {
37
- try {
38
- n(o);
39
- } catch (i) {
40
- console.error(`[emabuild] Error in "${t}" listener:`, i);
41
- }
42
- });
43
- }
44
- /** Remove all listeners, optionally scoped to a single event */
45
- removeAllListeners(t) {
46
- t ? this.listeners.delete(t) : this.listeners.clear();
47
- }
48
- }
49
- class ft {
50
- constructor(t = 50) {
51
- this.undoStack = [], this.redoStack = [], this.maxHistory = t;
52
- }
53
- /** Whether there are states to undo to */
54
- get canUndo() {
55
- return this.undoStack.length > 0;
56
- }
57
- /** Whether there are states to redo to */
58
- get canRedo() {
59
- return this.redoStack.length > 0;
60
- }
61
- /** Save current design to the undo stack before a mutation */
62
- push(t) {
63
- this.undoStack.push(structuredClone(t)), this.undoStack.length > this.maxHistory && this.undoStack.shift(), this.redoStack = [];
64
- }
65
- /** Restore the previous state, pushing current state to redo. Returns the restored design or undefined. */
66
- undo(t) {
67
- const o = this.undoStack.pop();
68
- if (o)
69
- return this.redoStack.push(structuredClone(t)), o;
70
- }
71
- /** Restore the next state, pushing current state to undo. Returns the restored design or undefined. */
72
- redo(t) {
73
- const o = this.redoStack.pop();
74
- if (o)
75
- return this.undoStack.push(structuredClone(t)), o;
76
- }
77
- /** Clear all history (e.g. when loading a new design) */
78
- clear() {
79
- this.undoStack = [], this.redoStack = [];
80
- }
81
- }
82
- function bt() {
83
- return {
84
- counters: { u_row: 0, u_column: 0 },
85
- body: {
86
- id: "u_body",
87
- rows: [],
88
- headers: [],
89
- footers: [],
90
- values: {
91
- backgroundColor: "#e7e7e7",
92
- contentAlign: "center",
93
- contentVerticalAlign: "center",
94
- contentWidth: "600px",
95
- fontFamily: { label: "Arial", value: "arial,helvetica,sans-serif" },
96
- textColor: "#000000",
97
- linkStyle: {
98
- body: !0,
99
- linkColor: "#0000ee",
100
- linkHoverColor: "#0000ee",
101
- linkUnderline: !0,
102
- linkHoverUnderline: !0
103
- },
104
- preheaderText: "",
105
- popupPosition: "center",
106
- popupWidth: "600px",
107
- popupHeight: "auto",
108
- borderRadius: "10px",
109
- popupBackgroundColor: "#FFFFFF",
110
- popupBackgroundImage: { url: "", fullWidth: !0, repeat: "no-repeat", center: !0, cover: !0 },
111
- popupOverlay_backgroundColor: "rgba(0, 0, 0, 0.1)",
112
- popupCloseButton_position: "top-right",
113
- popupCloseButton_backgroundColor: "#DDDDDD",
114
- popupCloseButton_iconColor: "#000000",
115
- popupCloseButton_borderRadius: "0px",
116
- popupCloseButton_margin: "0px",
117
- popupCloseButton_action: {
118
- name: "close_popup",
119
- attrs: { onClick: "document.querySelector('.u-popup-container').style.display = 'none';" }
120
- },
121
- _meta: { htmlID: "u_body", htmlClassNames: "u_body" }
122
- }
123
- },
124
- schemaVersion: 16
125
- };
126
- }
127
- function mt(e, t) {
128
- const o = e.next("u_row"), n = t.map(() => {
129
- const i = e.next("u_column");
130
- return {
131
- id: `u_column_${i}`,
132
- contents: [],
133
- values: {
134
- backgroundColor: "",
135
- padding: "0px",
136
- border: {},
137
- borderRadius: "0px",
138
- _meta: { htmlID: `u_column_${i}`, htmlClassNames: "u_column" }
139
- }
140
- };
141
- });
142
- return {
143
- id: `u_row_${o}`,
144
- cells: t,
145
- columns: n,
146
- values: {
147
- displayCondition: null,
148
- columns: !1,
149
- backgroundColor: "",
150
- columnsBackgroundColor: "",
151
- backgroundImage: { url: "", fullWidth: !0, repeat: !1, center: !0, cover: !1 },
152
- padding: "0px",
153
- anchor: "",
154
- hideDesktop: !1,
155
- hideMobile: !1,
156
- _meta: { htmlID: `u_row_${o}`, htmlClassNames: "u_row" }
157
- }
158
- };
159
- }
160
- function xt(e, t, o = {}) {
161
- const n = e.next(`u_content_${t}`), i = `u_content_${t}_${n}`;
162
- return {
163
- id: i,
164
- type: t,
165
- values: {
166
- containerPadding: "10px",
167
- anchor: "",
168
- hideDesktop: !1,
169
- hideMobile: !1,
170
- displayCondition: null,
171
- _meta: { htmlID: i, htmlClassNames: `u_content_${t}` },
172
- ...o
173
- }
174
- };
175
- }
176
- function N(e, t) {
177
- return e.body.rows.find((o) => o.id === t);
178
- }
179
- function j(e, t) {
180
- for (const o of e.body.rows) {
181
- const n = o.columns.find((i) => i.id === t);
182
- if (n) return n;
183
- }
184
- }
185
- function W(e, t) {
186
- for (const o of e.body.rows)
187
- for (const n of o.columns) {
188
- const i = n.contents.find((s) => s.id === t);
189
- if (i) return i;
190
- }
191
- }
192
- function yt(e, t) {
193
- for (const o of e.body.rows)
194
- for (const n of o.columns)
195
- if (n.contents.some((i) => i.id === t)) return n;
196
- }
197
- function wt(e, t) {
198
- for (const o of e.body.rows)
199
- if (o.columns.some((n) => n.id === t)) return o;
200
- }
201
- function q(e, t) {
202
- return e.body.rows.findIndex((o) => o.id === t);
203
- }
204
- class vt {
205
- constructor() {
206
- this.history = new ft(), this.counterManager = ut(), this.subscribers = /* @__PURE__ */ new Set(), this.channelSubscribers = /* @__PURE__ */ new Map(), this.events = new gt(), this._selectedId = null, this._hoveredId = null, this._viewMode = "desktop", this._activeTab = "content", this.design = bt();
207
- }
208
- // ── Subscriptions ──────────────────────────────────────────
209
- /** Subscribe to ALL state changes (legacy). Returns an unsubscribe function. */
210
- subscribe(t) {
211
- return this.subscribers.add(t), () => this.subscribers.delete(t);
212
- }
213
- /**
214
- * Subscribe to specific channels only. The callback is invoked only
215
- * when one of the listed channels fires. Returns an unsubscribe function.
216
- *
217
- * @example
218
- * ```ts
219
- * // Only re-render when the design changes, not on hover/selection
220
- * store.subscribeChannels(['design'], () => this.requestUpdate());
221
- * ```
222
- */
223
- subscribeChannels(t, o) {
224
- for (const n of t)
225
- this.channelSubscribers.has(n) || this.channelSubscribers.set(n, /* @__PURE__ */ new Set()), this.channelSubscribers.get(n).add(o);
226
- return () => {
227
- for (const n of t)
228
- this.channelSubscribers.get(n)?.delete(o);
229
- };
230
- }
231
- /** Notify legacy (all) subscribers */
232
- notify() {
233
- this.subscribers.forEach((t) => t());
234
- }
235
- /** Notify only subscribers of specific channels + legacy subscribers */
236
- notifyChannels(...t) {
237
- const o = /* @__PURE__ */ new Set();
238
- for (const n of this.subscribers) o.add(n);
239
- for (const n of t) {
240
- const i = this.channelSubscribers.get(n);
241
- if (i) for (const s of i) o.add(s);
242
- }
243
- o.forEach((n) => n());
244
- }
245
- // ── Getters ────────────────────────────────────────────────
246
- /** Get the full design document */
247
- getDesign() {
248
- return this.design;
249
- }
250
- /** Get the design body */
251
- getBody() {
252
- return this.design.body;
253
- }
254
- /** Get all rows */
255
- getRows() {
256
- return this.design.body.rows;
257
- }
258
- /** Get body-level values (background, fonts, etc.) */
259
- getBodyValues() {
260
- return this.design.body.values;
261
- }
262
- get selectedId() {
263
- return this._selectedId;
264
- }
265
- get hoveredId() {
266
- return this._hoveredId;
267
- }
268
- get viewMode() {
269
- return this._viewMode;
270
- }
271
- get activeTab() {
272
- return this._activeTab;
273
- }
274
- get canUndo() {
275
- return this.history.canUndo;
276
- }
277
- get canRedo() {
278
- return this.history.canRedo;
279
- }
280
- // ── Design Loading ─────────────────────────────────────────
281
- /** Load a design document, resetting history and selection */
282
- loadDesign(t) {
283
- this.design = structuredClone(t), this.counterManager.setCounters(this.design.counters), this.history.clear(), this._selectedId = null, this.notifyChannels("design", "selection"), this.events.emit("design:loaded", { design: this.design });
284
- }
285
- // ── Undo / Redo ────────────────────────────────────────────
286
- undo() {
287
- const t = this.history.undo(this.design);
288
- t && (this.design = t, this.counterManager.setCounters(this.design.counters), this.notifyChannels("design"), this.emitUpdate("content_updated"));
289
- }
290
- redo() {
291
- const t = this.history.redo(this.design);
292
- t && (this.design = t, this.counterManager.setCounters(this.design.counters), this.notifyChannels("design"), this.emitUpdate("content_updated"));
293
- }
294
- // ── Selection / UI State ───────────────────────────────────
295
- select(t) {
296
- this._selectedId = t, this.notifyChannels("selection");
297
- }
298
- hover(t) {
299
- this._hoveredId = t, this.notifyChannels("hover");
300
- }
301
- setViewMode(t) {
302
- this._viewMode = t, this.notifyChannels("viewMode");
303
- }
304
- setActiveTab(t) {
305
- this._activeTab = t, this.notifyChannels("activeTab");
306
- }
307
- // ── Row Operations ─────────────────────────────────────────
308
- /** Add a row at the given index (or at the end) */
309
- addRow(t, o) {
310
- this.history.push(this.design);
311
- const n = this.design.body.rows, i = structuredClone(t);
312
- o !== void 0 && o >= 0 && o <= n.length ? n.splice(o, 0, i) : n.push(i), this.syncCounters(), this.notifyChannels("design"), this.emitUpdate("row_added", i);
313
- }
314
- /** Remove a row by ID */
315
- removeRow(t) {
316
- const o = q(this.design, t);
317
- o !== -1 && (this.history.push(this.design), this.design.body.rows.splice(o, 1), this._selectedId === t && (this._selectedId = null), this.notifyChannels("design"), this.emitUpdate("row_removed"));
318
- }
319
- /** Move a row from one index to another */
320
- moveRow(t, o) {
321
- this.history.push(this.design);
322
- const n = this.design.body.rows, [i] = n.splice(t, 1);
323
- n.splice(o, 0, i), this.notifyChannels("design"), this.emitUpdate("row_reordered");
324
- }
325
- /** Duplicate a row, assigning fresh IDs to all nested elements */
326
- duplicateRow(t) {
327
- const o = N(this.design, t);
328
- if (!o) return;
329
- this.history.push(this.design);
330
- const n = structuredClone(o), i = this.counterManager.next("u_row");
331
- n.id = `u_row_${i}`, n.values._meta = { htmlID: n.id, htmlClassNames: "u_row" };
332
- for (const r of n.columns) {
333
- const a = this.counterManager.next("u_column");
334
- r.id = `u_column_${a}`, r.values._meta = { htmlID: r.id, htmlClassNames: "u_column" };
335
- for (const d of r.contents) {
336
- const c = this.counterManager.next(`u_content_${d.type}`);
337
- d.id = `u_content_${d.type}_${c}`, d.values._meta = { htmlID: d.id, htmlClassNames: `u_content_${d.type}` };
338
- }
339
- }
340
- const s = q(this.design, t);
341
- this.design.body.rows.splice(s + 1, 0, n), this.syncCounters(), this.notifyChannels("design"), this.emitUpdate("row_added", n);
342
- }
343
- /** Get the index of a row */
344
- getRowIndex(t) {
345
- return q(this.design, t);
346
- }
347
- /** Update row-level values */
348
- updateRowValues(t, o) {
349
- const n = N(this.design, t);
350
- n && (this.history.push(this.design), Object.assign(n.values, o), this.notifyChannels("design"), this.emitUpdate("content_updated"));
351
- }
352
- // ── Column Operations ──────────────────────────────────────
353
- /** Update column-level values */
354
- updateColumnValues(t, o) {
355
- const n = j(this.design, t);
356
- n && (this.history.push(this.design), Object.assign(n.values, o), this.notifyChannels("design"), this.emitUpdate("content_updated"));
357
- }
358
- // ── Content Operations ─────────────────────────────────────
359
- /** Add content to a column at the given index */
360
- addContent(t, o, n) {
361
- const i = j(this.design, t);
362
- if (!i) return;
363
- this.history.push(this.design);
364
- const s = structuredClone(o);
365
- n !== void 0 && n >= 0 && n <= i.contents.length ? i.contents.splice(n, 0, s) : i.contents.push(s), this.syncCounters(), this.notifyChannels("design"), this.emitUpdate("content_added", s);
366
- }
367
- /** Remove a content block by ID */
368
- removeContent(t) {
369
- for (const o of this.design.body.rows)
370
- for (const n of o.columns) {
371
- const i = n.contents.findIndex((s) => s.id === t);
372
- if (i !== -1) {
373
- this.history.push(this.design), n.contents.splice(i, 1), this._selectedId === t && (this._selectedId = null), this.notifyChannels("design"), this.emitUpdate("content_removed");
374
- return;
375
- }
376
- }
377
- }
378
- /** Update content values by ID */
379
- updateContentValues(t, o) {
380
- const n = W(this.design, t);
381
- n && (this.history.push(this.design), Object.assign(n.values, o), this.notifyChannels("design"), this.emitUpdate("content_updated"));
382
- }
383
- /** Move a content block to a different column at a given index */
384
- moveContent(t, o, n) {
385
- const i = W(this.design, t);
386
- if (!i) return;
387
- this.history.push(this.design);
388
- for (const r of this.design.body.rows)
389
- for (const a of r.columns) {
390
- const d = a.contents.findIndex((c) => c.id === t);
391
- if (d !== -1) {
392
- a.contents.splice(d, 1);
393
- break;
394
- }
395
- }
396
- const s = j(this.design, o);
397
- s && s.contents.splice(n, 0, i), this.notifyChannels("design"), this.emitUpdate("content_reordered");
398
- }
399
- /** Duplicate a content block, inserting the copy right after the original */
400
- duplicateContent(t) {
401
- const o = W(this.design, t);
402
- if (o)
403
- for (const n of this.design.body.rows)
404
- for (const i of n.columns) {
405
- const s = i.contents.findIndex((r) => r.id === t);
406
- if (s !== -1) {
407
- this.history.push(this.design);
408
- const r = structuredClone(o), a = this.counterManager.next(`u_content_${o.type}`);
409
- r.id = `u_content_${o.type}_${a}`, r.values._meta = { htmlID: r.id, htmlClassNames: `u_content_${o.type}` }, i.contents.splice(s + 1, 0, r), this.syncCounters(), this.notifyChannels("design"), this.emitUpdate("content_added", r);
410
- return;
411
- }
412
- }
413
- }
414
- // ── Body Values ────────────────────────────────────────────
415
- /** Update body-level values (background, fonts, etc.) */
416
- updateBodyValues(t) {
417
- this.history.push(this.design), Object.assign(this.design.body.values, t), this.notifyChannels("design"), this.emitUpdate("body_updated");
418
- }
419
- // ── Lookups (delegate to design-lookup) ────────────────────
420
- findRow(t) {
421
- return N(this.design, t);
422
- }
423
- findColumn(t) {
424
- return j(this.design, t);
425
- }
426
- findContent(t) {
427
- return W(this.design, t);
428
- }
429
- findParentColumn(t) {
430
- return yt(this.design, t);
431
- }
432
- findParentRow(t) {
433
- return wt(this.design, t);
434
- }
435
- // ── Factory Methods (delegate to design-factory) ───────────
436
- /** Create a new row with the given column layout */
437
- createRow(t) {
438
- const o = mt(this.counterManager, t);
439
- return this.syncCounters(), o;
440
- }
441
- /** Create a new content block for the given tool type */
442
- createContent(t, o = {}) {
443
- const n = xt(this.counterManager, t, o);
444
- return this.syncCounters(), n;
445
- }
446
- // ── Private Helpers ────────────────────────────────────────
447
- syncCounters() {
448
- this.design.counters = this.counterManager.getCounters();
449
- }
450
- emitUpdate(t, o) {
451
- this.events.emit("design:updated", { type: t, item: o });
452
- }
453
- }
454
- class $t {
455
- constructor() {
456
- this.tools = /* @__PURE__ */ new Map(), this.lazyLoaders = /* @__PURE__ */ new Map(), this.lazyMeta = /* @__PURE__ */ new Map(), this.loadingPromises = /* @__PURE__ */ new Map();
457
- }
458
- /** Register a tool eagerly (available immediately) */
459
- register(t) {
460
- this.tools.set(t.name, t), this.lazyLoaders.delete(t.name), this.lazyMeta.delete(t.name);
461
- }
462
- /**
463
- * Register a tool lazily. The tool's code is only loaded when first needed.
464
- * Provide metadata (name, label, icon) so the tool can appear in the sidebar.
465
- *
466
- * @param meta - Display metadata for the sidebar palette
467
- * @param loader - Async function that imports and returns the tool definition
468
- *
469
- * @example
470
- * ```ts
471
- * registry.registerLazy(
472
- * { name: 'timer', label: 'Timer', icon: '<svg>...</svg>', position: 11 },
473
- * () => import('./built-in/timer-tool.js').then(m => m.timerTool),
474
- * );
475
- * ```
476
- */
477
- registerLazy(t, o) {
478
- this.tools.has(t.name) || (this.lazyMeta.set(t.name, t), this.lazyLoaders.set(t.name, o));
479
- }
480
- /** Get a tool by name. Returns undefined if not loaded yet (use ensureLoaded for lazy tools). */
481
- get(t) {
482
- return this.tools.get(t);
483
- }
484
- /** Check if a tool is registered (eager or lazy) */
485
- has(t) {
486
- return this.tools.has(t) || this.lazyLoaders.has(t);
487
- }
488
- /** Check if a tool is fully loaded and ready to render */
489
- isLoaded(t) {
490
- return this.tools.has(t);
491
- }
492
- /**
493
- * Ensure a lazy tool is loaded. Returns the tool definition.
494
- * If the tool is already loaded, returns it immediately.
495
- * If it's being loaded, returns the in-flight promise.
496
- */
497
- async ensureLoaded(t) {
498
- if (this.tools.has(t)) return this.tools.get(t);
499
- if (this.loadingPromises.has(t)) return this.loadingPromises.get(t);
500
- const o = this.lazyLoaders.get(t);
501
- if (!o) return;
502
- const n = o().then((i) => (this.tools.set(t, i), this.lazyLoaders.delete(t), this.lazyMeta.delete(t), this.loadingPromises.delete(t), i));
503
- return this.loadingPromises.set(t, n), n;
504
- }
505
- /**
506
- * Get all tools for display in the sidebar palette.
507
- * Returns both loaded tools and lazy tool metadata, sorted by position.
508
- */
509
- getAll() {
510
- return Array.from(this.tools.values()).sort((t, o) => (t.position ?? 0) - (o.position ?? 0));
511
- }
512
- /**
513
- * Get all tool names and display metadata (including lazy tools not yet loaded).
514
- * Used by the sidebar to show all available tools.
515
- */
516
- getAllMeta() {
517
- const t = [];
518
- for (const o of this.tools.values())
519
- t.push({ name: o.name, label: o.label, icon: o.icon, position: o.position });
520
- for (const o of this.lazyMeta.values())
521
- t.push(o);
522
- return t.sort((o, n) => (o.position ?? 0) - (n.position ?? 0));
523
- }
524
- /** Get default values for a tool. Loads lazily if needed (sync — returns empty if not loaded). */
525
- getDefaultValues(t) {
526
- const o = this.tools.get(t);
527
- if (!o) return {};
528
- const n = { ...o.defaultValues };
529
- for (const i of Object.values(o.options))
530
- for (const [s, r] of Object.entries(i.options))
531
- s in n || (n[s] = r.defaultValue);
532
- return n;
533
- }
534
- /** Get property groups for a tool */
535
- getPropertyGroups(t) {
536
- return this.tools.get(t)?.options ?? {};
537
- }
538
- }
539
- const L = {
540
- /** ID of the content currently being dragged (null if not dragging content) */
541
- draggingContentId: null,
542
- startContentDrag(e) {
543
- this.draggingContentId = e;
544
- },
545
- reset() {
546
- this.draggingContentId = null;
547
- }
548
- };
549
- function Z(e) {
550
- const t = document.createElement("div");
551
- return Object.assign(t.style, {
552
- position: "absolute",
553
- left: "0",
554
- right: "0",
555
- height: "3px",
556
- background: e,
557
- borderRadius: "2px",
558
- pointerEvents: "none",
559
- zIndex: "1000",
560
- display: "none",
561
- boxShadow: `0 0 6px ${e}80`
562
- }), t;
563
- }
564
- function tt(e, t, o, n, i = "4px") {
565
- e.parentNode !== t && (e.remove(), t.appendChild(e));
566
- const r = (t instanceof ShadowRoot ? t.host : t).getBoundingClientRect();
567
- let a;
568
- o.length === 0 || n === 0 ? a = o.length === 0 ? 0 : o[0].getBoundingClientRect().top - r.top : n >= o.length ? a = o[o.length - 1].getBoundingClientRect().bottom - r.top : a = o[n].getBoundingClientRect().top - r.top, Object.assign(e.style, {
569
- display: "block",
570
- top: `${a}px`,
571
- left: i,
572
- right: i,
573
- width: "auto"
574
- });
575
- }
576
- function I(e) {
577
- e && (e.style.display = "none");
578
- }
579
- function K(e, t) {
580
- const o = (e instanceof ShadowRoot, e.children);
581
- for (const n of Array.from(o)) {
582
- const i = n;
583
- t(i), i.shadowRoot && K(i.shadowRoot, t), i.children?.length && K(i, t);
584
- }
585
- }
586
- function et(e, t) {
587
- const o = [];
588
- return K(e, (n) => {
589
- n.matches?.(t) && o.push(n);
590
- }), o;
591
- }
592
- class kt {
593
- constructor(t, o, n) {
594
- this.currentDrop = null, this.contentIndicator = null, this.rowIndicator = null, this.onDragOver = (i) => {
595
- const s = i.dataTransfer?.types || [], r = s.includes("application/maileditor-tool"), a = s.includes("application/maileditor-layout"), d = s.includes("application/maileditor-content") || !!L.draggingContentId;
596
- !r && !d && !a || (i.preventDefault(), i.dataTransfer.dropEffect = r || a ? "copy" : "move", a ? (this.currentDrop = this.findRowDropTarget(i.clientY), I(this.contentIndicator), this.showRowIndicator()) : (this.currentDrop = this.findContentDropTarget(i.clientX, i.clientY), I(this.rowIndicator), this.showContentIndicator()));
597
- }, this.onDrop = (i) => {
598
- i.preventDefault(), this.hideAllIndicators();
599
- const s = this.currentDrop, r = i.dataTransfer?.getData("application/maileditor-layout");
600
- if (r) {
601
- this.handleLayoutDrop(JSON.parse(r), s), this.reset();
602
- return;
603
- }
604
- const a = i.dataTransfer?.getData("application/maileditor-tool");
605
- if (a) {
606
- this.handleToolDrop(a, s), this.reset();
607
- return;
608
- }
609
- const d = i.dataTransfer?.getData("application/maileditor-content") || L.draggingContentId;
610
- d && this.handleContentDrop(d, s), this.reset();
611
- }, this.onDragEnd = () => {
612
- this.hideAllIndicators(), this.reset();
613
- }, this.onDragLeave = (i) => {
614
- const s = i.relatedTarget;
615
- (!s || !this.root.contains(s)) && (this.hideAllIndicators(), this.currentDrop = null);
616
- }, this.store = t, this.toolRegistry = o, this.root = n;
617
- }
618
- /** Attach all drag event listeners to the shadow root */
619
- attach() {
620
- this.root.addEventListener("dragover", this.onDragOver), this.root.addEventListener("drop", this.onDrop), this.root.addEventListener("dragend", this.onDragEnd), this.root.addEventListener("dragleave", this.onDragLeave), this.contentIndicator = Z("#3b82f6"), this.rowIndicator = Z("#8b5cf6");
621
- }
622
- /** Remove all event listeners and clean up indicators */
623
- detach() {
624
- this.root.removeEventListener("dragover", this.onDragOver), this.root.removeEventListener("drop", this.onDrop), this.root.removeEventListener("dragend", this.onDragEnd), this.root.removeEventListener("dragleave", this.onDragLeave), this.contentIndicator?.remove(), this.rowIndicator?.remove();
625
- }
626
- // ── Drop Handlers ──────────────────────────────────────────
627
- handleLayoutDrop(t, o = this.currentDrop) {
628
- const n = this.store.createRow(t), i = o?.type === "row" ? o.rowIndex : void 0;
629
- this.store.addRow(n, i);
630
- }
631
- async handleToolDrop(t, o) {
632
- if (await this.toolRegistry.ensureLoaded(t), o?.type === "content" && o.columnId) {
633
- const n = this.toolRegistry.getDefaultValues(t), i = this.store.createContent(t, n);
634
- this.store.addContent(o.columnId, i, o.contentIndex), this.store.select(i.id);
635
- } else {
636
- const n = this.store.createRow([1]);
637
- this.store.addRow(n);
638
- const i = this.toolRegistry.getDefaultValues(t), s = this.store.createContent(t, i);
639
- this.store.addContent(n.columns[0].id, s), this.store.select(s.id);
640
- }
641
- }
642
- handleContentDrop(t, o) {
643
- o?.type === "content" && o.columnId && (this.store.moveContent(t, o.columnId, o.contentIndex), this.store.select(t));
644
- }
645
- // ── Drop Target Detection ─────────────────────────────────
646
- findRowDropTarget(t) {
647
- const o = this.root.querySelector("me-editor-canvas");
648
- if (!o?.shadowRoot) return null;
649
- const n = Array.from(o.shadowRoot.querySelectorAll("me-row-renderer"));
650
- if (n.length === 0) return { type: "row", rowIndex: 0, y: 0 };
651
- let i = Math.abs(t - n[0].getBoundingClientRect().top), s = { type: "row", rowIndex: 0, y: n[0].getBoundingClientRect().top };
652
- for (let r = 0; r < n.length; r++) {
653
- const a = n[r].getBoundingClientRect().bottom, d = Math.abs(t - a);
654
- d < i && (i = d, s = { type: "row", rowIndex: r + 1, y: a });
655
- }
656
- return s;
657
- }
658
- findContentDropTarget(t, o) {
659
- const n = et(this.root, "me-column-renderer");
660
- let i = null, s = 1 / 0;
661
- for (const r of n) {
662
- const a = r.dataset.columnId;
663
- if (!a || !r.shadowRoot) continue;
664
- const d = r.getBoundingClientRect();
665
- if (t < d.left || t > d.right) continue;
666
- const c = Array.from(r.shadowRoot.querySelectorAll("me-content-renderer"));
667
- if (c.length === 0) {
668
- const u = Math.abs(o - (d.top + d.height / 2));
669
- u < s && (s = u, i = { type: "content", columnId: a, contentIndex: 0, y: d.top + d.height / 2 });
670
- continue;
671
- }
672
- const h = c[0].getBoundingClientRect().top;
673
- let g = Math.abs(o - h);
674
- g < s && (s = g, i = { type: "content", columnId: a, contentIndex: 0, y: h });
675
- for (let u = 0; u < c.length; u++) {
676
- const f = c[u].getBoundingClientRect(), y = c[u + 1]?.getBoundingClientRect(), x = y ? (f.bottom + y.top) / 2 : f.bottom;
677
- g = Math.abs(o - x), g < s && (s = g, i = { type: "content", columnId: a, contentIndex: u + 1, y: x });
678
- }
679
- }
680
- return i;
681
- }
682
- // ── Indicator Positioning ──────────────────────────────────
683
- showContentIndicator() {
684
- if (!this.contentIndicator || !this.currentDrop?.columnId) {
685
- I(this.contentIndicator);
686
- return;
687
- }
688
- const o = et(this.root, "me-column-renderer").find((i) => i.dataset.columnId === this.currentDrop.columnId);
689
- if (!o?.shadowRoot) return;
690
- const n = Array.from(o.shadowRoot.querySelectorAll("me-content-renderer"));
691
- tt(this.contentIndicator, o.shadowRoot, n, this.currentDrop.contentIndex ?? 0, "4px");
692
- }
693
- showRowIndicator() {
694
- if (!this.rowIndicator || !this.currentDrop) {
695
- I(this.rowIndicator);
696
- return;
697
- }
698
- const t = this.root.querySelector("me-editor-canvas"), o = t?.shadowRoot?.querySelector(".canvas-body");
699
- if (!o) return;
700
- const n = Array.from(t.shadowRoot.querySelectorAll("me-row-renderer"));
701
- tt(this.rowIndicator, o, n, this.currentDrop.rowIndex ?? 0, "0");
702
- }
703
- hideAllIndicators() {
704
- I(this.contentIndicator), I(this.rowIndicator);
705
- }
706
- reset() {
707
- this.currentDrop = null, L.reset();
708
- }
709
- }
710
- function l(e, t, o = "") {
711
- const n = e[t];
712
- return typeof n == "string" && n !== "" ? n : typeof n == "number" ? String(n) : o;
713
- }
714
- function ot(e) {
715
- return typeof e == "string" ? e : e && typeof e == "object" && "url" in e && e.url || "";
716
- }
717
- function nt(e) {
718
- if (e && typeof e == "object") {
719
- const t = e;
720
- return {
721
- width: typeof t.width == "number" ? t.width : void 0,
722
- maxWidth: typeof t.maxWidth == "string" ? t.maxWidth : void 0
723
- };
724
- }
725
- return {};
726
- }
727
- function Ct(e) {
728
- const t = e.action;
729
- if (t && typeof t == "object") {
730
- const o = t.values;
731
- return {
732
- href: o?.href || "",
733
- target: o?.target || "_blank"
734
- };
735
- }
736
- return {
737
- href: l(e, "href"),
738
- target: l(e, "target", "_blank")
739
- };
740
- }
741
- function it(e, t = "") {
742
- const o = e.text;
743
- if (typeof o == "string" && o !== "") return o;
744
- const n = e.textJson;
745
- if (typeof n == "string")
746
- try {
747
- const i = JSON.parse(n), s = [], r = (a) => {
748
- typeof a.text == "string" && s.push(a.text), a.type === "linebreak" && s.push("<br/>");
749
- const d = a.children;
750
- d && d.forEach(r);
751
- };
752
- if (r(i.root || i), s.length > 0) return s.join("");
753
- } catch {
754
- }
755
- return t;
756
- }
757
- function Y(e, t = "arial,helvetica,sans-serif") {
758
- const o = e.fontFamily;
759
- if (typeof o == "string" && o !== "") return o;
760
- if (o && typeof o == "object") {
761
- const n = o, i = n.value || t, s = n.url;
762
- if (s && typeof document < "u") {
763
- const r = `emabuild-font-${i.replace(/[^a-z]/gi, "")}`;
764
- if (!document.getElementById(r)) {
765
- const a = document.createElement("link");
766
- a.id = r, a.rel = "stylesheet", a.href = s, document.head.appendChild(a);
767
- }
768
- }
769
- return i;
770
- }
771
- return t;
772
- }
773
- function ye(e, t) {
774
- if (typeof e != "string") return t;
775
- try {
776
- return JSON.parse(e);
777
- } catch {
778
- return t;
779
- }
780
- }
781
- function z(e, t) {
782
- const { padding: o, align: n = "left", extraTdStyle: i = "" } = t;
783
- return `<table role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
784
- <tbody><tr><td style="${`padding:${o};font-family:arial,helvetica,sans-serif;${i}`}" align="${n}">
785
- ${e}
786
- </td></tr></tbody>
787
- </table>`;
788
- }
789
- function _t(e, t, o) {
790
- const { bgColor: n, textColor: i, fontSize: s, fontWeight: r, borderRadius: a } = o, d = parseInt(a) || 0;
791
- if (d <= 0) return "";
792
- const c = Math.round(d / 20 * 100);
793
- return `<!--[if mso]>
794
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${t}" style="height:auto;v-text-anchor:middle;width:auto;" arcsize="${c}%" stroke="f" fillcolor="${n}">
795
- <w:anchorlock/>
796
- <center style="color:${i};font-family:arial,helvetica,sans-serif;font-size:${s};font-weight:${r};">${e}</center>
797
- </v:roundrect>
798
- <![endif]-->`;
799
- }
800
- const Dt = {
801
- name: "text",
802
- label: "Text",
803
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>',
804
- supportedDisplayModes: ["email", "web"],
805
- position: 1,
806
- options: {
807
- text: {
808
- title: "Text",
809
- options: {
810
- text: {
811
- label: "Text Content",
812
- defaultValue: '<p style="font-size: 14px;">This is a new text block. Change the text.</p>',
813
- widget: "rich_text"
814
- }
815
- }
816
- },
817
- style: {
818
- title: "Style",
819
- options: {
820
- color: { label: "Text Color", defaultValue: "#000000", widget: "color_picker" },
821
- backgroundColor: { label: "Background Color", defaultValue: "", widget: "color_picker" },
822
- textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" },
823
- lineHeight: {
824
- label: "Line Height",
825
- defaultValue: "140%",
826
- widget: "dropdown",
827
- widgetParams: { options: [
828
- { label: "100%", value: "100%" },
829
- { label: "120%", value: "120%" },
830
- { label: "140%", value: "140%" },
831
- { label: "160%", value: "160%" },
832
- { label: "180%", value: "180%" },
833
- { label: "200%", value: "200%" }
834
- ] }
835
- }
836
- }
837
- },
838
- spacing: {
839
- title: "Spacing",
840
- options: {
841
- containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
842
- }
843
- },
844
- general: {
845
- title: "General",
846
- options: {
847
- anchor: { label: "Anchor", defaultValue: "", widget: "text" },
848
- hideDesktop: { label: "Hide on Desktop", defaultValue: !1, widget: "toggle" },
849
- hideMobile: { label: "Hide on Mobile", defaultValue: !1, widget: "toggle" }
850
- }
851
- }
852
- },
853
- defaultValues: {
854
- text: '<p style="font-size: 14px;">This is a new text block. Change the text.</p>',
855
- color: "#000000",
856
- backgroundColor: "",
857
- lineHeight: "140%",
858
- containerPadding: "10px",
859
- textAlign: "left"
860
- },
861
- renderer: {
862
- renderEditor(e) {
863
- const t = l(e, "containerPadding", "10px"), o = l(e, "backgroundColor", "transparent"), n = l(e, "color", "inherit"), i = l(e, "lineHeight", "140%"), s = Y(e), r = l(e, "text");
864
- return p`
865
- <div style="padding:${t};background-color:${o};color:${n};line-height:${i};font-family:${s};word-break:break-word;">
866
- ${U(r)}
867
- </div>
868
- `;
869
- },
870
- renderHtml(e) {
871
- const t = l(e, "containerPadding", "10px"), o = l(e, "backgroundColor"), n = l(e, "color", "#000000"), i = l(e, "lineHeight", "140%"), s = l(e, "textAlign", "left"), r = l(e, "text"), a = o ? `background-color:${o};` : "", d = `<div style="font-size:14px;color:${n};line-height:${i};text-align:${s};">${r}</div>`;
872
- return z(d, { padding: t, extraTdStyle: a });
873
- }
874
- }
875
- };
876
- const St = {
877
- name: "heading",
878
- label: "Heading",
879
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12h12"/><path d="M6 4v16"/><path d="M18 4v16"/></svg>',
880
- supportedDisplayModes: ["email", "web"],
881
- position: 2,
882
- options: {
883
- text: {
884
- title: "Heading",
885
- options: {
886
- text: { label: "Text", defaultValue: "Heading", widget: "text" },
887
- headingType: {
888
- label: "Heading Type",
889
- defaultValue: "h1",
890
- widget: "dropdown",
891
- widgetParams: { options: [
892
- { label: "H1", value: "h1" },
893
- { label: "H2", value: "h2" },
894
- { label: "H3", value: "h3" },
895
- { label: "H4", value: "h4" }
896
- ] }
897
- }
898
- }
899
- },
900
- style: {
901
- title: "Style",
902
- options: {
903
- fontSize: {
904
- label: "Font Size",
905
- defaultValue: "22px",
906
- widget: "dropdown",
907
- widgetParams: { options: [
908
- { label: "12px", value: "12px" },
909
- { label: "14px", value: "14px" },
910
- { label: "16px", value: "16px" },
911
- { label: "18px", value: "18px" },
912
- { label: "20px", value: "20px" },
913
- { label: "22px", value: "22px" },
914
- { label: "26px", value: "26px" },
915
- { label: "30px", value: "30px" },
916
- { label: "36px", value: "36px" },
917
- { label: "48px", value: "48px" },
918
- { label: "60px", value: "60px" }
919
- ] }
920
- },
921
- color: { label: "Text Color", defaultValue: "#000000", widget: "color_picker" },
922
- textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" },
923
- fontWeight: {
924
- label: "Font Weight",
925
- defaultValue: "700",
926
- widget: "dropdown",
927
- widgetParams: { options: [
928
- { label: "Normal", value: "400" },
929
- { label: "Medium", value: "500" },
930
- { label: "Semi Bold", value: "600" },
931
- { label: "Bold", value: "700" },
932
- { label: "Extra Bold", value: "800" }
933
- ] }
934
- },
935
- lineHeight: {
936
- label: "Line Height",
937
- defaultValue: "140%",
938
- widget: "dropdown",
939
- widgetParams: { options: [
940
- { label: "100%", value: "100%" },
941
- { label: "120%", value: "120%" },
942
- { label: "140%", value: "140%" },
943
- { label: "160%", value: "160%" },
944
- { label: "180%", value: "180%" },
945
- { label: "200%", value: "200%" }
946
- ] }
947
- },
948
- letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" }
949
- }
950
- },
951
- spacing: {
952
- title: "Spacing",
953
- options: {
954
- containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
955
- }
956
- },
957
- general: {
958
- title: "General",
959
- options: {
960
- anchor: { label: "Anchor", defaultValue: "", widget: "text" },
961
- hideDesktop: { label: "Hide on Desktop", defaultValue: !1, widget: "toggle" },
962
- hideMobile: { label: "Hide on Mobile", defaultValue: !1, widget: "toggle" }
963
- }
964
- }
965
- },
966
- defaultValues: {
967
- text: "Heading",
968
- headingType: "h1",
969
- fontSize: "22px",
970
- color: "#000000",
971
- textAlign: "left",
972
- fontWeight: "700",
973
- lineHeight: "140%",
974
- letterSpacing: "normal",
975
- containerPadding: "10px"
976
- },
977
- renderer: {
978
- renderEditor(e) {
979
- const t = {
980
- padding: l(e, "containerPadding", "10px"),
981
- fontSize: l(e, "fontSize", "22px"),
982
- color: l(e, "color", "#000000"),
983
- textAlign: l(e, "textAlign", "left"),
984
- fontWeight: l(e, "fontWeight", "700"),
985
- lineHeight: l(e, "lineHeight", "140%"),
986
- letterSpacing: l(e, "letterSpacing", "normal"),
987
- fontFamily: Y(e)
988
- }, o = it(e, "Heading");
989
- return p`<div style=${ht(t)}>${U(o)}</div>`;
990
- },
991
- renderHtml(e) {
992
- const t = l(e, "containerPadding", "10px"), o = l(e, "fontSize", "22px"), n = l(e, "color", "#000000"), i = l(e, "textAlign", "left"), s = l(e, "fontWeight", "700"), r = l(e, "lineHeight", "140%"), a = l(e, "letterSpacing", "normal"), d = Y(e), c = l(e, "headingType", "h1"), h = it(e, "Heading"), g = `<${c} style="margin:0;font-size:${o};color:${n};text-align:${i};font-weight:${s};line-height:${r};letter-spacing:${a};font-family:${d};">${h}</${c}>`;
993
- return z(g, { padding: t });
994
- }
995
- }
996
- }, Rt = {
997
- name: "paragraph",
998
- label: "Paragraph",
999
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="3" y1="14" x2="17" y2="14"/></svg>',
1000
- supportedDisplayModes: ["email", "web"],
1001
- position: 3,
1002
- options: {
1003
- text: {
1004
- title: "Paragraph",
1005
- options: {
1006
- text: {
1007
- label: "Text",
1008
- defaultValue: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
1009
- widget: "rich_text"
1010
- }
1011
- }
1012
- },
1013
- style: {
1014
- title: "Style",
1015
- options: {
1016
- color: { label: "Text Color", defaultValue: "#374151", widget: "color_picker" },
1017
- textAlign: { label: "Text Align", defaultValue: "left", widget: "alignment" },
1018
- lineHeight: { label: "Line Height", defaultValue: "160%", widget: "text" },
1019
- letterSpacing: { label: "Letter Spacing", defaultValue: "normal", widget: "text" }
1020
- }
1021
- },
1022
- spacing: {
1023
- title: "Spacing",
1024
- options: {
1025
- containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" }
1026
- }
1027
- }
1028
- },
1029
- defaultValues: {
1030
- text: '<p style="font-size:14px;line-height:1.6;">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>',
1031
- color: "#374151",
1032
- lineHeight: "160%",
1033
- letterSpacing: "normal",
1034
- textAlign: "left",
1035
- containerPadding: "10px"
1036
- },
1037
- renderer: {
1038
- renderEditor(e) {
1039
- const t = l(e, "containerPadding", "10px"), o = l(e, "color", "#374151"), n = l(e, "lineHeight", "160%");
1040
- return p`<div style="padding:${t};color:${o};line-height:${n};word-break:break-word;">${U(l(e, "text"))}</div>`;
1041
- },
1042
- renderHtml(e) {
1043
- const t = l(e, "containerPadding", "10px"), o = l(e, "color", "#374151"), n = l(e, "lineHeight", "160%"), i = l(e, "textAlign", "left"), s = l(e, "letterSpacing", "normal"), r = `<div style="font-size:14px;color:${o};line-height:${n};text-align:${i};letter-spacing:${s};word-wrap:break-word;">${l(e, "text")}</div>`;
1044
- return z(r, { padding: t });
1045
- }
1046
- }
1047
- }, Tt = {
1048
- name: "image",
1049
- label: "Image",
1050
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
1051
- supportedDisplayModes: ["email", "web"],
1052
- position: 3,
1053
- options: {
1054
- image: {
1055
- title: "Image",
1056
- options: {
1057
- src: { label: "Image URL", defaultValue: "", widget: "text" },
1058
- alt: { label: "Alt Text", defaultValue: "", widget: "text" },
1059
- href: { label: "Link URL", defaultValue: "", widget: "text" },
1060
- target: { label: "Link Target", defaultValue: "_blank", widget: "text" }
1061
- }
1062
- },
1063
- style: {
1064
- title: "Style",
1065
- options: {
1066
- width: {
1067
- label: "Width",
1068
- defaultValue: "100%",
1069
- widget: "dropdown",
1070
- widgetParams: { options: [
1071
- { label: "Auto", value: "auto" },
1072
- { label: "25%", value: "25%" },
1073
- { label: "50%", value: "50%" },
1074
- { label: "75%", value: "75%" },
1075
- { label: "100%", value: "100%" }
1076
- ] }
1077
- },
1078
- align: { label: "Align", defaultValue: "center", widget: "alignment" },
1079
- borderRadius: { label: "Border Radius", defaultValue: "0px", widget: "text" }
1080
- }
1081
- },
1082
- spacing: {
1083
- title: "Spacing",
1084
- options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
1085
- },
1086
- general: {
1087
- title: "General",
1088
- options: {
1089
- anchor: { label: "Anchor", defaultValue: "", widget: "text" },
1090
- hideDesktop: { label: "Hide on Desktop", defaultValue: !1, widget: "toggle" },
1091
- hideMobile: { label: "Hide on Mobile", defaultValue: !1, widget: "toggle" }
1092
- }
1093
- }
1094
- },
1095
- defaultValues: {
1096
- src: "https://placehold.co/600x200/e2e8f0/64748b?text=Drop+Image+Here",
1097
- alt: "Image",
1098
- href: "",
1099
- target: "_blank",
1100
- width: "100%",
1101
- maxWidth: "100%",
1102
- align: "center",
1103
- borderRadius: "0px",
1104
- containerPadding: "10px"
1105
- },
1106
- renderer: {
1107
- renderEditor(e) {
1108
- const t = l(e, "containerPadding", "10px"), o = ot(e.src), n = l(e, "alt"), s = nt(e.src).maxWidth || l(e, "width", "100%"), r = l(e, "borderRadius", "0px"), a = l(e, "textAlign", l(e, "align", "center"));
1109
- return o ? p`<div style="padding:${t};text-align:${a};"><img src=${o} alt=${n} style="display:inline-block;max-width:100%;width:${s};border-radius:${r};border:0;" /></div>` : p`<div style="padding:${t};text-align:${a};"><div style="background:#f1f5f9;border:2px dashed #cbd5e1;border-radius:8px;padding:40px 20px;text-align:center;color:#94a3b8;font-size:13px;">No image set. Enter a URL in the property panel.</div></div>`;
1110
- },
1111
- renderHtml(e, t) {
1112
- const o = l(e, "containerPadding", "10px"), n = ot(e.src), i = l(e, "alt"), s = Ct(e), r = nt(e.src), a = r.maxWidth || l(e, "width", "100%"), d = l(e, "borderRadius", "0px"), c = l(e, "textAlign", l(e, "align", "center"));
1113
- let h;
1114
- a.includes("%") ? h = Math.round(t.columnWidth * (parseFloat(a) / 100)) : a === "auto" ? h = r.width || t.columnWidth : h = parseInt(a) || t.columnWidth;
1115
- const g = d !== "0px" ? `border-radius:${d};` : "", u = `<img align="${c}" border="0" src="${n}" alt="${i}" title="${i}" style="display:block;border:0;height:auto;width:100%;max-width:${h}px;${g}" width="${h}" />`, f = s.href ? `<a href="${s.href}" target="${s.target}" style="text-decoration:none;">${u}</a>` : u;
1116
- return z(f, { padding: o, align: c });
1117
- }
1118
- }
1119
- }, It = {
1120
- name: "button",
1121
- label: "Button",
1122
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M12 7v10"/><path d="m8 12 4-3 4 3"/></svg>',
1123
- supportedDisplayModes: ["email", "web"],
1124
- position: 4,
1125
- options: {
1126
- button: {
1127
- title: "Button",
1128
- options: {
1129
- text: { label: "Button Text", defaultValue: "Click Me", widget: "text" },
1130
- href: { label: "Link URL", defaultValue: "#", widget: "text" },
1131
- target: { label: "Target", defaultValue: "_blank", widget: "text" }
1132
- }
1133
- },
1134
- style: {
1135
- title: "Style",
1136
- options: {
1137
- backgroundColor: { label: "Button Color", defaultValue: "#3b82f6", widget: "color_picker" },
1138
- textColor: { label: "Text Color", defaultValue: "#ffffff", widget: "color_picker" },
1139
- fontSize: {
1140
- label: "Font Size",
1141
- defaultValue: "14px",
1142
- widget: "dropdown",
1143
- widgetParams: { options: [
1144
- { label: "12px", value: "12px" },
1145
- { label: "13px", value: "13px" },
1146
- { label: "14px", value: "14px" },
1147
- { label: "16px", value: "16px" },
1148
- { label: "18px", value: "18px" },
1149
- { label: "20px", value: "20px" }
1150
- ] }
1151
- },
1152
- fontWeight: {
1153
- label: "Font Weight",
1154
- defaultValue: "700",
1155
- widget: "dropdown",
1156
- widgetParams: { options: [{ label: "Normal", value: "400" }, { label: "Bold", value: "700" }] }
1157
- },
1158
- borderRadius: { label: "Border Radius", defaultValue: "4px", widget: "text" },
1159
- buttonWidth: {
1160
- label: "Width",
1161
- defaultValue: "auto",
1162
- widget: "dropdown",
1163
- widgetParams: { options: [
1164
- { label: "Auto", value: "auto" },
1165
- { label: "100%", value: "100%" },
1166
- { label: "50%", value: "50%" }
1167
- ] }
1168
- },
1169
- textAlign: { label: "Align", defaultValue: "center", widget: "alignment" },
1170
- buttonPadding: { label: "Button Padding", defaultValue: "10px 20px", widget: "padding" },
1171
- borderColor: { label: "Border Color", defaultValue: "", widget: "color_picker" },
1172
- borderWidth: { label: "Border Width", defaultValue: "0px", widget: "text" }
1173
- }
1174
- },
1175
- spacing: {
1176
- title: "Spacing",
1177
- options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
1178
- },
1179
- general: {
1180
- title: "General",
1181
- options: {
1182
- anchor: { label: "Anchor", defaultValue: "", widget: "text" },
1183
- hideDesktop: { label: "Hide on Desktop", defaultValue: !1, widget: "toggle" },
1184
- hideMobile: { label: "Hide on Mobile", defaultValue: !1, widget: "toggle" }
1185
- }
1186
- }
1187
- },
1188
- defaultValues: {
1189
- text: "Click Me",
1190
- href: "#",
1191
- target: "_blank",
1192
- backgroundColor: "#3b82f6",
1193
- textColor: "#ffffff",
1194
- fontSize: "14px",
1195
- fontWeight: "700",
1196
- borderRadius: "4px",
1197
- buttonWidth: "auto",
1198
- textAlign: "center",
1199
- buttonPadding: "10px 20px",
1200
- borderColor: "",
1201
- borderWidth: "0px",
1202
- containerPadding: "10px"
1203
- },
1204
- renderer: {
1205
- renderEditor(e) {
1206
- const t = l(e, "containerPadding", "10px"), o = l(e, "backgroundColor", "#3b82f6"), n = l(e, "textColor", "#ffffff"), i = l(e, "fontSize", "14px"), s = l(e, "fontWeight", "700"), r = l(e, "borderRadius", "4px"), a = l(e, "buttonPadding", "10px 20px"), d = l(e, "text", "Click Me"), c = l(e, "textAlign", "center"), h = l(e, "buttonWidth", "auto"), g = l(e, "borderWidth", "0px"), u = l(e, "borderColor", o), f = g !== "0px" ? `border:${g} solid ${u};` : "border:none;", y = h === "auto" ? "display:inline-block;" : `display:block;width:${h};`;
1207
- return p`
1208
- <div style="padding:${t};text-align:${c};">
1209
- <a style="${y}background-color:${o};color:${n};font-size:${i};font-weight:${s};border-radius:${r};padding:${a};text-decoration:none;text-align:center;${f}cursor:pointer;font-family:arial,helvetica,sans-serif;">${d}</a>
1210
- </div>
1211
- `;
1212
- },
1213
- renderHtml(e) {
1214
- const t = l(e, "containerPadding", "10px"), o = l(e, "backgroundColor", "#3b82f6"), n = l(e, "textColor", "#ffffff"), i = l(e, "fontSize", "14px"), s = l(e, "fontWeight", "700"), r = l(e, "borderRadius", "4px"), a = l(e, "buttonPadding", "10px 20px"), d = l(e, "text", "Click Me"), c = l(e, "textAlign", "center"), h = l(e, "href", "#"), g = l(e, "target", "_blank"), u = l(e, "borderWidth", "0px"), f = l(e, "borderColor", o), y = u !== "0px" ? `border:${u} solid ${f};` : "border:none;", x = _t(d, h, { bgColor: o, textColor: n, fontSize: i, fontWeight: s, borderRadius: r }), m = x ? `${x}
1215
- <!--[if !mso]><!-->` : "<!--[if !mso]><!-->", v = `<div align="${c}">
1216
- ${m}
1217
- <a href="${h}" target="${g}" style="display:inline-block;text-decoration:none;text-align:center;color:${n};background-color:${o};border-radius:${r};font-size:${i};font-weight:${s};padding:${a};font-family:arial,helvetica,sans-serif;${y}mso-border-alt:none;"><span style="line-height:120%;">${d}</span></a>
1218
- <!--<![endif]-->
1219
- </div>`;
1220
- return z(v, { padding: t, align: c });
1221
- }
1222
- }
1223
- }, Mt = {
1224
- name: "divider",
1225
- label: "Divider",
1226
- icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="2" y1="12" x2="22" y2="12"/></svg>',
1227
- supportedDisplayModes: ["email", "web"],
1228
- position: 5,
1229
- options: {
1230
- style: {
1231
- title: "Style",
1232
- options: {
1233
- borderTopWidth: { label: "Width", defaultValue: "1px", widget: "text" },
1234
- borderTopStyle: {
1235
- label: "Style",
1236
- defaultValue: "solid",
1237
- widget: "dropdown",
1238
- widgetParams: { options: [
1239
- { label: "Solid", value: "solid" },
1240
- { label: "Dashed", value: "dashed" },
1241
- { label: "Dotted", value: "dotted" },
1242
- { label: "Double", value: "double" }
1243
- ] }
1244
- },
1245
- borderTopColor: { label: "Color", defaultValue: "#cccccc", widget: "color_picker" },
1246
- width: { label: "Line Width", defaultValue: "100%", widget: "text" }
1247
- }
1248
- },
1249
- spacing: {
1250
- title: "Spacing",
1251
- options: { containerPadding: { label: "Padding", defaultValue: "10px", widget: "padding" } }
1252
- },
1253
- general: {
1254
- title: "General",
1255
- options: {
1256
- hideDesktop: { label: "Hide on Desktop", defaultValue: !1, widget: "toggle" },
1257
- hideMobile: { label: "Hide on Mobile", defaultValue: !1, widget: "toggle" }
1258
- }
1259
- }
1260
- },
1261
- defaultValues: {
1262
- borderTopWidth: "1px",
1263
- borderTopStyle: "solid",
1264
- borderTopColor: "#cccccc",
1265
- width: "100%",
1266
- containerPadding: "10px"
1267
- },
1268
- renderer: {
1269
- renderEditor(e) {
1270
- const t = l(e, "containerPadding", "10px"), o = l(e, "width", "100%"), n = `${l(e, "borderTopWidth", "1px")} ${l(e, "borderTopStyle", "solid")} ${l(e, "borderTopColor", "#cccccc")}`;
1271
- return p`<div style="padding:${t};"><div style="border-top:${n};width:${o};margin:0 auto;"></div></div>`;
1272
- },
1273
- renderHtml(e) {
1274
- const t = l(e, "containerPadding", "10px"), o = l(e, "width", "100%"), n = `${l(e, "borderTopWidth", "1px")} ${l(e, "borderTopStyle", "solid")} ${l(e, "borderTopColor", "#cccccc")}`, i = `<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${o}" style="border-collapse:collapse;border-top:${n};"><tbody><tr><td style="font-size:0;line-height:0;">&nbsp;</td></tr></tbody></table>`;
1275
- return z(i, { padding: t, align: "center" });
1276
- }
1277
- }
1278
- }, Pt = [
1279
- Dt,
1280
- St,
1281
- Rt,
1282
- Tt,
1283
- It,
1284
- Mt
1285
- ], st = [
1286
- {
1287
- meta: { name: "html", label: "HTML", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>', position: 6 },
1288
- loader: () => import("./html-tool-D4ay2h-U.js").then((e) => e.htmlTool)
1289
- },
1290
- {
1291
- meta: { name: "social", label: "Social", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>', position: 8 },
1292
- loader: () => import("./social-tool-B8Jg2yE-.js").then((e) => e.socialTool)
1293
- },
1294
- {
1295
- meta: { name: "menu", label: "Menu", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>', position: 9 },
1296
- loader: () => import("./menu-tool-KvGDbaYD.js").then((e) => e.menuTool)
1297
- },
1298
- {
1299
- meta: { name: "video", label: "Video", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>', position: 10 },
1300
- loader: () => import("./video-tool-CdGVmZxz.js").then((e) => e.videoTool)
1301
- },
1302
- {
1303
- meta: { name: "timer", label: "Timer", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>', position: 11 },
1304
- loader: () => import("./timer-tool-Ck1ERDW-.js").then((e) => e.timerTool)
1305
- },
1306
- {
1307
- meta: { name: "table", label: "Table", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/><path d="M9 3v18"/><path d="M15 3v18"/></svg>', position: 12 },
1308
- loader: () => import("./table-tool-BzpD08dq.js").then((e) => e.tableTool)
1309
- },
1310
- {
1311
- meta: { name: "form", label: "Form", icon: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10"/><path d="M7 12h10"/><path d="M7 17h6"/></svg>', position: 13 },
1312
- loader: () => import("./form-tool-CduLiZgt.js").then((e) => e.formTool)
1313
- }
1314
- ];
1315
- function Et(e, t, o) {
1316
- const n = o.backgroundColor || "#e7e7e7", i = o.contentWidth || "600px", s = o.fontFamily?.value || "arial,helvetica,sans-serif", r = o.textColor || "#000000", a = o.preheaderText || "" || "&zwnj;", d = `<div style="display:none;font-size:1px;color:${n};line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">${a}${"&zwnj;&nbsp;".repeat(80)}</div>`;
1317
- return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1318
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
1319
- <head>
1320
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1321
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1322
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
1323
- <meta name="x-apple-disable-message-reformatting">
1324
- <meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
1325
- <meta name="color-scheme" content="light dark">
1326
- <meta name="supported-color-schemes" content="light dark">
1327
- <title></title>
1328
- <!--[if mso]>
1329
- <noscript><xml>
1330
- <o:OfficeDocumentSettings>
1331
- <o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch>
1332
- </o:OfficeDocumentSettings>
1333
- </xml></noscript>
1334
- <style type="text/css">
1335
- table, td, th { font-family: ${s} !important; }
1336
- </style>
1337
- <![endif]-->
1338
- <!--[if !mso]><!-->
1339
- <style type="text/css">
1340
- ${t}
1341
- </style>
1342
- <!--<![endif]-->
1343
- <style type="text/css">
1344
- body { margin: 0; padding: 0; word-break: normal; }
1345
- table, tr, td { vertical-align: top; border-collapse: collapse; }
1346
- p { margin: 0; }
1347
- a[x-apple-data-detectors='true'] { color: inherit !important; text-decoration: none !important; }
1348
- </style>
1349
- </head>
1350
- <body class="clean-body u_body" style="margin:0;padding:0;-webkit-text-size-adjust:100%;background-color:${n};color:${r};">
1351
- ${d}
1352
- <table id="u_body" role="presentation" style="border-collapse:collapse;border-spacing:0;margin:0 auto;background-color:${n};width:100%;" cellpadding="0" cellspacing="0" border="0">
1353
- <tbody>
1354
- <tr>
1355
- <td style="vertical-align:top;">
1356
- <!--[if (mso)|(IE)]><table width="${parseInt(i)}" align="center" cellpadding="0" cellspacing="0" border="0"><tr><td><![endif]-->
1357
- ${e}
1358
- <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
1359
- </td>
1360
- </tr>
1361
- </tbody>
1362
- </table>
1363
- </body>
1364
- </html>`;
1365
- }
1366
- function zt(e, t, o) {
1367
- const n = parseInt(t.contentWidth || "600"), i = e.values.backgroundColor || "", s = e.values.columnsBackgroundColor || "", r = e.values.padding || "0px", a = e.cells.reduce((m, w) => m + w, 0), d = i ? `background-color:${i};` : "", c = e.values.backgroundImage;
1368
- let h = "";
1369
- if (c?.url) {
1370
- const m = c.repeat === !0 || c.repeat === "repeat" ? "repeat" : "no-repeat", w = c.cover === !0 ? "cover" : c.fullWidth === !0 ? "100% auto" : "auto", v = c.center !== !1 ? "center top" : "left top";
1371
- h = `background-image:url('${c.url}');background-repeat:${m};background-position:${v};background-size:${w};`;
1372
- }
1373
- const g = e.columns.length > 1, u = e.columns.map((m, w) => {
1374
- const v = Math.round(e.cells[w] / a * n);
1375
- return { colHtml: Lt(m, v, s, t, o), colWidthPx: v };
1376
- });
1377
- let f;
1378
- if (g) {
1379
- const m = u.map(
1380
- ({ colHtml: w, colWidthPx: v }) => `<!--[if (mso)|(IE)]><td align="center" width="${v}" style="width:${v}px;padding:0px;" valign="top"><![endif]-->${w}<!--[if (mso)|(IE)]></td><![endif]-->`
1381
- );
1382
- f = `<!--[if (mso)|(IE)]><table role="presentation" width="${n}" cellpadding="0" cellspacing="0" border="0"><tr><![endif]-->${m.join("")}<!--[if (mso)|(IE)]></tr></table><![endif]-->`;
1383
- } else
1384
- f = u.map(({ colHtml: m }) => m).join("");
1385
- const y = e.values.hideDesktop ? " u_hide_desktop" : "", x = e.values.hideMobile ? " u_hide_mobile" : "";
1386
- return `<div class="u_row${y}${x}" style="padding:${r};${d}${h}">
1387
- <div style="margin:0 auto;max-width:${n}px;${g ? "font-size:0;" : ""}text-align:center;">${f}</div>
1388
- </div>`;
1389
- }
1390
- function Lt(e, t, o, n, i) {
1391
- const s = e.values.backgroundColor || o || "", r = e.values.padding || "0px", a = e.values.borderRadius || "0px", d = s ? `background-color:${s};` : "", c = e.contents.map((h) => {
1392
- const g = i.get(h.type);
1393
- if (!g) return `<!-- unknown tool: ${h.type} -->`;
1394
- const u = {
1395
- columnWidth: t,
1396
- displayMode: "email",
1397
- contentWidth: parseInt(n.contentWidth || "600"),
1398
- bodyValues: n
1399
- };
1400
- let f = g(h.values, u);
1401
- const y = !!h.values.hideDesktop, x = !!h.values.hideMobile;
1402
- return (y || x) && (f = `<div class="${[y && "u_hide_desktop", x && "u_hide_mobile"].filter(Boolean).join(" ")}">${f}</div>`), f;
1403
- }).join(`
1404
- `);
1405
- return `<div class="u_column" style="display:inline-block;vertical-align:top;width:${t}px;max-width:${t}px;font-size:14px;text-align:left;">
1406
- <div style="width:100%;${d}${a !== "0px" ? `border-radius:${a};` : ""}">
1407
- <div style="padding:${r};">
1408
- ${c || "&nbsp;"}
1409
- </div>
1410
- </div>
1411
- </div>`;
1412
- }
1413
- function Vt(e) {
1414
- return `
1415
- @media only screen and (min-width: ${e + 20}px) {
1416
- .u_row .u_column { display: inline-block !important; }
1417
- }
1418
-
1419
- @media only screen and (max-width: ${e + 20}px) {
1420
- .u_row .u_column {
1421
- display: block !important;
1422
- width: 100% !important;
1423
- max-width: 100% !important;
1424
- }
1425
- .u_row {
1426
- width: 100% !important;
1427
- }
1428
- }
1429
-
1430
- @media only screen and (max-width: 620px) {
1431
- .u_row-container {
1432
- max-width: 100% !important;
1433
- padding-left: 0 !important;
1434
- padding-right: 0 !important;
1435
- }
1436
- }
1437
-
1438
- @media (prefers-color-scheme: dark) {
1439
- /* Dark mode overrides — add per-client rules as needed */
1440
- }
1441
-
1442
- /* Outlook dark mode */
1443
- [data-ogsb] body,
1444
- [data-ogsb] table,
1445
- [data-ogsb] td {
1446
- /* Preserve original colors */
1447
- }
1448
-
1449
- .u_hide_desktop { display: block !important; }
1450
- .u_hide_mobile { display: block !important; }
1451
-
1452
- @media only screen and (max-width: ${e + 20}px) {
1453
- .u_hide_desktop { display: block !important; }
1454
- .u_hide_mobile { display: none !important; }
1455
- }
1456
-
1457
- @media only screen and (min-width: ${e + 21}px) {
1458
- .u_hide_desktop { display: none !important; }
1459
- .u_hide_mobile { display: block !important; }
1460
- }`;
1461
- }
1462
- const At = [
1463
- "box-sizing",
1464
- "float",
1465
- "overflow-wrap",
1466
- "word-break",
1467
- "word-wrap",
1468
- "outline",
1469
- "cursor",
1470
- "transition",
1471
- "animation",
1472
- "transform",
1473
- "position",
1474
- "z-index",
1475
- "display:\\s*flex",
1476
- "display:\\s*grid",
1477
- "gap"
1478
- ], Bt = new RegExp(
1479
- `(?:;\\s*|^\\s*)(${At.join("|")})\\s*:[^;]*;?`,
1480
- "gi"
1481
- ), Ot = /var\(--[^)]*\)/gi;
1482
- function Ht(e) {
1483
- return e.replace(/style="([^"]*)"/gi, (t, o) => {
1484
- let n = o;
1485
- return n = n.replace(Bt, ""), n = n.replace(Ot, "inherit"), n = n.replace(/;\s*;/g, ";").replace(/^\s*;\s*/, "").replace(/;\s*$/, "").trim(), n ? `style="${n}"` : "";
1486
- });
1487
- }
1488
- function jt(e, t, o) {
1489
- const n = e.body.values, i = parseInt(n.contentWidth || "600"), s = e.body.rows.map((u) => zt(u, n, t)).join(`
1490
- `), r = Vt(i), a = Ht(s);
1491
- let d = Et(a, r, n);
1492
- if (o?.mergeTags)
1493
- for (const [u, f] of Object.entries(o.mergeTags))
1494
- d = d.replaceAll(`{{${u}}}`, f);
1495
- const c = d.match(/<body[^>]*>([\s\S]*)<\/body>/i), h = d.match(/<style[^>]*>([\s\S]*?)<\/style>/gi), g = [];
1496
- return n.fontFamily?.url && g.push(n.fontFamily.url), {
1497
- design: structuredClone(e),
1498
- html: d,
1499
- chunks: {
1500
- body: c?.[1] ?? s,
1501
- css: h?.map((u) => u.replace(/<\/?style[^>]*>/gi, "")).join(`
1502
- `) ?? r,
1503
- fonts: g,
1504
- js: ""
1505
- }
1506
- };
1507
- }
1508
- class T {
1509
- /**
1510
- * @param host - The Lit component that owns this controller
1511
- * @param channels - Which store channels to subscribe to
1512
- */
1513
- constructor(t, o) {
1514
- this.store = null, this.host = t, this.channels = o, t.addController(this);
1515
- }
1516
- /** Set or change the store reference. Re-subscribes automatically. */
1517
- setStore(t) {
1518
- this.store !== t && (this.unsub?.(), this.store = t, this.subscribe());
1519
- }
1520
- hostConnected() {
1521
- this.subscribe();
1522
- }
1523
- hostDisconnected() {
1524
- this.unsub?.(), this.unsub = void 0;
1525
- }
1526
- subscribe() {
1527
- this.store && (this.unsub = this.store.subscribeChannels(this.channels, () => {
1528
- this.host.requestUpdate();
1529
- }));
1530
- }
1531
- }
1532
- var Wt = Object.defineProperty, Ut = Object.getOwnPropertyDescriptor, at = (e, t, o, n) => {
1533
- for (var i = n > 1 ? void 0 : n ? Ut(t, o) : t, s = e.length - 1, r; s >= 0; s--)
1534
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
1535
- return n && i && Wt(t, o, i), i;
1536
- };
1537
- let V = class extends $ {
1538
- constructor() {
1539
- super(...arguments), this.visible = !1;
1540
- }
1541
- exec(e, t) {
1542
- document.execCommand(e, !1, t), this.requestUpdate();
1543
- }
1544
- isActive(e) {
1545
- try {
1546
- return document.queryCommandState(e);
1547
- } catch {
1548
- return !1;
1549
- }
1550
- }
1551
- /** Position the toolbar above a given element */
1552
- positionAbove(e) {
1553
- const t = e.getBoundingClientRect();
1554
- this.style.left = `${t.left + t.width / 2}px`, this.style.top = `${t.top - 8}px`, this.style.transform = "translate(-50%, -100%)", this.classList.add("visible");
1555
- }
1556
- hide() {
1557
- this.classList.remove("visible");
1558
- }
1559
- handleLink() {
1560
- if (this.isActive("createLink"))
1561
- this.exec("unlink");
1562
- else {
1563
- const e = prompt("Enter URL:");
1564
- e && this.exec("createLink", e);
1565
- }
1566
- }
1567
- render() {
1568
- return p`
1569
- <div class="toolbar" @mousedown=${(e) => e.preventDefault()}>
1570
- <button class="btn ${this.isActive("bold") ? "active" : ""}"
1571
- @click=${() => this.exec("bold")} title="Bold (Ctrl+B)">
1572
- <strong>B</strong>
1573
- </button>
1574
- <button class="btn ${this.isActive("italic") ? "active" : ""}"
1575
- @click=${() => this.exec("italic")} title="Italic (Ctrl+I)">
1576
- <em>I</em>
1577
- </button>
1578
- <button class="btn ${this.isActive("underline") ? "active" : ""}"
1579
- @click=${() => this.exec("underline")} title="Underline (Ctrl+U)">
1580
- <u>U</u>
1581
- </button>
1582
- <button class="btn ${this.isActive("strikeThrough") ? "active" : ""}"
1583
- @click=${() => this.exec("strikeThrough")} title="Strikethrough">
1584
- <s>S</s>
1585
- </button>
1586
- <div class="separator"></div>
1587
- <button class="btn ${this.isActive("createLink") ? "active" : ""}"
1588
- @click=${this.handleLink} title="Link">
1589
- <svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
1590
- </button>
1591
- <div class="separator"></div>
1592
- <button class="btn" @click=${() => this.exec("justifyLeft")} title="Align Left">
1593
- <svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
1594
- </button>
1595
- <button class="btn" @click=${() => this.exec("justifyCenter")} title="Align Center">
1596
- <svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
1597
- </button>
1598
- <button class="btn" @click=${() => this.exec("justifyRight")} title="Align Right">
1599
- <svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>
1600
- </button>
1601
- </div>
1602
- `;
1603
- }
1604
- };
1605
- V.styles = k`
1606
- :host {
1607
- position: fixed;
1608
- z-index: 10000;
1609
- display: none;
1610
- }
1611
- :host(.visible) {
1612
- display: block;
1613
- animation: fadeIn 0.15s ease;
1614
- }
1615
- @keyframes fadeIn {
1616
- from { opacity: 0; transform: translateY(4px); }
1617
- to { opacity: 1; transform: translateY(0); }
1618
- }
1619
- .toolbar {
1620
- display: flex;
1621
- align-items: center;
1622
- gap: 2px;
1623
- padding: 4px 6px;
1624
- background: #1f2937;
1625
- border-radius: 8px;
1626
- box-shadow: 0 4px 16px rgba(0,0,0,0.2);
1627
- }
1628
- .btn {
1629
- width: 32px;
1630
- height: 32px;
1631
- display: flex;
1632
- align-items: center;
1633
- justify-content: center;
1634
- background: none;
1635
- border: none;
1636
- color: #d1d5db;
1637
- cursor: pointer;
1638
- border-radius: 4px;
1639
- font-size: 14px;
1640
- font-weight: 600;
1641
- transition: all 0.1s ease;
1642
- padding: 0;
1643
- }
1644
- .btn:hover { background: rgba(255,255,255,0.1); color: white; }
1645
- .btn.active { background: rgba(59,130,246,0.3); color: #93c5fd; }
1646
- .separator {
1647
- width: 1px;
1648
- height: 20px;
1649
- background: #374151;
1650
- margin: 0 2px;
1651
- }
1652
- .btn svg {
1653
- width: 16px;
1654
- height: 16px;
1655
- stroke: currentColor;
1656
- fill: none;
1657
- stroke-width: 2;
1658
- stroke-linecap: round;
1659
- stroke-linejoin: round;
1660
- }
1661
- `;
1662
- at([
1663
- b({ type: Boolean })
1664
- ], V.prototype, "visible", 2);
1665
- V = at([
1666
- C("me-inline-toolbar")
1667
- ], V);
1668
- var Ft = Object.defineProperty, Nt = Object.getOwnPropertyDescriptor, O = (e, t, o, n) => {
1669
- for (var i = n > 1 ? void 0 : n ? Nt(t, o) : t, s = e.length - 1, r; s >= 0; s--)
1670
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
1671
- return n && i && Ft(t, o, i), i;
1672
- };
1673
- const rt = /* @__PURE__ */ new Set(["text", "heading", "paragraph"]);
1674
- function lt(e) {
1675
- return e === "html" ? "html" : "text";
1676
- }
1677
- let D = class extends $ {
1678
- constructor() {
1679
- super(...arguments), this.storeCtrl = new T(this, ["design", "selection", "hover", "viewMode"]), this.editing = !1, this.toolbar = null, this.editableEl = null, this.saveDebounceTimer = null, this._onDragStart = (e) => {
1680
- if (this.editing) {
1681
- e.preventDefault();
1682
- return;
1683
- }
1684
- e.dataTransfer.setData("application/maileditor-content", this.content.id), e.dataTransfer.effectAllowed = "move", this.style.opacity = "0.4", L.startContentDrag(this.content.id);
1685
- }, this._onDragEnd = () => {
1686
- this.style.opacity = "1", L.reset();
1687
- }, this.handleInlineInput = () => {
1688
- this.saveDebounceTimer && clearTimeout(this.saveDebounceTimer), this.saveDebounceTimer = setTimeout(() => this.saveInlineContent(), 500), this.showToolbar();
1689
- }, this.handleInlineBlur = (e) => {
1690
- const t = e.relatedTarget;
1691
- t && (t.closest("me-inline-toolbar") || this.toolbar?.contains(t)) || setTimeout(() => {
1692
- !this.shadowRoot?.activeElement && !this.toolbar?.matches(":hover") && this.stopEditing();
1693
- }, 150);
1694
- }, this.handleInlineKeydown = (e) => {
1695
- e.key === "Escape" && (e.preventDefault(), e.stopPropagation(), this.stopEditing()), (e.key === "Delete" || e.key === "Backspace") && e.stopPropagation();
1696
- };
1697
- }
1698
- set store(e) {
1699
- this.storeCtrl.setStore(e);
1700
- }
1701
- get store() {
1702
- return this.storeCtrl.store;
1703
- }
1704
- // ── Event handlers ───────────────────────────────────────
1705
- handleClick(e) {
1706
- e.stopPropagation(), this.editing || this.store.select(this.content.id);
1707
- }
1708
- handleDblClick(e) {
1709
- e.stopPropagation(), rt.has(this.content.type) && this.startEditing();
1710
- }
1711
- handleMouseEnter() {
1712
- this.store.hover(this.content.id);
1713
- }
1714
- handleMouseLeave() {
1715
- this.store.hover(null);
1716
- }
1717
- handleDelete(e) {
1718
- e.stopPropagation(), this.store.removeContent(this.content.id);
1719
- }
1720
- handleDuplicate(e) {
1721
- e.stopPropagation(), this.store.duplicateContent(this.content.id);
1722
- }
1723
- // ── Inline editing ───────────────────────────────────────
1724
- startEditing() {
1725
- this.editing = !0, this.classList.add("editing"), this.setAttribute("draggable", "false"), this.updateComplete.then(() => {
1726
- const e = this.shadowRoot?.querySelector(".inline-editable");
1727
- if (e) {
1728
- this.editableEl = e, e.focus();
1729
- const t = window.getSelection(), o = document.createRange();
1730
- o.selectNodeContents(e), o.collapse(!1), t?.removeAllRanges(), t?.addRange(o), this.showToolbar();
1731
- }
1732
- });
1733
- }
1734
- stopEditing() {
1735
- this.editing && (this.saveInlineContent(), this.editing = !1, this.classList.remove("editing"), this.setAttribute("draggable", "true"), this.editableEl = null, this.toolbar?.hide());
1736
- }
1737
- saveInlineContent() {
1738
- if (!this.editableEl) return;
1739
- const e = this.editableEl.innerHTML, t = lt(this.content.type), o = this.content.values[t];
1740
- e !== o && this.store.updateContentValues(this.content.id, { [t]: e });
1741
- }
1742
- showToolbar() {
1743
- this.toolbar || (this.toolbar = document.createElement("me-inline-toolbar"), document.body.appendChild(this.toolbar)), this.editableEl && this.toolbar.positionAbove(this.editableEl);
1744
- }
1745
- // ── Lifecycle ────────────────────────────────────────────
1746
- connectedCallback() {
1747
- super.connectedCallback(), this.addEventListener("dragstart", this._onDragStart), this.addEventListener("dragend", this._onDragEnd);
1748
- }
1749
- disconnectedCallback() {
1750
- super.disconnectedCallback(), this.removeEventListener("dragstart", this._onDragStart), this.removeEventListener("dragend", this._onDragEnd), this.toolbar?.remove(), this.toolbar = null, this.saveDebounceTimer && clearTimeout(this.saveDebounceTimer);
1751
- }
1752
- // ── Render ───────────────────────────────────────────────
1753
- render() {
1754
- if (!this.store) return p``;
1755
- const e = this.store.selectedId === this.content.id, t = this.store.hoveredId === this.content.id, o = this.store.viewMode, n = !!this.content.values.hideDesktop, i = !!this.content.values.hideMobile, s = o === "desktop" && n || o === "mobile" && i;
1756
- this.classList.toggle("selected", e && !this.editing), this.classList.toggle("hovered", t && !this.editing), this.classList.toggle("hidden-in-view", s), this.editing || this.setAttribute("draggable", "true"), this.dataset.contentId = this.content.id, this.editing && !e && this.stopEditing();
1757
- const r = this.toolRegistry.get(this.content.type);
1758
- if (!r && this.toolRegistry.has(this.content.type))
1759
- return this.toolRegistry.ensureLoaded(this.content.type).then(() => this.requestUpdate()), p`<div style="padding:16px;text-align:center;color:#9ca3af;font-size:13px;">Loading ${this.content.type}...</div>`;
1760
- const a = n ? "Hidden on desktop" : i ? "Hidden on mobile" : "", d = rt.has(this.content.type);
1761
- if (this.editing && d && r) {
1762
- const h = this.content.values, g = lt(this.content.type), u = h[g] || "", f = this.getInlineStyles(h);
1763
- return p`
1764
- <div class="content-wrapper">
1765
- <div
1766
- class="inline-editable"
1767
- contenteditable="true"
1768
- style=${f}
1769
- .innerHTML=${u}
1770
- @input=${this.handleInlineInput}
1771
- @blur=${this.handleInlineBlur}
1772
- @keydown=${this.handleInlineKeydown}
1773
- ></div>
1774
- </div>
1775
- `;
1776
- }
1777
- const c = r?.renderer.renderEditor(this.content.values, {
1778
- isSelected: e,
1779
- isHovered: t,
1780
- columnWidth: 600,
1781
- displayMode: "email"
1782
- });
1783
- return p`
1784
- ${s ? p`<div class="hidden-badge">${a}</div>` : ""}
1785
- <div class="action-bar">
1786
- <button class="action-btn" @click=${this.handleDuplicate} title="Duplicate">&#9851;</button>
1787
- <button class="action-btn" @click=${this.handleDelete} title="Delete">&#10005;</button>
1788
- </div>
1789
- <div class="content-wrapper"
1790
- @click=${this.handleClick}
1791
- @dblclick=${this.handleDblClick}
1792
- @mouseenter=${this.handleMouseEnter}
1793
- @mouseleave=${this.handleMouseLeave}>
1794
- ${c ?? p`<div style="padding:10px;color:#999;font-style:italic;">Unknown tool: ${this.content.type}</div>`}
1795
- </div>
1796
- `;
1797
- }
1798
- /** Build inline CSS for the contenteditable area based on content values */
1799
- getInlineStyles(e) {
1800
- const t = [], o = (g, u = "") => {
1801
- const f = e[g];
1802
- return typeof f == "string" ? f : typeof f == "number" ? String(f) : u;
1803
- };
1804
- t.push(`padding:${o("containerPadding", "10px")}`);
1805
- const n = o("backgroundColor");
1806
- n && t.push(`background-color:${n}`);
1807
- const i = o("color");
1808
- i && t.push(`color:${i}`);
1809
- const s = o("fontSize");
1810
- s && t.push(`font-size:${s}`);
1811
- const r = o("fontWeight");
1812
- r && t.push(`font-weight:${r}`);
1813
- const a = o("lineHeight", "140%");
1814
- t.push(`line-height:${a}`);
1815
- const d = o("textAlign");
1816
- d && t.push(`text-align:${d}`);
1817
- const c = o("letterSpacing");
1818
- c && c !== "normal" && t.push(`letter-spacing:${c}`);
1819
- const h = e.fontFamily;
1820
- return typeof h == "string" ? t.push(`font-family:${h}`) : h && typeof h == "object" && t.push(`font-family:${h.value || "inherit"}`), t.push("word-break:break-word", "outline:none", "min-height:1em"), t.join(";");
1821
- }
1822
- };
1823
- D.styles = k`
1824
- :host {
1825
- display: block; position: relative; cursor: pointer;
1826
- transition: outline 0.15s ease, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
1827
- }
1828
- :host(.selected) { outline: 2px solid #3b82f6; outline-offset: -1px; }
1829
- :host(.hovered:not(.selected)) { outline: 2px dashed #93c5fd; outline-offset: -1px; }
1830
- :host(.editing) { outline: 2px solid #8b5cf6; outline-offset: -1px; cursor: text; }
1831
- :host(.just-dropped) {
1832
- animation: dropIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
1833
- }
1834
- :host(.hidden-in-view) { opacity: 0.3; position: relative; }
1835
- .hidden-badge {
1836
- position: absolute; top: 4px; right: 4px; z-index: 5;
1837
- background: #f59e0b; color: white; font-size: 10px; font-weight: 600;
1838
- padding: 2px 6px; border-radius: 3px; font-family: sans-serif;
1839
- pointer-events: none;
1840
- }
1841
- @keyframes dropIn {
1842
- 0% { opacity: 0; transform: scale(0.92) translateY(-8px); }
1843
- 100% { opacity: 1; transform: scale(1) translateY(0); }
1844
- }
1845
- .action-bar {
1846
- display: none; position: absolute; top: -28px; right: 4px;
1847
- background: #3b82f6; border-radius: 4px; padding: 2px; gap: 2px; z-index: 10;
1848
- }
1849
- :host(.selected:not(.editing)) .action-bar { display: flex; }
1850
- .action-btn {
1851
- background: none; border: none; color: white; cursor: pointer;
1852
- padding: 2px 6px; font-size: 12px; line-height: 1; border-radius: 2px;
1853
- }
1854
- .action-btn:hover { background: rgba(255,255,255,0.2); }
1855
- .content-wrapper { position: relative; }
1856
- .inline-editable {
1857
- outline: none;
1858
- cursor: text;
1859
- min-height: 1em;
1860
- }
1861
- .inline-editable:focus {
1862
- outline: none;
1863
- }
1864
- `;
1865
- O([
1866
- b({ attribute: !1 })
1867
- ], D.prototype, "content", 2);
1868
- O([
1869
- b({ attribute: !1 })
1870
- ], D.prototype, "store", 1);
1871
- O([
1872
- b({ attribute: !1 })
1873
- ], D.prototype, "toolRegistry", 2);
1874
- O([
1875
- pt()
1876
- ], D.prototype, "editing", 2);
1877
- D = O([
1878
- C("me-content-renderer")
1879
- ], D);
1880
- var qt = Object.defineProperty, Gt = Object.getOwnPropertyDescriptor, H = (e, t, o, n) => {
1881
- for (var i = n > 1 ? void 0 : n ? Gt(t, o) : t, s = e.length - 1, r; s >= 0; s--)
1882
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
1883
- return n && i && qt(t, o, i), i;
1884
- };
1885
- let S = class extends $ {
1886
- constructor() {
1887
- super(...arguments), this.storeCtrl = new T(this, ["design"]), this.widthPercent = 100;
1888
- }
1889
- set store(e) {
1890
- this.storeCtrl.setStore(e);
1891
- }
1892
- get store() {
1893
- return this.storeCtrl.store;
1894
- }
1895
- render() {
1896
- const e = this.column.values.padding || "0px", t = this.column.values.backgroundColor || "transparent", o = this.column.contents;
1897
- return this.dataset.columnId = this.column.id, this.style.width = `${this.widthPercent}%`, this.style.padding = e, this.style.backgroundColor = t, this.style.verticalAlign = "top", this.style.boxSizing = "border-box", o.length === 0 ? p`
1898
- <div class="empty-column" data-column-id=${this.column.id}>
1899
- Drag content here
1900
- </div>
1901
- ` : p`
1902
- ${o.map(
1903
- (n) => p`
1904
- <me-content-renderer
1905
- .content=${n}
1906
- .store=${this.store}
1907
- .toolRegistry=${this.toolRegistry}
1908
- ></me-content-renderer>
1909
- `
1910
- )}
1911
- `;
1912
- }
1913
- };
1914
- S.styles = k`
1915
- :host {
1916
- display: block;
1917
- min-height: 40px;
1918
- position: relative;
1919
- }
1920
- .empty-column {
1921
- display: flex;
1922
- align-items: center;
1923
- justify-content: center;
1924
- min-height: 60px;
1925
- border: 2px dashed #d1d5db;
1926
- border-radius: 4px;
1927
- color: #9ca3af;
1928
- font-size: 13px;
1929
- font-family: sans-serif;
1930
- margin: 4px;
1931
- }
1932
- .drop-indicator {
1933
- height: 3px;
1934
- background: #3b82f6;
1935
- border-radius: 2px;
1936
- margin: 0 4px;
1937
- opacity: 0;
1938
- transition: opacity 0.15s ease;
1939
- }
1940
- .drop-indicator.active {
1941
- opacity: 1;
1942
- }
1943
- `;
1944
- H([
1945
- b({ attribute: !1 })
1946
- ], S.prototype, "column", 2);
1947
- H([
1948
- b({ attribute: !1 })
1949
- ], S.prototype, "store", 1);
1950
- H([
1951
- b({ attribute: !1 })
1952
- ], S.prototype, "toolRegistry", 2);
1953
- H([
1954
- b({ type: Number })
1955
- ], S.prototype, "widthPercent", 2);
1956
- S = H([
1957
- C("me-column-renderer")
1958
- ], S);
1959
- var Kt = Object.defineProperty, Yt = Object.getOwnPropertyDescriptor, F = (e, t, o, n) => {
1960
- for (var i = n > 1 ? void 0 : n ? Yt(t, o) : t, s = e.length - 1, r; s >= 0; s--)
1961
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
1962
- return n && i && Kt(t, o, i), i;
1963
- };
1964
- let R = class extends $ {
1965
- constructor() {
1966
- super(...arguments), this.storeCtrl = new T(this, ["design", "viewMode"]);
1967
- }
1968
- set store(e) {
1969
- this.storeCtrl.setStore(e);
1970
- }
1971
- get store() {
1972
- return this.storeCtrl.store;
1973
- }
1974
- handleMoveUp(e) {
1975
- e.stopPropagation();
1976
- const t = this.store.getRowIndex(this.row.id);
1977
- t > 0 && this.store.moveRow(t, t - 1);
1978
- }
1979
- handleMoveDown(e) {
1980
- e.stopPropagation();
1981
- const t = this.store.getRowIndex(this.row.id), o = this.store.getRows().length;
1982
- t < o - 1 && this.store.moveRow(t, t + 1);
1983
- }
1984
- handleDuplicate(e) {
1985
- e.stopPropagation(), this.store.duplicateRow(this.row.id);
1986
- }
1987
- handleDelete(e) {
1988
- e.stopPropagation(), this.store.removeRow(this.row.id);
1989
- }
1990
- render() {
1991
- if (!this.store) return p``;
1992
- const { row: e, store: t, toolRegistry: o } = this, n = e.values.backgroundColor || "transparent", i = e.values.columnsBackgroundColor || "transparent", s = e.values.padding || "0px", r = e.cells.reduce((x, m) => x + m, 0), a = e.values.backgroundImage, d = typeof a == "object" && a?.url ? a.url : "";
1993
- let c = "";
1994
- if (d) {
1995
- const x = a.repeat === !0 || a.repeat === "repeat" ? "repeat" : "no-repeat", m = a.cover === !0 ? "cover" : a.fullWidth === !0 ? "100% auto" : "auto", w = a.center !== !1 ? "center" : "top left";
1996
- c = `background-image:url('${d}');background-size:${m};background-position:${w};background-repeat:${x};`;
1997
- }
1998
- const h = t.viewMode, g = !!e.values.hideDesktop, u = !!e.values.hideMobile, f = h === "desktop" && g || h === "mobile" && u, y = g ? "Hidden on desktop" : u ? "Hidden on mobile" : "";
1999
- return this.classList.toggle("hidden-in-view", f), this.setAttribute("draggable", "true"), this.dataset.rowId = e.id, p`
2000
- ${f ? p`<div class="row-hidden-badge">${y}</div>` : ""}
2001
- <div class="row-actions">
2002
- <button class="row-action-btn" @click=${this.handleMoveUp} title="Move Up">
2003
- <svg viewBox="0 0 24 24"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
2004
- </button>
2005
- <button class="row-action-btn" @click=${this.handleMoveDown} title="Move Down">
2006
- <svg viewBox="0 0 24 24"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
2007
- </button>
2008
- <button class="row-action-btn" @click=${this.handleDuplicate} title="Duplicate Row">
2009
- <svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
2010
- </button>
2011
- <button class="row-action-btn danger" @click=${this.handleDelete} title="Delete Row">
2012
- <svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
2013
- </button>
2014
- </div>
2015
- <div
2016
- class="row-wrapper"
2017
- style="background-color:${n};padding:${s};${c}"
2018
- >
2019
- ${e.columns.map((x, m) => {
2020
- const w = e.cells[m] / r * 100;
2021
- return p`
2022
- <me-column-renderer
2023
- .column=${x}
2024
- .store=${t}
2025
- .toolRegistry=${o}
2026
- .widthPercent=${w}
2027
- style="background-color:${i};"
2028
- ></me-column-renderer>
2029
- `;
2030
- })}
2031
- </div>
2032
- `;
2033
- }
2034
- };
2035
- R.styles = k`
2036
- :host {
2037
- display: block;
2038
- position: relative;
2039
- transition: opacity 0.2s ease;
2040
- }
2041
- :host(.hidden-in-view) { opacity: 0.3; }
2042
- .row-hidden-badge {
2043
- position: absolute; top: 4px; left: 4px; z-index: 5;
2044
- background: #f59e0b; color: white; font-size: 10px; font-weight: 600;
2045
- padding: 2px 6px; border-radius: 3px; font-family: sans-serif;
2046
- pointer-events: none;
2047
- }
2048
- .row-wrapper {
2049
- display: flex;
2050
- flex-wrap: nowrap;
2051
- position: relative;
2052
- min-height: 30px;
2053
- transition: box-shadow 0.15s ease;
2054
- }
2055
- :host(:hover) .row-wrapper {
2056
- box-shadow: inset 0 0 0 1px #93c5fd;
2057
- }
2058
- .row-actions {
2059
- display: none;
2060
- position: absolute;
2061
- right: -36px;
2062
- top: 50%;
2063
- transform: translateY(-50%);
2064
- flex-direction: column;
2065
- gap: 2px;
2066
- z-index: 10;
2067
- }
2068
- :host(:hover) .row-actions {
2069
- display: flex;
2070
- }
2071
- .row-action-btn {
2072
- width: 28px;
2073
- height: 28px;
2074
- display: flex;
2075
- align-items: center;
2076
- justify-content: center;
2077
- background: #6b7280;
2078
- color: white;
2079
- border: none;
2080
- border-radius: 4px;
2081
- cursor: pointer;
2082
- font-size: 12px;
2083
- line-height: 1;
2084
- transition: background 0.15s ease;
2085
- padding: 0;
2086
- }
2087
- .row-action-btn:hover { background: #3b82f6; }
2088
- .row-action-btn.danger:hover { background: #ef4444; }
2089
- .row-action-btn svg {
2090
- width: 14px;
2091
- height: 14px;
2092
- stroke: currentColor;
2093
- fill: none;
2094
- stroke-width: 2;
2095
- stroke-linecap: round;
2096
- stroke-linejoin: round;
2097
- }
2098
- `;
2099
- F([
2100
- b({ attribute: !1 })
2101
- ], R.prototype, "row", 2);
2102
- F([
2103
- b({ attribute: !1 })
2104
- ], R.prototype, "store", 1);
2105
- F([
2106
- b({ attribute: !1 })
2107
- ], R.prototype, "toolRegistry", 2);
2108
- R = F([
2109
- C("me-row-renderer")
2110
- ], R);
2111
- var Jt = Object.defineProperty, Xt = Object.getOwnPropertyDescriptor, J = (e, t, o, n) => {
2112
- for (var i = n > 1 ? void 0 : n ? Xt(t, o) : t, s = e.length - 1, r; s >= 0; s--)
2113
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
2114
- return n && i && Jt(t, o, i), i;
2115
- };
2116
- let M = class extends $ {
2117
- constructor() {
2118
- super(...arguments), this.storeCtrl = new T(this, ["design", "viewMode"]);
2119
- }
2120
- set store(e) {
2121
- this.storeCtrl.setStore(e);
2122
- }
2123
- get store() {
2124
- return this.storeCtrl.store;
2125
- }
2126
- handleCanvasClick() {
2127
- this.store.select(null);
2128
- }
2129
- setViewMode(e) {
2130
- this.store.setViewMode(e);
2131
- }
2132
- render() {
2133
- const e = this.store.getRows(), t = this.store.getBodyValues(), o = t.contentWidth || "600px", n = t.backgroundColor || "#e7e7e7", i = this.store.viewMode;
2134
- return p`
2135
- <div class="view-toggle">
2136
- <button
2137
- class="view-btn ${i === "desktop" ? "active" : ""}"
2138
- @click=${() => this.setViewMode("desktop")}
2139
- >Desktop</button>
2140
- <button
2141
- class="view-btn ${i === "mobile" ? "active" : ""}"
2142
- @click=${() => this.setViewMode("mobile")}
2143
- >Mobile</button>
2144
- </div>
2145
-
2146
- <div
2147
- class="canvas-body"
2148
- style="max-width:${i === "mobile" ? "375px" : o};background-color:${n};"
2149
- @click=${this.handleCanvasClick}
2150
- >
2151
- ${e.length === 0 ? p`
2152
- <div class="empty-canvas">
2153
- <div class="empty-icon">&#9776;</div>
2154
- <div>Drag a content block here</div>
2155
- </div>
2156
- ` : e.map(
2157
- (r) => p`
2158
- <me-row-renderer
2159
- .row=${r}
2160
- .store=${this.store}
2161
- .toolRegistry=${this.toolRegistry}
2162
- ></me-row-renderer>
2163
- `
2164
- )}
2165
- </div>
2166
- `;
2167
- }
2168
- };
2169
- M.styles = k`
2170
- :host {
2171
- display: block;
2172
- flex: 1;
2173
- overflow-y: auto;
2174
- background: #f3f4f6;
2175
- padding: 20px;
2176
- }
2177
- .canvas-body {
2178
- margin: 0 auto;
2179
- background: #ffffff;
2180
- box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
2181
- min-height: 200px;
2182
- position: relative;
2183
- }
2184
- .empty-canvas {
2185
- display: flex;
2186
- flex-direction: column;
2187
- align-items: center;
2188
- justify-content: center;
2189
- min-height: 300px;
2190
- color: #9ca3af;
2191
- font-family: sans-serif;
2192
- font-size: 14px;
2193
- gap: 8px;
2194
- }
2195
- .empty-icon {
2196
- font-size: 40px;
2197
- opacity: 0.4;
2198
- }
2199
- .view-toggle {
2200
- display: flex;
2201
- justify-content: center;
2202
- margin-bottom: 12px;
2203
- gap: 4px;
2204
- }
2205
- .view-btn {
2206
- padding: 6px 16px;
2207
- border: 1px solid #d1d5db;
2208
- background: white;
2209
- cursor: pointer;
2210
- font-size: 13px;
2211
- font-family: sans-serif;
2212
- color: #374151;
2213
- transition: all 0.15s ease;
2214
- }
2215
- .view-btn:first-child { border-radius: 6px 0 0 6px; }
2216
- .view-btn:last-child { border-radius: 0 6px 6px 0; }
2217
- .view-btn.active {
2218
- background: #3b82f6;
2219
- border-color: #3b82f6;
2220
- color: white;
2221
- }
2222
- `;
2223
- J([
2224
- b({ attribute: !1 })
2225
- ], M.prototype, "store", 1);
2226
- J([
2227
- b({ attribute: !1 })
2228
- ], M.prototype, "toolRegistry", 2);
2229
- M = J([
2230
- C("me-editor-canvas")
2231
- ], M);
2232
- var Qt = Object.defineProperty, Zt = Object.getOwnPropertyDescriptor, dt = (e, t, o, n) => {
2233
- for (var i = n > 1 ? void 0 : n ? Zt(t, o) : t, s = e.length - 1, r; s >= 0; s--)
2234
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
2235
- return n && i && Qt(t, o, i), i;
2236
- };
2237
- const G = [
2238
- // System / email-safe fonts
2239
- { label: "Arial", value: "arial,helvetica,sans-serif", url: "" },
2240
- { label: "Helvetica", value: "helvetica,sans-serif", url: "" },
2241
- { label: "Georgia", value: "georgia,serif", url: "" },
2242
- { label: "Times New Roman", value: "'times new roman',times,serif", url: "" },
2243
- { label: "Trebuchet MS", value: "trebuchet ms,helvetica,sans-serif", url: "" },
2244
- { label: "Verdana", value: "verdana,geneva,sans-serif", url: "" },
2245
- { label: "Courier New", value: "'courier new',courier,monospace", url: "" },
2246
- // Google Fonts
2247
- { label: "Lato", value: "'Lato',sans-serif", url: "https://fonts.googleapis.com/css?family=Lato:400,700" },
2248
- { label: "Montserrat", value: "'Montserrat',sans-serif", url: "https://fonts.googleapis.com/css?family=Montserrat:400,700" },
2249
- { label: "Old Standard TT", value: "'Old Standard TT',serif", url: "https://fonts.googleapis.com/css?family=Old+Standard+TT:400,700" },
2250
- { label: "Open Sans", value: "'Open Sans',sans-serif", url: "https://fonts.googleapis.com/css?family=Open+Sans:400,700" },
2251
- { label: "Pacifico", value: "'Pacifico',cursive", url: "https://fonts.googleapis.com/css?family=Pacifico:400" },
2252
- { label: "Playfair Display", value: "'Playfair Display',serif", url: "https://fonts.googleapis.com/css?family=Playfair+Display:400,700" },
2253
- { label: "Raleway", value: "'Raleway',sans-serif", url: "https://fonts.googleapis.com/css?family=Raleway:400,700" },
2254
- { label: "Roboto", value: "'Roboto',sans-serif", url: "https://fonts.googleapis.com/css?family=Roboto:400,700" },
2255
- { label: "Rubik", value: "'Rubik',sans-serif", url: "https://fonts.googleapis.com/css?family=Rubik:400,700" },
2256
- { label: "Source Sans Pro", value: "'Source Sans Pro',sans-serif", url: "https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" }
2257
- ];
2258
- let A = class extends $ {
2259
- constructor() {
2260
- super(...arguments), this.storeCtrl = new T(this, ["design"]);
2261
- }
2262
- set store(e) {
2263
- this.storeCtrl.setStore(e);
2264
- }
2265
- get store() {
2266
- return this.storeCtrl.store;
2267
- }
2268
- update_(e, t) {
2269
- this.store.updateBodyValues({ [e]: t });
2270
- }
2271
- updateLinkStyle(e, t) {
2272
- const o = this.store.getBodyValues().linkStyle;
2273
- this.store.updateBodyValues({ linkStyle: { ...o, [e]: t } });
2274
- }
2275
- updateFontFamily(e) {
2276
- const t = G.find((n) => n.value === e), o = { label: t?.label || e, value: e };
2277
- if (t?.url) {
2278
- o.url = t.url;
2279
- const n = `emabuild-font-${e.replace(/[^a-z]/gi, "")}`;
2280
- if (!document.getElementById(n)) {
2281
- const i = document.createElement("link");
2282
- i.id = n, i.rel = "stylesheet", i.href = t.url, document.head.appendChild(i);
2283
- }
2284
- }
2285
- this.store.updateBodyValues({ fontFamily: o });
2286
- }
2287
- render() {
2288
- const e = this.store.getBodyValues();
2289
- return p`
2290
- ${this.renderColorField("Background Color", e.backgroundColor || "#e7e7e7", (t) => this.update_("backgroundColor", t))}
2291
-
2292
- <p class="section-title" style="margin-top:16px;">Content</p>
2293
- <div class="field">
2294
- <label class="field-label">Content Width (px)</label>
2295
- <input class="input" type="number" .value=${parseInt(e.contentWidth || "600")} min="320" max="960" step="10"
2296
- @change=${(t) => this.update_("contentWidth", t.target.value + "px")} />
2297
- </div>
2298
- <div class="field">
2299
- <label class="field-label">Content Align</label>
2300
- <div class="align-group">
2301
- ${["left", "center", "right"].map((t) => p`
2302
- <button class="align-btn ${e.contentAlign === t ? "active" : ""}"
2303
- @click=${() => this.update_("contentAlign", t)}>${t}</button>
2304
- `)}
2305
- </div>
2306
- </div>
2307
-
2308
- <p class="section-title" style="margin-top:16px;">Typography</p>
2309
- <div class="field">
2310
- <label class="field-label">Font Family</label>
2311
- <select class="input" @change=${(t) => this.updateFontFamily(t.target.value)}>
2312
- <optgroup label="System Fonts">
2313
- ${G.filter((t) => !t.url).map((t) => p`<option value=${t.value} ?selected=${e.fontFamily?.value === t.value}>${t.label}</option>`)}
2314
- </optgroup>
2315
- <optgroup label="Google Fonts">
2316
- ${G.filter((t) => !!t.url).map((t) => p`<option value=${t.value} ?selected=${e.fontFamily?.value === t.value}>${t.label}</option>`)}
2317
- </optgroup>
2318
- </select>
2319
- </div>
2320
- ${this.renderColorField("Text Color", e.textColor || "#000000", (t) => this.update_("textColor", t))}
2321
-
2322
- <p class="section-title" style="margin-top:16px;">Links</p>
2323
- ${this.renderColorField("Link Color", e.linkStyle?.linkColor || "#0000ee", (t) => this.updateLinkStyle("linkColor", t))}
2324
- <div class="field">
2325
- <label style="display:flex;align-items:center;gap:8px;font-size:12px;color:#6b7280;cursor:pointer;">
2326
- <input type="checkbox" .checked=${e.linkStyle?.linkUnderline ?? !0}
2327
- @change=${(t) => this.updateLinkStyle("linkUnderline", t.target.checked)} />
2328
- Underline Links
2329
- </label>
2330
- </div>
2331
-
2332
- <p class="section-title" style="margin-top:16px;">Email</p>
2333
- <div class="field">
2334
- <label class="field-label">Preheader Text</label>
2335
- <textarea class="input" .value=${e.preheaderText || ""} placeholder="Preview text shown in inbox..."
2336
- style="min-height:60px;resize:vertical;font-family:inherit;"
2337
- @change=${(t) => this.update_("preheaderText", t.target.value)}></textarea>
2338
- </div>
2339
- `;
2340
- }
2341
- /** Reusable color field (swatch + hex input) */
2342
- renderColorField(e, t, o) {
2343
- return p`
2344
- <div class="field">
2345
- <label class="field-label">${e}</label>
2346
- <div class="color-row">
2347
- <input class="color-swatch" type="color" .value=${t} @input=${(n) => o(n.target.value)} />
2348
- <input class="input" type="text" .value=${t} style="flex:1;" @change=${(n) => o(n.target.value)} />
2349
- </div>
2350
- </div>
2351
- `;
2352
- }
2353
- };
2354
- A.styles = k`
2355
- :host { display: block; }
2356
- .section-title {
2357
- font-size: 11px; font-weight: 600; text-transform: uppercase;
2358
- color: #9ca3af; letter-spacing: 0.05em; margin: 0 0 8px 0;
2359
- }
2360
- .field { margin-bottom: 12px; }
2361
- .field-label { display: block; font-size: 12px; color: #6b7280; margin-bottom: 4px; }
2362
- .input {
2363
- width: 100%; padding: 5px 8px; border: 1px solid #d1d5db; border-radius: 4px;
2364
- font-size: 12px; box-sizing: border-box;
2365
- }
2366
- .input:focus { outline: none; border-color: #3b82f6; }
2367
- .color-row { display: flex; gap: 6px; align-items: center; }
2368
- .color-swatch {
2369
- width: 32px; height: 32px; border: 1px solid #d1d5db; border-radius: 4px;
2370
- padding: 0; cursor: pointer;
2371
- }
2372
- .align-group { display: flex; gap: 2px; }
2373
- .align-btn {
2374
- flex: 1; padding: 5px; border: 1px solid #d1d5db; background: white;
2375
- border-radius: 4px; cursor: pointer; font-size: 11px; color: #6b7280;
2376
- }
2377
- .align-btn.active { border-color: #3b82f6; background: #eff6ff; color: #3b82f6; }
2378
- `;
2379
- dt([
2380
- b({ attribute: !1 })
2381
- ], A.prototype, "store", 1);
2382
- A = dt([
2383
- C("me-body-settings")
2384
- ], A);
2385
- var te = Object.defineProperty, ee = Object.getOwnPropertyDescriptor, X = (e, t, o, n) => {
2386
- for (var i = n > 1 ? void 0 : n ? ee(t, o) : t, s = e.length - 1, r; s >= 0; s--)
2387
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
2388
- return n && i && te(t, o, i), i;
2389
- };
2390
- let P = class extends $ {
2391
- constructor() {
2392
- super(...arguments), this.storeCtrl = new T(this, ["activeTab"]);
2393
- }
2394
- set store(e) {
2395
- this.storeCtrl.setStore(e);
2396
- }
2397
- get store() {
2398
- return this.storeCtrl.store;
2399
- }
2400
- handleDragStart(e, t) {
2401
- e.dataTransfer.setData("application/maileditor-tool", t), e.dataTransfer.effectAllowed = "copy";
2402
- }
2403
- handleLayoutDragStart(e, t) {
2404
- e.dataTransfer.setData("application/maileditor-layout", JSON.stringify(t)), e.dataTransfer.effectAllowed = "copy";
2405
- }
2406
- addRowWithLayout(e) {
2407
- const t = this.store.createRow(e);
2408
- this.store.addRow(t);
2409
- }
2410
- render() {
2411
- const e = this.store.activeTab, t = this.toolRegistry.getAllMeta();
2412
- return p`
2413
- <div class="tabs">
2414
- <button class="tab ${e === "content" ? "active" : ""}" @click=${() => this.store.setActiveTab("content")}>Content</button>
2415
- <button class="tab ${e === "blocks" ? "active" : ""}" @click=${() => this.store.setActiveTab("blocks")}>Blocks</button>
2416
- <button class="tab ${e === "body" ? "active" : ""}" @click=${() => this.store.setActiveTab("body")}>Body</button>
2417
- </div>
2418
-
2419
- <div class="tab-content">
2420
- ${e === "content" ? this.renderContentTab(t) : ""}
2421
- ${e === "blocks" ? this.renderBlocksTab() : ""}
2422
- ${e === "body" ? this.renderBodyTab() : ""}
2423
- </div>
2424
- `;
2425
- }
2426
- renderContentTab(e) {
2427
- return p`
2428
- <p class="section-title">Content</p>
2429
- <div class="tool-grid">
2430
- ${e.map(
2431
- (t) => p`
2432
- <div
2433
- class="tool-item"
2434
- draggable="true"
2435
- @dragstart=${(o) => this.handleDragStart(o, t.name)}
2436
- >
2437
- <div class="tool-icon">${U(t.icon)}</div>
2438
- <span>${t.label}</span>
2439
- </div>
2440
- `
2441
- )}
2442
- </div>
2443
-
2444
- <p class="section-title" style="margin-top:20px;">Layout</p>
2445
- <div class="layout-grid">
2446
- ${this.renderLayoutOption([1], "100%")}
2447
- ${this.renderLayoutOption([1, 1], "50/50")}
2448
- ${this.renderLayoutOption([1, 1, 1], "33/33/33")}
2449
- ${this.renderLayoutOption([2, 1], "66/33")}
2450
- ${this.renderLayoutOption([1, 2], "33/66")}
2451
- ${this.renderLayoutOption([1, 1, 1, 1], "25x4")}
2452
- </div>
2453
- `;
2454
- }
2455
- renderLayoutOption(e, t) {
2456
- const o = e.reduce((n, i) => n + i, 0);
2457
- return p`
2458
- <div
2459
- class="layout-item"
2460
- draggable="true"
2461
- @click=${() => this.addRowWithLayout(e)}
2462
- @dragstart=${(n) => this.handleLayoutDragStart(n, e)}
2463
- title=${t}
2464
- >
2465
- ${e.map(
2466
- (n) => p`<div class="layout-col" style="width:${n / o * 100}%;"></div>`
2467
- )}
2468
- </div>
2469
- `;
2470
- }
2471
- renderBlocksTab() {
2472
- return p`
2473
- <p style="color:#9ca3af;font-size:13px;text-align:center;margin-top:40px;">
2474
- No saved blocks yet.<br/>Select a row in the editor and save it as a block.
2475
- </p>
2476
- `;
2477
- }
2478
- renderBodyTab() {
2479
- return p`<me-body-settings .store=${this.store}></me-body-settings>`;
2480
- }
2481
- };
2482
- P.styles = k`
2483
- :host {
2484
- display: flex;
2485
- flex-direction: column;
2486
- width: 280px;
2487
- min-width: 280px;
2488
- background: #ffffff;
2489
- border-right: 1px solid #e5e7eb;
2490
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2491
- overflow-y: auto;
2492
- }
2493
- .tabs {
2494
- display: flex;
2495
- border-bottom: 1px solid #e5e7eb;
2496
- background: #f9fafb;
2497
- }
2498
- .tab {
2499
- flex: 1;
2500
- padding: 10px 8px;
2501
- border: none;
2502
- background: none;
2503
- cursor: pointer;
2504
- font-size: 12px;
2505
- font-weight: 500;
2506
- color: #6b7280;
2507
- text-align: center;
2508
- transition: all 0.15s ease;
2509
- border-bottom: 2px solid transparent;
2510
- }
2511
- .tab:hover {
2512
- color: #374151;
2513
- background: #f3f4f6;
2514
- }
2515
- .tab.active {
2516
- color: #3b82f6;
2517
- border-bottom-color: #3b82f6;
2518
- }
2519
- .tab-content {
2520
- padding: 12px;
2521
- flex: 1;
2522
- }
2523
- .section-title {
2524
- font-size: 11px;
2525
- font-weight: 600;
2526
- text-transform: uppercase;
2527
- color: #9ca3af;
2528
- letter-spacing: 0.05em;
2529
- margin: 0 0 8px 0;
2530
- }
2531
- .tool-grid {
2532
- display: grid;
2533
- grid-template-columns: 1fr 1fr;
2534
- gap: 8px;
2535
- }
2536
- .tool-item {
2537
- display: flex;
2538
- flex-direction: column;
2539
- align-items: center;
2540
- gap: 6px;
2541
- padding: 12px 8px;
2542
- border: 1px solid #e5e7eb;
2543
- border-radius: 6px;
2544
- cursor: grab;
2545
- background: white;
2546
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
2547
- font-size: 12px;
2548
- color: #374151;
2549
- }
2550
- .tool-item:hover {
2551
- border-color: #3b82f6;
2552
- background: #eff6ff;
2553
- transform: translateY(-2px);
2554
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
2555
- }
2556
- .tool-item:hover .tool-icon {
2557
- transform: scale(1.15);
2558
- }
2559
- .tool-item:active {
2560
- transform: translateY(0px);
2561
- box-shadow: 0 1px 4px rgba(59, 130, 246, 0.1);
2562
- }
2563
- .tool-item:active {
2564
- cursor: grabbing;
2565
- }
2566
- .tool-icon {
2567
- width: 20px;
2568
- height: 20px;
2569
- display: flex;
2570
- align-items: center;
2571
- transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
2572
- justify-content: center;
2573
- color: #6b7280;
2574
- }
2575
- .tool-icon svg {
2576
- width: 20px;
2577
- height: 20px;
2578
- }
2579
- .layout-grid {
2580
- display: grid;
2581
- grid-template-columns: 1fr 1fr 1fr;
2582
- gap: 8px;
2583
- margin-top: 16px;
2584
- }
2585
- .layout-item {
2586
- display: flex;
2587
- align-items: center;
2588
- justify-content: center;
2589
- gap: 2px;
2590
- padding: 10px 4px;
2591
- border: 1px solid #e5e7eb;
2592
- border-radius: 6px;
2593
- cursor: pointer;
2594
- background: white;
2595
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
2596
- }
2597
- .layout-item:hover {
2598
- border-color: #3b82f6;
2599
- background: #eff6ff;
2600
- transform: translateY(-2px);
2601
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
2602
- }
2603
- .layout-item:hover .layout-col {
2604
- background: #93c5fd;
2605
- }
2606
- .layout-item:active {
2607
- transform: translateY(0px);
2608
- }
2609
- .layout-col {
2610
- height: 20px;
2611
- background: #d1d5db;
2612
- border-radius: 2px;
2613
- transition: background 0.2s ease;
2614
- }
2615
- `;
2616
- X([
2617
- b({ attribute: !1 })
2618
- ], P.prototype, "store", 1);
2619
- X([
2620
- b({ attribute: !1 })
2621
- ], P.prototype, "toolRegistry", 2);
2622
- P = X([
2623
- C("me-editor-sidebar")
2624
- ], P);
2625
- function oe(e, t, o) {
2626
- const n = e && /^#[0-9a-fA-F]{3,8}$/.test(e) ? e : "#000000";
2627
- return p`
2628
- <div class="prop-row">
2629
- <label class="prop-label">${o}</label>
2630
- <div class="prop-color">
2631
- <input class="color-swatch" type="color" .value=${n}
2632
- @input=${(i) => t(i.target.value)} />
2633
- <input class="prop-input" type="text" .value=${e ?? ""} style="flex:1;"
2634
- @change=${(i) => t(i.target.value)} />
2635
- </div>
2636
- </div>
2637
- `;
2638
- }
2639
- function ne(e, t, o, n) {
2640
- const i = n?.options || [];
2641
- return p`
2642
- <div class="prop-row">
2643
- <label class="prop-label">${o}</label>
2644
- <select class="prop-input" @change=${(s) => t(s.target.value)}>
2645
- ${i.map((s) => p`<option value=${s.value} ?selected=${e === s.value}>${s.label}</option>`)}
2646
- </select>
2647
- </div>
2648
- `;
2649
- }
2650
- function ie(e, t, o) {
2651
- return p`
2652
- <div class="prop-row">
2653
- <label class="prop-label">${o}</label>
2654
- <div style="display:flex;gap:2px;">
2655
- ${["left", "center", "right"].map((n) => p`
2656
- <button
2657
- style="flex:1;padding:6px;border:1px solid ${e === n ? "#3b82f6" : "#d1d5db"};background:${e === n ? "#eff6ff" : "white"};border-radius:4px;cursor:pointer;font-size:11px;text-transform:capitalize;color:${e === n ? "#3b82f6" : "#6b7280"};"
2658
- @click=${() => t(n)}
2659
- >${n}</button>
2660
- `)}
2661
- </div>
2662
- </div>
2663
- `;
2664
- }
2665
- function se(e) {
2666
- const t = (e || "0px").split(/\s+/).map((r) => parseInt(r) || 0), o = t[0], n = t[1] ?? o, i = t[2] ?? o, s = t[3] ?? n;
2667
- return [o, n, i, s];
2668
- }
2669
- function re(e, t, o, n) {
2670
- return e === t && t === o && o === n ? `${e}px` : e === o && t === n ? `${e}px ${t}px` : `${e}px ${t}px ${o}px ${n}px`;
2671
- }
2672
- function le(e, t, o) {
2673
- const [n, i, s, r] = se(e), a = (c, h, g, u) => t(re(c, h, g, u));
2674
- return p`
2675
- <div class="prop-row">
2676
- <label class="prop-label">${o}</label>
2677
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;">
2678
- ${[
2679
- { label: "T", val: n, change: (c) => a(c, i, s, r) },
2680
- { label: "R", val: i, change: (c) => a(n, c, s, r) },
2681
- { label: "B", val: s, change: (c) => a(n, i, c, r) },
2682
- { label: "L", val: r, change: (c) => a(n, i, s, c) }
2683
- ].map((c) => p`
2684
- <div style="display:flex;align-items:center;gap:4px;">
2685
- <span style="font-size:10px;color:#9ca3af;width:12px;">${c.label}</span>
2686
- <input type="number" .value=${c.val} min="0"
2687
- @change=${(h) => c.change(parseInt(h.target.value) || 0)}
2688
- style="flex:1;padding:4px 6px;border:1px solid #d1d5db;border-radius:3px;font-size:12px;width:50px;" />
2689
- </div>
2690
- `)}
2691
- </div>
2692
- </div>
2693
- `;
2694
- }
2695
- function ae(e, t, o) {
2696
- return p`
2697
- <div class="prop-row">
2698
- <div class="prop-toggle">
2699
- <input type="checkbox" .checked=${!!e}
2700
- @change=${(n) => t(n.target.checked)} />
2701
- <label class="prop-label" style="margin:0;">${o}</label>
2702
- </div>
2703
- </div>
2704
- `;
2705
- }
2706
- function de(e, t, o) {
2707
- return p`
2708
- <div class="prop-row">
2709
- <label class="prop-label">${o}</label>
2710
- <input class="prop-input" type="text" .value=${e ?? ""}
2711
- @change=${(n) => t(n.target.value)} />
2712
- </div>
2713
- `;
2714
- }
2715
- function ce(e, t, o) {
2716
- return p`
2717
- <div class="prop-row">
2718
- <label class="prop-label">${o}</label>
2719
- <textarea class="prop-input"
2720
- style="min-height:100px;font-family:'SF Mono',Menlo,monospace;font-size:12px;"
2721
- .value=${e ?? ""}
2722
- @change=${(n) => t(n.target.value)}
2723
- ></textarea>
2724
- </div>
2725
- `;
2726
- }
2727
- var pe = Object.defineProperty, he = Object.getOwnPropertyDescriptor, Q = (e, t, o, n) => {
2728
- for (var i = n > 1 ? void 0 : n ? he(t, o) : t, s = e.length - 1, r; s >= 0; s--)
2729
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
2730
- return n && i && pe(t, o, i), i;
2731
- };
2732
- let E = class extends $ {
2733
- constructor() {
2734
- super(...arguments), this.storeCtrl = new T(this, ["design", "selection"]);
2735
- }
2736
- set store(e) {
2737
- this.storeCtrl.setStore(e);
2738
- }
2739
- get store() {
2740
- return this.storeCtrl.store;
2741
- }
2742
- /** Create a change handler bound to a specific content ID and property key */
2743
- onChange(e, t) {
2744
- return (o) => {
2745
- this.store.updateContentValues(e, { [t]: o });
2746
- };
2747
- }
2748
- render() {
2749
- const e = this.store.selectedId;
2750
- if (!e)
2751
- return p`<div class="no-selection">Select an element to edit its properties</div>`;
2752
- const t = this.store.findContent(e);
2753
- if (!t)
2754
- return p`<div class="no-selection">Select an element to edit its properties</div>`;
2755
- const o = this.toolRegistry.get(t.type);
2756
- return o ? p`
2757
- <div class="header">
2758
- <p class="header-title">${o.label}</p>
2759
- <p class="header-type">${t.type}</p>
2760
- </div>
2761
- ${Object.entries(o.options).map(([, n]) => this.renderGroup(t, n))}
2762
- ` : p`<div class="no-selection">Unknown tool: ${t.type}</div>`;
2763
- }
2764
- renderGroup(e, t) {
2765
- return p`
2766
- <div class="group">
2767
- <div class="group-title">${t.title}</div>
2768
- <div class="group-body">
2769
- ${Object.entries(t.options).map(
2770
- ([o, n]) => this.renderWidget(e, o, n)
2771
- )}
2772
- </div>
2773
- </div>
2774
- `;
2775
- }
2776
- /** Delegate to the correct widget function based on the property's widget type */
2777
- renderWidget(e, t, o) {
2778
- const n = e.values[t] ?? o.defaultValue, i = this.onChange(e.id, t);
2779
- switch (o.widget) {
2780
- case "color_picker":
2781
- return oe(n, i, o.label);
2782
- case "toggle":
2783
- return ae(n, i, o.label);
2784
- case "rich_text":
2785
- return ce(n, i, o.label);
2786
- case "dropdown":
2787
- return ne(n, i, o.label, o.widgetParams);
2788
- case "alignment":
2789
- return ie(n, i, o.label);
2790
- case "padding":
2791
- return le(n, i, o.label);
2792
- case "text":
2793
- default:
2794
- return de(n, i, o.label);
2795
- }
2796
- }
2797
- };
2798
- E.styles = k`
2799
- :host {
2800
- display: flex; flex-direction: column; width: 300px; min-width: 300px;
2801
- background: #fff; border-left: 1px solid #e5e7eb;
2802
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2803
- overflow-y: auto;
2804
- }
2805
- .header { padding: 12px 16px; border-bottom: 1px solid #e5e7eb; background: #f9fafb; }
2806
- .header-title { font-size: 14px; font-weight: 600; color: #111827; margin: 0; }
2807
- .header-type { font-size: 11px; color: #9ca3af; text-transform: uppercase; margin: 2px 0 0 0; }
2808
- .no-selection {
2809
- display: flex; flex-direction: column; align-items: center; justify-content: center;
2810
- flex: 1; color: #9ca3af; font-size: 13px; gap: 8px; padding: 40px 20px; text-align: center;
2811
- }
2812
- .group { border-bottom: 1px solid #f3f4f6; }
2813
- .group-title {
2814
- padding: 10px 16px; font-size: 12px; font-weight: 600; color: #6b7280;
2815
- background: #f9fafb; cursor: pointer; user-select: none;
2816
- display: flex; align-items: center; justify-content: space-between;
2817
- }
2818
- .group-title:hover { background: #f3f4f6; }
2819
- .group-body { padding: 12px 16px; }
2820
- .prop-row { margin-bottom: 12px; }
2821
- .prop-label { display: block; font-size: 12px; color: #6b7280; margin-bottom: 4px; }
2822
- .prop-input {
2823
- width: 100%; padding: 6px 8px; border: 1px solid #d1d5db; border-radius: 4px;
2824
- font-size: 13px; color: #111827; background: white; box-sizing: border-box;
2825
- }
2826
- .prop-input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.15); }
2827
- .prop-toggle { display: flex; align-items: center; gap: 8px; }
2828
- .prop-color { display: flex; align-items: center; gap: 8px; }
2829
- .color-swatch { width: 28px; height: 28px; border-radius: 4px; border: 1px solid #d1d5db; cursor: pointer; padding: 0; }
2830
- textarea.prop-input { min-height: 80px; resize: vertical; font-family: monospace; }
2831
- `;
2832
- Q([
2833
- b({ attribute: !1 })
2834
- ], E.prototype, "store", 1);
2835
- Q([
2836
- b({ attribute: !1 })
2837
- ], E.prototype, "toolRegistry", 2);
2838
- E = Q([
2839
- C("me-property-panel")
2840
- ], E);
2841
- var ue = Object.defineProperty, ge = Object.getOwnPropertyDescriptor, ct = (e, t, o, n) => {
2842
- for (var i = n > 1 ? void 0 : n ? ge(t, o) : t, s = e.length - 1, r; s >= 0; s--)
2843
- (r = e[s]) && (i = (n ? r(t, o, i) : r(i)) || i);
2844
- return n && i && ue(t, o, i), i;
2845
- };
2846
- let B = class extends $ {
2847
- constructor() {
2848
- super(...arguments), this.options = {}, this.store = new vt(), this.toolRegistry = new $t(), this.dragManager = null, this.callbacks = /* @__PURE__ */ new Map(), this.unsubscribe = null, this._handleKeydown = (e) => {
2849
- const t = e.metaKey || e.ctrlKey, n = e.composedPath().some((i) => {
2850
- const s = i?.tagName;
2851
- return s === "INPUT" || s === "TEXTAREA" || s === "SELECT";
2852
- });
2853
- n && !t || (t && e.key === "z" && !e.shiftKey ? (e.preventDefault(), this.store.undo()) : t && (e.key === "y" || e.key === "z" && e.shiftKey) ? (e.preventDefault(), this.store.redo()) : (e.key === "Delete" || e.key === "Backspace") && this.store.selectedId && !n ? (e.preventDefault(), this.store.removeContent(this.store.selectedId)) : e.key === "Escape" && this.store.select(null));
2854
- };
2855
- }
2856
- connectedCallback() {
2857
- super.connectedCallback(), this.registerBuiltInTools(), this.applyOptions(), this.setAttribute("tabindex", "0"), this.addEventListener("keydown", this._handleKeydown);
2858
- }
2859
- firstUpdated() {
2860
- this.dragManager = new kt(this.store, this.toolRegistry, this.shadowRoot), this.dragManager.attach(), this.store.events.on("design:loaded", (e) => {
2861
- this.dispatchEvent(new CustomEvent("design:loaded", { detail: e, bubbles: !0, composed: !0 }));
2862
- }), this.store.events.on("design:updated", (e) => {
2863
- this.dispatchEvent(new CustomEvent("design:updated", { detail: e, bubbles: !0, composed: !0 }));
2864
- }), this.dispatchEvent(new CustomEvent("editor:ready", { bubbles: !0, composed: !0 })), this.preloadLazyTools();
2865
- }
2866
- disconnectedCallback() {
2867
- super.disconnectedCallback(), this.dragManager?.detach(), this.unsubscribe?.(), this.store.events.removeAllListeners(), this.removeEventListener("keydown", this._handleKeydown);
2868
- }
2869
- // ----------------------------------------------------------
2870
- // Public API — public API
2871
- // ----------------------------------------------------------
2872
- loadDesign(e) {
2873
- this.store.loadDesign(e);
2874
- }
2875
- saveDesign(e) {
2876
- e(structuredClone(this.store.getDesign()));
2877
- }
2878
- exportHtml(e, t) {
2879
- const o = this.store.getDesign(), n = /* @__PURE__ */ new Set();
2880
- for (const s of o.body.rows)
2881
- for (const r of s.columns)
2882
- for (const a of r.contents)
2883
- n.add(a.type);
2884
- const i = Array.from(n).filter((s) => !this.toolRegistry.isLoaded(s)).map((s) => this.toolRegistry.ensureLoaded(s));
2885
- i.length > 0 ? Promise.all(i).then(() => this.doExport(o, e, t)) : this.doExport(o, e, t);
2886
- }
2887
- doExport(e, t, o) {
2888
- const n = /* @__PURE__ */ new Map();
2889
- for (const i of this.toolRegistry.getAll())
2890
- n.set(i.name, (s, r) => i.renderer.renderHtml(s, r));
2891
- t(jt(e, n, o));
2892
- }
2893
- async exportHtmlAsync(e) {
2894
- return new Promise((t) => this.exportHtml(t, e));
2895
- }
2896
- registerTool(e) {
2897
- this.toolRegistry.register(e), this.requestUpdate();
2898
- }
2899
- registerPropertyEditor(e, t) {
2900
- }
2901
- registerTab(e) {
2902
- }
2903
- registerCallback(e, t) {
2904
- this.callbacks.set(e, t);
2905
- }
2906
- setMergeTags(e) {
2907
- }
2908
- undo() {
2909
- this.store.undo();
2910
- }
2911
- redo() {
2912
- this.store.redo();
2913
- }
2914
- setBodyValues(e) {
2915
- this.store.updateBodyValues(e);
2916
- }
2917
- // ----------------------------------------------------------
2918
- // Internal
2919
- // ----------------------------------------------------------
2920
- /**
2921
- * Preload lazy tools during browser idle time.
2922
- * Uses requestIdleCallback to avoid blocking the main thread.
2923
- * Falls back to setTimeout(1000) for browsers without idle callback support.
2924
- */
2925
- preloadLazyTools() {
2926
- const e = window.requestIdleCallback ?? ((t) => setTimeout(t, 1e3));
2927
- for (const { meta: t } of st)
2928
- e(() => {
2929
- this.toolRegistry.ensureLoaded(t.name);
2930
- });
2931
- }
2932
- registerBuiltInTools() {
2933
- for (const e of Pt)
2934
- this.toolRegistry.register(e);
2935
- for (const { meta: e, loader: t } of st)
2936
- this.toolRegistry.registerLazy(e, t);
2937
- }
2938
- applyOptions() {
2939
- this.options.design && this.store.loadDesign(this.options.design);
2940
- }
2941
- render() {
2942
- return p`
2943
- <me-editor-sidebar
2944
- .store=${this.store}
2945
- .toolRegistry=${this.toolRegistry}
2946
- ></me-editor-sidebar>
2947
- <me-editor-canvas
2948
- .store=${this.store}
2949
- .toolRegistry=${this.toolRegistry}
2950
- ></me-editor-canvas>
2951
- <me-property-panel
2952
- .store=${this.store}
2953
- .toolRegistry=${this.toolRegistry}
2954
- ></me-property-panel>
2955
- `;
2956
- }
2957
- };
2958
- B.styles = k`
2959
- :host {
2960
- display: flex;
2961
- width: 100%;
2962
- height: 100%;
2963
- flex: 1;
2964
- min-height: 500px;
2965
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2966
- color: #111827;
2967
- box-sizing: border-box;
2968
- overflow: hidden;
2969
- position: relative;
2970
- }
2971
- * { box-sizing: border-box; }
2972
- `;
2973
- ct([
2974
- b({ type: Object })
2975
- ], B.prototype, "options", 2);
2976
- B = ct([
2977
- C("mail-editor")
2978
- ], B);
2979
- function _(e, t) {
2980
- customElements.get(e) || customElements.define(e, t);
2981
- }
2982
- _("mail-editor", B);
2983
- _("me-editor-canvas", M);
2984
- _("me-row-renderer", R);
2985
- _("me-column-renderer", S);
2986
- _("me-content-renderer", D);
2987
- _("me-editor-sidebar", P);
2988
- _("me-body-settings", A);
2989
- _("me-property-panel", E);
2990
- _("me-inline-toolbar", V);
2991
- const we = customElements.get("mail-editor") !== void 0;
2992
- export {
2993
- we as E,
2994
- B as M,
2995
- $t as T,
2996
- vt as a,
2997
- z as e,
2998
- ye as j,
2999
- l as s
3000
- };
3001
- //# sourceMappingURL=index-BoKIXJKv.js.map