best-email-builder 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +11 -1
  3. package/dist/{chunk-2PMZMNKA.js → chunk-MTALS5GC.js} +98 -3
  4. package/dist/chunk-MTALS5GC.js.map +1 -0
  5. package/dist/index.js +977 -299
  6. package/dist/index.js.map +1 -1
  7. package/dist/server.js +13 -13
  8. package/dist/server.js.map +1 -1
  9. package/dist/styles.css +1 -1
  10. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +1 -1
  11. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +18 -0
  12. package/dist/types/blocks/columns/index.d.ts +3 -0
  13. package/dist/types/blocks/heading/index.d.ts +3 -0
  14. package/dist/types/blocks/hero/index.d.ts +3 -0
  15. package/dist/types/blocks/navbar/index.d.ts +6 -0
  16. package/dist/types/blocks/social/index.d.ts +13 -0
  17. package/dist/types/core/EmailEditor.d.ts +12 -4
  18. package/dist/types/core/constants.d.ts +1 -1
  19. package/dist/types/features/templates/TemplateGallery.d.ts +3 -0
  20. package/dist/types/features/templates/gallery.d.ts +41 -0
  21. package/dist/types/features/templates/index.d.ts +2 -0
  22. package/dist/types/features/templates/starter.d.ts +22 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/server/compileMjml.d.ts +3 -3
  25. package/dist/types/server/render.d.ts +4 -4
  26. package/dist/types/server/templates.d.ts +2 -2
  27. package/dist/types/ui/BlockSidebar.d.ts +2 -1
  28. package/dist/types/ui/Drawer.d.ts +19 -0
  29. package/dist/types/ui/EmailBuilder.d.ts +2 -0
  30. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  31. package/dist/types/ui/Toolbar.d.ts +9 -2
  32. package/dist/types/ui/primitives/button.d.ts +1 -1
  33. package/dist/types/ui/useLayoutWidth.d.ts +10 -0
  34. package/package.json +8 -7
  35. package/dist/chunk-2PMZMNKA.js.map +0 -1
  36. package/dist/chunk-3EOCXTI2.js +0 -652
  37. package/dist/chunk-3EOCXTI2.js.map +0 -1
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  unknownVariables,
30
30
  usedVariables,
31
31
  validateCustomVariable
32
- } from "./chunk-2PMZMNKA.js";
32
+ } from "./chunk-MTALS5GC.js";
33
33
 
34
34
  // src/core/events.js
35
35
  function createEmitter({ onError } = {}) {
@@ -136,7 +136,7 @@ function createEmailEditor({ adapter, blocks, features = null }) {
136
136
  return resolved;
137
137
  }
138
138
  function blockForTag(tagName) {
139
- const all = blocks.list();
139
+ const all = blocks.list((b) => !b.insertOnly);
140
140
  return all.find((b) => b.tagName === tagName && b.kind === "structure") || all.find((b) => b.tagName === tagName) || null;
141
141
  }
142
142
  function toEmailSelection(selection) {
@@ -302,9 +302,14 @@ function createEmailEditor({ adapter, blocks, features = null }) {
302
302
  requireReady("exportMjml");
303
303
  return adapter.exportMjml();
304
304
  },
305
- exportHtml() {
305
+ /**
306
+ * The email (or the given MJML) compiled by the CANVAS compiler — for
307
+ * display only (thumbnails, inspection). The HTML that is stored and sent
308
+ * is always compiled by the server.
309
+ */
310
+ exportHtml(mjml = null) {
306
311
  requireReady("exportHtml");
307
- return adapter.exportHtml();
312
+ return adapter.exportHtml(mjml === null || mjml === void 0 ? null : String(mjml));
308
313
  },
309
314
  setDevice(device) {
310
315
  requireReady("setDevice");
@@ -426,12 +431,23 @@ function buildEngineConfig({ container, mjmlPlugin, locales = {}, locale = "en"
426
431
  };
427
432
  }
428
433
 
434
+ // src/adapters/grapesjs/unwrapModule.js
435
+ function unwrapModule(mod) {
436
+ let value = mod;
437
+ for (let depth = 0; depth < 3 && isInteropLayer(value); depth += 1) value = value.default;
438
+ return value;
439
+ }
440
+ function isInteropLayer(value) {
441
+ if (value === null || typeof value !== "object" || !("default" in value)) return false;
442
+ return value.__esModule === true || Object.prototype.toString.call(value) === "[object Module]";
443
+ }
444
+
429
445
  // src/adapters/grapesjs/GrapesJsAdapter.js
430
446
  var ENGINE_NAME = "grapesjs-mjml";
431
- var ENGINE_VERSION = "0.22.16";
447
+ var ENGINE_VERSION = "0.23.6";
432
448
  var BLOCK_PREFIX = "eb-";
433
449
  var LOCKED_TYPES = /* @__PURE__ */ new Set(["mjml", "mj-head", "mj-body", "wrapper"]);
434
- var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button"]);
450
+ var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button", "mj-navbar-link"]);
435
451
  var RICH_TYPES = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
436
452
  var ENGINE_LOCALES = {
437
453
  fr: () => Promise.all([import("grapesjs/locale/fr.mjs"), import("grapesjs-mjml/locale/fr")])
@@ -443,11 +459,10 @@ async function defaultLoadEngine(locale) {
443
459
  import("grapesjs-mjml"),
444
460
  loadLocales ? loadLocales() : Promise.resolve(null)
445
461
  ]);
446
- const unwrap = (mod) => mod.default || mod;
447
462
  return {
448
- grapesjs: unwrap(grapesjs),
449
- mjmlPlugin: unwrap(mjml),
450
- locales: localeModules ? { engine: unwrap(localeModules[0]), mjml: unwrap(localeModules[1]) } : {}
463
+ grapesjs: unwrapModule(grapesjs),
464
+ mjmlPlugin: unwrapModule(mjml),
465
+ locales: localeModules ? { engine: unwrapModule(localeModules[0]), mjml: unwrapModule(localeModules[1]) } : {}
451
466
  };
452
467
  }
453
468
  var ENGINE_SVG = /^\s*<svg[\s>]/i;
@@ -571,11 +586,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
571
586
  function placementFor(block, target) {
572
587
  const root = body();
573
588
  if (!root) throw new Error("GrapesJsAdapter: email body not found");
574
- const section = target ? closest(target, ["mj-section", "mj-wrapper", "mj-hero"]) : null;
589
+ const section2 = target ? closest(target, ["mj-section", "mj-wrapper", "mj-hero"]) : null;
575
590
  const afterSection = () => {
576
- if (section && section.parent()) {
577
- const coll = section.parent().components();
578
- return { collection: coll, at: coll.indexOf(section) + 1, wrap: false };
591
+ if (section2 && section2.parent()) {
592
+ const coll = section2.parent().components();
593
+ return { collection: coll, at: coll.indexOf(section2) + 1, wrap: false };
579
594
  }
580
595
  return { collection: root.components(), at: root.components().length, wrap: false };
581
596
  };
@@ -584,11 +599,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
584
599
  if (type === "mj-column") {
585
600
  return { collection: target.components(), at: target.components().length, wrap: false };
586
601
  }
587
- const column = target ? closest(target, ["mj-column"]) : null;
588
- if (column && target !== column) {
602
+ const column2 = target ? closest(target, ["mj-column"]) : null;
603
+ if (column2 && target !== column2) {
589
604
  let child = target;
590
- while (child.parent() && child.parent() !== column) child = child.parent();
591
- const coll = column.components();
605
+ while (child.parent() && child.parent() !== column2) child = child.parent();
606
+ const coll = column2.components();
592
607
  return { collection: coll, at: coll.indexOf(child) + 1, wrap: false };
593
608
  }
594
609
  return { ...afterSection(), wrap: true };
@@ -803,8 +818,9 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
803
818
  exportMjml() {
804
819
  return String(requireEditor().runCommand("mjml-code") || "").replace(PLACEHOLDER_SRC_ATTR, "").trim();
805
820
  },
806
- exportHtml() {
807
- const result = requireEditor().runCommand("mjml-code-to-html", { mjml: adapter.exportMjml() }) || {};
821
+ exportHtml(mjml = null) {
822
+ const source = mjml ?? adapter.exportMjml();
823
+ const result = requireEditor().runCommand("mjml-code-to-html", { mjml: source }) || {};
808
824
  return {
809
825
  html: String(result.html || ""),
810
826
  errors: (result.errors || []).map((e) => ({
@@ -971,6 +987,30 @@ var sectionBlock = {
971
987
  order: 901,
972
988
  properties: [
973
989
  prop.background("background-color", "Background"),
990
+ // A background image keeps the color under it: shown when images are blocked.
991
+ { key: "background-url", label: "Background image", type: "image", group: GROUPS.colors },
992
+ {
993
+ key: "background-size",
994
+ label: "Image size",
995
+ type: "select",
996
+ group: GROUPS.colors,
997
+ options: [
998
+ { value: "cover", label: "Fill the section" },
999
+ { value: "contain", label: "Whole image" },
1000
+ { value: "auto", label: "Original size" }
1001
+ ]
1002
+ },
1003
+ {
1004
+ key: "background-position",
1005
+ label: "Image position",
1006
+ type: "select",
1007
+ group: GROUPS.colors,
1008
+ options: [
1009
+ { value: "center center", label: "Center" },
1010
+ { value: "center top", label: "Top" },
1011
+ { value: "center bottom", label: "Bottom" }
1012
+ ]
1013
+ },
974
1014
  prop.padding("padding", "Padding"),
975
1015
  prop.radius(24)
976
1016
  ]
@@ -1019,6 +1059,16 @@ var textBlock = {
1019
1059
  prop.fontFamily(),
1020
1060
  prop.fontSize(10, 48),
1021
1061
  prop.lineHeight(),
1062
+ {
1063
+ key: "font-weight",
1064
+ label: "Weight",
1065
+ type: "select",
1066
+ group: GROUPS.typography,
1067
+ options: [
1068
+ { value: "normal", label: "Normal" },
1069
+ { value: "bold", label: "Bold" }
1070
+ ]
1071
+ },
1022
1072
  prop.color("Text"),
1023
1073
  prop.background("container-background-color", "Background"),
1024
1074
  { ...prop.align(), options: [...prop.align().options || [], { value: "justify", label: "Justify" }] },
@@ -1162,6 +1212,133 @@ var spacerBlock = {
1162
1212
  ]
1163
1213
  };
1164
1214
 
1215
+ // src/blocks/heading/index.js
1216
+ var MJML6 = `<mj-text font-family="${FONT_STACK}" font-size="26px" line-height="34px" font-weight="bold" color="${PALETTE.navy}" align="left">Your headline</mj-text>`;
1217
+ var headingBlock = {
1218
+ id: "heading",
1219
+ label: "Heading",
1220
+ description: "Large bold title",
1221
+ icon: "Heading",
1222
+ kind: "content",
1223
+ tagName: "mj-text",
1224
+ insertOnly: true,
1225
+ order: 5,
1226
+ mjml: MJML6,
1227
+ sectionMjml: inWhiteSection(MJML6),
1228
+ editableContent: true,
1229
+ properties: []
1230
+ };
1231
+
1232
+ // src/blocks/social/index.js
1233
+ var SOCIAL_NETWORKS = [
1234
+ { value: "facebook-noshare", label: "Facebook", href: "https://www.facebook.com/" },
1235
+ { value: "instagram-noshare", label: "Instagram", href: "https://www.instagram.com/" },
1236
+ { value: "linkedin-noshare", label: "LinkedIn", href: "https://www.linkedin.com/" },
1237
+ { value: "x-noshare", label: "X", href: "https://x.com/" },
1238
+ { value: "youtube-noshare", label: "YouTube", href: "https://www.youtube.com/" },
1239
+ { value: "github-noshare", label: "GitHub", href: "https://github.com/" }
1240
+ ];
1241
+ var DEFAULT_NETWORKS = ["facebook-noshare", "instagram-noshare", "linkedin-noshare", "x-noshare"];
1242
+ var MJML7 = '<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">' + DEFAULT_NETWORKS.map((name) => {
1243
+ const network = SOCIAL_NETWORKS.find((n) => n.value === name);
1244
+ return `<mj-social-element name="${name}" href="${network?.href}"></mj-social-element>`;
1245
+ }).join("") + "</mj-social>";
1246
+ var socialBlock = {
1247
+ id: "social",
1248
+ label: "Social links",
1249
+ description: "Icons linking to your profiles",
1250
+ icon: "Share2",
1251
+ kind: "content",
1252
+ tagName: "mj-social",
1253
+ order: 60,
1254
+ mjml: MJML7,
1255
+ sectionMjml: inWhiteSection(MJML7),
1256
+ properties: [
1257
+ { key: "icon-size", label: "Icon size", type: "size", group: GROUPS.layout, min: 16, max: 48, unit: "px" },
1258
+ prop.radius(24),
1259
+ prop.align(),
1260
+ {
1261
+ key: "mode",
1262
+ label: "Direction",
1263
+ type: "select",
1264
+ group: GROUPS.layout,
1265
+ options: [
1266
+ { value: "horizontal", label: "Horizontal" },
1267
+ { value: "vertical", label: "Vertical" }
1268
+ ]
1269
+ },
1270
+ prop.padding("inner-padding", "Space around each icon"),
1271
+ prop.padding()
1272
+ ]
1273
+ };
1274
+ var socialElementBlock = {
1275
+ id: "social-element",
1276
+ label: "Social link",
1277
+ icon: "Link",
1278
+ kind: "structure",
1279
+ tagName: "mj-social-element",
1280
+ order: 903,
1281
+ properties: [
1282
+ {
1283
+ key: "name",
1284
+ label: "Network",
1285
+ type: "select",
1286
+ group: GROUPS.content,
1287
+ options: SOCIAL_NETWORKS.map(({ value, label }) => ({ value, label }))
1288
+ },
1289
+ prop.href("Profile URL"),
1290
+ prop.background("background-color", "Icon background")
1291
+ ]
1292
+ };
1293
+
1294
+ // src/blocks/navbar/index.js
1295
+ function link(label, href) {
1296
+ return `<mj-navbar-link href="${href}" font-family="${FONT_STACK}" font-size="14px" color="${PALETTE.navy}" padding-top="8px" padding-right="12px" padding-bottom="8px" padding-left="12px">${label}</mj-navbar-link>`;
1297
+ }
1298
+ var MJML8 = '<mj-navbar align="center">' + link("Shop", "https://example.com/shop") + link("Blog", "https://example.com/blog") + link("Contact", "https://example.com/contact") + "</mj-navbar>";
1299
+ var navbarBlock = {
1300
+ id: "navbar",
1301
+ label: "Menu",
1302
+ description: "Row of links to your site",
1303
+ icon: "Menu",
1304
+ kind: "content",
1305
+ tagName: "mj-navbar",
1306
+ order: 70,
1307
+ mjml: MJML8,
1308
+ sectionMjml: inWhiteSection(MJML8),
1309
+ properties: [prop.align()]
1310
+ };
1311
+ var navbarLinkBlock = {
1312
+ id: "navbar-link",
1313
+ label: "Menu link",
1314
+ icon: "Link",
1315
+ kind: "structure",
1316
+ tagName: "mj-navbar-link",
1317
+ order: 904,
1318
+ editableContent: true,
1319
+ properties: [
1320
+ prop.href("Link URL"),
1321
+ prop.color("Text"),
1322
+ prop.fontFamily(),
1323
+ prop.fontSize(10, 24),
1324
+ prop.padding()
1325
+ ]
1326
+ };
1327
+
1328
+ // src/blocks/hero/index.js
1329
+ var MJML9 = `<mj-section background-color="${PALETTE.navy}" background-size="cover" background-position="center center" background-repeat="no-repeat" padding-top="56px" padding-bottom="56px"><mj-column><mj-text font-family="${FONT_STACK}" font-size="30px" line-height="38px" font-weight="bold" color="${PALETTE.white}" align="center" padding-left="32px" padding-right="32px">Your big announcement</mj-text><mj-text font-family="${FONT_STACK}" font-size="16px" line-height="24px" color="${PALETTE.border}" align="center" padding-left="32px" padding-right="32px">One sentence that makes people want to read on.</mj-text><mj-button href="${DEFAULT_BUTTON_URL}" background-color="${PALETTE.white}" color="${PALETTE.navy}" font-family="${FONT_STACK}" font-size="15px" font-weight="bold" border-radius="6px" inner-padding="12px 28px" align="center">Learn more</mj-button></mj-column></mj-section>`;
1330
+ var heroBlock = {
1331
+ id: "hero",
1332
+ label: "Hero banner",
1333
+ description: "Background image with a title and a button",
1334
+ icon: "GalleryHorizontal",
1335
+ kind: "layout",
1336
+ tagName: "mj-section",
1337
+ order: 110,
1338
+ mjml: MJML9,
1339
+ properties: []
1340
+ };
1341
+
1165
1342
  // src/blocks/columns/index.js
1166
1343
  function sectionWith(count) {
1167
1344
  const columns = Array.from(
@@ -1186,20 +1363,41 @@ function layout(count, icon) {
1186
1363
  var oneColumnBlock = layout(1, "Square");
1187
1364
  var twoColumnsBlock = layout(2, "Columns2");
1188
1365
  var threeColumnsBlock = layout(3, "Columns3");
1366
+ var fourColumnsBlock = layout(4, "Columns4");
1367
+ var IMAGE_TEXT_MJML = `<mj-section background-color="${PALETTE.surface}"><mj-column vertical-align="middle"><mj-image align="center"></mj-image></mj-column><mj-column vertical-align="middle"><mj-text font-family="${FONT_STACK}" font-size="20px" line-height="28px" font-weight="bold" color="${PALETTE.navy}" align="left">Title</mj-text>` + textMjml("A few words about it.") + `<mj-button href="${DEFAULT_BUTTON_URL}" background-color="${PALETTE.navy}" color="${PALETTE.white}" font-family="${FONT_STACK}" font-size="14px" font-weight="bold" border-radius="6px" inner-padding="10px 22px" align="left">Learn more</mj-button></mj-column></mj-section>`;
1368
+ var imageTextBlock = {
1369
+ id: "image-text",
1370
+ label: "Image + text",
1371
+ description: "Image beside a title, text and button",
1372
+ icon: "LayoutPanelLeft",
1373
+ kind: "layout",
1374
+ tagName: "mj-section",
1375
+ order: 105,
1376
+ mjml: IMAGE_TEXT_MJML,
1377
+ properties: []
1378
+ };
1189
1379
 
1190
1380
  // src/blocks/index.js
1191
1381
  var BLOCKS = [
1382
+ headingBlock,
1192
1383
  textBlock,
1193
1384
  imageBlock,
1194
1385
  buttonBlock,
1195
1386
  dividerBlock,
1196
1387
  spacerBlock,
1388
+ socialBlock,
1389
+ navbarBlock,
1197
1390
  oneColumnBlock,
1198
1391
  twoColumnsBlock,
1199
1392
  threeColumnsBlock,
1393
+ fourColumnsBlock,
1394
+ imageTextBlock,
1395
+ heroBlock,
1200
1396
  bodyBlock,
1201
1397
  sectionBlock,
1202
- columnBlock
1398
+ columnBlock,
1399
+ socialElementBlock,
1400
+ navbarLinkBlock
1203
1401
  ];
1204
1402
  var emailBlocks = createRegistry("emailBlocks", BLOCKS);
1205
1403
 
@@ -2497,7 +2695,7 @@ function TestEmailButton({ context }) {
2497
2695
  r.error ? ` \u2014 ${r.error}` : ""
2498
2696
  ] })
2499
2697
  ] }, r.email)),
2500
- report.invalid.map((email) => /* @__PURE__ */ jsx11("li", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Ignored (invalid): {email}", { email }) }, `invalid-${email}`))
2698
+ report.invalid.map((email2) => /* @__PURE__ */ jsx11("li", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Ignored (invalid): {email}", { email: email2 }) }, `invalid-${email2}`))
2501
2699
  ] }),
2502
2700
  /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
2503
2701
  /* @__PURE__ */ jsx11(Button, { variant: "outline", onClick: () => setOpen(false), children: t("Close") }),
@@ -2519,17 +2717,9 @@ var testEmailFeature = {
2519
2717
  isAvailable: (context) => typeof context.storage?.sendTest === "function"
2520
2718
  };
2521
2719
 
2522
- // src/features/index.js
2523
- var FEATURES = [variablesFeature, autosaveFeature, versionsFeature, previewFeature, testEmailFeature];
2524
- var emailBuilderFeatures = createRegistry("emailBuilderFeatures", FEATURES);
2525
- function isFeatureEnabled(id) {
2526
- const feature = emailBuilderFeatures.get(id);
2527
- return Boolean(feature && feature.enabled !== false);
2528
- }
2529
-
2530
- // src/ui/EmailBuilder.jsx
2531
- import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef7, useState as useState12 } from "react";
2532
- import { AlertTriangle as AlertTriangle2, Loader2 as Loader25, Save } from "lucide-react";
2720
+ // src/features/templates/TemplateGallery.jsx
2721
+ import { useMemo as useMemo5, useState as useState8 } from "react";
2722
+ import { LayoutTemplate } from "lucide-react";
2533
2723
 
2534
2724
  // src/features/templates/starter.js
2535
2725
  function escapeXml(text) {
@@ -2537,17 +2727,23 @@ function escapeXml(text) {
2537
2727
  }
2538
2728
  var SIDE = 'padding-left="32px" padding-right="32px"';
2539
2729
  var TEXT = `font-family="${FONT_STACK}" color="${PALETTE.ink}"`;
2540
- function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = identity } = {}) {
2730
+ function senderHeaderMjml(sender, t) {
2731
+ const name = escapeXml(sender.name || t("Your company"));
2732
+ return sender.logoUrl ? `<mj-image src="${escapeXml(sender.logoUrl)}" alt="${name}" width="${Number(sender.logoWidth) || 140}px" align="left" ${SIDE}></mj-image>` : `<mj-text ${TEXT} font-size="18px" line-height="24px" font-weight="bold" ${SIDE}>${name}</mj-text>`;
2733
+ }
2734
+ function footerSectionMjml(sender, t) {
2541
2735
  const name = escapeXml(sender.name || t("Your company"));
2542
2736
  const footer = [name, sender.address ? escapeXml(sender.address) : ""].filter(Boolean).join(" \xB7 ");
2543
- const header = sender.logoUrl ? `<mj-image src="${escapeXml(sender.logoUrl)}" alt="${name}" width="${Number(sender.logoWidth) || 140}px" align="left" ${SIDE}></mj-image>` : `<mj-text ${TEXT} font-size="18px" line-height="24px" font-weight="bold" ${SIDE}>${name}</mj-text>`;
2737
+ return `<mj-section padding-top="20px" padding-bottom="28px"><mj-column><mj-text font-family="${FONT_STACK}" font-size="12px" line-height="18px" color="#6d7484" align="center" ${SIDE}>${footer}<br /><a href="{{ unsubscribe }}" style="color:#6d7484;text-decoration:underline;">${escapeXml(t("Unsubscribe"))}</a></mj-text></mj-column></mj-section>`;
2738
+ }
2739
+ function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = identity } = {}) {
2544
2740
  return [
2545
2741
  "<mjml>",
2546
2742
  `<mj-body background-color="${PALETTE.canvas}" width="600px">`,
2547
2743
  // Header: logo or sender name.
2548
2744
  `<mj-section background-color="${PALETTE.surface}" padding-top="28px" padding-bottom="8px">`,
2549
2745
  "<mj-column>",
2550
- header,
2746
+ senderHeaderMjml(sender, t),
2551
2747
  "</mj-column>",
2552
2748
  "</mj-section>",
2553
2749
  // Body: headline, text, button.
@@ -2559,17 +2755,316 @@ function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = id
2559
2755
  "</mj-column>",
2560
2756
  "</mj-section>",
2561
2757
  // Footer: sender and unsubscribe link.
2562
- '<mj-section padding-top="20px" padding-bottom="28px">',
2563
- "<mj-column>",
2564
- `<mj-text font-family="${FONT_STACK}" font-size="12px" line-height="18px" color="#6d7484" align="center" ${SIDE}>${footer}<br /><a href="{{ unsubscribe }}" style="color:#6d7484;text-decoration:underline;">${escapeXml(t("Unsubscribe"))}</a></mj-text>`,
2565
- "</mj-column>",
2566
- "</mj-section>",
2758
+ footerSectionMjml(sender, t),
2567
2759
  "</mj-body>",
2568
2760
  "</mjml>"
2569
2761
  ].join("");
2570
2762
  }
2571
2763
  var STARTER_MJML = createStarterMjml();
2572
2764
 
2765
+ // src/features/templates/gallery.js
2766
+ var WHITE_SECTION = `background-color="${PALETTE.surface}"`;
2767
+ function section(inner, { top = 8, bottom = 8, x = 0 } = {}) {
2768
+ const sides = x ? ` padding-left="${x}px" padding-right="${x}px"` : "";
2769
+ return `<mj-section ${WHITE_SECTION} padding-top="${top}px" padding-bottom="${bottom}px"${sides}>${inner}</mj-section>`;
2770
+ }
2771
+ function column(inner, attrs = "") {
2772
+ return `<mj-column${attrs ? ` ${attrs}` : ""}>${inner}</mj-column>`;
2773
+ }
2774
+ function heading(text, { size = 24, color = PALETTE.navy, align = "left", side = true } = {}) {
2775
+ return `<mj-text font-family="${FONT_STACK}" font-size="${size}px" line-height="${Math.round(size * 1.3)}px" font-weight="bold" color="${color}" align="${align}"${side ? ` ${SIDE}` : ""}>${text}</mj-text>`;
2776
+ }
2777
+ function paragraph(html, { size = 15, color = PALETTE.ink, align = "left", side = true } = {}) {
2778
+ return `<mj-text font-family="${FONT_STACK}" font-size="${size}px" line-height="${Math.round(size * 1.6)}px" color="${color}" align="${align}"${side ? ` ${SIDE}` : ""}>${html}</mj-text>`;
2779
+ }
2780
+ function button(label, { align = "left", background = PALETTE.navy, color = PALETTE.white, side = true } = {}) {
2781
+ return `<mj-button href="${DEFAULT_BUTTON_URL}" background-color="${background}" color="${color}" font-family="${FONT_STACK}" font-size="15px" font-weight="bold" border-radius="6px" inner-padding="12px 28px" align="${align}"${side ? ` ${SIDE}` : ""}>${label}</mj-button>`;
2782
+ }
2783
+ var IMAGE = '<mj-image align="center"></mj-image>';
2784
+ function header(sender, t) {
2785
+ return `<mj-section ${WHITE_SECTION} padding-top="28px" padding-bottom="8px">${column(senderHeaderMjml(sender, t))}</mj-section>`;
2786
+ }
2787
+ function banner({ title, text, cta, background = PALETTE.navy }) {
2788
+ return `<mj-section background-color="${background}" background-size="cover" background-position="center center" background-repeat="no-repeat" padding-top="48px" padding-bottom="48px">` + column(
2789
+ heading(title, { size: 30, color: PALETTE.white, align: "center" }) + paragraph(text, { size: 16, color: PALETTE.border, align: "center" }) + (cta ? button(cta, { align: "center", background: PALETTE.white, color: PALETTE.navy }) : "")
2790
+ ) + "</mj-section>";
2791
+ }
2792
+ function social() {
2793
+ const elements = ["facebook-noshare", "instagram-noshare", "linkedin-noshare"].map((name) => `<mj-social-element name="${name}" href="${SOCIAL_NETWORKS.find((n) => n.value === name)?.href}"></mj-social-element>`).join("");
2794
+ return section(
2795
+ column(
2796
+ `<mj-social mode="horizontal" align="center" icon-size="28px" border-radius="14px" inner-padding="6px">${elements}</mj-social>`
2797
+ ),
2798
+ { top: 16, bottom: 16 }
2799
+ );
2800
+ }
2801
+ function email(body) {
2802
+ return `<mjml><mj-body background-color="${PALETTE.canvas}" width="600px">${body}</mj-body></mjml>`;
2803
+ }
2804
+ var esc = escapeXml;
2805
+ var TEMPLATE_GALLERY = [
2806
+ {
2807
+ id: "blank",
2808
+ name: "Blank",
2809
+ description: "An empty page to build from scratch",
2810
+ create: () => email("")
2811
+ },
2812
+ {
2813
+ id: "simple",
2814
+ name: "Simple",
2815
+ description: "Logo, headline, text and a button",
2816
+ create: ({ sender, t }) => createStarterMjml({ sender, t })
2817
+ },
2818
+ {
2819
+ id: "newsletter",
2820
+ name: "Newsletter",
2821
+ description: "Monthly news: banner, two articles, highlights",
2822
+ meta: (t) => ({
2823
+ subject: t("The news of the month"),
2824
+ preheader: t("What changed, what is coming, and two stories worth your time.")
2825
+ }),
2826
+ create: ({ sender, t }) => email(
2827
+ header(sender, t) + banner({
2828
+ title: esc(t("The news of the month")),
2829
+ text: esc(t("What changed, what is coming, and two stories worth your time.")),
2830
+ cta: null
2831
+ }) + [1, 2].map(
2832
+ (n) => section(
2833
+ column(IMAGE, 'vertical-align="middle"') + column(
2834
+ heading(esc(t("Article {n}", { n })), { size: 20, side: false }) + paragraph(esc(t("Two or three sentences that sum up the story and make people want the rest.")), {
2835
+ side: false
2836
+ }) + button(esc(t("Read more")), { side: false }),
2837
+ 'vertical-align="middle"'
2838
+ ),
2839
+ { top: 16, bottom: 16, x: 16 }
2840
+ )
2841
+ ).join("") + section(column(heading(esc(t("In brief")), { size: 20 })), { top: 20, bottom: 0 }) + section(
2842
+ [t("A new feature"), t("An event"), t("A tip")].map(
2843
+ (title) => column(
2844
+ heading(esc(title), { size: 16, side: false }) + paragraph(esc(t("One sentence about it.")), { size: 14, side: false })
2845
+ )
2846
+ ).join(""),
2847
+ { bottom: 20, x: 16 }
2848
+ ) + social() + footerSectionMjml(sender, t)
2849
+ )
2850
+ },
2851
+ {
2852
+ id: "welcome",
2853
+ name: "Welcome",
2854
+ description: "Greets a new customer and lists the first steps",
2855
+ meta: (t) => ({ subject: t("Welcome aboard!"), preheader: t("Three steps to get started.") }),
2856
+ create: ({ sender, t }) => email(
2857
+ header(sender, t) + section(
2858
+ column(
2859
+ heading(esc(t("Welcome, {name}!", { name: "{{contact.firstname}}" })), { size: 28 }) + paragraph(esc(t("Thank you for joining us. Here is how to get the most out of your account.")))
2860
+ ),
2861
+ { top: 16, bottom: 8 }
2862
+ ) + section(
2863
+ [
2864
+ [t("1. Complete your profile"), t("So we can tailor what we send you.")],
2865
+ [t("2. Explore"), t("Take a tour of what you can do.")],
2866
+ [t("3. Ask us anything"), t("Reply to this email: a person reads it.")]
2867
+ ].map(
2868
+ ([title, text]) => column(
2869
+ heading(esc(title), { size: 16, side: false }) + paragraph(esc(text), { size: 14, side: false }),
2870
+ `background-color="${PALETTE.canvas}" padding-top="12px" padding-bottom="12px" padding-left="12px" padding-right="12px"`
2871
+ )
2872
+ ).join(""),
2873
+ { x: 24 }
2874
+ ) + section(column(button(esc(t("Get started")), { align: "center" })), { top: 16, bottom: 28 }) + footerSectionMjml(sender, t)
2875
+ )
2876
+ },
2877
+ {
2878
+ id: "promotion",
2879
+ name: "Promotion",
2880
+ description: "A sale with a discount code and two products",
2881
+ meta: (t) => ({ subject: t("30% off this weekend only"), preheader: t("With the code SPRING30, until Sunday midnight.") }),
2882
+ create: ({ sender, t }) => email(
2883
+ header(sender, t) + banner({
2884
+ title: esc(t("30% off everything")),
2885
+ text: esc(t("This weekend only, with the code SPRING30.")),
2886
+ cta: esc(t("Shop the sale")),
2887
+ background: PALETTE.brand
2888
+ }) + section(
2889
+ [t("Product one"), t("Product two")].map(
2890
+ (name) => column(
2891
+ IMAGE + heading(esc(name), { size: 18, side: false }) + paragraph(esc(t("\u20AC49 instead of \u20AC70")), { size: 15, side: false }) + button(esc(t("Buy")), { side: false })
2892
+ )
2893
+ ).join(""),
2894
+ { top: 20, bottom: 20, x: 16 }
2895
+ ) + section(
2896
+ column(
2897
+ paragraph(esc(t("Offer valid until Sunday midnight, while stocks last.")), {
2898
+ size: 12,
2899
+ color: "#6d7484",
2900
+ align: "center"
2901
+ })
2902
+ )
2903
+ ) + footerSectionMjml(sender, t)
2904
+ )
2905
+ },
2906
+ {
2907
+ id: "event",
2908
+ name: "Event invitation",
2909
+ description: "Date, place and a button to book a seat",
2910
+ meta: (t) => ({ subject: t("You're invited"), preheader: t("Save the date \u2014 seats are limited.") }),
2911
+ create: ({ sender, t }) => email(
2912
+ header(sender, t) + banner({
2913
+ title: esc(t("You're invited")),
2914
+ text: esc(t("An evening of talks and drinks with the team and our customers.")),
2915
+ cta: null
2916
+ }) + section(
2917
+ column(heading(esc(t("When")), { size: 16, side: false }) + paragraph(esc(t("Thursday, 7 pm")), { side: false })) + column(heading(esc(t("Where")), { size: 16, side: false }) + paragraph(esc(t("Our offices, 1 Main Street")), { side: false })),
2918
+ { top: 24, x: 16 }
2919
+ ) + section(
2920
+ column(
2921
+ paragraph(esc(t("Seats are limited: book yours now, it takes ten seconds."))) + button(esc(t("Book my seat")), { align: "left" })
2922
+ ),
2923
+ { bottom: 28 }
2924
+ ) + footerSectionMjml(sender, t)
2925
+ )
2926
+ }
2927
+ ];
2928
+ function buildGallery({ templates = TEMPLATE_GALLERY, sender = {}, t = identity } = {}) {
2929
+ return templates.map((template) => {
2930
+ const meta = template.meta ? template.meta(t) : {};
2931
+ return {
2932
+ id: template.id,
2933
+ name: t(template.name),
2934
+ description: t(template.description),
2935
+ mjml: template.create({ sender, t }),
2936
+ subject: meta.subject || "",
2937
+ preheader: meta.preheader || ""
2938
+ };
2939
+ });
2940
+ }
2941
+ var THUMBNAIL_IMAGE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 300'%3E%3Crect width='600' height='300' fill='%23dfe6f0'/%3E%3C/svg%3E";
2942
+ function withThumbnailImages(mjml) {
2943
+ return String(mjml || "").replace(/<mj-image(?![^>]*\ssrc=)([^>]*)>/g, `<mj-image src="${THUMBNAIL_IMAGE}"$1>`);
2944
+ }
2945
+
2946
+ // src/features/templates/TemplateGallery.jsx
2947
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2948
+ var THUMBNAIL_SCALE = 0.38;
2949
+ var EMAIL_WIDTH = 600;
2950
+ function Thumbnail({ html, title }) {
2951
+ return /* @__PURE__ */ jsx12(
2952
+ "div",
2953
+ {
2954
+ className: "eb:relative eb:h-[220px] eb:overflow-hidden eb:rounded-md eb:border eb:border-border eb:bg-surface-75",
2955
+ "aria-hidden": "true",
2956
+ children: /* @__PURE__ */ jsx12(
2957
+ "iframe",
2958
+ {
2959
+ title,
2960
+ sandbox: "",
2961
+ srcDoc: html,
2962
+ tabIndex: -1,
2963
+ loading: "lazy",
2964
+ className: "eb:pointer-events-none eb:absolute eb:top-0 eb:left-1/2 eb:origin-top eb:border-0",
2965
+ style: {
2966
+ width: EMAIL_WIDTH,
2967
+ height: 220 / THUMBNAIL_SCALE,
2968
+ transform: `translateX(-50%) scale(${THUMBNAIL_SCALE})`
2969
+ }
2970
+ }
2971
+ )
2972
+ }
2973
+ );
2974
+ }
2975
+ function TemplateGalleryButton({ context }) {
2976
+ const { editor, ready, templates, isDirty, markDirty, template, setMeta } = context;
2977
+ const { t, confirm, notify } = useUi();
2978
+ const [open, setOpen] = useState8(false);
2979
+ const cards = useMemo5(() => {
2980
+ if (!open || !ready) return [];
2981
+ return templates.map((entry) => {
2982
+ try {
2983
+ return { ...entry, html: editor.exportHtml(withThumbnailImages(entry.mjml)).html };
2984
+ } catch {
2985
+ return { ...entry, html: "" };
2986
+ }
2987
+ });
2988
+ }, [open, ready, templates, editor]);
2989
+ const apply = async (entry) => {
2990
+ if (isDirty()) {
2991
+ setOpen(false);
2992
+ const ok = await confirm({
2993
+ title: t("Replace the current email?"),
2994
+ description: t("Unsaved changes will be replaced."),
2995
+ confirmLabel: t("Replace"),
2996
+ destructive: true
2997
+ });
2998
+ if (!ok) return;
2999
+ }
3000
+ editor.loadMjml(entry.mjml);
3001
+ setMeta((meta) => ({
3002
+ ...meta,
3003
+ subject: meta.subject || entry.subject,
3004
+ preheader: meta.preheader || entry.preheader
3005
+ }));
3006
+ if (template.id) markDirty();
3007
+ setOpen(false);
3008
+ notify("success", t("\u201C{name}\u201D template applied.", { name: entry.name }));
3009
+ };
3010
+ return /* @__PURE__ */ jsxs10(Fragment4, { children: [
3011
+ /* @__PURE__ */ jsxs10(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setOpen(true), children: [
3012
+ /* @__PURE__ */ jsx12(LayoutTemplate, {}),
3013
+ t("Templates")
3014
+ ] }),
3015
+ /* @__PURE__ */ jsx12(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs10(DialogContent, { className: "eb:max-h-[calc(100dvh-2rem)] eb:overflow-y-auto eb:sm:max-w-4xl", "data-testid": "eb-template-gallery", children: [
3016
+ /* @__PURE__ */ jsxs10(DialogHeader, { children: [
3017
+ /* @__PURE__ */ jsx12(DialogTitle, { children: t("Start from a template") }),
3018
+ /* @__PURE__ */ jsx12(DialogDescription, { children: t("Picking a template replaces the email being edited.") })
3019
+ ] }),
3020
+ /* @__PURE__ */ jsx12("ul", { className: "eb:grid eb:grid-cols-1 eb:gap-3 eb:sm:grid-cols-2 eb:md:grid-cols-3", children: cards.map((card) => /* @__PURE__ */ jsx12("li", { children: /* @__PURE__ */ jsxs10(
3021
+ "button",
3022
+ {
3023
+ type: "button",
3024
+ onClick: () => apply(card),
3025
+ "data-template-id": card.id,
3026
+ className: "eb:group eb:flex eb:w-full eb:flex-col eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-background eb:p-2 eb:text-left eb:transition-colors eb:hover:border-primary/50 eb:hover:bg-primary/5 eb:focus-visible:outline-none eb:focus-visible:ring-2 eb:focus-visible:ring-ring/50",
3027
+ children: [
3028
+ /* @__PURE__ */ jsx12(Thumbnail, { html: card.html, title: card.name }),
3029
+ /* @__PURE__ */ jsxs10("span", { className: "eb:px-1", children: [
3030
+ /* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-[13px] eb:font-medium eb:text-foreground", children: card.name }),
3031
+ /* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-[12px] eb:text-foreground-muted", children: card.description })
3032
+ ] })
3033
+ ]
3034
+ }
3035
+ ) }, card.id)) })
3036
+ ] }) })
3037
+ ] });
3038
+ }
3039
+
3040
+ // src/features/templates/index.js
3041
+ var templatesFeature = {
3042
+ id: "templates",
3043
+ label: "Templates",
3044
+ order: 5,
3045
+ toolbar: TemplateGalleryButton,
3046
+ isAvailable: (context) => Array.isArray(context.templates) && context.templates.length > 0
3047
+ };
3048
+
3049
+ // src/features/index.js
3050
+ var FEATURES = [
3051
+ templatesFeature,
3052
+ variablesFeature,
3053
+ autosaveFeature,
3054
+ versionsFeature,
3055
+ previewFeature,
3056
+ testEmailFeature
3057
+ ];
3058
+ var emailBuilderFeatures = createRegistry("emailBuilderFeatures", FEATURES);
3059
+ function isFeatureEnabled(id) {
3060
+ const feature = emailBuilderFeatures.get(id);
3061
+ return Boolean(feature && feature.enabled !== false);
3062
+ }
3063
+
3064
+ // src/ui/EmailBuilder.jsx
3065
+ import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState14 } from "react";
3066
+ import { AlertTriangle as AlertTriangle2, Loader2 as Loader25, Plus as Plus2, Save, SlidersHorizontal } from "lucide-react";
3067
+
2573
3068
  // src/features/autosave/scheduler.js
2574
3069
  function createAutosaveScheduler({
2575
3070
  run,
@@ -2869,7 +3364,7 @@ function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PAT
2869
3364
 
2870
3365
  // src/ui/EmailCanvas.jsx
2871
3366
  import { useEffect as useEffect4, useRef as useRef3 } from "react";
2872
- import { jsx as jsx12 } from "react/jsx-runtime";
3367
+ import { jsx as jsx13 } from "react/jsx-runtime";
2873
3368
  function EmailCanvas({ editor, document = null, onReady, onError, className }) {
2874
3369
  const ref = useRef3(null);
2875
3370
  const documentRef = useRef3(document);
@@ -2888,23 +3383,35 @@ function EmailCanvas({ editor, document = null, onReady, onError, className }) {
2888
3383
  editor.destroy();
2889
3384
  };
2890
3385
  }, [editor]);
2891
- return /* @__PURE__ */ jsx12("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
3386
+ return /* @__PURE__ */ jsx13("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
2892
3387
  }
2893
3388
 
2894
3389
  // src/ui/BlockSidebar.jsx
2895
3390
  import {
2896
3391
  Columns2,
2897
3392
  Columns3,
3393
+ Columns4,
3394
+ GalleryHorizontal,
3395
+ Heading,
2898
3396
  Image as ImageIcon,
3397
+ LayoutPanelLeft,
3398
+ Menu,
2899
3399
  MoveVertical,
2900
3400
  RectangleHorizontal,
2901
3401
  SeparatorHorizontal,
3402
+ Share2,
2902
3403
  Square,
2903
3404
  Type
2904
3405
  } from "lucide-react";
2905
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
3406
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2906
3407
  var ICONS = {
3408
+ Heading,
2907
3409
  Type,
3410
+ Share2,
3411
+ Menu,
3412
+ Columns4,
3413
+ LayoutPanelLeft,
3414
+ GalleryHorizontal,
2908
3415
  Image: ImageIcon,
2909
3416
  RectangleHorizontal,
2910
3417
  SeparatorHorizontal,
@@ -2920,7 +3427,7 @@ var GROUPS2 = [
2920
3427
  function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
2921
3428
  const t = useT();
2922
3429
  const Icon = ICONS[block.icon] || Square;
2923
- return /* @__PURE__ */ jsx13(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs10(
3430
+ return /* @__PURE__ */ jsx14(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs11(
2924
3431
  "button",
2925
3432
  {
2926
3433
  type: "button",
@@ -2940,26 +3447,28 @@ function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
2940
3447
  disabled && "eb:cursor-not-allowed eb:opacity-50"
2941
3448
  ),
2942
3449
  children: [
2943
- /* @__PURE__ */ jsx13(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
2944
- /* @__PURE__ */ jsx13("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
3450
+ /* @__PURE__ */ jsx14(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
3451
+ /* @__PURE__ */ jsx14("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
2945
3452
  ]
2946
3453
  }
2947
3454
  ) });
2948
3455
  }
2949
- function BlockSidebar({ editor, disabled = false }) {
3456
+ function BlockSidebar({ editor, disabled = false, onAdded }) {
2950
3457
  const t = useT();
2951
3458
  const blocks = editor.listBlocks();
2952
- return /* @__PURE__ */ jsx13("aside", { "aria-label": t("Blocks"), className: "eb:min-h-0 eb:space-y-5 eb:overflow-y-auto eb:border-r eb:border-border eb:p-3", children: GROUPS2.map((group) => {
3459
+ return /* @__PURE__ */ jsx14("aside", { "aria-label": t("Blocks"), className: "eb:min-h-0 eb:space-y-5 eb:overflow-y-auto eb:border-r eb:border-border eb:p-3", children: GROUPS2.map((group) => {
2953
3460
  const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
2954
3461
  if (!items.length) return null;
2955
- return /* @__PURE__ */ jsxs10("section", { className: "eb:space-y-2", children: [
2956
- /* @__PURE__ */ jsx13(SectionLabel, { children: t(group.title) }),
2957
- /* @__PURE__ */ jsx13("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx13(
3462
+ return /* @__PURE__ */ jsxs11("section", { className: "eb:space-y-2", children: [
3463
+ /* @__PURE__ */ jsx14(SectionLabel, { children: t(group.title) }),
3464
+ /* @__PURE__ */ jsx14("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx14(
2958
3465
  BlockTile,
2959
3466
  {
2960
3467
  block,
2961
3468
  disabled,
2962
- onAdd: (id) => editor.addBlock(id),
3469
+ onAdd: (id) => {
3470
+ if (editor.addBlock(id)) onAdded?.(id);
3471
+ },
2963
3472
  onDragStart: (id, event) => editor.startBlockDrag(id, event),
2964
3473
  onDragEnd: (cancel) => editor.endBlockDrag(cancel)
2965
3474
  },
@@ -2971,15 +3480,15 @@ function BlockSidebar({ editor, disabled = false }) {
2971
3480
 
2972
3481
  // src/ui/ElementActions.jsx
2973
3482
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash22 } from "lucide-react";
2974
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
3483
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2975
3484
  function ElementActions({ editor, selection }) {
2976
3485
  const t = useT();
2977
3486
  if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
2978
3487
  const horizontal = selection.tagName === "mj-column";
2979
3488
  const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
2980
3489
  const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
2981
- return /* @__PURE__ */ jsxs11("div", { className: "eb:flex eb:items-center eb:gap-0.5 eb:border-b eb:border-border eb:px-3 eb:py-1.5", role: "toolbar", "aria-label": t("Element actions"), children: [
2982
- /* @__PURE__ */ jsx14(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx14(
3490
+ return /* @__PURE__ */ jsxs12("div", { className: "eb:flex eb:items-center eb:gap-0.5 eb:border-b eb:border-border eb:px-3 eb:py-1.5", role: "toolbar", "aria-label": t("Element actions"), children: [
3491
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx15(
2983
3492
  Button,
2984
3493
  {
2985
3494
  variant: "ghost",
@@ -2987,10 +3496,10 @@ function ElementActions({ editor, selection }) {
2987
3496
  "aria-label": prevLabel,
2988
3497
  disabled: !selection.canMoveUp,
2989
3498
  onClick: () => editor.move(-1, selection.id),
2990
- children: /* @__PURE__ */ jsx14(Prev, {})
3499
+ children: /* @__PURE__ */ jsx15(Prev, {})
2991
3500
  }
2992
3501
  ) }),
2993
- /* @__PURE__ */ jsx14(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx14(
3502
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx15(
2994
3503
  Button,
2995
3504
  {
2996
3505
  variant: "ghost",
@@ -2998,10 +3507,10 @@ function ElementActions({ editor, selection }) {
2998
3507
  "aria-label": nextLabel,
2999
3508
  disabled: !selection.canMoveDown,
3000
3509
  onClick: () => editor.move(1, selection.id),
3001
- children: /* @__PURE__ */ jsx14(Next, {})
3510
+ children: /* @__PURE__ */ jsx15(Next, {})
3002
3511
  }
3003
3512
  ) }),
3004
- /* @__PURE__ */ jsx14(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx14(
3513
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx15(
3005
3514
  Button,
3006
3515
  {
3007
3516
  variant: "ghost",
@@ -3009,10 +3518,10 @@ function ElementActions({ editor, selection }) {
3009
3518
  "aria-label": t("Duplicate"),
3010
3519
  disabled: !selection.canDuplicate,
3011
3520
  onClick: () => editor.duplicate(selection.id),
3012
- children: /* @__PURE__ */ jsx14(Copy, {})
3521
+ children: /* @__PURE__ */ jsx15(Copy, {})
3013
3522
  }
3014
3523
  ) }),
3015
- /* @__PURE__ */ jsx14(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx14(
3524
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx15(
3016
3525
  Button,
3017
3526
  {
3018
3527
  variant: "ghost",
@@ -3021,24 +3530,24 @@ function ElementActions({ editor, selection }) {
3021
3530
  disabled: !selection.canRemove,
3022
3531
  onClick: () => editor.remove(selection.id),
3023
3532
  className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
3024
- children: /* @__PURE__ */ jsx14(Trash22, {})
3533
+ children: /* @__PURE__ */ jsx15(Trash22, {})
3025
3534
  }
3026
3535
  ) })
3027
3536
  ] });
3028
3537
  }
3029
3538
 
3030
3539
  // src/ui/PropertiesPanel.jsx
3031
- import { useMemo as useMemo5, useRef as useRef5 } from "react";
3540
+ import { useMemo as useMemo6, useRef as useRef5 } from "react";
3032
3541
  import { X as X2 } from "lucide-react";
3033
3542
 
3034
3543
  // src/ui/fields.jsx
3035
- import { useEffect as useEffect5, useRef as useRef4, useState as useState9 } from "react";
3544
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState10 } from "react";
3036
3545
 
3037
3546
  // src/ui/primitives/color-picker.jsx
3038
- import { useState as useState8 } from "react";
3547
+ import { useState as useState9 } from "react";
3039
3548
  import { HexAlphaColorPicker } from "react-colorful";
3040
3549
  import { Check } from "lucide-react";
3041
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
3550
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
3042
3551
  var PRESET_COLORS = [
3043
3552
  "#8B5CF6",
3044
3553
  "#A78BFA",
@@ -3069,34 +3578,34 @@ function normalizeHex(v) {
3069
3578
  }
3070
3579
  function ColorPicker({ value, onChange, id, className }) {
3071
3580
  const t = useT();
3072
- const [open, setOpen] = useState8(false);
3581
+ const [open, setOpen] = useState9(false);
3073
3582
  const valid = isValidHex(value);
3074
3583
  const normalized = valid ? normalizeHex(value) : "";
3075
3584
  const emit = (hex) => onChange(normalizeHex(hex));
3076
- return /* @__PURE__ */ jsxs12("div", { className: cn("eb:flex eb:gap-2", className), children: [
3077
- /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
3078
- /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
3585
+ return /* @__PURE__ */ jsxs13("div", { className: cn("eb:flex eb:gap-2", className), children: [
3586
+ /* @__PURE__ */ jsxs13(Popover, { open, onOpenChange: setOpen, children: [
3587
+ /* @__PURE__ */ jsx16(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx16(
3079
3588
  "button",
3080
3589
  {
3081
3590
  type: "button",
3082
3591
  "aria-label": t("Pick a colour"),
3083
3592
  title: t("Pick a colour"),
3084
3593
  className: "eb-checker eb:relative eb:h-8 eb:w-[44px] eb:shrink-0 eb:overflow-hidden eb:rounded-lg2 eb:shadow-sm eb:ring-1 eb:ring-black/10 eb:ring-inset eb:transition-transform eb:hover:scale-[1.04]",
3085
- children: /* @__PURE__ */ jsx15("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" }, "aria-hidden": true })
3594
+ children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" }, "aria-hidden": true })
3086
3595
  }
3087
3596
  ) }),
3088
- /* @__PURE__ */ jsxs12(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
3089
- /* @__PURE__ */ jsx15("div", { className: "eb-picker", children: /* @__PURE__ */ jsx15(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
3090
- /* @__PURE__ */ jsxs12("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
3091
- /* @__PURE__ */ jsx15(
3597
+ /* @__PURE__ */ jsxs13(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
3598
+ /* @__PURE__ */ jsx16("div", { className: "eb-picker", children: /* @__PURE__ */ jsx16(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
3599
+ /* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
3600
+ /* @__PURE__ */ jsx16(
3092
3601
  "span",
3093
3602
  {
3094
3603
  className: "eb-checker eb:relative eb:size-8 eb:shrink-0 eb:overflow-hidden eb:rounded-lg2 eb:ring-1 eb:ring-black/10 eb:ring-inset",
3095
3604
  "aria-hidden": true,
3096
- children: /* @__PURE__ */ jsx15("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
3605
+ children: /* @__PURE__ */ jsx16("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
3097
3606
  }
3098
3607
  ),
3099
- /* @__PURE__ */ jsx15(
3608
+ /* @__PURE__ */ jsx16(
3100
3609
  Input,
3101
3610
  {
3102
3611
  value: value || "",
@@ -3108,7 +3617,7 @@ function ColorPicker({ value, onChange, id, className }) {
3108
3617
  }
3109
3618
  )
3110
3619
  ] }),
3111
- /* @__PURE__ */ jsx15("div", { className: "eb:grid eb:grid-cols-6 eb:gap-1.5 eb:border-t eb:border-border eb:pt-2.5", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsx15(
3620
+ /* @__PURE__ */ jsx16("div", { className: "eb:grid eb:grid-cols-6 eb:gap-1.5 eb:border-t eb:border-border eb:pt-2.5", children: PRESET_COLORS.map((c) => /* @__PURE__ */ jsx16(
3112
3621
  "button",
3113
3622
  {
3114
3623
  type: "button",
@@ -3117,13 +3626,13 @@ function ColorPicker({ value, onChange, id, className }) {
3117
3626
  "aria-label": c,
3118
3627
  className: "eb:relative eb:size-6 eb:rounded-lg2 eb:ring-1 eb:ring-black/10 eb:ring-inset eb:transition-transform eb:hover:scale-110",
3119
3628
  style: { backgroundColor: c },
3120
- children: normalized === c && /* @__PURE__ */ jsx15(Check, { className: "eb:absolute eb:inset-0 eb:m-auto eb:size-3.5 eb:text-white eb:drop-shadow-[0_1px_1px_rgba(0,0,0,0.6)]" })
3629
+ children: normalized === c && /* @__PURE__ */ jsx16(Check, { className: "eb:absolute eb:inset-0 eb:m-auto eb:size-3.5 eb:text-white eb:drop-shadow-[0_1px_1px_rgba(0,0,0,0.6)]" })
3121
3630
  },
3122
3631
  c
3123
3632
  )) })
3124
3633
  ] })
3125
3634
  ] }),
3126
- /* @__PURE__ */ jsx15(
3635
+ /* @__PURE__ */ jsx16(
3127
3636
  Input,
3128
3637
  {
3129
3638
  id,
@@ -3139,7 +3648,7 @@ function ColorPicker({ value, onChange, id, className }) {
3139
3648
 
3140
3649
  // src/ui/fields.jsx
3141
3650
  import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload } from "lucide-react";
3142
- import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
3651
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
3143
3652
  var COLOR_DEBOUNCE_MS = 250;
3144
3653
  function emailColor(value) {
3145
3654
  const v = String(value || "").trim();
@@ -3148,7 +3657,7 @@ function emailColor(value) {
3148
3657
  return null;
3149
3658
  }
3150
3659
  function useDraft(value, toDraft) {
3151
- const [draft, setDraft] = useState9(() => toDraft(value));
3660
+ const [draft, setDraft] = useState10(() => toDraft(value));
3152
3661
  const ref = useRef4(null);
3153
3662
  const convert = useRef4(toDraft);
3154
3663
  convert.current = toDraft;
@@ -3161,10 +3670,10 @@ function useDraft(value, toDraft) {
3161
3670
  }
3162
3671
  var asText = (v) => v || "";
3163
3672
  function FieldLabel({ id, children }) {
3164
- return /* @__PURE__ */ jsx16(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
3673
+ return /* @__PURE__ */ jsx17(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
3165
3674
  }
3166
3675
  function FieldError({ children }) {
3167
- return children ? /* @__PURE__ */ jsx16("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
3676
+ return children ? /* @__PURE__ */ jsx17("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
3168
3677
  }
3169
3678
  function TextField({ id, property, value, onCommit, addon = null, error = null }) {
3170
3679
  const t = useT();
@@ -3195,13 +3704,13 @@ function TextField({ id, property, value, onCommit, addon = null, error = null }
3195
3704
  onKeyDown: keyDown,
3196
3705
  "aria-invalid": invalid || void 0
3197
3706
  };
3198
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3199
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3200
- property.type === "textarea" ? /* @__PURE__ */ jsx16(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:gap-2", children: [
3201
- /* @__PURE__ */ jsx16(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
3707
+ return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
3708
+ /* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
3709
+ property.type === "textarea" ? /* @__PURE__ */ jsx17(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:gap-2", children: [
3710
+ /* @__PURE__ */ jsx17(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
3202
3711
  addon
3203
- ] }) : /* @__PURE__ */ jsx16(Input, { ...shared }),
3204
- /* @__PURE__ */ jsx16(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
3712
+ ] }) : /* @__PURE__ */ jsx17(Input, { ...shared }),
3713
+ /* @__PURE__ */ jsx17(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
3205
3714
  ] });
3206
3715
  }
3207
3716
  function ImageField({ id, property, value, onCommit, services }) {
@@ -3210,8 +3719,8 @@ function ImageField({ id, property, value, onCommit, services }) {
3210
3719
  /** @type {HTMLInputElement|null} */
3211
3720
  null
3212
3721
  );
3213
- const [busy, setBusy] = useState9(false);
3214
- const [error, setError] = useState9(
3722
+ const [busy, setBusy] = useState10(false);
3723
+ const [error, setError] = useState10(
3215
3724
  /** @type {string|null} */
3216
3725
  null
3217
3726
  );
@@ -3233,8 +3742,8 @@ function ImageField({ id, property, value, onCommit, services }) {
3233
3742
  setBusy(false);
3234
3743
  }
3235
3744
  };
3236
- const addon = upload ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
3237
- /* @__PURE__ */ jsx16(
3745
+ const addon = upload ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
3746
+ /* @__PURE__ */ jsx17(
3238
3747
  "input",
3239
3748
  {
3240
3749
  ref: inputRef,
@@ -3247,7 +3756,7 @@ function ImageField({ id, property, value, onCommit, services }) {
3247
3756
  onChange: pick
3248
3757
  }
3249
3758
  ),
3250
- /* @__PURE__ */ jsxs13(
3759
+ /* @__PURE__ */ jsxs14(
3251
3760
  Button,
3252
3761
  {
3253
3762
  type: "button",
@@ -3256,13 +3765,13 @@ function ImageField({ id, property, value, onCommit, services }) {
3256
3765
  disabled: busy,
3257
3766
  onClick: () => inputRef.current?.click(),
3258
3767
  children: [
3259
- busy ? /* @__PURE__ */ jsx16(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx16(Upload, {}),
3768
+ busy ? /* @__PURE__ */ jsx17(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Upload, {}),
3260
3769
  t("Upload")
3261
3770
  ]
3262
3771
  }
3263
3772
  )
3264
3773
  ] }) : null;
3265
- return /* @__PURE__ */ jsx16(TextField, { id, property, value, onCommit, addon, error });
3774
+ return /* @__PURE__ */ jsx17(TextField, { id, property, value, onCommit, addon, error });
3266
3775
  }
3267
3776
  var asNumber = (v) => {
3268
3777
  const n = Number.parseFloat(String(v || ""));
@@ -3284,10 +3793,10 @@ function SizeField({ id, property, value, onCommit }) {
3284
3793
  const next = `${n}${property.unit || "px"}`;
3285
3794
  if (next !== value) onCommit(next);
3286
3795
  };
3287
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3288
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3289
- /* @__PURE__ */ jsxs13("div", { className: "eb:relative", children: [
3290
- /* @__PURE__ */ jsx16(
3796
+ return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
3797
+ /* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
3798
+ /* @__PURE__ */ jsxs14("div", { className: "eb:relative", children: [
3799
+ /* @__PURE__ */ jsx17(
3291
3800
  Input,
3292
3801
  {
3293
3802
  id,
@@ -3305,7 +3814,7 @@ function SizeField({ id, property, value, onCommit }) {
3305
3814
  className: "eb:pr-9 eb:tabular-nums"
3306
3815
  }
3307
3816
  ),
3308
- /* @__PURE__ */ jsx16("span", { className: "eb:pointer-events-none eb:absolute eb:right-3 eb:top-1/2 eb:-translate-y-1/2 eb:text-[12px] eb:text-foreground-muted", children: property.unit || "px" })
3817
+ /* @__PURE__ */ jsx17("span", { className: "eb:pointer-events-none eb:absolute eb:right-3 eb:top-1/2 eb:-translate-y-1/2 eb:text-[12px] eb:text-foreground-muted", children: property.unit || "px" })
3309
3818
  ] })
3310
3819
  ] });
3311
3820
  }
@@ -3328,18 +3837,18 @@ function ColorField({ id, property, value, onCommit }) {
3328
3837
  }
3329
3838
  }, COLOR_DEBOUNCE_MS);
3330
3839
  };
3331
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3332
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3333
- /* @__PURE__ */ jsx16(ColorPicker, { id, value: draft, onChange: change })
3840
+ return /* @__PURE__ */ jsxs14("div", { ref, className: "eb:space-y-1.5", children: [
3841
+ /* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
3842
+ /* @__PURE__ */ jsx17(ColorPicker, { id, value: draft, onChange: change })
3334
3843
  ] });
3335
3844
  }
3336
3845
  var DEFAULT_OPTION = "__default__";
3337
3846
  function SelectField({ id, property, value, onCommit }) {
3338
3847
  const t = useT();
3339
3848
  const current = value || DEFAULT_OPTION;
3340
- return /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
3341
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3342
- /* @__PURE__ */ jsxs13(
3849
+ return /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1.5", children: [
3850
+ /* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
3851
+ /* @__PURE__ */ jsxs14(
3343
3852
  Select,
3344
3853
  {
3345
3854
  value: current,
@@ -3348,10 +3857,10 @@ function SelectField({ id, property, value, onCommit }) {
3348
3857
  if (v !== (value || "")) onCommit(v || null);
3349
3858
  },
3350
3859
  children: [
3351
- /* @__PURE__ */ jsx16(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx16(SelectValue, { placeholder: t("Default") }) }),
3352
- /* @__PURE__ */ jsxs13(SelectContent, { children: [
3353
- !(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx16(SelectItem, { value: DEFAULT_OPTION, children: t("Default") }),
3354
- (property.options || []).map((o) => /* @__PURE__ */ jsx16(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
3860
+ /* @__PURE__ */ jsx17(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx17(SelectValue, { placeholder: t("Default") }) }),
3861
+ /* @__PURE__ */ jsxs14(SelectContent, { children: [
3862
+ !(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx17(SelectItem, { value: DEFAULT_OPTION, children: t("Default") }),
3863
+ (property.options || []).map((o) => /* @__PURE__ */ jsx17(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
3355
3864
  ] })
3356
3865
  ]
3357
3866
  }
@@ -3361,9 +3870,9 @@ function SelectField({ id, property, value, onCommit }) {
3361
3870
  var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
3362
3871
  function AlignField({ id, property, value, onCommit }) {
3363
3872
  const t = useT();
3364
- return /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
3365
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3366
- /* @__PURE__ */ jsx16(
3873
+ return /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1.5", children: [
3874
+ /* @__PURE__ */ jsx17(FieldLabel, { id, children: t(property.label) }),
3875
+ /* @__PURE__ */ jsx17(
3367
3876
  "div",
3368
3877
  {
3369
3878
  id,
@@ -3373,7 +3882,7 @@ function AlignField({ id, property, value, onCommit }) {
3373
3882
  children: (property.options || []).map((option) => {
3374
3883
  const Icon = ALIGN_ICONS[option.value] || AlignLeft;
3375
3884
  const active = value === option.value;
3376
- return /* @__PURE__ */ jsx16(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx16(
3885
+ return /* @__PURE__ */ jsx17(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx17(
3377
3886
  "button",
3378
3887
  {
3379
3888
  type: "button",
@@ -3385,7 +3894,7 @@ function AlignField({ id, property, value, onCommit }) {
3385
3894
  "eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
3386
3895
  active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
3387
3896
  ),
3388
- children: /* @__PURE__ */ jsx16(Icon, { className: "eb:h-3.5 eb:w-3.5" })
3897
+ children: /* @__PURE__ */ jsx17(Icon, { className: "eb:h-3.5 eb:w-3.5" })
3389
3898
  }
3390
3899
  ) }, option.value);
3391
3900
  })
@@ -3407,10 +3916,10 @@ function SpacingField({ id, property, value, onCommit }) {
3407
3916
  const next = formatSpacing(sides.map((s) => `${Math.max(0, Math.min(200, Number.parseFloat(s) || 0))}px`));
3408
3917
  if (next !== (value || "")) onCommit(next);
3409
3918
  };
3410
- return /* @__PURE__ */ jsxs13("fieldset", { ref, className: "eb:space-y-1.5", children: [
3411
- /* @__PURE__ */ jsx16("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
3412
- /* @__PURE__ */ jsx16("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1", children: [
3413
- /* @__PURE__ */ jsx16(
3919
+ return /* @__PURE__ */ jsxs14("fieldset", { ref, className: "eb:space-y-1.5", children: [
3920
+ /* @__PURE__ */ jsx17("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
3921
+ /* @__PURE__ */ jsx17("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs14("div", { className: "eb:space-y-1", children: [
3922
+ /* @__PURE__ */ jsx17(
3414
3923
  Input,
3415
3924
  {
3416
3925
  id: `${id}-${side}`,
@@ -3428,7 +3937,7 @@ function SpacingField({ id, property, value, onCommit }) {
3428
3937
  className: "eb:px-2 eb:text-center eb:tabular-nums"
3429
3938
  }
3430
3939
  ),
3431
- /* @__PURE__ */ jsx16("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
3940
+ /* @__PURE__ */ jsx17("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
3432
3941
  ] }, side)) })
3433
3942
  ] });
3434
3943
  }
@@ -3446,15 +3955,15 @@ var FIELDS = {
3446
3955
  function PropertyField({ property, value, onCommit, idPrefix = "eb", services = null }) {
3447
3956
  const Field = FIELDS[property.type] || TextField;
3448
3957
  const id = `${idPrefix}-${property.key}`;
3449
- return /* @__PURE__ */ jsx16(Field, { id, property, value, onCommit, services });
3958
+ return /* @__PURE__ */ jsx17(Field, { id, property, value, onCommit, services });
3450
3959
  }
3451
3960
 
3452
3961
  // src/features/variables/VariableInsertButton.jsx
3453
- import { useState as useState10 } from "react";
3962
+ import { useState as useState11 } from "react";
3454
3963
  import { Braces as Braces3 } from "lucide-react";
3455
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
3964
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
3456
3965
  function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
3457
- const [open, setOpen] = useState10(false);
3966
+ const [open, setOpen] = useState11(false);
3458
3967
  const insert = (variable) => {
3459
3968
  const input = inputRef.current;
3460
3969
  const text = String(value || "");
@@ -3470,14 +3979,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
3470
3979
  input.setSelectionRange(start + token.length, start + token.length);
3471
3980
  });
3472
3981
  };
3473
- return /* @__PURE__ */ jsxs14(Popover, { open, onOpenChange: setOpen, children: [
3474
- /* @__PURE__ */ jsx17(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx17(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx17(Braces3, {}) }) }),
3475
- /* @__PURE__ */ jsx17(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx17(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
3982
+ return /* @__PURE__ */ jsxs15(Popover, { open, onOpenChange: setOpen, children: [
3983
+ /* @__PURE__ */ jsx18(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx18(Braces3, {}) }) }),
3984
+ /* @__PURE__ */ jsx18(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx18(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
3476
3985
  ] });
3477
3986
  }
3478
3987
 
3479
3988
  // src/ui/PropertiesPanel.jsx
3480
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
3989
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
3481
3990
  var GROUP_ORDER = Object.values(GROUPS);
3482
3991
  function grouped(properties) {
3483
3992
  const map = /* @__PURE__ */ new Map();
@@ -3491,9 +4000,9 @@ function grouped(properties) {
3491
4000
  }
3492
4001
  function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
3493
4002
  const t = useT();
3494
- return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3495
- /* @__PURE__ */ jsx18(SectionLabel, { children: t(group) }),
3496
- properties.map((property) => /* @__PURE__ */ jsx18(
4003
+ return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
4004
+ /* @__PURE__ */ jsx19(SectionLabel, { children: t(group) }),
4005
+ properties.map((property) => /* @__PURE__ */ jsx19(
3497
4006
  PropertyField,
3498
4007
  {
3499
4008
  idPrefix,
@@ -3507,33 +4016,33 @@ function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
3507
4016
  ] }, group));
3508
4017
  }
3509
4018
  var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROUPS.content };
3510
- function PanelHeader({ title, onClose }) {
4019
+ function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
3511
4020
  const t = useT();
3512
- return /* @__PURE__ */ jsxs15("header", { className: "eb:sticky eb:top-0 eb:z-10 eb:flex eb:h-11 eb:shrink-0 eb:items-center eb:gap-2 eb:border-b eb:border-border eb:bg-background eb:px-4", children: [
3513
- /* @__PURE__ */ jsx18("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx18(TruncatedText, { children: title }) }),
3514
- onClose && /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Deselect"), children: /* @__PURE__ */ jsx18(X2, {}) })
4021
+ return /* @__PURE__ */ jsxs16("header", { className: "eb:sticky eb:top-0 eb:z-10 eb:flex eb:h-11 eb:shrink-0 eb:items-center eb:gap-2 eb:border-b eb:border-border eb:bg-background eb:px-4", children: [
4022
+ /* @__PURE__ */ jsx19("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx19(TruncatedText, { children: title }) }),
4023
+ onClose && /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t(closeLabel), children: /* @__PURE__ */ jsx19(X2, {}) })
3515
4024
  ] });
3516
4025
  }
3517
- function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null }) {
4026
+ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, onDismiss = null }) {
3518
4027
  const t = useT();
3519
4028
  const subjectRef = useRef5(null);
3520
4029
  const preheaderRef = useRef5(null);
3521
4030
  const rootId = editor.getRootId();
3522
- const root = useMemo5(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
4031
+ const root = useMemo6(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
3523
4032
  const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
3524
4033
  const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
3525
- return /* @__PURE__ */ jsxs15(Fragment5, { children: [
3526
- /* @__PURE__ */ jsx18(PanelHeader, { title: t("Email") }),
3527
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-5 eb:p-4", children: [
3528
- /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3529
- /* @__PURE__ */ jsx18(SectionLabel, { children: t("Template") }),
3530
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3531
- /* @__PURE__ */ jsxs15(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
4034
+ return /* @__PURE__ */ jsxs16(Fragment6, { children: [
4035
+ /* @__PURE__ */ jsx19(PanelHeader, { title: t("Email"), onClose: onDismiss, closeLabel: "Close" }),
4036
+ /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
4037
+ /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
4038
+ /* @__PURE__ */ jsx19(SectionLabel, { children: t("Template") }),
4039
+ /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
4040
+ /* @__PURE__ */ jsxs16(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
3532
4041
  t("Name"),
3533
4042
  " ",
3534
- /* @__PURE__ */ jsx18("span", { className: "eb:text-destructive", children: "*" })
4043
+ /* @__PURE__ */ jsx19("span", { className: "eb:text-destructive", children: "*" })
3535
4044
  ] }),
3536
- /* @__PURE__ */ jsx18(
4045
+ /* @__PURE__ */ jsx19(
3537
4046
  Input,
3538
4047
  {
3539
4048
  id: "eb-meta-name",
@@ -3543,12 +4052,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3543
4052
  "aria-invalid": showErrors && !meta.name.trim() || void 0
3544
4053
  }
3545
4054
  ),
3546
- showErrors && !meta.name.trim() && /* @__PURE__ */ jsx18("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
4055
+ showErrors && !meta.name.trim() && /* @__PURE__ */ jsx19("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
3547
4056
  ] }),
3548
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3549
- /* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
3550
- /* @__PURE__ */ jsx18(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
3551
- variables && /* @__PURE__ */ jsx18(
4057
+ /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
4058
+ /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
4059
+ /* @__PURE__ */ jsx19(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
4060
+ variables && /* @__PURE__ */ jsx19(
3552
4061
  VariableInsertButton,
3553
4062
  {
3554
4063
  variables,
@@ -3559,12 +4068,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3559
4068
  }
3560
4069
  )
3561
4070
  ] }),
3562
- /* @__PURE__ */ jsx18(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") })
4071
+ /* @__PURE__ */ jsx19(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") })
3563
4072
  ] }),
3564
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3565
- /* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
3566
- /* @__PURE__ */ jsx18(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
3567
- variables && /* @__PURE__ */ jsx18(
4073
+ /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
4074
+ /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
4075
+ /* @__PURE__ */ jsx19(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
4076
+ variables && /* @__PURE__ */ jsx19(
3568
4077
  VariableInsertButton,
3569
4078
  {
3570
4079
  variables,
@@ -3575,7 +4084,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3575
4084
  }
3576
4085
  )
3577
4086
  ] }),
3578
- /* @__PURE__ */ jsx18(
4087
+ /* @__PURE__ */ jsx19(
3579
4088
  Textarea,
3580
4089
  {
3581
4090
  ref: preheaderRef,
@@ -3589,7 +4098,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3589
4098
  )
3590
4099
  ] })
3591
4100
  ] }),
3592
- root && bodyBlock2 && /* @__PURE__ */ jsx18(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
4101
+ root && bodyBlock2 && /* @__PURE__ */ jsx19(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
3593
4102
  ] })
3594
4103
  ] });
3595
4104
  }
@@ -3602,11 +4111,12 @@ function PropertiesPanel({
3602
4111
  actions = null,
3603
4112
  showErrors = false,
3604
4113
  services = null,
3605
- variables = null
4114
+ variables = null,
4115
+ onDismiss = null
3606
4116
  }) {
3607
4117
  const t = useT();
3608
4118
  const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
3609
- return /* @__PURE__ */ jsx18("aside", { "aria-label": t("Properties"), className: "eb:min-h-0 eb:overflow-y-auto eb:border-l eb:border-border", "data-testid": "eb-properties", children: !selection ? /* @__PURE__ */ jsx18(
4119
+ return /* @__PURE__ */ jsx19("aside", { "aria-label": t("Properties"), className: "eb:min-h-0 eb:overflow-y-auto eb:border-l eb:border-border", "data-testid": "eb-properties", children: !selection ? /* @__PURE__ */ jsx19(
3610
4120
  TemplateSettings,
3611
4121
  {
3612
4122
  editor,
@@ -3614,15 +4124,23 @@ function PropertiesPanel({
3614
4124
  onMetaChange,
3615
4125
  changes,
3616
4126
  showErrors,
3617
- variables
4127
+ variables,
4128
+ onDismiss
3618
4129
  }
3619
- ) : /* @__PURE__ */ jsxs15(Fragment5, { children: [
3620
- /* @__PURE__ */ jsx18(PanelHeader, { title: t(selection.label), onClose: () => editor.select(null) }),
4130
+ ) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
4131
+ /* @__PURE__ */ jsx19(
4132
+ PanelHeader,
4133
+ {
4134
+ title: t(selection.label),
4135
+ onClose: onDismiss || (() => editor.select(null)),
4136
+ closeLabel: onDismiss ? "Close" : "Deselect"
4137
+ }
4138
+ ),
3621
4139
  actions,
3622
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-5 eb:p-4", children: [
3623
- selection.tagName === "mj-button" && /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3624
- /* @__PURE__ */ jsx18(SectionLabel, { children: t(GROUPS.content) }),
3625
- /* @__PURE__ */ jsx18(
4140
+ /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
4141
+ selection.content !== null && selection.content !== void 0 && /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
4142
+ /* @__PURE__ */ jsx19(SectionLabel, { children: t(GROUPS.content) }),
4143
+ /* @__PURE__ */ jsx19(
3626
4144
  PropertyField,
3627
4145
  {
3628
4146
  idPrefix: "eb-prop",
@@ -3633,15 +4151,15 @@ function PropertiesPanel({
3633
4151
  `${selection.id}:content`
3634
4152
  )
3635
4153
  ] }),
3636
- selection.tagName === "mj-text" && /* @__PURE__ */ jsx18("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
3637
- /* @__PURE__ */ jsx18(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
4154
+ selection.tagName === "mj-text" && /* @__PURE__ */ jsx19("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
4155
+ /* @__PURE__ */ jsx19(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
3638
4156
  ] })
3639
4157
  ] }) });
3640
4158
  }
3641
4159
 
3642
4160
  // src/ui/Toolbar.jsx
3643
4161
  import { ArrowLeft as ArrowLeft2, Monitor as Monitor2, Redo2, Smartphone as Smartphone2, Undo2 } from "lucide-react";
3644
- import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
4162
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
3645
4163
  function Toolbar({
3646
4164
  title,
3647
4165
  status,
@@ -3653,44 +4171,112 @@ function Toolbar({
3653
4171
  device,
3654
4172
  onDeviceChange,
3655
4173
  onBack,
3656
- actions = null
4174
+ actions = null,
4175
+ primary = null,
4176
+ compact = false
3657
4177
  }) {
3658
4178
  const t = useT();
3659
- return /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-12 eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-3", children: [
3660
- /* @__PURE__ */ jsxs16(Button, { variant: "ghost", size: "sm-compact", onClick: onBack, children: [
3661
- /* @__PURE__ */ jsx19(ArrowLeft2, {}),
3662
- t("Back")
3663
- ] }),
3664
- /* @__PURE__ */ jsx19(Separator, { orientation: "vertical", className: "eb:h-5" }),
3665
- /* @__PURE__ */ jsxs16("div", { className: "eb:min-w-0 eb:max-w-[320px] eb:flex-1", children: [
3666
- /* @__PURE__ */ jsx19("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx19(TruncatedText, { children: title || t("New template") }) }),
3667
- /* @__PURE__ */ jsx19("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
4179
+ const identity2 = /* @__PURE__ */ jsxs17(Fragment7, { children: [
4180
+ /* @__PURE__ */ jsxs17(
4181
+ Button,
4182
+ {
4183
+ variant: "ghost",
4184
+ size: compact ? "icon-sm-compact" : "sm-compact",
4185
+ onClick: onBack,
4186
+ "aria-label": compact ? t("Back") : void 0,
4187
+ children: [
4188
+ /* @__PURE__ */ jsx20(ArrowLeft2, {}),
4189
+ !compact && t("Back")
4190
+ ]
4191
+ }
4192
+ ),
4193
+ /* @__PURE__ */ jsx20(Separator, { orientation: "vertical", className: "eb:h-5" }),
4194
+ /* @__PURE__ */ jsxs17("div", { className: cn("eb:min-w-0 eb:flex-1", !compact && "eb:max-w-[320px]"), children: [
4195
+ /* @__PURE__ */ jsx20("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx20(TruncatedText, { children: title || t("New template") }) }),
4196
+ /* @__PURE__ */ jsx20("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
4197
+ ] })
4198
+ ] });
4199
+ const editing = /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: [
4200
+ /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
4201
+ /* @__PURE__ */ jsx20(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx20(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx20(Undo2, {}) }) }),
4202
+ /* @__PURE__ */ jsx20(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx20(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx20(Redo2, {}) }) })
3668
4203
  ] }),
3669
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
3670
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
3671
- /* @__PURE__ */ jsx19(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx19(Undo2, {}) }) }),
3672
- /* @__PURE__ */ jsx19(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx19(Redo2, {}) }) })
4204
+ /* @__PURE__ */ jsx20(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs17(TabsList, { "aria-label": t("Device"), children: [
4205
+ /* @__PURE__ */ jsxs17(TabsTrigger, { value: "desktop", disabled: !ready, "aria-label": compact ? t("Desktop") : void 0, children: [
4206
+ /* @__PURE__ */ jsx20(Monitor2, {}),
4207
+ !compact && t("Desktop")
3673
4208
  ] }),
3674
- /* @__PURE__ */ jsx19(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs16(TabsList, { "aria-label": t("Device"), children: [
3675
- /* @__PURE__ */ jsxs16(TabsTrigger, { value: "desktop", disabled: !ready, children: [
3676
- /* @__PURE__ */ jsx19(Monitor2, {}),
3677
- t("Desktop")
3678
- ] }),
3679
- /* @__PURE__ */ jsxs16(TabsTrigger, { value: "mobile", disabled: !ready, children: [
3680
- /* @__PURE__ */ jsx19(Smartphone2, {}),
3681
- t("Mobile")
3682
- ] })
3683
- ] }) })
3684
- ] }),
3685
- /* @__PURE__ */ jsx19("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", children: actions })
4209
+ /* @__PURE__ */ jsxs17(TabsTrigger, { value: "mobile", disabled: !ready, "aria-label": compact ? t("Mobile") : void 0, children: [
4210
+ /* @__PURE__ */ jsx20(Smartphone2, {}),
4211
+ !compact && t("Mobile")
4212
+ ] })
4213
+ ] }) })
4214
+ ] });
4215
+ if (compact) {
4216
+ return /* @__PURE__ */ jsxs17("div", { className: "eb:shrink-0 eb:border-b eb:border-border", "data-layout": "compact", children: [
4217
+ /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:h-12 eb:items-center eb:gap-2 eb:px-2", children: [
4218
+ identity2,
4219
+ primary
4220
+ ] }),
4221
+ /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:items-center eb:gap-2 eb:overflow-x-auto eb:px-2 eb:pb-2", "data-testid": "eb-toolbar-actions", children: [
4222
+ editing,
4223
+ /* @__PURE__ */ jsx20(Separator, { orientation: "vertical", className: "eb:h-5 eb:shrink-0" }),
4224
+ /* @__PURE__ */ jsx20("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: actions })
4225
+ ] })
4226
+ ] });
4227
+ }
4228
+ return /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:h-12 eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-3", children: [
4229
+ identity2,
4230
+ editing,
4231
+ /* @__PURE__ */ jsxs17("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", "data-testid": "eb-toolbar-actions", children: [
4232
+ actions,
4233
+ primary
4234
+ ] })
4235
+ ] });
4236
+ }
4237
+
4238
+ // src/ui/Drawer.jsx
4239
+ import { useEffect as useEffect6 } from "react";
4240
+ import { X as X3 } from "lucide-react";
4241
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
4242
+ function Drawer({ open, side = "left", title, onClose, children, testId, header: header2 = true }) {
4243
+ const t = useT();
4244
+ useEffect6(() => {
4245
+ if (!open) return void 0;
4246
+ const onKey = (e) => {
4247
+ if (e.key === "Escape") onClose();
4248
+ };
4249
+ window.addEventListener("keydown", onKey);
4250
+ return () => window.removeEventListener("keydown", onKey);
4251
+ }, [open, onClose]);
4252
+ return /* @__PURE__ */ jsxs18("div", { className: cn("eb:absolute eb:inset-0 eb:z-20", !open && "eb:hidden"), "data-testid": testId, "data-open": open ? "true" : "false", children: [
4253
+ /* @__PURE__ */ jsx21("div", { className: "eb:absolute eb:inset-0 eb:bg-black/30", onClick: onClose, "aria-hidden": "true" }),
4254
+ /* @__PURE__ */ jsxs18(
4255
+ "section",
4256
+ {
4257
+ role: "dialog",
4258
+ "aria-label": title,
4259
+ className: cn(
4260
+ "eb:absolute eb:inset-y-0 eb:flex eb:w-[min(340px,88%)] eb:flex-col eb:bg-background eb:shadow-lg",
4261
+ side === "left" ? "eb:left-0 eb:border-r eb:border-border" : "eb:right-0 eb:border-l eb:border-border"
4262
+ ),
4263
+ children: [
4264
+ header2 && /* @__PURE__ */ jsxs18("header", { className: "eb:flex eb:h-10 eb:shrink-0 eb:items-center eb:justify-between eb:border-b eb:border-border eb:px-3", children: [
4265
+ /* @__PURE__ */ jsx21("h3", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: title }),
4266
+ /* @__PURE__ */ jsx21(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx21(X3, {}) })
4267
+ ] }),
4268
+ /* @__PURE__ */ jsx21("div", { className: "eb:grid eb:min-h-0 eb:flex-1", children })
4269
+ ]
4270
+ }
4271
+ )
3686
4272
  ] });
3687
4273
  }
3688
4274
 
3689
4275
  // src/ui/useEmailEditorState.js
3690
- import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef6, useState as useState11 } from "react";
4276
+ import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef6, useState as useState12 } from "react";
3691
4277
  var DIRTY_CHECK_MS = 150;
3692
4278
  function useEmailEditorState(editor) {
3693
- const [state, setState] = useState11(() => ({
4279
+ const [state, setState] = useState12(() => ({
3694
4280
  status: editor?.status || "idle",
3695
4281
  selection: null,
3696
4282
  canUndo: false,
@@ -3724,7 +4310,7 @@ function useEmailEditorState(editor) {
3724
4310
  forced.current = true;
3725
4311
  setState((s) => ({ ...s, dirty: true }));
3726
4312
  }, []);
3727
- useEffect6(() => {
4313
+ useEffect7(() => {
3728
4314
  if (!editor) return void 0;
3729
4315
  const patch = (values) => setState((s) => ({ ...s, ...values }));
3730
4316
  const measure = () => {
@@ -3770,8 +4356,33 @@ function useEmailEditorState(editor) {
3770
4356
  return { ...state, markClean, markDirty, checkDirty };
3771
4357
  }
3772
4358
 
4359
+ // src/ui/useLayoutWidth.js
4360
+ import { useEffect as useEffect8, useState as useState13 } from "react";
4361
+ var LAYOUT_BREAKPOINTS = { wide: 1180, medium: 760 };
4362
+ function layoutFor(width) {
4363
+ if (width >= LAYOUT_BREAKPOINTS.wide) return "wide";
4364
+ if (width >= LAYOUT_BREAKPOINTS.medium) return "medium";
4365
+ return "narrow";
4366
+ }
4367
+ function useLayoutWidth(ref) {
4368
+ const [layout2, setLayout] = useState13(
4369
+ /** @type {'wide'|'medium'|'narrow'} */
4370
+ "wide"
4371
+ );
4372
+ useEffect8(() => {
4373
+ const el = ref.current;
4374
+ if (!el || typeof ResizeObserver === "undefined") return void 0;
4375
+ const measure = () => setLayout(layoutFor(el.getBoundingClientRect().width));
4376
+ measure();
4377
+ const observer = new ResizeObserver(measure);
4378
+ observer.observe(el);
4379
+ return () => observer.disconnect();
4380
+ }, [ref]);
4381
+ return layout2;
4382
+ }
4383
+
3773
4384
  // src/ui/EmailBuilder.jsx
3774
- import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
4385
+ import { Fragment as Fragment8, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3775
4386
  var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
3776
4387
  function metaOf(template) {
3777
4388
  return {
@@ -3789,6 +4400,7 @@ function Builder({
3789
4400
  variables: variablesProp,
3790
4401
  sender,
3791
4402
  starterMjml,
4403
+ templates: galleryProp,
3792
4404
  testRecipients,
3793
4405
  initialMjml,
3794
4406
  initialMeta,
@@ -3799,11 +4411,11 @@ function Builder({
3799
4411
  className
3800
4412
  }) {
3801
4413
  const { t, locale, notify, confirm } = useUi();
3802
- const editor = useMemo6(() => injected || createDefaultEmailEditor({ locale }), [injected, locale]);
3803
- const storage = useMemo6(() => injectedStorage || createHttpStorage(), [injectedStorage]);
3804
- const systemVariables = useMemo6(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
3805
- const [customVariables, setCustomVariables] = useState12([]);
3806
- const variables = useMemo6(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
4414
+ const editor = useMemo7(() => injected || createDefaultEmailEditor({ locale }), [injected, locale]);
4415
+ const storage = useMemo7(() => injectedStorage || createHttpStorage(), [injectedStorage]);
4416
+ const systemVariables = useMemo7(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
4417
+ const [customVariables, setCustomVariables] = useState14([]);
4418
+ const variables = useMemo7(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
3807
4419
  const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
3808
4420
  const reloadVariables = useCallback3(async () => {
3809
4421
  if (typeof storage.listVariables !== "function") return;
@@ -3813,23 +4425,37 @@ function Builder({
3813
4425
  notify("error", error.message);
3814
4426
  }
3815
4427
  }, [storage, notify]);
3816
- useEffect7(() => {
4428
+ useEffect9(() => {
3817
4429
  reloadVariables();
3818
4430
  }, [reloadVariables]);
3819
- const starter = useMemo6(() => starterMjml || createStarterMjml({ sender, t }), [starterMjml, sender, t]);
3820
- const services = useMemo6(() => ({ uploadImage: (file) => storage.uploadImage(file) }), [storage]);
4431
+ const starter = useMemo7(() => starterMjml || createStarterMjml({ sender, t }), [starterMjml, sender, t]);
4432
+ const templates = useMemo7(
4433
+ () => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: sender || {}, t }),
4434
+ [galleryProp, sender, t]
4435
+ );
4436
+ const services = useMemo7(() => ({ uploadImage: (file) => storage.uploadImage(file) }), [storage]);
3821
4437
  const state = useEmailEditorState(editor);
3822
4438
  const autosaveOn = isFeatureEnabled("autosave");
3823
4439
  const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
3824
- const [template, setTemplate] = useState12({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
3825
- const [boot, setBoot] = useState12({ status: "loading", document: null, draft: false, message: null });
3826
- const [meta, setMeta] = useState12(EMPTY_META);
3827
- const [savedMeta, setSavedMeta] = useState12(EMPTY_META);
3828
- const [saving, setSaving] = useState12(null);
3829
- const [showErrors, setShowErrors] = useState12(false);
3830
- const [autosaveBlocked, setAutosaveBlocked] = useState12(null);
3831
- const [engineError, setEngineError] = useState12(null);
3832
- const [attempt, setAttempt] = useState12(0);
4440
+ const [template, setTemplate] = useState14({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
4441
+ const [boot, setBoot] = useState14({ status: "loading", document: null, draft: false, message: null });
4442
+ const [meta, setMeta] = useState14(EMPTY_META);
4443
+ const [savedMeta, setSavedMeta] = useState14(EMPTY_META);
4444
+ const [saving, setSaving] = useState14(null);
4445
+ const [showErrors, setShowErrors] = useState14(false);
4446
+ const [autosaveBlocked, setAutosaveBlocked] = useState14(null);
4447
+ const [engineError, setEngineError] = useState14(null);
4448
+ const [attempt, setAttempt] = useState14(0);
4449
+ const rootRef = useRef7(null);
4450
+ const layout2 = useLayoutWidth(rootRef);
4451
+ const compact = layout2 !== "wide";
4452
+ const blocksInDrawer = layout2 === "narrow";
4453
+ const [drawer, setDrawer] = useState14(null);
4454
+ const closeDrawer = useCallback3(() => setDrawer(null), []);
4455
+ useEffect9(() => {
4456
+ if (!compact) setDrawer(null);
4457
+ else if (!blocksInDrawer) setDrawer((d) => d === "blocks" ? null : d);
4458
+ }, [compact, blocksInDrawer]);
3833
4459
  const ready = state.status === "ready";
3834
4460
  const metaDirty = !sameMeta(meta, savedMeta);
3835
4461
  const dirty = state.dirty || metaDirty;
@@ -3838,7 +4464,7 @@ function Builder({
3838
4464
  [locale]
3839
4465
  );
3840
4466
  const booted = useRef7(false);
3841
- useEffect7(() => {
4467
+ useEffect9(() => {
3842
4468
  if (booted.current) return;
3843
4469
  booted.current = true;
3844
4470
  (async () => {
@@ -3887,7 +4513,7 @@ function Builder({
3887
4513
  if (!boot.document) editor.loadMjml(initialMjml || starter);
3888
4514
  }, [editor, boot.document, starter, initialMjml]);
3889
4515
  const { markDirty, markClean, checkDirty } = state;
3890
- useEffect7(() => {
4516
+ useEffect9(() => {
3891
4517
  if (ready && boot.draft) markDirty();
3892
4518
  }, [ready, boot.draft, markDirty]);
3893
4519
  const isDirty = useCallback3(
@@ -3896,17 +4522,17 @@ function Builder({
3896
4522
  );
3897
4523
  const reportDirty = useRef7(onDirtyChange);
3898
4524
  reportDirty.current = onDirtyChange;
3899
- useEffect7(() => {
4525
+ useEffect9(() => {
3900
4526
  reportDirty.current?.(dirty);
3901
4527
  }, [dirty]);
3902
- useEffect7(() => {
4528
+ useEffect9(() => {
3903
4529
  if (!dirtyCheckRef) return void 0;
3904
4530
  dirtyCheckRef.current = isDirty;
3905
4531
  return () => {
3906
4532
  dirtyCheckRef.current = null;
3907
4533
  };
3908
4534
  }, [dirtyCheckRef, isDirty]);
3909
- useEffect7(() => {
4535
+ useEffect9(() => {
3910
4536
  if (!dirty) return void 0;
3911
4537
  const warn = (e) => {
3912
4538
  e.preventDefault();
@@ -3917,7 +4543,7 @@ function Builder({
3917
4543
  }, [dirty]);
3918
4544
  const savingRef = useRef7(false);
3919
4545
  const latest = useRef7({});
3920
- latest.current = { template, meta, onSaved };
4546
+ latest.current = { template, meta, onSaved, compact };
3921
4547
  const save = useCallback3(
3922
4548
  async ({ auto = false } = {}) => {
3923
4549
  if (savingRef.current || editor.status !== "ready") return false;
@@ -3926,6 +4552,7 @@ function Builder({
3926
4552
  if (!auto) {
3927
4553
  setShowErrors(true);
3928
4554
  editor.select(null);
4555
+ setDrawer((d) => d === null && latest.current.compact ? "properties" : d);
3929
4556
  notify("error", t("Give the template a name before saving it."));
3930
4557
  }
3931
4558
  return false;
@@ -3998,7 +4625,7 @@ function Builder({
3998
4625
  },
3999
4626
  [editor, storage, markClean, notify, confirm, t]
4000
4627
  );
4001
- useEffect7(() => {
4628
+ useEffect9(() => {
4002
4629
  const onKey = (e) => {
4003
4630
  if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
4004
4631
  e.preventDefault();
@@ -4008,11 +4635,11 @@ function Builder({
4008
4635
  window.addEventListener("keydown", onKey, true);
4009
4636
  return () => window.removeEventListener("keydown", onKey, true);
4010
4637
  }, [save]);
4011
- useEffect7(() => {
4638
+ useEffect9(() => {
4012
4639
  if (!ready) return void 0;
4013
4640
  return editor.addShortcut("save", "mod+s", () => save());
4014
4641
  }, [ready, editor, save]);
4015
- const scheduler = useMemo6(
4642
+ const scheduler = useMemo7(
4016
4643
  () => createAutosaveScheduler({
4017
4644
  run: () => save({ auto: true }),
4018
4645
  idleMs: autosaveOptions.idleMs,
@@ -4020,13 +4647,13 @@ function Builder({
4020
4647
  }),
4021
4648
  [save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
4022
4649
  );
4023
- useEffect7(() => () => scheduler.dispose(), [scheduler]);
4024
- useEffect7(() => {
4650
+ useEffect9(() => () => scheduler.dispose(), [scheduler]);
4651
+ useEffect9(() => {
4025
4652
  if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
4026
4653
  if (dirty) scheduler.notify();
4027
4654
  else scheduler.reset();
4028
4655
  }, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
4029
- useEffect7(() => {
4656
+ useEffect9(() => {
4030
4657
  if (!autosaveOn || !ready || template.id || !dirty) return void 0;
4031
4658
  const timer = setTimeout(() => {
4032
4659
  if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
@@ -4051,6 +4678,8 @@ function Builder({
4051
4678
  storage,
4052
4679
  template,
4053
4680
  meta,
4681
+ setMeta,
4682
+ templates,
4054
4683
  ready,
4055
4684
  isDirty,
4056
4685
  markDirty,
@@ -4064,15 +4693,32 @@ function Builder({
4064
4693
  const toolbarFeatures = emailBuilderFeatures.list(
4065
4694
  (f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
4066
4695
  );
4067
- return /* @__PURE__ */ jsxs17(
4696
+ const properties = /* @__PURE__ */ jsx22(
4697
+ PropertiesPanel,
4698
+ {
4699
+ onDismiss: compact ? closeDrawer : null,
4700
+ editor,
4701
+ selection: ready ? state.selection : null,
4702
+ meta,
4703
+ onMetaChange: setMeta,
4704
+ changes: state.changes,
4705
+ showErrors,
4706
+ actions: /* @__PURE__ */ jsx22(ElementActions, { editor, selection: state.selection }),
4707
+ services,
4708
+ variables
4709
+ }
4710
+ );
4711
+ return /* @__PURE__ */ jsxs19(
4068
4712
  "div",
4069
4713
  {
4714
+ ref: rootRef,
4070
4715
  className: cn("eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground", className),
4071
4716
  "data-testid": "email-builder",
4072
4717
  "data-status": state.status,
4718
+ "data-layout": layout2,
4073
4719
  "data-template-id": template.id || "",
4074
4720
  children: [
4075
- /* @__PURE__ */ jsx20(
4721
+ /* @__PURE__ */ jsx22(
4076
4722
  Toolbar,
4077
4723
  {
4078
4724
  title: meta.name,
@@ -4085,80 +4731,110 @@ function Builder({
4085
4731
  device: state.device,
4086
4732
  onDeviceChange: (device) => editor.setDevice(device),
4087
4733
  onBack: onClose ? back : void 0,
4088
- actions: /* @__PURE__ */ jsxs17(Fragment6, { children: [
4089
- toolbarFeatures.map((feature) => {
4090
- const Action = feature.toolbar;
4091
- return /* @__PURE__ */ jsx20(Action, { context: featureContext }, feature.id);
4092
- }),
4093
- /* @__PURE__ */ jsxs17(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
4094
- saving === "manual" ? /* @__PURE__ */ jsx20(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx20(Save, {}),
4095
- t("Save")
4096
- ] })
4734
+ compact,
4735
+ actions: toolbarFeatures.map((feature) => {
4736
+ const Action = feature.toolbar;
4737
+ return /* @__PURE__ */ jsx22(Action, { context: featureContext }, feature.id);
4738
+ }),
4739
+ primary: /* @__PURE__ */ jsxs19(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
4740
+ saving === "manual" ? /* @__PURE__ */ jsx22(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx22(Save, {}),
4741
+ t("Save")
4097
4742
  ] })
4098
4743
  }
4099
4744
  ),
4100
- /* @__PURE__ */ jsxs17("div", { className: "eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-[224px_minmax(0,1fr)_296px] eb:grid-rows-1", children: [
4101
- /* @__PURE__ */ jsx20(BlockSidebar, { editor, disabled: !ready }),
4102
- /* @__PURE__ */ jsx20("div", { className: "eb:relative eb:min-w-0 eb:bg-surface-75", "aria-busy": !ready, children: boot.status === "error" || engineError ? /* @__PURE__ */ jsxs17("div", { role: "alert", className: "eb:flex eb:h-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:p-6 eb:text-center", children: [
4103
- /* @__PURE__ */ jsx20(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4104
- /* @__PURE__ */ jsx20("p", { className: "eb:text-[13px] eb:text-foreground", children: boot.status === "error" ? t("The template could not be loaded.") : t("The builder could not start.") }),
4105
- /* @__PURE__ */ jsx20("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
4106
- engineError && /* @__PURE__ */ jsx20(
4107
- Button,
4108
- {
4109
- variant: "outline",
4110
- size: "sm-compact",
4111
- onClick: () => {
4112
- setEngineError(null);
4113
- setAttempt((n) => n + 1);
4114
- },
4115
- children: t("Retry")
4116
- }
4117
- )
4118
- ] }) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
4119
- boot.status === "ready" && /* @__PURE__ */ jsx20(
4120
- EmailCanvas,
4121
- {
4122
- editor,
4123
- document: boot.document,
4124
- onReady,
4125
- onError: (error) => setEngineError(error.message)
4126
- },
4127
- attempt
4745
+ /* @__PURE__ */ jsxs19(
4746
+ "div",
4747
+ {
4748
+ className: cn(
4749
+ "eb:relative eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1",
4750
+ layout2 === "wide" && "eb:grid-cols-[224px_minmax(0,1fr)_296px]",
4751
+ layout2 === "medium" && "eb:grid-cols-[200px_minmax(0,1fr)]",
4752
+ layout2 === "narrow" && "eb:grid-cols-1"
4128
4753
  ),
4129
- !ready && /* @__PURE__ */ jsxs17("div", { className: "eb:absolute eb:inset-0 eb:flex eb:items-center eb:justify-center eb:bg-surface-75", role: "status", "aria-live": "polite", children: [
4130
- /* @__PURE__ */ jsx20(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
4131
- /* @__PURE__ */ jsx20("span", { className: "eb:sr-only", children: t("Loading the builder") })
4132
- ] })
4133
- ] }) }),
4134
- /* @__PURE__ */ jsx20(
4135
- PropertiesPanel,
4754
+ children: [
4755
+ !blocksInDrawer && /* @__PURE__ */ jsx22(BlockSidebar, { editor, disabled: !ready, onAdded: compact ? () => setDrawer("properties") : void 0 }),
4756
+ /* @__PURE__ */ jsx22("div", { className: "eb:relative eb:min-w-0 eb:bg-surface-75", "aria-busy": !ready, children: boot.status === "error" || engineError ? /* @__PURE__ */ jsxs19("div", { role: "alert", className: "eb:flex eb:h-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:p-6 eb:text-center", children: [
4757
+ /* @__PURE__ */ jsx22(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4758
+ /* @__PURE__ */ jsx22("p", { className: "eb:text-[13px] eb:text-foreground", children: boot.status === "error" ? t("The template could not be loaded.") : t("The builder could not start.") }),
4759
+ /* @__PURE__ */ jsx22("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
4760
+ engineError && /* @__PURE__ */ jsx22(
4761
+ Button,
4762
+ {
4763
+ variant: "outline",
4764
+ size: "sm-compact",
4765
+ onClick: () => {
4766
+ setEngineError(null);
4767
+ setAttempt((n) => n + 1);
4768
+ },
4769
+ children: t("Retry")
4770
+ }
4771
+ )
4772
+ ] }) : /* @__PURE__ */ jsxs19(Fragment8, { children: [
4773
+ boot.status === "ready" && /* @__PURE__ */ jsx22(
4774
+ EmailCanvas,
4775
+ {
4776
+ editor,
4777
+ document: boot.document,
4778
+ onReady,
4779
+ onError: (error) => setEngineError(error.message)
4780
+ },
4781
+ attempt
4782
+ ),
4783
+ !ready && /* @__PURE__ */ jsxs19("div", { className: "eb:absolute eb:inset-0 eb:flex eb:items-center eb:justify-center eb:bg-surface-75", role: "status", "aria-live": "polite", children: [
4784
+ /* @__PURE__ */ jsx22(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
4785
+ /* @__PURE__ */ jsx22("span", { className: "eb:sr-only", children: t("Loading the builder") })
4786
+ ] })
4787
+ ] }) }),
4788
+ !compact && properties,
4789
+ blocksInDrawer && /* @__PURE__ */ jsx22(Drawer, { open: drawer === "blocks", side: "left", title: t("Add a block"), onClose: closeDrawer, testId: "eb-blocks-drawer", children: /* @__PURE__ */ jsx22(BlockSidebar, { editor, disabled: !ready, onAdded: () => setDrawer("properties") }) }),
4790
+ compact && /* @__PURE__ */ jsx22(
4791
+ Drawer,
4792
+ {
4793
+ open: drawer === "properties",
4794
+ side: "right",
4795
+ title: t("Properties"),
4796
+ onClose: closeDrawer,
4797
+ testId: "eb-properties-drawer",
4798
+ header: false,
4799
+ children: properties
4800
+ }
4801
+ )
4802
+ ]
4803
+ }
4804
+ ),
4805
+ compact && /* @__PURE__ */ jsxs19("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2 eb:border-t eb:border-border eb:px-2 eb:py-2", "data-testid": "eb-compact-bar", children: [
4806
+ blocksInDrawer && /* @__PURE__ */ jsxs19(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setDrawer("blocks"), children: [
4807
+ /* @__PURE__ */ jsx22(Plus2, {}),
4808
+ t("Add a block")
4809
+ ] }),
4810
+ /* @__PURE__ */ jsxs19(
4811
+ Button,
4136
4812
  {
4137
- editor,
4138
- selection: ready ? state.selection : null,
4139
- meta,
4140
- onMetaChange: setMeta,
4141
- changes: state.changes,
4142
- showErrors,
4143
- actions: /* @__PURE__ */ jsx20(ElementActions, { editor, selection: state.selection }),
4144
- services,
4145
- variables
4813
+ variant: state.selection ? "default" : "outline",
4814
+ size: "sm-compact",
4815
+ className: "eb:ml-auto eb:min-w-0",
4816
+ disabled: !ready,
4817
+ onClick: () => setDrawer("properties"),
4818
+ children: [
4819
+ /* @__PURE__ */ jsx22(SlidersHorizontal, {}),
4820
+ /* @__PURE__ */ jsx22("span", { className: "eb:truncate", children: ready && state.selection ? t("Edit: {element}", { element: t(state.selection.label) }) : t("Email settings") })
4821
+ ]
4146
4822
  }
4147
4823
  )
4148
4824
  ] }),
4149
- /* @__PURE__ */ jsx20(NoticeArea, {})
4825
+ /* @__PURE__ */ jsx22(NoticeArea, {})
4150
4826
  ]
4151
4827
  }
4152
4828
  );
4153
4829
  }
4154
4830
  function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
4155
- return /* @__PURE__ */ jsx20(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx20(Builder, { ...props, templateId: props.templateId ?? null }) });
4831
+ return /* @__PURE__ */ jsx22(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx22(Builder, { ...props, templateId: props.templateId ?? null }) });
4156
4832
  }
4157
4833
 
4158
4834
  // src/ui/EmailBuilderBoundary.jsx
4159
4835
  import { Component } from "react";
4160
4836
  import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft3, RefreshCw as RefreshCw2 } from "lucide-react";
4161
- import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
4837
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
4162
4838
  var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
4163
4839
  var english = (text) => text;
4164
4840
  var EmailBuilderBoundary = class extends Component {
@@ -4183,23 +4859,23 @@ var EmailBuilderBoundary = class extends Component {
4183
4859
  const { error, attempt } = this.state;
4184
4860
  const t = this.props.t || english;
4185
4861
  if (!error) {
4186
- return /* @__PURE__ */ jsx21("div", { style: { height: "100%" }, children: this.props.children }, attempt);
4862
+ return /* @__PURE__ */ jsx23("div", { style: { height: "100%" }, children: this.props.children }, attempt);
4187
4863
  }
4188
- return /* @__PURE__ */ jsxs18(
4864
+ return /* @__PURE__ */ jsxs20(
4189
4865
  "div",
4190
4866
  {
4191
4867
  role: "alert",
4192
4868
  className: "eb-root eb:flex eb:h-full eb:flex-col eb:items-center eb:justify-center eb:gap-3 eb:p-6 eb:text-center",
4193
4869
  children: [
4194
- /* @__PURE__ */ jsx21(AlertTriangle3, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4195
- /* @__PURE__ */ jsx21("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
4196
- /* @__PURE__ */ jsxs18("div", { className: "eb:flex eb:gap-2", children: [
4197
- this.props.onClose && /* @__PURE__ */ jsxs18(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
4198
- /* @__PURE__ */ jsx21(ArrowLeft3, {}),
4870
+ /* @__PURE__ */ jsx23(AlertTriangle3, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4871
+ /* @__PURE__ */ jsx23("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
4872
+ /* @__PURE__ */ jsxs20("div", { className: "eb:flex eb:gap-2", children: [
4873
+ this.props.onClose && /* @__PURE__ */ jsxs20(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
4874
+ /* @__PURE__ */ jsx23(ArrowLeft3, {}),
4199
4875
  t("Back")
4200
4876
  ] }),
4201
- /* @__PURE__ */ jsxs18(Button, { size: "sm", onClick: this.retry, children: [
4202
- /* @__PURE__ */ jsx21(RefreshCw2, {}),
4877
+ /* @__PURE__ */ jsxs20(Button, { size: "sm", onClick: this.retry, children: [
4878
+ /* @__PURE__ */ jsx23(RefreshCw2, {}),
4203
4879
  t("Retry")
4204
4880
  ] })
4205
4881
  ] })
@@ -4232,10 +4908,12 @@ export {
4232
4908
  LOCALES,
4233
4909
  STARTER_MJML,
4234
4910
  StorageError,
4911
+ TEMPLATE_GALLERY,
4235
4912
  UiProvider,
4236
4913
  absoluteAssetUrls,
4237
4914
  applyVariables,
4238
4915
  applyVariablesToText,
4916
+ buildGallery,
4239
4917
  createDefaultEmailEditor,
4240
4918
  createDocument,
4241
4919
  createEmailEditor,