best-email-builder 0.1.1 → 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 (33) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +11 -1
  3. package/dist/{chunk-2DZ6BRIJ.js → chunk-MTALS5GC.js} +98 -3
  4. package/dist/chunk-MTALS5GC.js.map +1 -0
  5. package/dist/index.js +963 -295
  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/blocks/columns/index.d.ts +3 -0
  12. package/dist/types/blocks/heading/index.d.ts +3 -0
  13. package/dist/types/blocks/hero/index.d.ts +3 -0
  14. package/dist/types/blocks/navbar/index.d.ts +6 -0
  15. package/dist/types/blocks/social/index.d.ts +13 -0
  16. package/dist/types/core/EmailEditor.d.ts +12 -4
  17. package/dist/types/core/constants.d.ts +1 -1
  18. package/dist/types/features/templates/TemplateGallery.d.ts +3 -0
  19. package/dist/types/features/templates/gallery.d.ts +41 -0
  20. package/dist/types/features/templates/index.d.ts +2 -0
  21. package/dist/types/features/templates/starter.d.ts +22 -0
  22. package/dist/types/index.d.ts +1 -0
  23. package/dist/types/server/compileMjml.d.ts +3 -3
  24. package/dist/types/server/render.d.ts +4 -4
  25. package/dist/types/server/templates.d.ts +2 -2
  26. package/dist/types/ui/BlockSidebar.d.ts +2 -1
  27. package/dist/types/ui/Drawer.d.ts +19 -0
  28. package/dist/types/ui/EmailBuilder.d.ts +2 -0
  29. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  30. package/dist/types/ui/Toolbar.d.ts +9 -2
  31. package/dist/types/ui/useLayoutWidth.d.ts +10 -0
  32. package/package.json +6 -6
  33. package/dist/chunk-2DZ6BRIJ.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-2DZ6BRIJ.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");
@@ -439,10 +444,10 @@ function isInteropLayer(value) {
439
444
 
440
445
  // src/adapters/grapesjs/GrapesJsAdapter.js
441
446
  var ENGINE_NAME = "grapesjs-mjml";
442
- var ENGINE_VERSION = "0.22.16";
447
+ var ENGINE_VERSION = "0.23.6";
443
448
  var BLOCK_PREFIX = "eb-";
444
449
  var LOCKED_TYPES = /* @__PURE__ */ new Set(["mjml", "mj-head", "mj-body", "wrapper"]);
445
- var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button"]);
450
+ var LABEL_TYPES = /* @__PURE__ */ new Set(["mj-button", "mj-navbar-link"]);
446
451
  var RICH_TYPES = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
447
452
  var ENGINE_LOCALES = {
448
453
  fr: () => Promise.all([import("grapesjs/locale/fr.mjs"), import("grapesjs-mjml/locale/fr")])
@@ -581,11 +586,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
581
586
  function placementFor(block, target) {
582
587
  const root = body();
583
588
  if (!root) throw new Error("GrapesJsAdapter: email body not found");
584
- 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;
585
590
  const afterSection = () => {
586
- if (section && section.parent()) {
587
- const coll = section.parent().components();
588
- 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 };
589
594
  }
590
595
  return { collection: root.components(), at: root.components().length, wrap: false };
591
596
  };
@@ -594,11 +599,11 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
594
599
  if (type === "mj-column") {
595
600
  return { collection: target.components(), at: target.components().length, wrap: false };
596
601
  }
597
- const column = target ? closest(target, ["mj-column"]) : null;
598
- if (column && target !== column) {
602
+ const column2 = target ? closest(target, ["mj-column"]) : null;
603
+ if (column2 && target !== column2) {
599
604
  let child = target;
600
- while (child.parent() && child.parent() !== column) child = child.parent();
601
- const coll = column.components();
605
+ while (child.parent() && child.parent() !== column2) child = child.parent();
606
+ const coll = column2.components();
602
607
  return { collection: coll, at: coll.indexOf(child) + 1, wrap: false };
603
608
  }
604
609
  return { ...afterSection(), wrap: true };
@@ -813,8 +818,9 @@ function createGrapesJsAdapter({ locale = "en", loadEngine = defaultLoadEngine }
813
818
  exportMjml() {
814
819
  return String(requireEditor().runCommand("mjml-code") || "").replace(PLACEHOLDER_SRC_ATTR, "").trim();
815
820
  },
816
- exportHtml() {
817
- 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 }) || {};
818
824
  return {
819
825
  html: String(result.html || ""),
820
826
  errors: (result.errors || []).map((e) => ({
@@ -981,6 +987,30 @@ var sectionBlock = {
981
987
  order: 901,
982
988
  properties: [
983
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
+ },
984
1014
  prop.padding("padding", "Padding"),
985
1015
  prop.radius(24)
986
1016
  ]
@@ -1029,6 +1059,16 @@ var textBlock = {
1029
1059
  prop.fontFamily(),
1030
1060
  prop.fontSize(10, 48),
1031
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
+ },
1032
1072
  prop.color("Text"),
1033
1073
  prop.background("container-background-color", "Background"),
1034
1074
  { ...prop.align(), options: [...prop.align().options || [], { value: "justify", label: "Justify" }] },
@@ -1172,6 +1212,133 @@ var spacerBlock = {
1172
1212
  ]
1173
1213
  };
1174
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
+
1175
1342
  // src/blocks/columns/index.js
1176
1343
  function sectionWith(count) {
1177
1344
  const columns = Array.from(
@@ -1196,20 +1363,41 @@ function layout(count, icon) {
1196
1363
  var oneColumnBlock = layout(1, "Square");
1197
1364
  var twoColumnsBlock = layout(2, "Columns2");
1198
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
+ };
1199
1379
 
1200
1380
  // src/blocks/index.js
1201
1381
  var BLOCKS = [
1382
+ headingBlock,
1202
1383
  textBlock,
1203
1384
  imageBlock,
1204
1385
  buttonBlock,
1205
1386
  dividerBlock,
1206
1387
  spacerBlock,
1388
+ socialBlock,
1389
+ navbarBlock,
1207
1390
  oneColumnBlock,
1208
1391
  twoColumnsBlock,
1209
1392
  threeColumnsBlock,
1393
+ fourColumnsBlock,
1394
+ imageTextBlock,
1395
+ heroBlock,
1210
1396
  bodyBlock,
1211
1397
  sectionBlock,
1212
- columnBlock
1398
+ columnBlock,
1399
+ socialElementBlock,
1400
+ navbarLinkBlock
1213
1401
  ];
1214
1402
  var emailBlocks = createRegistry("emailBlocks", BLOCKS);
1215
1403
 
@@ -2507,7 +2695,7 @@ function TestEmailButton({ context }) {
2507
2695
  r.error ? ` \u2014 ${r.error}` : ""
2508
2696
  ] })
2509
2697
  ] }, r.email)),
2510
- 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}`))
2511
2699
  ] }),
2512
2700
  /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
2513
2701
  /* @__PURE__ */ jsx11(Button, { variant: "outline", onClick: () => setOpen(false), children: t("Close") }),
@@ -2529,17 +2717,9 @@ var testEmailFeature = {
2529
2717
  isAvailable: (context) => typeof context.storage?.sendTest === "function"
2530
2718
  };
2531
2719
 
2532
- // src/features/index.js
2533
- var FEATURES = [variablesFeature, autosaveFeature, versionsFeature, previewFeature, testEmailFeature];
2534
- var emailBuilderFeatures = createRegistry("emailBuilderFeatures", FEATURES);
2535
- function isFeatureEnabled(id) {
2536
- const feature = emailBuilderFeatures.get(id);
2537
- return Boolean(feature && feature.enabled !== false);
2538
- }
2539
-
2540
- // src/ui/EmailBuilder.jsx
2541
- import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef7, useState as useState12 } from "react";
2542
- 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";
2543
2723
 
2544
2724
  // src/features/templates/starter.js
2545
2725
  function escapeXml(text) {
@@ -2547,17 +2727,23 @@ function escapeXml(text) {
2547
2727
  }
2548
2728
  var SIDE = 'padding-left="32px" padding-right="32px"';
2549
2729
  var TEXT = `font-family="${FONT_STACK}" color="${PALETTE.ink}"`;
2550
- 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) {
2551
2735
  const name = escapeXml(sender.name || t("Your company"));
2552
2736
  const footer = [name, sender.address ? escapeXml(sender.address) : ""].filter(Boolean).join(" \xB7 ");
2553
- 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 } = {}) {
2554
2740
  return [
2555
2741
  "<mjml>",
2556
2742
  `<mj-body background-color="${PALETTE.canvas}" width="600px">`,
2557
2743
  // Header: logo or sender name.
2558
2744
  `<mj-section background-color="${PALETTE.surface}" padding-top="28px" padding-bottom="8px">`,
2559
2745
  "<mj-column>",
2560
- header,
2746
+ senderHeaderMjml(sender, t),
2561
2747
  "</mj-column>",
2562
2748
  "</mj-section>",
2563
2749
  // Body: headline, text, button.
@@ -2569,17 +2755,316 @@ function createStarterMjml({ sender = {}, buttonUrl = DEFAULT_BUTTON_URL, t = id
2569
2755
  "</mj-column>",
2570
2756
  "</mj-section>",
2571
2757
  // Footer: sender and unsubscribe link.
2572
- '<mj-section padding-top="20px" padding-bottom="28px">',
2573
- "<mj-column>",
2574
- `<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>`,
2575
- "</mj-column>",
2576
- "</mj-section>",
2758
+ footerSectionMjml(sender, t),
2577
2759
  "</mj-body>",
2578
2760
  "</mjml>"
2579
2761
  ].join("");
2580
2762
  }
2581
2763
  var STARTER_MJML = createStarterMjml();
2582
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
+
2583
3068
  // src/features/autosave/scheduler.js
2584
3069
  function createAutosaveScheduler({
2585
3070
  run,
@@ -2879,7 +3364,7 @@ function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PAT
2879
3364
 
2880
3365
  // src/ui/EmailCanvas.jsx
2881
3366
  import { useEffect as useEffect4, useRef as useRef3 } from "react";
2882
- import { jsx as jsx12 } from "react/jsx-runtime";
3367
+ import { jsx as jsx13 } from "react/jsx-runtime";
2883
3368
  function EmailCanvas({ editor, document = null, onReady, onError, className }) {
2884
3369
  const ref = useRef3(null);
2885
3370
  const documentRef = useRef3(document);
@@ -2898,23 +3383,35 @@ function EmailCanvas({ editor, document = null, onReady, onError, className }) {
2898
3383
  editor.destroy();
2899
3384
  };
2900
3385
  }, [editor]);
2901
- 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" });
2902
3387
  }
2903
3388
 
2904
3389
  // src/ui/BlockSidebar.jsx
2905
3390
  import {
2906
3391
  Columns2,
2907
3392
  Columns3,
3393
+ Columns4,
3394
+ GalleryHorizontal,
3395
+ Heading,
2908
3396
  Image as ImageIcon,
3397
+ LayoutPanelLeft,
3398
+ Menu,
2909
3399
  MoveVertical,
2910
3400
  RectangleHorizontal,
2911
3401
  SeparatorHorizontal,
3402
+ Share2,
2912
3403
  Square,
2913
3404
  Type
2914
3405
  } from "lucide-react";
2915
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
3406
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2916
3407
  var ICONS = {
3408
+ Heading,
2917
3409
  Type,
3410
+ Share2,
3411
+ Menu,
3412
+ Columns4,
3413
+ LayoutPanelLeft,
3414
+ GalleryHorizontal,
2918
3415
  Image: ImageIcon,
2919
3416
  RectangleHorizontal,
2920
3417
  SeparatorHorizontal,
@@ -2930,7 +3427,7 @@ var GROUPS2 = [
2930
3427
  function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
2931
3428
  const t = useT();
2932
3429
  const Icon = ICONS[block.icon] || Square;
2933
- 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(
2934
3431
  "button",
2935
3432
  {
2936
3433
  type: "button",
@@ -2950,26 +3447,28 @@ function BlockTile({ block, disabled, onAdd, onDragStart, onDragEnd }) {
2950
3447
  disabled && "eb:cursor-not-allowed eb:opacity-50"
2951
3448
  ),
2952
3449
  children: [
2953
- /* @__PURE__ */ jsx13(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
2954
- /* @__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) })
2955
3452
  ]
2956
3453
  }
2957
3454
  ) });
2958
3455
  }
2959
- function BlockSidebar({ editor, disabled = false }) {
3456
+ function BlockSidebar({ editor, disabled = false, onAdded }) {
2960
3457
  const t = useT();
2961
3458
  const blocks = editor.listBlocks();
2962
- 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) => {
2963
3460
  const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
2964
3461
  if (!items.length) return null;
2965
- return /* @__PURE__ */ jsxs10("section", { className: "eb:space-y-2", children: [
2966
- /* @__PURE__ */ jsx13(SectionLabel, { children: t(group.title) }),
2967
- /* @__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(
2968
3465
  BlockTile,
2969
3466
  {
2970
3467
  block,
2971
3468
  disabled,
2972
- onAdd: (id) => editor.addBlock(id),
3469
+ onAdd: (id) => {
3470
+ if (editor.addBlock(id)) onAdded?.(id);
3471
+ },
2973
3472
  onDragStart: (id, event) => editor.startBlockDrag(id, event),
2974
3473
  onDragEnd: (cancel) => editor.endBlockDrag(cancel)
2975
3474
  },
@@ -2981,15 +3480,15 @@ function BlockSidebar({ editor, disabled = false }) {
2981
3480
 
2982
3481
  // src/ui/ElementActions.jsx
2983
3482
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash22 } from "lucide-react";
2984
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
3483
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2985
3484
  function ElementActions({ editor, selection }) {
2986
3485
  const t = useT();
2987
3486
  if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
2988
3487
  const horizontal = selection.tagName === "mj-column";
2989
3488
  const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
2990
3489
  const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
2991
- 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: [
2992
- /* @__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(
2993
3492
  Button,
2994
3493
  {
2995
3494
  variant: "ghost",
@@ -2997,10 +3496,10 @@ function ElementActions({ editor, selection }) {
2997
3496
  "aria-label": prevLabel,
2998
3497
  disabled: !selection.canMoveUp,
2999
3498
  onClick: () => editor.move(-1, selection.id),
3000
- children: /* @__PURE__ */ jsx14(Prev, {})
3499
+ children: /* @__PURE__ */ jsx15(Prev, {})
3001
3500
  }
3002
3501
  ) }),
3003
- /* @__PURE__ */ jsx14(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx14(
3502
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx15(
3004
3503
  Button,
3005
3504
  {
3006
3505
  variant: "ghost",
@@ -3008,10 +3507,10 @@ function ElementActions({ editor, selection }) {
3008
3507
  "aria-label": nextLabel,
3009
3508
  disabled: !selection.canMoveDown,
3010
3509
  onClick: () => editor.move(1, selection.id),
3011
- children: /* @__PURE__ */ jsx14(Next, {})
3510
+ children: /* @__PURE__ */ jsx15(Next, {})
3012
3511
  }
3013
3512
  ) }),
3014
- /* @__PURE__ */ jsx14(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx14(
3513
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx15(
3015
3514
  Button,
3016
3515
  {
3017
3516
  variant: "ghost",
@@ -3019,10 +3518,10 @@ function ElementActions({ editor, selection }) {
3019
3518
  "aria-label": t("Duplicate"),
3020
3519
  disabled: !selection.canDuplicate,
3021
3520
  onClick: () => editor.duplicate(selection.id),
3022
- children: /* @__PURE__ */ jsx14(Copy, {})
3521
+ children: /* @__PURE__ */ jsx15(Copy, {})
3023
3522
  }
3024
3523
  ) }),
3025
- /* @__PURE__ */ jsx14(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx14(
3524
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx15(
3026
3525
  Button,
3027
3526
  {
3028
3527
  variant: "ghost",
@@ -3031,24 +3530,24 @@ function ElementActions({ editor, selection }) {
3031
3530
  disabled: !selection.canRemove,
3032
3531
  onClick: () => editor.remove(selection.id),
3033
3532
  className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
3034
- children: /* @__PURE__ */ jsx14(Trash22, {})
3533
+ children: /* @__PURE__ */ jsx15(Trash22, {})
3035
3534
  }
3036
3535
  ) })
3037
3536
  ] });
3038
3537
  }
3039
3538
 
3040
3539
  // src/ui/PropertiesPanel.jsx
3041
- import { useMemo as useMemo5, useRef as useRef5 } from "react";
3540
+ import { useMemo as useMemo6, useRef as useRef5 } from "react";
3042
3541
  import { X as X2 } from "lucide-react";
3043
3542
 
3044
3543
  // src/ui/fields.jsx
3045
- 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";
3046
3545
 
3047
3546
  // src/ui/primitives/color-picker.jsx
3048
- import { useState as useState8 } from "react";
3547
+ import { useState as useState9 } from "react";
3049
3548
  import { HexAlphaColorPicker } from "react-colorful";
3050
3549
  import { Check } from "lucide-react";
3051
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
3550
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
3052
3551
  var PRESET_COLORS = [
3053
3552
  "#8B5CF6",
3054
3553
  "#A78BFA",
@@ -3079,34 +3578,34 @@ function normalizeHex(v) {
3079
3578
  }
3080
3579
  function ColorPicker({ value, onChange, id, className }) {
3081
3580
  const t = useT();
3082
- const [open, setOpen] = useState8(false);
3581
+ const [open, setOpen] = useState9(false);
3083
3582
  const valid = isValidHex(value);
3084
3583
  const normalized = valid ? normalizeHex(value) : "";
3085
3584
  const emit = (hex) => onChange(normalizeHex(hex));
3086
- return /* @__PURE__ */ jsxs12("div", { className: cn("eb:flex eb:gap-2", className), children: [
3087
- /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
3088
- /* @__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(
3089
3588
  "button",
3090
3589
  {
3091
3590
  type: "button",
3092
3591
  "aria-label": t("Pick a colour"),
3093
3592
  title: t("Pick a colour"),
3094
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]",
3095
- 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 })
3096
3595
  }
3097
3596
  ) }),
3098
- /* @__PURE__ */ jsxs12(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
3099
- /* @__PURE__ */ jsx15("div", { className: "eb-picker", children: /* @__PURE__ */ jsx15(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
3100
- /* @__PURE__ */ jsxs12("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
3101
- /* @__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(
3102
3601
  "span",
3103
3602
  {
3104
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",
3105
3604
  "aria-hidden": true,
3106
- 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" } })
3107
3606
  }
3108
3607
  ),
3109
- /* @__PURE__ */ jsx15(
3608
+ /* @__PURE__ */ jsx16(
3110
3609
  Input,
3111
3610
  {
3112
3611
  value: value || "",
@@ -3118,7 +3617,7 @@ function ColorPicker({ value, onChange, id, className }) {
3118
3617
  }
3119
3618
  )
3120
3619
  ] }),
3121
- /* @__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(
3122
3621
  "button",
3123
3622
  {
3124
3623
  type: "button",
@@ -3127,13 +3626,13 @@ function ColorPicker({ value, onChange, id, className }) {
3127
3626
  "aria-label": c,
3128
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",
3129
3628
  style: { backgroundColor: c },
3130
- 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)]" })
3131
3630
  },
3132
3631
  c
3133
3632
  )) })
3134
3633
  ] })
3135
3634
  ] }),
3136
- /* @__PURE__ */ jsx15(
3635
+ /* @__PURE__ */ jsx16(
3137
3636
  Input,
3138
3637
  {
3139
3638
  id,
@@ -3149,7 +3648,7 @@ function ColorPicker({ value, onChange, id, className }) {
3149
3648
 
3150
3649
  // src/ui/fields.jsx
3151
3650
  import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload } from "lucide-react";
3152
- 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";
3153
3652
  var COLOR_DEBOUNCE_MS = 250;
3154
3653
  function emailColor(value) {
3155
3654
  const v = String(value || "").trim();
@@ -3158,7 +3657,7 @@ function emailColor(value) {
3158
3657
  return null;
3159
3658
  }
3160
3659
  function useDraft(value, toDraft) {
3161
- const [draft, setDraft] = useState9(() => toDraft(value));
3660
+ const [draft, setDraft] = useState10(() => toDraft(value));
3162
3661
  const ref = useRef4(null);
3163
3662
  const convert = useRef4(toDraft);
3164
3663
  convert.current = toDraft;
@@ -3171,10 +3670,10 @@ function useDraft(value, toDraft) {
3171
3670
  }
3172
3671
  var asText = (v) => v || "";
3173
3672
  function FieldLabel({ id, children }) {
3174
- 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 });
3175
3674
  }
3176
3675
  function FieldError({ children }) {
3177
- 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;
3178
3677
  }
3179
3678
  function TextField({ id, property, value, onCommit, addon = null, error = null }) {
3180
3679
  const t = useT();
@@ -3205,13 +3704,13 @@ function TextField({ id, property, value, onCommit, addon = null, error = null }
3205
3704
  onKeyDown: keyDown,
3206
3705
  "aria-invalid": invalid || void 0
3207
3706
  };
3208
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3209
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3210
- property.type === "textarea" ? /* @__PURE__ */ jsx16(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:gap-2", children: [
3211
- /* @__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" }),
3212
3711
  addon
3213
- ] }) : /* @__PURE__ */ jsx16(Input, { ...shared }),
3214
- /* @__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 })
3215
3714
  ] });
3216
3715
  }
3217
3716
  function ImageField({ id, property, value, onCommit, services }) {
@@ -3220,8 +3719,8 @@ function ImageField({ id, property, value, onCommit, services }) {
3220
3719
  /** @type {HTMLInputElement|null} */
3221
3720
  null
3222
3721
  );
3223
- const [busy, setBusy] = useState9(false);
3224
- const [error, setError] = useState9(
3722
+ const [busy, setBusy] = useState10(false);
3723
+ const [error, setError] = useState10(
3225
3724
  /** @type {string|null} */
3226
3725
  null
3227
3726
  );
@@ -3243,8 +3742,8 @@ function ImageField({ id, property, value, onCommit, services }) {
3243
3742
  setBusy(false);
3244
3743
  }
3245
3744
  };
3246
- const addon = upload ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
3247
- /* @__PURE__ */ jsx16(
3745
+ const addon = upload ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
3746
+ /* @__PURE__ */ jsx17(
3248
3747
  "input",
3249
3748
  {
3250
3749
  ref: inputRef,
@@ -3257,7 +3756,7 @@ function ImageField({ id, property, value, onCommit, services }) {
3257
3756
  onChange: pick
3258
3757
  }
3259
3758
  ),
3260
- /* @__PURE__ */ jsxs13(
3759
+ /* @__PURE__ */ jsxs14(
3261
3760
  Button,
3262
3761
  {
3263
3762
  type: "button",
@@ -3266,13 +3765,13 @@ function ImageField({ id, property, value, onCommit, services }) {
3266
3765
  disabled: busy,
3267
3766
  onClick: () => inputRef.current?.click(),
3268
3767
  children: [
3269
- busy ? /* @__PURE__ */ jsx16(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx16(Upload, {}),
3768
+ busy ? /* @__PURE__ */ jsx17(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Upload, {}),
3270
3769
  t("Upload")
3271
3770
  ]
3272
3771
  }
3273
3772
  )
3274
3773
  ] }) : null;
3275
- return /* @__PURE__ */ jsx16(TextField, { id, property, value, onCommit, addon, error });
3774
+ return /* @__PURE__ */ jsx17(TextField, { id, property, value, onCommit, addon, error });
3276
3775
  }
3277
3776
  var asNumber = (v) => {
3278
3777
  const n = Number.parseFloat(String(v || ""));
@@ -3294,10 +3793,10 @@ function SizeField({ id, property, value, onCommit }) {
3294
3793
  const next = `${n}${property.unit || "px"}`;
3295
3794
  if (next !== value) onCommit(next);
3296
3795
  };
3297
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3298
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3299
- /* @__PURE__ */ jsxs13("div", { className: "eb:relative", children: [
3300
- /* @__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(
3301
3800
  Input,
3302
3801
  {
3303
3802
  id,
@@ -3315,7 +3814,7 @@ function SizeField({ id, property, value, onCommit }) {
3315
3814
  className: "eb:pr-9 eb:tabular-nums"
3316
3815
  }
3317
3816
  ),
3318
- /* @__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" })
3319
3818
  ] })
3320
3819
  ] });
3321
3820
  }
@@ -3338,18 +3837,18 @@ function ColorField({ id, property, value, onCommit }) {
3338
3837
  }
3339
3838
  }, COLOR_DEBOUNCE_MS);
3340
3839
  };
3341
- return /* @__PURE__ */ jsxs13("div", { ref, className: "eb:space-y-1.5", children: [
3342
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3343
- /* @__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 })
3344
3843
  ] });
3345
3844
  }
3346
3845
  var DEFAULT_OPTION = "__default__";
3347
3846
  function SelectField({ id, property, value, onCommit }) {
3348
3847
  const t = useT();
3349
3848
  const current = value || DEFAULT_OPTION;
3350
- return /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
3351
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3352
- /* @__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(
3353
3852
  Select,
3354
3853
  {
3355
3854
  value: current,
@@ -3358,10 +3857,10 @@ function SelectField({ id, property, value, onCommit }) {
3358
3857
  if (v !== (value || "")) onCommit(v || null);
3359
3858
  },
3360
3859
  children: [
3361
- /* @__PURE__ */ jsx16(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx16(SelectValue, { placeholder: t("Default") }) }),
3362
- /* @__PURE__ */ jsxs13(SelectContent, { children: [
3363
- !(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx16(SelectItem, { value: DEFAULT_OPTION, children: t("Default") }),
3364
- (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))
3365
3864
  ] })
3366
3865
  ]
3367
3866
  }
@@ -3371,9 +3870,9 @@ function SelectField({ id, property, value, onCommit }) {
3371
3870
  var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
3372
3871
  function AlignField({ id, property, value, onCommit }) {
3373
3872
  const t = useT();
3374
- return /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
3375
- /* @__PURE__ */ jsx16(FieldLabel, { id, children: t(property.label) }),
3376
- /* @__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(
3377
3876
  "div",
3378
3877
  {
3379
3878
  id,
@@ -3383,7 +3882,7 @@ function AlignField({ id, property, value, onCommit }) {
3383
3882
  children: (property.options || []).map((option) => {
3384
3883
  const Icon = ALIGN_ICONS[option.value] || AlignLeft;
3385
3884
  const active = value === option.value;
3386
- return /* @__PURE__ */ jsx16(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx16(
3885
+ return /* @__PURE__ */ jsx17(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx17(
3387
3886
  "button",
3388
3887
  {
3389
3888
  type: "button",
@@ -3395,7 +3894,7 @@ function AlignField({ id, property, value, onCommit }) {
3395
3894
  "eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
3396
3895
  active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
3397
3896
  ),
3398
- 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" })
3399
3898
  }
3400
3899
  ) }, option.value);
3401
3900
  })
@@ -3417,10 +3916,10 @@ function SpacingField({ id, property, value, onCommit }) {
3417
3916
  const next = formatSpacing(sides.map((s) => `${Math.max(0, Math.min(200, Number.parseFloat(s) || 0))}px`));
3418
3917
  if (next !== (value || "")) onCommit(next);
3419
3918
  };
3420
- return /* @__PURE__ */ jsxs13("fieldset", { ref, className: "eb:space-y-1.5", children: [
3421
- /* @__PURE__ */ jsx16("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
3422
- /* @__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: [
3423
- /* @__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(
3424
3923
  Input,
3425
3924
  {
3426
3925
  id: `${id}-${side}`,
@@ -3438,7 +3937,7 @@ function SpacingField({ id, property, value, onCommit }) {
3438
3937
  className: "eb:px-2 eb:text-center eb:tabular-nums"
3439
3938
  }
3440
3939
  ),
3441
- /* @__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) })
3442
3941
  ] }, side)) })
3443
3942
  ] });
3444
3943
  }
@@ -3456,15 +3955,15 @@ var FIELDS = {
3456
3955
  function PropertyField({ property, value, onCommit, idPrefix = "eb", services = null }) {
3457
3956
  const Field = FIELDS[property.type] || TextField;
3458
3957
  const id = `${idPrefix}-${property.key}`;
3459
- return /* @__PURE__ */ jsx16(Field, { id, property, value, onCommit, services });
3958
+ return /* @__PURE__ */ jsx17(Field, { id, property, value, onCommit, services });
3460
3959
  }
3461
3960
 
3462
3961
  // src/features/variables/VariableInsertButton.jsx
3463
- import { useState as useState10 } from "react";
3962
+ import { useState as useState11 } from "react";
3464
3963
  import { Braces as Braces3 } from "lucide-react";
3465
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
3964
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
3466
3965
  function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
3467
- const [open, setOpen] = useState10(false);
3966
+ const [open, setOpen] = useState11(false);
3468
3967
  const insert = (variable) => {
3469
3968
  const input = inputRef.current;
3470
3969
  const text = String(value || "");
@@ -3480,14 +3979,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
3480
3979
  input.setSelectionRange(start + token.length, start + token.length);
3481
3980
  });
3482
3981
  };
3483
- return /* @__PURE__ */ jsxs14(Popover, { open, onOpenChange: setOpen, children: [
3484
- /* @__PURE__ */ jsx17(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx17(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx17(Braces3, {}) }) }),
3485
- /* @__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" }) })
3486
3985
  ] });
3487
3986
  }
3488
3987
 
3489
3988
  // src/ui/PropertiesPanel.jsx
3490
- 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";
3491
3990
  var GROUP_ORDER = Object.values(GROUPS);
3492
3991
  function grouped(properties) {
3493
3992
  const map = /* @__PURE__ */ new Map();
@@ -3501,9 +4000,9 @@ function grouped(properties) {
3501
4000
  }
3502
4001
  function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
3503
4002
  const t = useT();
3504
- return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3505
- /* @__PURE__ */ jsx18(SectionLabel, { children: t(group) }),
3506
- 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(
3507
4006
  PropertyField,
3508
4007
  {
3509
4008
  idPrefix,
@@ -3517,33 +4016,33 @@ function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
3517
4016
  ] }, group));
3518
4017
  }
3519
4018
  var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROUPS.content };
3520
- function PanelHeader({ title, onClose }) {
4019
+ function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
3521
4020
  const t = useT();
3522
- 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: [
3523
- /* @__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 }) }),
3524
- 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, {}) })
3525
4024
  ] });
3526
4025
  }
3527
- function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null }) {
4026
+ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, onDismiss = null }) {
3528
4027
  const t = useT();
3529
4028
  const subjectRef = useRef5(null);
3530
4029
  const preheaderRef = useRef5(null);
3531
4030
  const rootId = editor.getRootId();
3532
- const root = useMemo5(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
4031
+ const root = useMemo6(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
3533
4032
  const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
3534
4033
  const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
3535
- return /* @__PURE__ */ jsxs15(Fragment5, { children: [
3536
- /* @__PURE__ */ jsx18(PanelHeader, { title: t("Email") }),
3537
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-5 eb:p-4", children: [
3538
- /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3539
- /* @__PURE__ */ jsx18(SectionLabel, { children: t("Template") }),
3540
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3541
- /* @__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: [
3542
4041
  t("Name"),
3543
4042
  " ",
3544
- /* @__PURE__ */ jsx18("span", { className: "eb:text-destructive", children: "*" })
4043
+ /* @__PURE__ */ jsx19("span", { className: "eb:text-destructive", children: "*" })
3545
4044
  ] }),
3546
- /* @__PURE__ */ jsx18(
4045
+ /* @__PURE__ */ jsx19(
3547
4046
  Input,
3548
4047
  {
3549
4048
  id: "eb-meta-name",
@@ -3553,12 +4052,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3553
4052
  "aria-invalid": showErrors && !meta.name.trim() || void 0
3554
4053
  }
3555
4054
  ),
3556
- 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") })
3557
4056
  ] }),
3558
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3559
- /* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
3560
- /* @__PURE__ */ jsx18(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
3561
- 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(
3562
4061
  VariableInsertButton,
3563
4062
  {
3564
4063
  variables,
@@ -3569,12 +4068,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3569
4068
  }
3570
4069
  )
3571
4070
  ] }),
3572
- /* @__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") })
3573
4072
  ] }),
3574
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-1.5", children: [
3575
- /* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
3576
- /* @__PURE__ */ jsx18(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
3577
- 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(
3578
4077
  VariableInsertButton,
3579
4078
  {
3580
4079
  variables,
@@ -3585,7 +4084,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3585
4084
  }
3586
4085
  )
3587
4086
  ] }),
3588
- /* @__PURE__ */ jsx18(
4087
+ /* @__PURE__ */ jsx19(
3589
4088
  Textarea,
3590
4089
  {
3591
4090
  ref: preheaderRef,
@@ -3599,7 +4098,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
3599
4098
  )
3600
4099
  ] })
3601
4100
  ] }),
3602
- 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" })
3603
4102
  ] })
3604
4103
  ] });
3605
4104
  }
@@ -3612,11 +4111,12 @@ function PropertiesPanel({
3612
4111
  actions = null,
3613
4112
  showErrors = false,
3614
4113
  services = null,
3615
- variables = null
4114
+ variables = null,
4115
+ onDismiss = null
3616
4116
  }) {
3617
4117
  const t = useT();
3618
4118
  const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
3619
- 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(
3620
4120
  TemplateSettings,
3621
4121
  {
3622
4122
  editor,
@@ -3624,15 +4124,23 @@ function PropertiesPanel({
3624
4124
  onMetaChange,
3625
4125
  changes,
3626
4126
  showErrors,
3627
- variables
4127
+ variables,
4128
+ onDismiss
3628
4129
  }
3629
- ) : /* @__PURE__ */ jsxs15(Fragment5, { children: [
3630
- /* @__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
+ ),
3631
4139
  actions,
3632
- /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-5 eb:p-4", children: [
3633
- selection.tagName === "mj-button" && /* @__PURE__ */ jsxs15("section", { className: "eb:space-y-3", children: [
3634
- /* @__PURE__ */ jsx18(SectionLabel, { children: t(GROUPS.content) }),
3635
- /* @__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(
3636
4144
  PropertyField,
3637
4145
  {
3638
4146
  idPrefix: "eb-prop",
@@ -3643,15 +4151,15 @@ function PropertiesPanel({
3643
4151
  `${selection.id}:content`
3644
4152
  )
3645
4153
  ] }),
3646
- selection.tagName === "mj-text" && /* @__PURE__ */ jsx18("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
3647
- /* @__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 })
3648
4156
  ] })
3649
4157
  ] }) });
3650
4158
  }
3651
4159
 
3652
4160
  // src/ui/Toolbar.jsx
3653
4161
  import { ArrowLeft as ArrowLeft2, Monitor as Monitor2, Redo2, Smartphone as Smartphone2, Undo2 } from "lucide-react";
3654
- 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";
3655
4163
  function Toolbar({
3656
4164
  title,
3657
4165
  status,
@@ -3663,44 +4171,112 @@ function Toolbar({
3663
4171
  device,
3664
4172
  onDeviceChange,
3665
4173
  onBack,
3666
- actions = null
4174
+ actions = null,
4175
+ primary = null,
4176
+ compact = false
3667
4177
  }) {
3668
4178
  const t = useT();
3669
- 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: [
3670
- /* @__PURE__ */ jsxs16(Button, { variant: "ghost", size: "sm-compact", onClick: onBack, children: [
3671
- /* @__PURE__ */ jsx19(ArrowLeft2, {}),
3672
- t("Back")
3673
- ] }),
3674
- /* @__PURE__ */ jsx19(Separator, { orientation: "vertical", className: "eb:h-5" }),
3675
- /* @__PURE__ */ jsxs16("div", { className: "eb:min-w-0 eb:max-w-[320px] eb:flex-1", children: [
3676
- /* @__PURE__ */ jsx19("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx19(TruncatedText, { children: title || t("New template") }) }),
3677
- /* @__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, {}) }) })
3678
4203
  ] }),
3679
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
3680
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
3681
- /* @__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, {}) }) }),
3682
- /* @__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")
3683
4208
  ] }),
3684
- /* @__PURE__ */ jsx19(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs16(TabsList, { "aria-label": t("Device"), children: [
3685
- /* @__PURE__ */ jsxs16(TabsTrigger, { value: "desktop", disabled: !ready, children: [
3686
- /* @__PURE__ */ jsx19(Monitor2, {}),
3687
- t("Desktop")
3688
- ] }),
3689
- /* @__PURE__ */ jsxs16(TabsTrigger, { value: "mobile", disabled: !ready, children: [
3690
- /* @__PURE__ */ jsx19(Smartphone2, {}),
3691
- t("Mobile")
3692
- ] })
3693
- ] }) })
3694
- ] }),
3695
- /* @__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
+ )
3696
4272
  ] });
3697
4273
  }
3698
4274
 
3699
4275
  // src/ui/useEmailEditorState.js
3700
- 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";
3701
4277
  var DIRTY_CHECK_MS = 150;
3702
4278
  function useEmailEditorState(editor) {
3703
- const [state, setState] = useState11(() => ({
4279
+ const [state, setState] = useState12(() => ({
3704
4280
  status: editor?.status || "idle",
3705
4281
  selection: null,
3706
4282
  canUndo: false,
@@ -3734,7 +4310,7 @@ function useEmailEditorState(editor) {
3734
4310
  forced.current = true;
3735
4311
  setState((s) => ({ ...s, dirty: true }));
3736
4312
  }, []);
3737
- useEffect6(() => {
4313
+ useEffect7(() => {
3738
4314
  if (!editor) return void 0;
3739
4315
  const patch = (values) => setState((s) => ({ ...s, ...values }));
3740
4316
  const measure = () => {
@@ -3780,8 +4356,33 @@ function useEmailEditorState(editor) {
3780
4356
  return { ...state, markClean, markDirty, checkDirty };
3781
4357
  }
3782
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
+
3783
4384
  // src/ui/EmailBuilder.jsx
3784
- 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";
3785
4386
  var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
3786
4387
  function metaOf(template) {
3787
4388
  return {
@@ -3799,6 +4400,7 @@ function Builder({
3799
4400
  variables: variablesProp,
3800
4401
  sender,
3801
4402
  starterMjml,
4403
+ templates: galleryProp,
3802
4404
  testRecipients,
3803
4405
  initialMjml,
3804
4406
  initialMeta,
@@ -3809,11 +4411,11 @@ function Builder({
3809
4411
  className
3810
4412
  }) {
3811
4413
  const { t, locale, notify, confirm } = useUi();
3812
- const editor = useMemo6(() => injected || createDefaultEmailEditor({ locale }), [injected, locale]);
3813
- const storage = useMemo6(() => injectedStorage || createHttpStorage(), [injectedStorage]);
3814
- const systemVariables = useMemo6(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
3815
- const [customVariables, setCustomVariables] = useState12([]);
3816
- 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]);
3817
4419
  const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
3818
4420
  const reloadVariables = useCallback3(async () => {
3819
4421
  if (typeof storage.listVariables !== "function") return;
@@ -3823,23 +4425,37 @@ function Builder({
3823
4425
  notify("error", error.message);
3824
4426
  }
3825
4427
  }, [storage, notify]);
3826
- useEffect7(() => {
4428
+ useEffect9(() => {
3827
4429
  reloadVariables();
3828
4430
  }, [reloadVariables]);
3829
- const starter = useMemo6(() => starterMjml || createStarterMjml({ sender, t }), [starterMjml, sender, t]);
3830
- 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]);
3831
4437
  const state = useEmailEditorState(editor);
3832
4438
  const autosaveOn = isFeatureEnabled("autosave");
3833
4439
  const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
3834
- const [template, setTemplate] = useState12({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
3835
- const [boot, setBoot] = useState12({ status: "loading", document: null, draft: false, message: null });
3836
- const [meta, setMeta] = useState12(EMPTY_META);
3837
- const [savedMeta, setSavedMeta] = useState12(EMPTY_META);
3838
- const [saving, setSaving] = useState12(null);
3839
- const [showErrors, setShowErrors] = useState12(false);
3840
- const [autosaveBlocked, setAutosaveBlocked] = useState12(null);
3841
- const [engineError, setEngineError] = useState12(null);
3842
- 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]);
3843
4459
  const ready = state.status === "ready";
3844
4460
  const metaDirty = !sameMeta(meta, savedMeta);
3845
4461
  const dirty = state.dirty || metaDirty;
@@ -3848,7 +4464,7 @@ function Builder({
3848
4464
  [locale]
3849
4465
  );
3850
4466
  const booted = useRef7(false);
3851
- useEffect7(() => {
4467
+ useEffect9(() => {
3852
4468
  if (booted.current) return;
3853
4469
  booted.current = true;
3854
4470
  (async () => {
@@ -3897,7 +4513,7 @@ function Builder({
3897
4513
  if (!boot.document) editor.loadMjml(initialMjml || starter);
3898
4514
  }, [editor, boot.document, starter, initialMjml]);
3899
4515
  const { markDirty, markClean, checkDirty } = state;
3900
- useEffect7(() => {
4516
+ useEffect9(() => {
3901
4517
  if (ready && boot.draft) markDirty();
3902
4518
  }, [ready, boot.draft, markDirty]);
3903
4519
  const isDirty = useCallback3(
@@ -3906,17 +4522,17 @@ function Builder({
3906
4522
  );
3907
4523
  const reportDirty = useRef7(onDirtyChange);
3908
4524
  reportDirty.current = onDirtyChange;
3909
- useEffect7(() => {
4525
+ useEffect9(() => {
3910
4526
  reportDirty.current?.(dirty);
3911
4527
  }, [dirty]);
3912
- useEffect7(() => {
4528
+ useEffect9(() => {
3913
4529
  if (!dirtyCheckRef) return void 0;
3914
4530
  dirtyCheckRef.current = isDirty;
3915
4531
  return () => {
3916
4532
  dirtyCheckRef.current = null;
3917
4533
  };
3918
4534
  }, [dirtyCheckRef, isDirty]);
3919
- useEffect7(() => {
4535
+ useEffect9(() => {
3920
4536
  if (!dirty) return void 0;
3921
4537
  const warn = (e) => {
3922
4538
  e.preventDefault();
@@ -3927,7 +4543,7 @@ function Builder({
3927
4543
  }, [dirty]);
3928
4544
  const savingRef = useRef7(false);
3929
4545
  const latest = useRef7({});
3930
- latest.current = { template, meta, onSaved };
4546
+ latest.current = { template, meta, onSaved, compact };
3931
4547
  const save = useCallback3(
3932
4548
  async ({ auto = false } = {}) => {
3933
4549
  if (savingRef.current || editor.status !== "ready") return false;
@@ -3936,6 +4552,7 @@ function Builder({
3936
4552
  if (!auto) {
3937
4553
  setShowErrors(true);
3938
4554
  editor.select(null);
4555
+ setDrawer((d) => d === null && latest.current.compact ? "properties" : d);
3939
4556
  notify("error", t("Give the template a name before saving it."));
3940
4557
  }
3941
4558
  return false;
@@ -4008,7 +4625,7 @@ function Builder({
4008
4625
  },
4009
4626
  [editor, storage, markClean, notify, confirm, t]
4010
4627
  );
4011
- useEffect7(() => {
4628
+ useEffect9(() => {
4012
4629
  const onKey = (e) => {
4013
4630
  if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
4014
4631
  e.preventDefault();
@@ -4018,11 +4635,11 @@ function Builder({
4018
4635
  window.addEventListener("keydown", onKey, true);
4019
4636
  return () => window.removeEventListener("keydown", onKey, true);
4020
4637
  }, [save]);
4021
- useEffect7(() => {
4638
+ useEffect9(() => {
4022
4639
  if (!ready) return void 0;
4023
4640
  return editor.addShortcut("save", "mod+s", () => save());
4024
4641
  }, [ready, editor, save]);
4025
- const scheduler = useMemo6(
4642
+ const scheduler = useMemo7(
4026
4643
  () => createAutosaveScheduler({
4027
4644
  run: () => save({ auto: true }),
4028
4645
  idleMs: autosaveOptions.idleMs,
@@ -4030,13 +4647,13 @@ function Builder({
4030
4647
  }),
4031
4648
  [save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
4032
4649
  );
4033
- useEffect7(() => () => scheduler.dispose(), [scheduler]);
4034
- useEffect7(() => {
4650
+ useEffect9(() => () => scheduler.dispose(), [scheduler]);
4651
+ useEffect9(() => {
4035
4652
  if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
4036
4653
  if (dirty) scheduler.notify();
4037
4654
  else scheduler.reset();
4038
4655
  }, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
4039
- useEffect7(() => {
4656
+ useEffect9(() => {
4040
4657
  if (!autosaveOn || !ready || template.id || !dirty) return void 0;
4041
4658
  const timer = setTimeout(() => {
4042
4659
  if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
@@ -4061,6 +4678,8 @@ function Builder({
4061
4678
  storage,
4062
4679
  template,
4063
4680
  meta,
4681
+ setMeta,
4682
+ templates,
4064
4683
  ready,
4065
4684
  isDirty,
4066
4685
  markDirty,
@@ -4074,15 +4693,32 @@ function Builder({
4074
4693
  const toolbarFeatures = emailBuilderFeatures.list(
4075
4694
  (f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
4076
4695
  );
4077
- 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(
4078
4712
  "div",
4079
4713
  {
4714
+ ref: rootRef,
4080
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),
4081
4716
  "data-testid": "email-builder",
4082
4717
  "data-status": state.status,
4718
+ "data-layout": layout2,
4083
4719
  "data-template-id": template.id || "",
4084
4720
  children: [
4085
- /* @__PURE__ */ jsx20(
4721
+ /* @__PURE__ */ jsx22(
4086
4722
  Toolbar,
4087
4723
  {
4088
4724
  title: meta.name,
@@ -4095,80 +4731,110 @@ function Builder({
4095
4731
  device: state.device,
4096
4732
  onDeviceChange: (device) => editor.setDevice(device),
4097
4733
  onBack: onClose ? back : void 0,
4098
- actions: /* @__PURE__ */ jsxs17(Fragment6, { children: [
4099
- toolbarFeatures.map((feature) => {
4100
- const Action = feature.toolbar;
4101
- return /* @__PURE__ */ jsx20(Action, { context: featureContext }, feature.id);
4102
- }),
4103
- /* @__PURE__ */ jsxs17(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
4104
- saving === "manual" ? /* @__PURE__ */ jsx20(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx20(Save, {}),
4105
- t("Save")
4106
- ] })
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")
4107
4742
  ] })
4108
4743
  }
4109
4744
  ),
4110
- /* @__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: [
4111
- /* @__PURE__ */ jsx20(BlockSidebar, { editor, disabled: !ready }),
4112
- /* @__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: [
4113
- /* @__PURE__ */ jsx20(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4114
- /* @__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.") }),
4115
- /* @__PURE__ */ jsx20("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
4116
- engineError && /* @__PURE__ */ jsx20(
4117
- Button,
4118
- {
4119
- variant: "outline",
4120
- size: "sm-compact",
4121
- onClick: () => {
4122
- setEngineError(null);
4123
- setAttempt((n) => n + 1);
4124
- },
4125
- children: t("Retry")
4126
- }
4127
- )
4128
- ] }) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
4129
- boot.status === "ready" && /* @__PURE__ */ jsx20(
4130
- EmailCanvas,
4131
- {
4132
- editor,
4133
- document: boot.document,
4134
- onReady,
4135
- onError: (error) => setEngineError(error.message)
4136
- },
4137
- 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"
4138
4753
  ),
4139
- !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: [
4140
- /* @__PURE__ */ jsx20(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
4141
- /* @__PURE__ */ jsx20("span", { className: "eb:sr-only", children: t("Loading the builder") })
4142
- ] })
4143
- ] }) }),
4144
- /* @__PURE__ */ jsx20(
4145
- 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,
4146
4812
  {
4147
- editor,
4148
- selection: ready ? state.selection : null,
4149
- meta,
4150
- onMetaChange: setMeta,
4151
- changes: state.changes,
4152
- showErrors,
4153
- actions: /* @__PURE__ */ jsx20(ElementActions, { editor, selection: state.selection }),
4154
- services,
4155
- 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
+ ]
4156
4822
  }
4157
4823
  )
4158
4824
  ] }),
4159
- /* @__PURE__ */ jsx20(NoticeArea, {})
4825
+ /* @__PURE__ */ jsx22(NoticeArea, {})
4160
4826
  ]
4161
4827
  }
4162
4828
  );
4163
4829
  }
4164
4830
  function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
4165
- 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 }) });
4166
4832
  }
4167
4833
 
4168
4834
  // src/ui/EmailBuilderBoundary.jsx
4169
4835
  import { Component } from "react";
4170
4836
  import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft3, RefreshCw as RefreshCw2 } from "lucide-react";
4171
- import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
4837
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
4172
4838
  var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
4173
4839
  var english = (text) => text;
4174
4840
  var EmailBuilderBoundary = class extends Component {
@@ -4193,23 +4859,23 @@ var EmailBuilderBoundary = class extends Component {
4193
4859
  const { error, attempt } = this.state;
4194
4860
  const t = this.props.t || english;
4195
4861
  if (!error) {
4196
- 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);
4197
4863
  }
4198
- return /* @__PURE__ */ jsxs18(
4864
+ return /* @__PURE__ */ jsxs20(
4199
4865
  "div",
4200
4866
  {
4201
4867
  role: "alert",
4202
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",
4203
4869
  children: [
4204
- /* @__PURE__ */ jsx21(AlertTriangle3, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
4205
- /* @__PURE__ */ jsx21("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
4206
- /* @__PURE__ */ jsxs18("div", { className: "eb:flex eb:gap-2", children: [
4207
- this.props.onClose && /* @__PURE__ */ jsxs18(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
4208
- /* @__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, {}),
4209
4875
  t("Back")
4210
4876
  ] }),
4211
- /* @__PURE__ */ jsxs18(Button, { size: "sm", onClick: this.retry, children: [
4212
- /* @__PURE__ */ jsx21(RefreshCw2, {}),
4877
+ /* @__PURE__ */ jsxs20(Button, { size: "sm", onClick: this.retry, children: [
4878
+ /* @__PURE__ */ jsx23(RefreshCw2, {}),
4213
4879
  t("Retry")
4214
4880
  ] })
4215
4881
  ] })
@@ -4242,10 +4908,12 @@ export {
4242
4908
  LOCALES,
4243
4909
  STARTER_MJML,
4244
4910
  StorageError,
4911
+ TEMPLATE_GALLERY,
4245
4912
  UiProvider,
4246
4913
  absoluteAssetUrls,
4247
4914
  applyVariables,
4248
4915
  applyVariablesToText,
4916
+ buildGallery,
4249
4917
  createDefaultEmailEditor,
4250
4918
  createDocument,
4251
4919
  createEmailEditor,