best-email-builder 0.4.0 → 0.5.1

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 (83) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +130 -44
  3. package/dist/ai-server.js +68 -0
  4. package/dist/ai-server.js.map +1 -0
  5. package/dist/ai.js +852 -0
  6. package/dist/ai.js.map +1 -0
  7. package/dist/builder.js +16 -8
  8. package/dist/chunk-2RKVJQSD.js +595 -0
  9. package/dist/chunk-2RKVJQSD.js.map +1 -0
  10. package/dist/{chunk-NAEGU6PR.js → chunk-2Y7CDXZM.js} +18 -29
  11. package/dist/chunk-2Y7CDXZM.js.map +1 -0
  12. package/dist/chunk-4U7L2NYQ.js +356 -0
  13. package/dist/chunk-4U7L2NYQ.js.map +1 -0
  14. package/dist/{chunk-MY6WPDT4.js → chunk-7QQH6MTV.js} +473 -994
  15. package/dist/chunk-7QQH6MTV.js.map +1 -0
  16. package/dist/{chunk-JNHFZXDL.js → chunk-BAA5XMWY.js} +645 -646
  17. package/dist/chunk-BAA5XMWY.js.map +1 -0
  18. package/dist/{chunk-TGDZ4NFI.js → chunk-CNJZLZRH.js} +3 -44
  19. package/dist/chunk-CNJZLZRH.js.map +1 -0
  20. package/dist/chunk-H4W65GBX.js +119 -0
  21. package/dist/chunk-H4W65GBX.js.map +1 -0
  22. package/dist/{chunk-HHLOE6OZ.js → chunk-JI36FNWE.js} +12 -333
  23. package/dist/chunk-JI36FNWE.js.map +1 -0
  24. package/dist/{chunk-AT73Y7JA.js → chunk-N7DEVAIP.js} +49 -581
  25. package/dist/chunk-N7DEVAIP.js.map +1 -0
  26. package/dist/chunk-OWVXKMLI.js +121 -0
  27. package/dist/chunk-OWVXKMLI.js.map +1 -0
  28. package/dist/chunk-SAIW46IC.js +98 -0
  29. package/dist/chunk-SAIW46IC.js.map +1 -0
  30. package/dist/chunk-UFKCLU2D.js +255 -0
  31. package/dist/chunk-UFKCLU2D.js.map +1 -0
  32. package/dist/chunk-W424KPCR.js +251 -0
  33. package/dist/chunk-W424KPCR.js.map +1 -0
  34. package/dist/index.js +31 -37
  35. package/dist/index.js.map +1 -1
  36. package/dist/library.js +12 -7
  37. package/dist/server.js +40 -98
  38. package/dist/server.js.map +1 -1
  39. package/dist/styles.css +1 -1
  40. package/dist/types/ai/ConnectAi.d.ts +12 -0
  41. package/dist/types/ai/GenerateButton.d.ts +4 -0
  42. package/dist/types/ai/SubjectIdeas.d.ts +25 -0
  43. package/dist/types/ai/createAi.d.ts +66 -0
  44. package/dist/types/{features/generate → ai}/draft.d.ts +56 -3
  45. package/dist/types/{features/generate → ai}/generator.d.ts +7 -2
  46. package/dist/types/ai/i18n/fr.d.ts +2 -0
  47. package/dist/types/ai/i18n/index.d.ts +2 -0
  48. package/dist/types/ai/index.d.ts +9 -0
  49. package/dist/types/ai/keys.d.ts +46 -0
  50. package/dist/types/ai/providers.d.ts +75 -0
  51. package/dist/types/{features/generate → ai}/rewrite.d.ts +1 -1
  52. package/dist/types/ai/server.d.ts +9 -0
  53. package/dist/types/{features/generate → ai}/subjects.d.ts +18 -4
  54. package/dist/types/core/constants.d.ts +1 -1
  55. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  56. package/dist/types/features/preview/darkMode.d.ts +33 -16
  57. package/dist/types/features/variables/VariablePicker.d.ts +3 -2
  58. package/dist/types/i18n/index.d.ts +8 -0
  59. package/dist/types/index.d.ts +1 -7
  60. package/dist/types/renderer/prepareMjml.d.ts +8 -2
  61. package/dist/types/server/handler.d.ts +5 -4
  62. package/dist/types/server/index.d.ts +2 -9
  63. package/dist/types/server/render.d.ts +5 -2
  64. package/dist/types/storage/client.d.ts +22 -40
  65. package/dist/types/ui/EmailBuilder.d.ts +5 -1
  66. package/dist/types/ui/EmailStudio.d.ts +6 -1
  67. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  68. package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
  69. package/package.json +15 -1
  70. package/dist/chunk-AT73Y7JA.js.map +0 -1
  71. package/dist/chunk-HHLOE6OZ.js.map +0 -1
  72. package/dist/chunk-JNHFZXDL.js.map +0 -1
  73. package/dist/chunk-MY6WPDT4.js.map +0 -1
  74. package/dist/chunk-NAEGU6PR.js.map +0 -1
  75. package/dist/chunk-TGDZ4NFI.js.map +0 -1
  76. package/dist/types/features/generate/GenerateButton.d.ts +0 -3
  77. package/dist/types/features/generate/SubjectIdeas.d.ts +0 -16
  78. package/dist/types/features/generate/index.d.ts +0 -2
  79. /package/dist/types/{features/generate → ai}/RewriteText.d.ts +0 -0
  80. /package/dist/types/{features/generate → ai}/chip.d.ts +0 -0
  81. /package/dist/types/{features/generate → ai}/claude.d.ts +0 -0
  82. /package/dist/types/{features/generate → ai}/prompt.d.ts +0 -0
  83. /package/dist/types/{features/generate → ai}/purposes.d.ts +0 -0
@@ -1,3 +1,26 @@
1
+ import {
2
+ FRAME_SCROLLBAR_CSS,
3
+ PreviewButton,
4
+ TEMPLATE_GALLERY,
5
+ brandOf,
6
+ buildGallery,
7
+ createStarterMjml,
8
+ socialBlock,
9
+ socialElementBlock,
10
+ useBrand,
11
+ useLayoutWidth,
12
+ withThumbnailImages
13
+ } from "./chunk-2RKVJQSD.js";
14
+ import {
15
+ BRAND_NOTES,
16
+ countImagesWithoutPicture,
17
+ logoWidthFor
18
+ } from "./chunk-4U7L2NYQ.js";
19
+ import {
20
+ cleanRichText,
21
+ escapeText,
22
+ textOfHtml
23
+ } from "./chunk-SAIW46IC.js";
1
24
  import {
2
25
  Button,
3
26
  Dialog,
@@ -5,18 +28,14 @@ import {
5
28
  DialogDescription,
6
29
  DialogHeader,
7
30
  DialogTitle,
8
- DropdownMenu,
9
- DropdownMenuContent,
10
- DropdownMenuItem,
11
- DropdownMenuTrigger,
12
- FRAME_SCROLLBAR_CSS,
13
31
  Input,
14
32
  Label,
15
33
  NoticeArea,
16
34
  Popover,
17
35
  PopoverContent,
18
36
  PopoverTrigger,
19
- PreviewButton,
37
+ SEARCH_KEYS,
38
+ SearchKeyHint,
20
39
  SectionLabel,
21
40
  Select,
22
41
  SelectContent,
@@ -25,7 +44,6 @@ import {
25
44
  SelectValue,
26
45
  Separator,
27
46
  Skeleton,
28
- TEMPLATE_GALLERY,
29
47
  Tabs,
30
48
  TabsList,
31
49
  TabsTrigger,
@@ -33,42 +51,34 @@ import {
33
51
  TooltipWrap,
34
52
  TruncatedText,
35
53
  UiProvider,
36
- brandOf,
37
- buildGallery,
38
54
  cn,
39
55
  createHttpStorage,
40
- createStarterMjml,
41
- socialBlock,
42
- socialElementBlock,
43
- useBrand,
44
- useLayoutWidth,
56
+ useSearchShortcut,
45
57
  useT,
46
- useUi,
47
- withThumbnailImages
48
- } from "./chunk-AT73Y7JA.js";
58
+ useUi
59
+ } from "./chunk-N7DEVAIP.js";
60
+ import {
61
+ EMAIL_BUILDER_VERSION,
62
+ EMAIL_DEVICES,
63
+ createDocument,
64
+ imageUploadError,
65
+ readDocument
66
+ } from "./chunk-H4W65GBX.js";
49
67
  import {
50
- BRAND_NOTES,
51
- INLINE_TAGS,
52
68
  LONGHAND_SPACING,
53
- MAX_DETAILS_LENGTH,
54
- MAX_REWRITE_INSTRUCTION,
55
- REWRITE_ACTIONS,
56
- REWRITE_LANGUAGES,
57
- emailWords,
69
+ MJML_DEFAULTS,
58
70
  formatSpacing,
59
- isSafeUrl,
60
- logoWidthFor,
61
71
  parseSpacing,
62
- purposesByCategory,
63
72
  toAttributes,
64
73
  toValues
65
- } from "./chunk-JNHFZXDL.js";
74
+ } from "./chunk-OWVXKMLI.js";
75
+ import {
76
+ isSafeUrl
77
+ } from "./chunk-W424KPCR.js";
66
78
  import {
67
79
  CUSTOM_GROUP,
68
80
  DEFAULT_BUTTON_URL,
69
81
  DEFAULT_VARIABLES,
70
- EMAIL_BUILDER_VERSION,
71
- EMAIL_DEVICES,
72
82
  FONT_OPTIONS,
73
83
  FONT_STACK,
74
84
  GROUPS,
@@ -84,7 +94,6 @@ import {
84
94
  cloneWithNewIds,
85
95
  closestOfTags,
86
96
  contrastRatio,
87
- createDocument,
88
97
  createNode,
89
98
  defineVariables,
90
99
  escapeXml,
@@ -93,11 +102,11 @@ import {
93
102
  fromProject,
94
103
  hasBrand,
95
104
  identity,
96
- imageUploadError,
97
105
  inWhiteSection,
98
106
  insertNodes,
99
107
  keyFromLabel,
100
108
  mergeVariables,
109
+ messagesOf,
101
110
  moveNode,
102
111
  parentOf,
103
112
  parseMjml,
@@ -105,7 +114,6 @@ import {
105
114
  pathTo,
106
115
  positionOf,
107
116
  prop,
108
- readDocument,
109
117
  readableOn,
110
118
  removeNode,
111
119
  serializeMjml,
@@ -115,7 +123,7 @@ import {
115
123
  tokenOf,
116
124
  translatorFor,
117
125
  validateCustomVariable
118
- } from "./chunk-HHLOE6OZ.js";
126
+ } from "./chunk-JI36FNWE.js";
119
127
 
120
128
  // src/core/events.js
121
129
  function createEmitter({ onError } = {}) {
@@ -148,62 +156,6 @@ function createEmitter({ onError } = {}) {
148
156
  };
149
157
  }
150
158
 
151
- // src/core/mjmlDefaults.js
152
- var MJML_DEFAULTS = {
153
- "mj-body": { width: "600px" },
154
- "mj-section": {
155
- "background-repeat": "repeat",
156
- "background-size": "auto",
157
- "background-position": "top center",
158
- padding: "20px 0",
159
- "text-align": "center"
160
- },
161
- "mj-wrapper": { "background-repeat": "repeat", "background-size": "auto", "background-position": "top center", padding: "20px 0" },
162
- "mj-column": { "vertical-align": "top" },
163
- "mj-text": {
164
- align: "left",
165
- color: "#000000",
166
- "font-family": "Ubuntu, Helvetica, Arial, sans-serif",
167
- "font-size": "13px",
168
- "line-height": "1",
169
- padding: "10px 25px"
170
- },
171
- "mj-image": { align: "center", height: "auto", padding: "10px 25px" },
172
- "mj-button": {
173
- align: "center",
174
- "background-color": "#414141",
175
- "border-radius": "3px",
176
- color: "#ffffff",
177
- "font-family": "Ubuntu, Helvetica, Arial, sans-serif",
178
- "font-size": "13px",
179
- "font-weight": "normal",
180
- "inner-padding": "10px 25px",
181
- "line-height": "120%",
182
- padding: "10px 25px"
183
- },
184
- "mj-divider": { "border-color": "#000000", "border-style": "solid", "border-width": "4px", padding: "10px 25px", width: "100%", align: "center" },
185
- "mj-spacer": { height: "20px" },
186
- "mj-social": {
187
- align: "center",
188
- "border-radius": "3px",
189
- "icon-size": "20px",
190
- mode: "horizontal",
191
- padding: "10px 25px"
192
- },
193
- "mj-social-element": { "border-radius": "3px", padding: "4px" },
194
- "mj-navbar": { align: "center" },
195
- "mj-navbar-link": {
196
- color: "#000000",
197
- "font-family": "Ubuntu, Helvetica, Arial, sans-serif",
198
- "font-size": "13px",
199
- "font-weight": "normal",
200
- "line-height": "22px",
201
- padding: "15px 10px",
202
- "text-transform": "uppercase"
203
- },
204
- "mj-hero": { "background-color": "#ffffff", padding: "0px" }
205
- };
206
-
207
159
  // src/core/EmailEditor.js
208
160
  function resolveBlock(block, t = identity) {
209
161
  const text = (value) => typeof value === "function" ? String(value(t)) : value;
@@ -298,7 +250,7 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
298
250
  get adapterName() {
299
251
  return adapter.name;
300
252
  },
301
- async initialize({ container, document: document2 = null }) {
253
+ async initialize({ container, document = null }) {
302
254
  if (!container) throw new Error("EmailEditor: missing container");
303
255
  if (status !== "idle") {
304
256
  throw new Error(`EmailEditor: initialization refused (status "${status}")`);
@@ -309,7 +261,7 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
309
261
  await adapter.initialize({
310
262
  container,
311
263
  blocks: blocks.list(),
312
- document: document2 ? readDocument(document2) : null,
264
+ document: document ? readDocument(document) : null,
313
265
  t,
314
266
  labelOf: (selection) => t(labelFor(selection, blockForTag(selection.tagName))),
315
267
  onEvent: (type, payload) => {
@@ -417,18 +369,18 @@ function createEmailEditor({ adapter, blocks, features = null, t = identity }) {
417
369
  */
418
370
  loadDocument(raw, { mjml: mjml6 = null } = {}) {
419
371
  requireReady("loadDocument");
420
- const document2 = readDocument(raw);
421
- if (document2.engine !== adapter.name) {
372
+ const document = readDocument(raw);
373
+ if (document.engine !== adapter.name) {
422
374
  if (mjml6) {
423
375
  adapter.loadMjml(String(mjml6));
424
376
  adapter.clearHistory();
425
377
  return;
426
378
  }
427
379
  throw new Error(
428
- `EmailEditor: document written for engine "${document2.engine}", editor runs on "${adapter.name}"`
380
+ `EmailEditor: document written for engine "${document.engine}", editor runs on "${adapter.name}"`
429
381
  );
430
382
  }
431
- adapter.loadProject(document2.project);
383
+ adapter.loadProject(document.project);
432
384
  adapter.clearHistory();
433
385
  },
434
386
  loadMjml(mjml6) {
@@ -1244,6 +1196,11 @@ function VariablePicker({ variables, onPick, onCreate = null, onEdit = null, fil
1244
1196
  const [query, setQuery] = useState3("");
1245
1197
  const [active, setActive] = useState3(0);
1246
1198
  const listRef = useRef(null);
1199
+ const searchRef = useRef(
1200
+ /** @type {HTMLInputElement|null} */
1201
+ null
1202
+ );
1203
+ useSearchShortcut(searchRef);
1247
1204
  const matches = useMemo2(() => {
1248
1205
  const q = query.trim().toLowerCase();
1249
1206
  return variables.filter((v) => filter ? filter(v) : true).filter(
@@ -1275,15 +1232,18 @@ function VariablePicker({ variables, onPick, onCreate = null, onEdit = null, fil
1275
1232
  /* @__PURE__ */ jsx3(
1276
1233
  "input",
1277
1234
  {
1235
+ ref: searchRef,
1278
1236
  autoFocus: true,
1279
1237
  value: query,
1280
1238
  onChange: (e) => setQuery(e.target.value),
1281
1239
  onKeyDown,
1282
1240
  placeholder: t("Search variables\u2026"),
1283
1241
  "aria-label": t("Search variables\u2026"),
1284
- className: "eb:h-8 eb:w-full eb:rounded-md eb:bg-surface-75 eb:pr-2 eb:pl-7 eb:text-[13px] eb:text-foreground eb:outline-none eb:placeholder:text-foreground-muted eb:focus-visible:ring-2 eb:focus-visible:ring-ring/40"
1242
+ "aria-keyshortcuts": SEARCH_KEYS,
1243
+ className: "eb:h-8 eb:w-full eb:rounded-md eb:bg-surface-75 eb:pr-14 eb:pl-7 eb:text-[13px] eb:text-foreground eb:outline-none eb:placeholder:text-foreground-muted eb:focus-visible:ring-2 eb:focus-visible:ring-ring/40"
1285
1244
  }
1286
- )
1245
+ ),
1246
+ /* @__PURE__ */ jsx3(SearchKeyHint, { className: "eb:absolute eb:top-1/2 eb:right-4 eb:-translate-y-1/2" })
1287
1247
  ] }),
1288
1248
  /* @__PURE__ */ jsxs3("div", { ref: listRef, role: "listbox", "aria-label": t("Variables"), className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:p-1", children: [
1289
1249
  ordered.length === 0 && /* @__PURE__ */ jsxs3("div", { className: "eb:px-2 eb:py-3 eb:text-center eb:text-[12px] eb:text-foreground-muted", children: [
@@ -1694,222 +1654,15 @@ var templatesFeature = {
1694
1654
  isAvailable: (context) => Array.isArray(context.templates) && context.templates.length > 0
1695
1655
  };
1696
1656
 
1697
- // src/features/generate/GenerateButton.jsx
1698
- import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef2, useState as useState7 } from "react";
1699
- import { Check, Loader2 as Loader23, Search as Search2, Sparkles, TriangleAlert } from "lucide-react";
1700
-
1701
- // src/features/generate/chip.js
1702
- var chipClassName = (selected = false) => cn(
1703
- "eb:inline-flex eb:h-7 eb:items-center eb:gap-1 eb:rounded-full eb:border eb:px-3 eb:text-[12px] eb:font-medium eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50 eb:disabled:opacity-50",
1704
- selected ? "eb:border-primary eb:bg-primary eb:text-primary-foreground" : "eb:border-border eb:bg-background eb:text-foreground eb:hover:border-primary/50 eb:hover:bg-primary/5"
1705
- );
1706
-
1707
- // src/features/generate/GenerateButton.jsx
1708
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1709
- var fold = (text) => text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
1710
- function GenerateButton({ context }) {
1711
- const { editor, ready, storage, isDirty, markDirty, template, setMeta, variables, sender, brand } = context;
1712
- const { t, locale, notify } = useUi();
1713
- const [open, setOpen] = useState7(false);
1714
- const [purpose, setPurpose] = useState7(
1715
- /** @type {string|null} */
1716
- null
1717
- );
1718
- const [details, setDetails] = useState7("");
1719
- const [query, setQuery] = useState7("");
1720
- const [busy, setBusy] = useState7(false);
1721
- const [error, setError] = useState7("");
1722
- const [seconds, setSeconds] = useState7(0);
1723
- const [dirty, setDirty] = useState7(false);
1724
- const abortRef = useRef2(
1725
- /** @type {AbortController|null} */
1726
- null
1727
- );
1728
- const searchRef = useRef2(
1729
- /** @type {HTMLInputElement|null} */
1730
- null
1731
- );
1732
- useEffect4(() => () => abortRef.current?.abort(), []);
1733
- useEffect4(() => {
1734
- if (!busy) return void 0;
1735
- setSeconds(0);
1736
- const timer = setInterval(() => setSeconds((s) => s + 1), 1e3);
1737
- return () => clearInterval(timer);
1738
- }, [busy]);
1739
- const groups = useMemo4(() => {
1740
- const words = fold(query.trim()).split(/\s+/).filter(Boolean);
1741
- return purposesByCategory().map((group) => ({
1742
- ...group,
1743
- purposes: group.purposes.filter((entry) => {
1744
- const label = fold(`${t(entry.label)} ${entry.label} ${t(group.label)}`);
1745
- return words.every((word) => label.includes(word));
1746
- })
1747
- })).filter((group) => group.purposes.length > 0);
1748
- }, [query, t]);
1749
- const openDialog = () => {
1750
- setError("");
1751
- setDirty(isDirty());
1752
- setOpen(true);
1753
- };
1754
- const close = (next) => {
1755
- if (next) return;
1756
- abortRef.current?.abort();
1757
- setOpen(false);
1758
- };
1759
- const generate = async () => {
1760
- if (busy || !purpose && !details.trim()) return;
1761
- setError("");
1762
- setBusy(true);
1763
- const controller = new AbortController();
1764
- abortRef.current = controller;
1765
- try {
1766
- const email = await storage.generate(
1767
- {
1768
- purpose,
1769
- details: details.trim(),
1770
- language: String(locale || "en").slice(0, 2),
1771
- sender: sender || {},
1772
- ...hasBrand(brand) ? { brand: { primary: brand.primary, heading: brand.heading, font: brand.font } } : {},
1773
- variables: (variables || []).map((variable) => ({ key: variable.key, label: variable.label }))
1774
- },
1775
- { signal: controller.signal }
1776
- );
1777
- if (controller.signal.aborted) return;
1778
- editor.loadMjml(hasBrand(brand) ? brandTemplate(email.mjml, brand, { sender, t }) : email.mjml);
1779
- setMeta((meta) => ({
1780
- ...meta,
1781
- name: meta.name || email.name,
1782
- subject: email.subject || meta.subject,
1783
- preheader: email.preheader || meta.preheader
1784
- }));
1785
- if (template.id) markDirty();
1786
- setOpen(false);
1787
- notify("success", t("Your email is ready. Edit anything, then save."));
1788
- } catch (err) {
1789
- if (!controller.signal.aborted) setError(t(err?.message || "The email could not be generated."));
1790
- } finally {
1791
- if (abortRef.current === controller) abortRef.current = null;
1792
- setBusy(false);
1793
- }
1794
- };
1795
- const cancel = () => {
1796
- abortRef.current?.abort();
1797
- setBusy(false);
1798
- };
1799
- const canGenerate = Boolean(purpose || details.trim());
1800
- return /* @__PURE__ */ jsxs7(Fragment4, { children: [
1801
- /* @__PURE__ */ jsxs7(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, children: [
1802
- /* @__PURE__ */ jsx7(Sparkles, {}),
1803
- t("Generate")
1804
- ] }),
1805
- /* @__PURE__ */ jsx7(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs7(
1806
- DialogContent,
1807
- {
1808
- className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl",
1809
- "data-testid": "eb-generate",
1810
- onOpenAutoFocus: (event) => {
1811
- event.preventDefault();
1812
- searchRef.current?.focus();
1813
- },
1814
- children: [
1815
- /* @__PURE__ */ jsxs7(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-3", children: [
1816
- /* @__PURE__ */ jsx7(DialogTitle, { children: t("Generate an email") }),
1817
- /* @__PURE__ */ jsx7(DialogDescription, { children: t("Pick what it is for. AI writes a complete email that opens here, ready to edit.") })
1818
- ] }),
1819
- /* @__PURE__ */ jsx7("div", { className: "eb:px-6 eb:pb-3", children: /* @__PURE__ */ jsxs7("div", { className: "eb:relative", children: [
1820
- /* @__PURE__ */ jsx7(Search2, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted", "aria-hidden": "true" }),
1821
- /* @__PURE__ */ jsx7(
1822
- Input,
1823
- {
1824
- ref: searchRef,
1825
- type: "search",
1826
- value: query,
1827
- onChange: (event) => setQuery(event.target.value),
1828
- placeholder: t("Search: sale, password, birthday\u2026"),
1829
- "aria-label": t("Search"),
1830
- className: "eb:pl-8",
1831
- disabled: busy
1832
- }
1833
- )
1834
- ] }) }),
1835
- /* @__PURE__ */ jsx7("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-4", "data-testid": "eb-generate-purposes", children: /* @__PURE__ */ jsxs7("fieldset", { disabled: busy, className: "eb:m-0 eb:min-w-0 eb:border-0 eb:p-0", children: [
1836
- /* @__PURE__ */ jsx7("legend", { className: "eb:sr-only", children: t("What is the email for?") }),
1837
- groups.length === 0 && /* @__PURE__ */ jsx7("p", { className: "eb:py-6 eb:text-center eb:text-[13px] eb:text-foreground-muted", children: t("Nothing matches. Describe your email in the details below.") }),
1838
- /* @__PURE__ */ jsx7("div", { className: "eb:flex eb:flex-col eb:gap-4", children: groups.map((group) => /* @__PURE__ */ jsxs7("section", { className: "eb:flex eb:flex-col eb:gap-2", "aria-label": t(group.label), children: [
1839
- /* @__PURE__ */ jsx7(SectionLabel, { children: t(group.label) }),
1840
- /* @__PURE__ */ jsx7("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: group.purposes.map((entry) => {
1841
- const selected = purpose === entry.id;
1842
- return /* @__PURE__ */ jsxs7(
1843
- "button",
1844
- {
1845
- type: "button",
1846
- "aria-pressed": selected,
1847
- "data-purpose": entry.id,
1848
- onClick: () => setPurpose(selected ? null : entry.id),
1849
- className: chipClassName(selected),
1850
- children: [
1851
- selected && /* @__PURE__ */ jsx7(Check, { className: "eb:size-3", "aria-hidden": "true" }),
1852
- t(entry.label)
1853
- ]
1854
- },
1855
- entry.id
1856
- );
1857
- }) })
1858
- ] }, group.id)) })
1859
- ] }) }),
1860
- /* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
1861
- /* @__PURE__ */ jsx7(
1862
- Textarea,
1863
- {
1864
- value: details,
1865
- onChange: (event) => setDetails(event.target.value.slice(0, MAX_DETAILS_LENGTH)),
1866
- placeholder: t("Details (optional): product, dates, offer, tone, language\u2026"),
1867
- "aria-label": t("Details (optional)"),
1868
- rows: 2,
1869
- className: "eb:min-h-14",
1870
- disabled: busy,
1871
- onKeyDown: (event) => {
1872
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) generate();
1873
- }
1874
- }
1875
- ),
1876
- error ? /* @__PURE__ */ jsxs7("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
1877
- /* @__PURE__ */ jsx7(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
1878
- error
1879
- ] }) : dirty && !busy && /* @__PURE__ */ jsx7("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The email being edited will be replaced, unsaved changes included.") }),
1880
- /* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:items-center eb:justify-end eb:gap-2", children: [
1881
- busy && /* @__PURE__ */ jsx7("p", { className: "eb:mr-auto eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: t("Writing your email\u2026 {seconds} s", { seconds }) }),
1882
- busy && /* @__PURE__ */ jsx7(Button, { variant: "outline", size: "sm", onClick: cancel, children: t("Cancel") }),
1883
- /* @__PURE__ */ jsxs7(Button, { size: "sm", onClick: generate, disabled: !canGenerate || busy, "data-testid": "eb-generate-submit", children: [
1884
- busy ? /* @__PURE__ */ jsx7(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx7(Sparkles, {}),
1885
- t("Generate")
1886
- ] })
1887
- ] })
1888
- ] })
1889
- ]
1890
- }
1891
- ) })
1892
- ] });
1893
- }
1894
-
1895
- // src/features/generate/index.js
1896
- var generateFeature = {
1897
- id: "generate",
1898
- label: "Generate",
1899
- order: 4,
1900
- toolbar: GenerateButton,
1901
- isAvailable: (context) => typeof context.storage?.generate === "function"
1902
- };
1903
-
1904
1657
  // src/features/brand/BrandButton.jsx
1905
- import { useEffect as useEffect5, useRef as useRef3, useState as useState9 } from "react";
1906
- import { Globe, ImageOff, Loader2 as Loader24, Palette, Trash2 as Trash22, TriangleAlert as TriangleAlert2, Upload, WandSparkles } from "lucide-react";
1658
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
1659
+ import { Globe, ImageOff, Loader2 as Loader23, Palette, Trash2 as Trash22, TriangleAlert, Upload, WandSparkles } from "lucide-react";
1907
1660
 
1908
1661
  // src/ui/primitives/color-picker.jsx
1909
- import { useState as useState8 } from "react";
1662
+ import { useState as useState7 } from "react";
1910
1663
  import { HexAlphaColorPicker } from "react-colorful";
1911
- import { Check as Check2 } from "lucide-react";
1912
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1664
+ import { Check } from "lucide-react";
1665
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1913
1666
  var PRESET_COLORS = [
1914
1667
  "#8B5CF6",
1915
1668
  "#A78BFA",
@@ -1940,20 +1693,20 @@ function normalizeHex(v) {
1940
1693
  }
1941
1694
  function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
1942
1695
  const t = useT();
1943
- const [open, setOpen] = useState8(false);
1696
+ const [open, setOpen] = useState7(false);
1944
1697
  const valid = isValidHex(value);
1945
1698
  const normalized = valid ? normalizeHex(value) : "";
1946
1699
  const emit = (hex) => onChange(normalizeHex(hex));
1947
- return /* @__PURE__ */ jsxs8("div", { className: cn("eb:flex eb:gap-2", className), children: [
1948
- /* @__PURE__ */ jsxs8(Popover, { open, onOpenChange: setOpen, children: [
1949
- /* @__PURE__ */ jsx8(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx8(
1700
+ return /* @__PURE__ */ jsxs7("div", { className: cn("eb:flex eb:gap-2", className), children: [
1701
+ /* @__PURE__ */ jsxs7(Popover, { open, onOpenChange: setOpen, children: [
1702
+ /* @__PURE__ */ jsx7(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
1950
1703
  "button",
1951
1704
  {
1952
1705
  type: "button",
1953
1706
  "aria-label": t("Pick a colour"),
1954
1707
  title: t("Pick a colour"),
1955
1708
  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]",
1956
- children: /* @__PURE__ */ jsx8(
1709
+ children: /* @__PURE__ */ jsx7(
1957
1710
  "span",
1958
1711
  {
1959
1712
  className: "eb:absolute eb:inset-0",
@@ -1963,18 +1716,18 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
1963
1716
  )
1964
1717
  }
1965
1718
  ) }),
1966
- /* @__PURE__ */ jsxs8(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
1967
- /* @__PURE__ */ jsx8("div", { className: "eb-picker", children: /* @__PURE__ */ jsx8(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
1968
- /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
1969
- /* @__PURE__ */ jsx8(
1719
+ /* @__PURE__ */ jsxs7(PopoverContent, { align: "start", className: "eb:w-64 eb:space-y-3 eb:p-3", children: [
1720
+ /* @__PURE__ */ jsx7("div", { className: "eb-picker", children: /* @__PURE__ */ jsx7(HexAlphaColorPicker, { color: valid ? value : FALLBACK_COLOR, onChange: emit }) }),
1721
+ /* @__PURE__ */ jsxs7("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
1722
+ /* @__PURE__ */ jsx7(
1970
1723
  "span",
1971
1724
  {
1972
1725
  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",
1973
1726
  "aria-hidden": true,
1974
- children: /* @__PURE__ */ jsx8("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
1727
+ children: /* @__PURE__ */ jsx7("span", { className: "eb:absolute eb:inset-0", style: { backgroundColor: valid ? value : "transparent" } })
1975
1728
  }
1976
1729
  ),
1977
- /* @__PURE__ */ jsx8(
1730
+ /* @__PURE__ */ jsx7(
1978
1731
  Input,
1979
1732
  {
1980
1733
  value: value || "",
@@ -1986,7 +1739,7 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
1986
1739
  }
1987
1740
  )
1988
1741
  ] }),
1989
- /* @__PURE__ */ jsx8("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__ */ jsx8(
1742
+ /* @__PURE__ */ jsx7("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__ */ jsx7(
1990
1743
  "button",
1991
1744
  {
1992
1745
  type: "button",
@@ -1995,13 +1748,13 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
1995
1748
  "aria-label": c,
1996
1749
  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",
1997
1750
  style: { backgroundColor: c },
1998
- children: normalized === c && /* @__PURE__ */ jsx8(Check2, { 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)]" })
1751
+ children: normalized === c && /* @__PURE__ */ jsx7(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)]" })
1999
1752
  },
2000
1753
  c
2001
1754
  )) })
2002
1755
  ] })
2003
1756
  ] }),
2004
- /* @__PURE__ */ jsx8(
1757
+ /* @__PURE__ */ jsx7(
2005
1758
  Input,
2006
1759
  {
2007
1760
  id,
@@ -2016,7 +1769,7 @@ function ColorPicker({ value, onChange, id, className, placeholder = "" }) {
2016
1769
  }
2017
1770
 
2018
1771
  // src/features/brand/BrandButton.jsx
2019
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1772
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2020
1773
  var toForm = (brand) => ({
2021
1774
  name: brand.name || "",
2022
1775
  logoUrl: brand.logoUrl || "",
@@ -2030,31 +1783,31 @@ var isLogoUrl = (url) => !url || url.startsWith("/") || /^https:\/\//i.test(url)
2030
1783
  function BrandButton({ context }) {
2031
1784
  const { editor, ready, storage, brand, setBrand, hostSender } = context;
2032
1785
  const { t, notify } = useUi();
2033
- const [open, setOpen] = useState9(false);
2034
- const [form, setForm] = useState9(() => toForm(brand || {}));
2035
- const [applyNow, setApplyNow] = useState9(true);
2036
- const [reading, setReading] = useState9(false);
2037
- const [readError, setReadError] = useState9("");
2038
- const [notes, setNotes] = useState9(
1786
+ const [open, setOpen] = useState8(false);
1787
+ const [form, setForm] = useState8(() => toForm(brand || {}));
1788
+ const [applyNow, setApplyNow] = useState8(true);
1789
+ const [reading, setReading] = useState8(false);
1790
+ const [readError, setReadError] = useState8("");
1791
+ const [notes, setNotes] = useState8(
2039
1792
  /** @type {string[]} */
2040
1793
  []
2041
1794
  );
2042
- const [uploading, setUploading] = useState9(false);
2043
- const [saving, setSaving] = useState9(false);
2044
- const [error, setError] = useState9("");
2045
- const abortRef = useRef3(
1795
+ const [uploading, setUploading] = useState8(false);
1796
+ const [saving, setSaving] = useState8(false);
1797
+ const [error, setError] = useState8("");
1798
+ const abortRef = useRef2(
2046
1799
  /** @type {AbortController|null} */
2047
1800
  null
2048
1801
  );
2049
- const fileRef = useRef3(
1802
+ const fileRef = useRef2(
2050
1803
  /** @type {HTMLInputElement|null} */
2051
1804
  null
2052
1805
  );
2053
1806
  const canRead = typeof storage.importBrand === "function";
2054
1807
  const canUpload = typeof storage.uploadImage === "function";
2055
- useEffect5(() => () => abortRef.current?.abort(), []);
1808
+ useEffect4(() => () => abortRef.current?.abort(), []);
2056
1809
  const logoUrl = form.logoUrl;
2057
- useEffect5(() => {
1810
+ useEffect4(() => {
2058
1811
  if (!logoUrl || !isLogoUrl(logoUrl)) return void 0;
2059
1812
  let live = true;
2060
1813
  const image = new Image();
@@ -2163,11 +1916,11 @@ function BrandButton({ context }) {
2163
1916
  }
2164
1917
  };
2165
1918
  const swatch = brand?.primary;
2166
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
2167
- /* @__PURE__ */ jsxs9(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, "data-testid": "eb-brand-button", children: [
2168
- /* @__PURE__ */ jsx9(Palette, {}),
1919
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1920
+ /* @__PURE__ */ jsxs8(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: openDialog, "data-testid": "eb-brand-button", children: [
1921
+ /* @__PURE__ */ jsx8(Palette, {}),
2169
1922
  t("Brand"),
2170
- swatch && /* @__PURE__ */ jsx9(
1923
+ swatch && /* @__PURE__ */ jsx8(
2171
1924
  "span",
2172
1925
  {
2173
1926
  "aria-hidden": "true",
@@ -2176,18 +1929,18 @@ function BrandButton({ context }) {
2176
1929
  }
2177
1930
  )
2178
1931
  ] }),
2179
- /* @__PURE__ */ jsx9(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs9(
1932
+ /* @__PURE__ */ jsx8(Dialog, { open, onOpenChange: close, children: /* @__PURE__ */ jsxs8(
2180
1933
  DialogContent,
2181
1934
  {
2182
1935
  className: "eb:flex eb:max-h-[calc(100dvh-2rem)] eb:flex-col eb:gap-0 eb:p-0 eb:sm:max-w-2xl",
2183
1936
  "data-testid": "eb-brand",
2184
1937
  children: [
2185
- /* @__PURE__ */ jsxs9(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-4", children: [
2186
- /* @__PURE__ */ jsx9(DialogTitle, { children: t("Your brand") }),
2187
- /* @__PURE__ */ jsx9(DialogDescription, { children: t("Set it once: new emails, templates and blocks take your logo, colors and font.") })
1938
+ /* @__PURE__ */ jsxs8(DialogHeader, { className: "eb:gap-1.5 eb:px-6 eb:pt-6 eb:pb-4", children: [
1939
+ /* @__PURE__ */ jsx8(DialogTitle, { children: t("Your brand") }),
1940
+ /* @__PURE__ */ jsx8(DialogDescription, { children: t("Set it once: new emails, templates and blocks take your logo, colors and font.") })
2188
1941
  ] }),
2189
- /* @__PURE__ */ jsxs9("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-5", children: [
2190
- canRead && /* @__PURE__ */ jsxs9(
1942
+ /* @__PURE__ */ jsxs8("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-y-auto eb:border-y eb:border-border eb:px-6 eb:py-5", children: [
1943
+ canRead && /* @__PURE__ */ jsxs8(
2191
1944
  "form",
2192
1945
  {
2193
1946
  className: "eb:mb-5 eb:flex eb:flex-col eb:gap-1.5",
@@ -2196,11 +1949,11 @@ function BrandButton({ context }) {
2196
1949
  readWebsite();
2197
1950
  },
2198
1951
  children: [
2199
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-website", children: t("Your website") }),
2200
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-2 eb:sm:flex-row", children: [
2201
- /* @__PURE__ */ jsxs9("div", { className: "eb:relative eb:min-w-0 eb:flex-1", children: [
2202
- /* @__PURE__ */ jsx9(Globe, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted", "aria-hidden": "true" }),
2203
- /* @__PURE__ */ jsx9(
1952
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-website", children: t("Your website") }),
1953
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-2 eb:sm:flex-row", children: [
1954
+ /* @__PURE__ */ jsxs8("div", { className: "eb:relative eb:min-w-0 eb:flex-1", children: [
1955
+ /* @__PURE__ */ jsx8(Globe, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted", "aria-hidden": "true" }),
1956
+ /* @__PURE__ */ jsx8(
2204
1957
  Input,
2205
1958
  {
2206
1959
  id: "eb-brand-website",
@@ -2214,28 +1967,28 @@ function BrandButton({ context }) {
2214
1967
  }
2215
1968
  )
2216
1969
  ] }),
2217
- /* @__PURE__ */ jsxs9(Button, { type: "submit", size: "sm", disabled: reading || !form.website.trim(), children: [
2218
- reading ? /* @__PURE__ */ jsx9(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx9(WandSparkles, {}),
1970
+ /* @__PURE__ */ jsxs8(Button, { type: "submit", size: "sm", disabled: reading || !form.website.trim(), children: [
1971
+ reading ? /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx8(WandSparkles, {}),
2219
1972
  t("Get my brand")
2220
1973
  ] })
2221
1974
  ] }),
2222
- reading ? /* @__PURE__ */ jsx9("p", { role: "status", className: "eb:text-[12px] eb:text-foreground-muted", children: t("Reading the website\u2026") }) : readError ? /* @__PURE__ */ jsxs9("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
2223
- /* @__PURE__ */ jsx9(TriangleAlert2, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
1975
+ reading ? /* @__PURE__ */ jsx8("p", { role: "status", className: "eb:text-[12px] eb:text-foreground-muted", children: t("Reading the website\u2026") }) : readError ? /* @__PURE__ */ jsxs8("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
1976
+ /* @__PURE__ */ jsx8(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
2224
1977
  readError
2225
- ] }) : notes.length ? /* @__PURE__ */ jsx9("ul", { className: "eb:flex eb:flex-col eb:gap-1 eb:text-[12px] eb:text-foreground-muted", "data-testid": "eb-brand-notes", children: notes.map((note) => /* @__PURE__ */ jsx9("li", { children: t(BRAND_NOTES[note]) }, note)) }) : /* @__PURE__ */ jsx9("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The logo, colors and font are read from it. You can change them before saving.") })
1978
+ ] }) : notes.length ? /* @__PURE__ */ jsx8("ul", { className: "eb:flex eb:flex-col eb:gap-1 eb:text-[12px] eb:text-foreground-muted", "data-testid": "eb-brand-notes", children: notes.map((note) => /* @__PURE__ */ jsx8("li", { children: t(BRAND_NOTES[note]) }, note)) }) : /* @__PURE__ */ jsx8("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("The logo, colors and font are read from it. You can change them before saving.") })
2226
1979
  ]
2227
1980
  }
2228
1981
  ),
2229
- /* @__PURE__ */ jsxs9("div", { className: "eb:grid eb:gap-6 eb:sm:grid-cols-[minmax(0,1fr)_220px]", children: [
2230
- /* @__PURE__ */ jsxs9("fieldset", { disabled: reading, className: "eb:m-0 eb:flex eb:min-w-0 eb:flex-col eb:gap-4 eb:border-0 eb:p-0", children: [
2231
- /* @__PURE__ */ jsx9("legend", { className: "eb:sr-only", children: t("Your brand") }),
2232
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2233
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-logo", children: t("Logo") }),
2234
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:items-center eb:gap-3", children: [
2235
- /* @__PURE__ */ jsx9("div", { className: "eb-checker eb:flex eb:h-12 eb:w-24 eb:shrink-0 eb:items-center eb:justify-center eb:overflow-hidden eb:rounded-lg2 eb:ring-1 eb:ring-border eb:ring-inset", children: form.logoUrl && !logoInvalid ? /* @__PURE__ */ jsx9("img", { src: form.logoUrl, alt: t("Logo"), className: "eb:max-h-10 eb:max-w-20 eb:object-contain" }) : /* @__PURE__ */ jsx9(ImageOff, { className: "eb:size-4 eb:text-foreground-muted", "aria-hidden": "true" }) }),
2236
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-wrap eb:gap-2", children: [
2237
- canUpload && /* @__PURE__ */ jsxs9(Fragment5, { children: [
2238
- /* @__PURE__ */ jsx9(
1982
+ /* @__PURE__ */ jsxs8("div", { className: "eb:grid eb:gap-6 eb:sm:grid-cols-[minmax(0,1fr)_220px]", children: [
1983
+ /* @__PURE__ */ jsxs8("fieldset", { disabled: reading, className: "eb:m-0 eb:flex eb:min-w-0 eb:flex-col eb:gap-4 eb:border-0 eb:p-0", children: [
1984
+ /* @__PURE__ */ jsx8("legend", { className: "eb:sr-only", children: t("Your brand") }),
1985
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
1986
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-logo", children: t("Logo") }),
1987
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:items-center eb:gap-3", children: [
1988
+ /* @__PURE__ */ jsx8("div", { className: "eb-checker eb:flex eb:h-12 eb:w-24 eb:shrink-0 eb:items-center eb:justify-center eb:overflow-hidden eb:rounded-lg2 eb:ring-1 eb:ring-border eb:ring-inset", children: form.logoUrl && !logoInvalid ? /* @__PURE__ */ jsx8("img", { src: form.logoUrl, alt: t("Logo"), className: "eb:max-h-10 eb:max-w-20 eb:object-contain" }) : /* @__PURE__ */ jsx8(ImageOff, { className: "eb:size-4 eb:text-foreground-muted", "aria-hidden": "true" }) }),
1989
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-wrap eb:gap-2", children: [
1990
+ canUpload && /* @__PURE__ */ jsxs8(Fragment4, { children: [
1991
+ /* @__PURE__ */ jsx8(
2239
1992
  "input",
2240
1993
  {
2241
1994
  ref: fileRef,
@@ -2248,18 +2001,18 @@ function BrandButton({ context }) {
2248
2001
  onChange: pickLogo
2249
2002
  }
2250
2003
  ),
2251
- /* @__PURE__ */ jsxs9(Button, { type: "button", variant: "outline", size: "sm", disabled: uploading, onClick: () => fileRef.current?.click(), children: [
2252
- uploading ? /* @__PURE__ */ jsx9(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx9(Upload, {}),
2004
+ /* @__PURE__ */ jsxs8(Button, { type: "button", variant: "outline", size: "sm", disabled: uploading, onClick: () => fileRef.current?.click(), children: [
2005
+ uploading ? /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx8(Upload, {}),
2253
2006
  t("Upload")
2254
2007
  ] })
2255
2008
  ] }),
2256
- form.logoUrl && /* @__PURE__ */ jsxs9(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => set({ logoUrl: "", logoWidth: 0 }), children: [
2257
- /* @__PURE__ */ jsx9(Trash22, {}),
2009
+ form.logoUrl && /* @__PURE__ */ jsxs8(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => set({ logoUrl: "", logoWidth: 0 }), children: [
2010
+ /* @__PURE__ */ jsx8(Trash22, {}),
2258
2011
  t("Remove")
2259
2012
  ] })
2260
2013
  ] })
2261
2014
  ] }),
2262
- /* @__PURE__ */ jsx9(
2015
+ /* @__PURE__ */ jsx8(
2263
2016
  Input,
2264
2017
  {
2265
2018
  id: "eb-brand-logo",
@@ -2270,11 +2023,11 @@ function BrandButton({ context }) {
2270
2023
  className: "eb:text-[12px]"
2271
2024
  }
2272
2025
  ),
2273
- logoInvalid && /* @__PURE__ */ jsx9("p", { className: "eb:text-[12px] eb:text-destructive", children: t("An https:// address is required") })
2026
+ logoInvalid && /* @__PURE__ */ jsx8("p", { className: "eb:text-[12px] eb:text-destructive", children: t("An https:// address is required") })
2274
2027
  ] }),
2275
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2276
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-name", children: t("Company name") }),
2277
- /* @__PURE__ */ jsx9(
2028
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2029
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-name", children: t("Company name") }),
2030
+ /* @__PURE__ */ jsx8(
2278
2031
  Input,
2279
2032
  {
2280
2033
  id: "eb-brand-name",
@@ -2285,36 +2038,36 @@ function BrandButton({ context }) {
2285
2038
  }
2286
2039
  )
2287
2040
  ] }),
2288
- /* @__PURE__ */ jsxs9("div", { className: "eb:grid eb:gap-4 eb:sm:grid-cols-2", children: [
2289
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2290
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-primary", children: t("Main color") }),
2291
- /* @__PURE__ */ jsx9(ColorPicker, { id: "eb-brand-primary", value: form.primary, onChange: (primary) => set({ primary }), placeholder: PALETTE.navy }),
2292
- /* @__PURE__ */ jsx9("p", { className: "eb:text-[11px] eb:text-foreground-muted", children: t("Buttons, banners, links") })
2041
+ /* @__PURE__ */ jsxs8("div", { className: "eb:grid eb:gap-4 eb:sm:grid-cols-2", children: [
2042
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2043
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-primary", children: t("Main color") }),
2044
+ /* @__PURE__ */ jsx8(ColorPicker, { id: "eb-brand-primary", value: form.primary, onChange: (primary) => set({ primary }), placeholder: PALETTE.navy }),
2045
+ /* @__PURE__ */ jsx8("p", { className: "eb:text-[11px] eb:text-foreground-muted", children: t("Buttons, banners, links") })
2293
2046
  ] }),
2294
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2295
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-heading", children: t("Headings color") }),
2296
- /* @__PURE__ */ jsx9(ColorPicker, { id: "eb-brand-heading", value: form.heading, onChange: (heading) => set({ heading }), placeholder: PALETTE.navy })
2047
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2048
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-heading", children: t("Headings color") }),
2049
+ /* @__PURE__ */ jsx8(ColorPicker, { id: "eb-brand-heading", value: form.heading, onChange: (heading) => set({ heading }), placeholder: PALETTE.navy })
2297
2050
  ] })
2298
2051
  ] }),
2299
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2300
- /* @__PURE__ */ jsx9(Label, { htmlFor: "eb-brand-font", children: t("Font") }),
2301
- /* @__PURE__ */ jsxs9(Select, { value: form.font || FONT_STACK, onValueChange: (font) => set({ font }), children: [
2302
- /* @__PURE__ */ jsx9(SelectTrigger, { id: "eb-brand-font", children: /* @__PURE__ */ jsx9(SelectValue, {}) }),
2303
- /* @__PURE__ */ jsx9(SelectContent, { children: FONT_OPTIONS.map((option) => /* @__PURE__ */ jsx9(SelectItem, { value: option.value, children: /* @__PURE__ */ jsx9("span", { style: { fontFamily: option.value }, children: option.label }) }, option.value)) })
2052
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2053
+ /* @__PURE__ */ jsx8(Label, { htmlFor: "eb-brand-font", children: t("Font") }),
2054
+ /* @__PURE__ */ jsxs8(Select, { value: form.font || FONT_STACK, onValueChange: (font) => set({ font }), children: [
2055
+ /* @__PURE__ */ jsx8(SelectTrigger, { id: "eb-brand-font", children: /* @__PURE__ */ jsx8(SelectValue, {}) }),
2056
+ /* @__PURE__ */ jsx8(SelectContent, { children: FONT_OPTIONS.map((option) => /* @__PURE__ */ jsx8(SelectItem, { value: option.value, children: /* @__PURE__ */ jsx8("span", { style: { fontFamily: option.value }, children: option.label }) }, option.value)) })
2304
2057
  ] })
2305
2058
  ] })
2306
2059
  ] }),
2307
- /* @__PURE__ */ jsx9(BrandPreview, { brand: draft, fallbackName: hostSender?.name || t("Your company") })
2060
+ /* @__PURE__ */ jsx8(BrandPreview, { brand: draft, fallbackName: hostSender?.name || t("Your company") })
2308
2061
  ] })
2309
2062
  ] }),
2310
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
2311
- error && /* @__PURE__ */ jsxs9("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
2312
- /* @__PURE__ */ jsx9(TriangleAlert2, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
2063
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:px-6 eb:pt-3 eb:pb-6", children: [
2064
+ error && /* @__PURE__ */ jsxs8("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
2065
+ /* @__PURE__ */ jsx8(TriangleAlert, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
2313
2066
  error
2314
2067
  ] }),
2315
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:sm:flex-row eb:sm:items-center", children: [
2316
- /* @__PURE__ */ jsxs9("label", { className: "eb:flex eb:cursor-pointer eb:items-center eb:gap-2 eb:text-[13px] eb:sm:mr-auto", children: [
2317
- /* @__PURE__ */ jsx9(
2068
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-3 eb:sm:flex-row eb:sm:items-center", children: [
2069
+ /* @__PURE__ */ jsxs8("label", { className: "eb:flex eb:cursor-pointer eb:items-center eb:gap-2 eb:text-[13px] eb:sm:mr-auto", children: [
2070
+ /* @__PURE__ */ jsx8(
2318
2071
  "input",
2319
2072
  {
2320
2073
  type: "checkbox",
@@ -2326,10 +2079,10 @@ function BrandButton({ context }) {
2326
2079
  ),
2327
2080
  t("Also apply to this email")
2328
2081
  ] }),
2329
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
2330
- /* @__PURE__ */ jsx9(Button, { variant: "outline", size: "sm", onClick: () => close(false), children: t("Cancel") }),
2331
- /* @__PURE__ */ jsxs9(Button, { size: "sm", onClick: save, disabled: saving || reading || uploading || logoInvalid, "data-testid": "eb-brand-save", children: [
2332
- saving && /* @__PURE__ */ jsx9(Loader24, { className: "eb:animate-spin" }),
2082
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:justify-end eb:gap-2", children: [
2083
+ /* @__PURE__ */ jsx8(Button, { variant: "outline", size: "sm", onClick: () => close(false), children: t("Cancel") }),
2084
+ /* @__PURE__ */ jsxs8(Button, { size: "sm", onClick: save, disabled: saving || reading || uploading || logoInvalid, "data-testid": "eb-brand-save", children: [
2085
+ saving && /* @__PURE__ */ jsx8(Loader23, { className: "eb:animate-spin" }),
2333
2086
  t("Save")
2334
2087
  ] })
2335
2088
  ] })
@@ -2348,30 +2101,30 @@ function BrandPreview({ brand, fallbackName }) {
2348
2101
  const link2 = contrastRatio(primary, PALETTE.white) >= 3 ? primary : heading;
2349
2102
  const font = brand.font || FONT_STACK;
2350
2103
  const name = brand.name || fallbackName;
2351
- return /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2352
- /* @__PURE__ */ jsx9("span", { className: "eb:text-[12px] eb:font-medium eb:text-foreground-muted", children: t("Preview") }),
2353
- /* @__PURE__ */ jsx9(
2104
+ return /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5", children: [
2105
+ /* @__PURE__ */ jsx8("span", { className: "eb:text-[12px] eb:font-medium eb:text-foreground-muted", children: t("Preview") }),
2106
+ /* @__PURE__ */ jsx8(
2354
2107
  "div",
2355
2108
  {
2356
2109
  className: "eb:overflow-hidden eb:rounded-lg2 eb:p-2.5 eb:ring-1 eb:ring-border",
2357
2110
  style: { backgroundColor: PALETTE.canvas, fontFamily: font },
2358
2111
  "data-testid": "eb-brand-preview",
2359
2112
  "aria-hidden": "true",
2360
- children: /* @__PURE__ */ jsxs9("div", { className: "eb:overflow-hidden eb:rounded-[3px]", style: { backgroundColor: PALETTE.white }, children: [
2361
- /* @__PURE__ */ jsx9("div", { className: "eb:px-3 eb:pt-3 eb:pb-2", children: brand.logoUrl ? /* @__PURE__ */ jsx9("img", { src: brand.logoUrl, alt: "", style: { width: Math.round((brand.logoWidth || 140) * 0.45), maxWidth: "100%" } }) : /* @__PURE__ */ jsx9("span", { className: "eb:text-[11px] eb:font-bold", style: { color: PALETTE.ink }, children: name }) }),
2362
- /* @__PURE__ */ jsxs9("div", { className: "eb:px-3 eb:py-4 eb:text-center", style: { backgroundColor: primary, color: onPrimary }, children: [
2363
- /* @__PURE__ */ jsx9("div", { className: "eb:text-[13px] eb:leading-tight eb:font-bold", children: t("Big news") }),
2364
- /* @__PURE__ */ jsx9("div", { className: "eb:mt-1 eb:text-[9px] eb:opacity-90", children: t("A short line under the title.") })
2113
+ children: /* @__PURE__ */ jsxs8("div", { className: "eb:overflow-hidden eb:rounded-[3px]", style: { backgroundColor: PALETTE.white }, children: [
2114
+ /* @__PURE__ */ jsx8("div", { className: "eb:px-3 eb:pt-3 eb:pb-2", children: brand.logoUrl ? /* @__PURE__ */ jsx8("img", { src: brand.logoUrl, alt: "", style: { width: Math.round((brand.logoWidth || 140) * 0.45), maxWidth: "100%" } }) : /* @__PURE__ */ jsx8("span", { className: "eb:text-[11px] eb:font-bold", style: { color: PALETTE.ink }, children: name }) }),
2115
+ /* @__PURE__ */ jsxs8("div", { className: "eb:px-3 eb:py-4 eb:text-center", style: { backgroundColor: primary, color: onPrimary }, children: [
2116
+ /* @__PURE__ */ jsx8("div", { className: "eb:text-[13px] eb:leading-tight eb:font-bold", children: t("Big news") }),
2117
+ /* @__PURE__ */ jsx8("div", { className: "eb:mt-1 eb:text-[9px] eb:opacity-90", children: t("A short line under the title.") })
2365
2118
  ] }),
2366
- /* @__PURE__ */ jsxs9("div", { className: "eb:flex eb:flex-col eb:gap-1.5 eb:px-3 eb:py-3", children: [
2367
- /* @__PURE__ */ jsx9("div", { className: "eb:text-[11px] eb:leading-tight eb:font-bold", style: { color: heading }, children: t("A headline") }),
2368
- /* @__PURE__ */ jsxs9("div", { className: "eb:text-[9px] eb:leading-snug", style: { color: PALETTE.ink }, children: [
2119
+ /* @__PURE__ */ jsxs8("div", { className: "eb:flex eb:flex-col eb:gap-1.5 eb:px-3 eb:py-3", children: [
2120
+ /* @__PURE__ */ jsx8("div", { className: "eb:text-[11px] eb:leading-tight eb:font-bold", style: { color: heading }, children: t("A headline") }),
2121
+ /* @__PURE__ */ jsxs8("div", { className: "eb:text-[9px] eb:leading-snug", style: { color: PALETTE.ink }, children: [
2369
2122
  t("Some text, with"),
2370
2123
  " ",
2371
- /* @__PURE__ */ jsx9("span", { className: "eb:underline", style: { color: link2 }, children: t("a link") }),
2124
+ /* @__PURE__ */ jsx8("span", { className: "eb:underline", style: { color: link2 }, children: t("a link") }),
2372
2125
  "."
2373
2126
  ] }),
2374
- /* @__PURE__ */ jsx9(
2127
+ /* @__PURE__ */ jsx8(
2375
2128
  "span",
2376
2129
  {
2377
2130
  className: cn("eb:mt-1 eb:self-start eb:rounded-[3px] eb:px-2.5 eb:py-1 eb:text-[9px] eb:font-bold"),
@@ -2380,7 +2133,7 @@ function BrandPreview({ brand, fallbackName }) {
2380
2133
  }
2381
2134
  )
2382
2135
  ] }),
2383
- /* @__PURE__ */ jsx9("div", { className: "eb:px-3 eb:pb-2.5 eb:text-center eb:text-[7px]", style: { color: "#6d7484" }, children: name })
2136
+ /* @__PURE__ */ jsx8("div", { className: "eb:px-3 eb:pb-2.5 eb:text-center eb:text-[7px]", style: { color: "#6d7484" }, children: name })
2384
2137
  ] })
2385
2138
  }
2386
2139
  )
@@ -2398,7 +2151,6 @@ var brandFeature = {
2398
2151
 
2399
2152
  // src/features/index.js
2400
2153
  var FEATURES = [
2401
- generateFeature,
2402
2154
  templatesFeature,
2403
2155
  brandFeature,
2404
2156
  variablesFeature,
@@ -2529,93 +2281,6 @@ function betweenSections(body, point, boxOf, skip, wrap) {
2529
2281
  };
2530
2282
  }
2531
2283
 
2532
- // src/engine/richText.js
2533
- var DROPPED_WITH_CONTENT = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "template", "svg", "math", "head", "title", "meta", "link"]);
2534
- var KEPT_ATTRIBUTES = {
2535
- "*": /* @__PURE__ */ new Set(["style"]),
2536
- a: /* @__PURE__ */ new Set(["href", "target", "title", "rel"]),
2537
- font: /* @__PURE__ */ new Set(["color", "face", "size"])
2538
- };
2539
- var KEPT_STYLES = /* @__PURE__ */ new Set([
2540
- "color",
2541
- "background-color",
2542
- "font-weight",
2543
- "font-style",
2544
- "font-size",
2545
- "font-family",
2546
- "text-decoration",
2547
- "text-align",
2548
- "line-height",
2549
- "letter-spacing"
2550
- ]);
2551
- function cleanStyle(style) {
2552
- return style.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
2553
- const colon = part.indexOf(":");
2554
- if (colon === -1) return null;
2555
- const name = part.slice(0, colon).trim().toLowerCase();
2556
- const value = part.slice(colon + 1).trim();
2557
- if (!KEPT_STYLES.has(name) || /expression|url\s*\(|javascript:/i.test(value)) return null;
2558
- return `${name}:${value}`;
2559
- }).filter(Boolean).join(";");
2560
- }
2561
- function cleanAttributes(element) {
2562
- const tag = element.tagName.toLowerCase();
2563
- const own = (
2564
- /** @type {Record<string, Set<string>>} */
2565
- KEPT_ATTRIBUTES[tag]
2566
- );
2567
- for (const attribute of [...element.attributes]) {
2568
- const name = attribute.name.toLowerCase();
2569
- if (!KEPT_ATTRIBUTES["*"].has(name) && !(own && own.has(name))) {
2570
- element.removeAttribute(attribute.name);
2571
- continue;
2572
- }
2573
- if (name === "href" && !isSafeUrl(attribute.value, "link")) element.removeAttribute(attribute.name);
2574
- if (name === "style") {
2575
- const style = cleanStyle(attribute.value);
2576
- if (style) element.setAttribute("style", style);
2577
- else element.removeAttribute("style");
2578
- }
2579
- }
2580
- if (tag === "a" && element.getAttribute("target") === "_blank") element.setAttribute("rel", "noopener");
2581
- }
2582
- function cleanChildren(node) {
2583
- for (const child of [...node.childNodes]) {
2584
- if (child.nodeType === 8) {
2585
- child.remove();
2586
- continue;
2587
- }
2588
- if (child.nodeType !== 1) continue;
2589
- const element = (
2590
- /** @type {Element} */
2591
- child
2592
- );
2593
- const tag = element.tagName.toLowerCase();
2594
- if (DROPPED_WITH_CONTENT.has(tag)) {
2595
- element.remove();
2596
- continue;
2597
- }
2598
- cleanChildren(element);
2599
- if (!INLINE_TAGS.has(tag)) {
2600
- element.replaceWith(...element.childNodes);
2601
- continue;
2602
- }
2603
- cleanAttributes(element);
2604
- }
2605
- }
2606
- function cleanRichText(html, document2) {
2607
- const template = document2.createElement("template");
2608
- template.innerHTML = String(html || "");
2609
- cleanChildren(template.content);
2610
- return template.innerHTML.replace(/<br>/g, "<br />").replace(/(<br \/>)+$/, "").trim();
2611
- }
2612
- function escapeText(text) {
2613
- return String(text ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2614
- }
2615
- function textOfHtml(html) {
2616
- return String(html || "").replace(/<br\s*\/?>/gi, " ").replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&").trim();
2617
- }
2618
-
2619
2284
  // src/engine/canvasMjml.js
2620
2285
  var MARKED_TAGS = /* @__PURE__ */ new Set([
2621
2286
  "mj-body",
@@ -2737,7 +2402,7 @@ function sameKind(from, to) {
2737
2402
  );
2738
2403
  }
2739
2404
  function morphChildren(from, to, skip = () => false) {
2740
- const document2 = (
2405
+ const document = (
2741
2406
  /** @type {Document} */
2742
2407
  from.ownerDocument || from
2743
2408
  );
@@ -2746,9 +2411,9 @@ function morphChildren(from, to, skip = () => false) {
2746
2411
  const target = targets[i];
2747
2412
  const current = from.childNodes[i];
2748
2413
  if (!current) {
2749
- from.appendChild(document2.importNode(target, true));
2414
+ from.appendChild(document.importNode(target, true));
2750
2415
  } else if (!sameKind(current, target)) {
2751
- from.replaceChild(document2.importNode(target, true), current);
2416
+ from.replaceChild(document.importNode(target, true), current);
2752
2417
  } else if (current.nodeType === 1) {
2753
2418
  const element = (
2754
2419
  /** @type {Element} */
@@ -4063,7 +3728,7 @@ function createNativeAdapter({ loadCompiler = defaultLoadCompiler } = {}) {
4063
3728
  get root() {
4064
3729
  return root;
4065
3730
  },
4066
- async initialize({ container: host, document: document2, onEvent, t: translate, labelOf: label }) {
3731
+ async initialize({ container: host, document, onEvent, t: translate, labelOf: label }) {
4067
3732
  if (view) throw new Error("NativeAdapter: already initialized");
4068
3733
  const ticket = ++generation;
4069
3734
  const loaded = await loadCompiler();
@@ -4073,7 +3738,7 @@ function createNativeAdapter({ loadCompiler = defaultLoadCompiler } = {}) {
4073
3738
  if (label) labelOf = label;
4074
3739
  compile = loaded;
4075
3740
  container = host;
4076
- root = document2 ? fromProject(document2.project) : parseMjml(EMPTY_MJML);
3741
+ root = document ? fromProject(document.project) : parseMjml(EMPTY_MJML);
4077
3742
  selectedId = null;
4078
3743
  history = { past: [], future: [], key: null, at: 0 };
4079
3744
  view = createCanvasView({
@@ -4302,14 +3967,14 @@ function createDefaultEmailEditor(options = {}) {
4302
3967
  }
4303
3968
 
4304
3969
  // src/ui/EmailCanvas.jsx
4305
- import { useEffect as useEffect6, useRef as useRef4 } from "react";
4306
- import { jsx as jsx10 } from "react/jsx-runtime";
4307
- function EmailCanvas({ editor, document: document2 = null, onReady, onError, className }) {
4308
- const ref = useRef4(null);
4309
- const documentRef = useRef4(document2);
4310
- const callbacks = useRef4({ onReady, onError });
3970
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
3971
+ import { jsx as jsx9 } from "react/jsx-runtime";
3972
+ function EmailCanvas({ editor, document = null, onReady, onError, className }) {
3973
+ const ref = useRef3(null);
3974
+ const documentRef = useRef3(document);
3975
+ const callbacks = useRef3({ onReady, onError });
4311
3976
  callbacks.current = { onReady, onError };
4312
- useEffect6(() => {
3977
+ useEffect5(() => {
4313
3978
  if (!editor || !ref.current) return void 0;
4314
3979
  let alive = true;
4315
3980
  editor.initialize({ container: ref.current, document: documentRef.current }).then(() => {
@@ -4322,12 +3987,12 @@ function EmailCanvas({ editor, document: document2 = null, onReady, onError, cla
4322
3987
  editor.destroy();
4323
3988
  };
4324
3989
  }, [editor]);
4325
- return /* @__PURE__ */ jsx10("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
3990
+ return /* @__PURE__ */ jsx9("div", { ref, className: cn("eb-canvas eb:h-full eb:w-full", className), "data-testid": "eb-canvas" });
4326
3991
  }
4327
3992
 
4328
3993
  // src/ui/EmailBuilder.jsx
4329
- import { useCallback as useCallback2, useEffect as useEffect12, useMemo as useMemo6, useRef as useRef11, useState as useState15 } from "react";
4330
- import { AlertTriangle, Loader2 as Loader28, Plus as Plus2, Save, SlidersHorizontal } from "lucide-react";
3994
+ import { useCallback as useCallback2, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef8, useState as useState12 } from "react";
3995
+ import { AlertTriangle, Loader2 as Loader25, Plus as Plus2, Save, SlidersHorizontal } from "lucide-react";
4331
3996
 
4332
3997
  // src/features/autosave/scheduler.js
4333
3998
  function createAutosaveScheduler({
@@ -4386,11 +4051,11 @@ function defaultStore() {
4386
4051
  return null;
4387
4052
  }
4388
4053
  }
4389
- function saveLocalDraft({ document: document2, meta }, store = defaultStore()) {
4054
+ function saveLocalDraft({ document, meta }, store = defaultStore()) {
4390
4055
  const target = usable(store);
4391
4056
  if (!target) return false;
4392
4057
  try {
4393
- target.setItem(KEY2, JSON.stringify({ savedAt: Date.now(), document: document2, meta }));
4058
+ target.setItem(KEY2, JSON.stringify({ savedAt: Date.now(), document, meta }));
4394
4059
  return true;
4395
4060
  } catch {
4396
4061
  return false;
@@ -4437,8 +4102,8 @@ import {
4437
4102
  Square,
4438
4103
  Type
4439
4104
  } from "lucide-react";
4440
- import { useRef as useRef5 } from "react";
4441
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
4105
+ import { useRef as useRef4 } from "react";
4106
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
4442
4107
  var DRAG_THRESHOLD = 5;
4443
4108
  var ICONS2 = {
4444
4109
  Heading,
@@ -4463,8 +4128,8 @@ var GROUPS2 = [
4463
4128
  function BlockTile({ block, disabled, onAdd, onDragStart }) {
4464
4129
  const t = useT();
4465
4130
  const Icon = ICONS2[block.icon] || Square;
4466
- const press = useRef5(null);
4467
- return /* @__PURE__ */ jsx11(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs10(
4131
+ const press = useRef4(null);
4132
+ return /* @__PURE__ */ jsx10(TooltipWrap, { label: block.description ? t(block.description) : block.description, children: /* @__PURE__ */ jsxs9(
4468
4133
  "button",
4469
4134
  {
4470
4135
  type: "button",
@@ -4504,8 +4169,8 @@ function BlockTile({ block, disabled, onAdd, onDragStart }) {
4504
4169
  disabled && "eb:cursor-not-allowed eb:opacity-50"
4505
4170
  ),
4506
4171
  children: [
4507
- /* @__PURE__ */ jsx11(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
4508
- /* @__PURE__ */ jsx11("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
4172
+ /* @__PURE__ */ jsx10(Icon, { className: "eb:h-5 eb:w-5 eb:text-foreground-lighter", "aria-hidden": "true" }),
4173
+ /* @__PURE__ */ jsx10("span", { className: "eb:text-[12px] eb:text-foreground", children: t(block.label) })
4509
4174
  ]
4510
4175
  }
4511
4176
  ) });
@@ -4513,12 +4178,12 @@ function BlockTile({ block, disabled, onAdd, onDragStart }) {
4513
4178
  function BlockSidebar({ editor, disabled = false, onAdded }) {
4514
4179
  const t = useT();
4515
4180
  const blocks = editor.listBlocks();
4516
- return /* @__PURE__ */ jsx11("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) => {
4181
+ return /* @__PURE__ */ jsx10("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) => {
4517
4182
  const items = blocks.filter((b) => b.kind === group.kind && b.mjml);
4518
4183
  if (!items.length) return null;
4519
- return /* @__PURE__ */ jsxs10("section", { className: "eb:space-y-2", children: [
4520
- /* @__PURE__ */ jsx11(SectionLabel, { children: t(group.title) }),
4521
- /* @__PURE__ */ jsx11("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx11(
4184
+ return /* @__PURE__ */ jsxs9("section", { className: "eb:space-y-2", children: [
4185
+ /* @__PURE__ */ jsx10(SectionLabel, { children: t(group.title) }),
4186
+ /* @__PURE__ */ jsx10("div", { className: "eb:grid eb:grid-cols-2 eb:gap-2", children: items.map((block) => /* @__PURE__ */ jsx10(
4522
4187
  BlockTile,
4523
4188
  {
4524
4189
  block,
@@ -4536,15 +4201,15 @@ function BlockSidebar({ editor, disabled = false, onAdded }) {
4536
4201
 
4537
4202
  // src/ui/ElementActions.jsx
4538
4203
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Copy, Trash2 as Trash23 } from "lucide-react";
4539
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
4204
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
4540
4205
  function ElementActions({ editor, selection }) {
4541
4206
  const t = useT();
4542
4207
  if (!selection || !selection.canRemove && !selection.canDuplicate) return null;
4543
4208
  const horizontal = selection.tagName === "mj-column";
4544
4209
  const [Prev, Next] = horizontal ? [ArrowLeft, ArrowRight] : [ArrowUp, ArrowDown];
4545
4210
  const [prevLabel, nextLabel] = horizontal ? [t("Move left"), t("Move right")] : [t("Move up"), t("Move down")];
4546
- 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: [
4547
- /* @__PURE__ */ jsx12(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx12(
4211
+ return /* @__PURE__ */ jsxs10("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: [
4212
+ /* @__PURE__ */ jsx11(TooltipWrap, { label: prevLabel, children: /* @__PURE__ */ jsx11(
4548
4213
  Button,
4549
4214
  {
4550
4215
  variant: "ghost",
@@ -4552,10 +4217,10 @@ function ElementActions({ editor, selection }) {
4552
4217
  "aria-label": prevLabel,
4553
4218
  disabled: !selection.canMoveUp,
4554
4219
  onClick: () => editor.move(-1, selection.id),
4555
- children: /* @__PURE__ */ jsx12(Prev, {})
4220
+ children: /* @__PURE__ */ jsx11(Prev, {})
4556
4221
  }
4557
4222
  ) }),
4558
- /* @__PURE__ */ jsx12(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx12(
4223
+ /* @__PURE__ */ jsx11(TooltipWrap, { label: nextLabel, children: /* @__PURE__ */ jsx11(
4559
4224
  Button,
4560
4225
  {
4561
4226
  variant: "ghost",
@@ -4563,10 +4228,10 @@ function ElementActions({ editor, selection }) {
4563
4228
  "aria-label": nextLabel,
4564
4229
  disabled: !selection.canMoveDown,
4565
4230
  onClick: () => editor.move(1, selection.id),
4566
- children: /* @__PURE__ */ jsx12(Next, {})
4231
+ children: /* @__PURE__ */ jsx11(Next, {})
4567
4232
  }
4568
4233
  ) }),
4569
- /* @__PURE__ */ jsx12(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx12(
4234
+ /* @__PURE__ */ jsx11(TooltipWrap, { label: t("Duplicate"), children: /* @__PURE__ */ jsx11(
4570
4235
  Button,
4571
4236
  {
4572
4237
  variant: "ghost",
@@ -4574,10 +4239,10 @@ function ElementActions({ editor, selection }) {
4574
4239
  "aria-label": t("Duplicate"),
4575
4240
  disabled: !selection.canDuplicate,
4576
4241
  onClick: () => editor.duplicate(selection.id),
4577
- children: /* @__PURE__ */ jsx12(Copy, {})
4242
+ children: /* @__PURE__ */ jsx11(Copy, {})
4578
4243
  }
4579
4244
  ) }),
4580
- /* @__PURE__ */ jsx12(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx12(
4245
+ /* @__PURE__ */ jsx11(TooltipWrap, { label: t("Delete (Del)"), children: /* @__PURE__ */ jsx11(
4581
4246
  Button,
4582
4247
  {
4583
4248
  variant: "ghost",
@@ -4586,20 +4251,20 @@ function ElementActions({ editor, selection }) {
4586
4251
  disabled: !selection.canRemove,
4587
4252
  onClick: () => editor.remove(selection.id),
4588
4253
  className: "eb:ml-auto eb:text-foreground-muted eb:hover:bg-destructive/10 eb:hover:text-destructive",
4589
- children: /* @__PURE__ */ jsx12(Trash23, {})
4254
+ children: /* @__PURE__ */ jsx11(Trash23, {})
4590
4255
  }
4591
4256
  ) })
4592
4257
  ] });
4593
4258
  }
4594
4259
 
4595
4260
  // src/ui/PropertiesPanel.jsx
4596
- import { useMemo as useMemo5, useRef as useRef9 } from "react";
4261
+ import { useMemo as useMemo4, useRef as useRef6 } from "react";
4597
4262
  import { X } from "lucide-react";
4598
4263
 
4599
4264
  // src/ui/fields.jsx
4600
- import { useEffect as useEffect7, useRef as useRef6, useState as useState10 } from "react";
4601
- import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader25, Upload as Upload2 } from "lucide-react";
4602
- import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4265
+ import { useEffect as useEffect6, useRef as useRef5, useState as useState9 } from "react";
4266
+ import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Loader2 as Loader24, Upload as Upload2 } from "lucide-react";
4267
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
4603
4268
  var COLOR_DEBOUNCE_MS = 250;
4604
4269
  function emailColor(value) {
4605
4270
  const v = String(value || "").trim();
@@ -4608,11 +4273,11 @@ function emailColor(value) {
4608
4273
  return null;
4609
4274
  }
4610
4275
  function useDraft(value, toDraft) {
4611
- const [draft, setDraft] = useState10(() => toDraft(value));
4612
- const ref = useRef6(null);
4613
- const convert = useRef6(toDraft);
4276
+ const [draft, setDraft] = useState9(() => toDraft(value));
4277
+ const ref = useRef5(null);
4278
+ const convert = useRef5(toDraft);
4614
4279
  convert.current = toDraft;
4615
- useEffect7(() => {
4280
+ useEffect6(() => {
4616
4281
  const el = ref.current;
4617
4282
  const focused = el && el.ownerDocument?.activeElement && el.contains(el.ownerDocument.activeElement);
4618
4283
  if (!focused) setDraft(convert.current(value));
@@ -4621,10 +4286,10 @@ function useDraft(value, toDraft) {
4621
4286
  }
4622
4287
  var asText = (v) => v || "";
4623
4288
  function FieldLabel({ id, children }) {
4624
- return /* @__PURE__ */ jsx13(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
4289
+ return /* @__PURE__ */ jsx12(Label, { htmlFor: id, className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children });
4625
4290
  }
4626
4291
  function FieldError({ children }) {
4627
- return children ? /* @__PURE__ */ jsx13("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
4292
+ return children ? /* @__PURE__ */ jsx12("p", { className: "eb:text-[12px] eb:text-destructive", children }) : null;
4628
4293
  }
4629
4294
  function TextField({ id, property, value, fallback = "", onCommit, addon = null, error = null }) {
4630
4295
  const t = useT();
@@ -4655,23 +4320,23 @@ function TextField({ id, property, value, fallback = "", onCommit, addon = null,
4655
4320
  onKeyDown: keyDown,
4656
4321
  "aria-invalid": invalid || void 0
4657
4322
  };
4658
- return /* @__PURE__ */ jsxs12("div", { ref, className: "eb:space-y-1.5", children: [
4659
- /* @__PURE__ */ jsx13(FieldLabel, { id, children: t(property.label) }),
4660
- property.type === "textarea" ? /* @__PURE__ */ jsx13(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs12("div", { className: "eb:flex eb:gap-2", children: [
4661
- /* @__PURE__ */ jsx13(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
4323
+ return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
4324
+ /* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
4325
+ property.type === "textarea" ? /* @__PURE__ */ jsx12(Textarea, { rows: 3, ...shared }) : addon ? /* @__PURE__ */ jsxs11("div", { className: "eb:flex eb:gap-2", children: [
4326
+ /* @__PURE__ */ jsx12(Input, { ...shared, className: "eb:min-w-0 eb:flex-1" }),
4662
4327
  addon
4663
- ] }) : /* @__PURE__ */ jsx13(Input, { ...shared }),
4664
- /* @__PURE__ */ jsx13(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
4328
+ ] }) : /* @__PURE__ */ jsx12(Input, { ...shared }),
4329
+ /* @__PURE__ */ jsx12(FieldError, { children: invalid ? kind === "image" ? t("An https:// address is required") : t("Address not allowed") : error })
4665
4330
  ] });
4666
4331
  }
4667
4332
  function ImageField({ id, property, value, onCommit, services }) {
4668
4333
  const t = useT();
4669
- const inputRef = useRef6(
4334
+ const inputRef = useRef5(
4670
4335
  /** @type {HTMLInputElement|null} */
4671
4336
  null
4672
4337
  );
4673
- const [busy, setBusy] = useState10(false);
4674
- const [error, setError] = useState10(
4338
+ const [busy, setBusy] = useState9(false);
4339
+ const [error, setError] = useState9(
4675
4340
  /** @type {string|null} */
4676
4341
  null
4677
4342
  );
@@ -4693,8 +4358,8 @@ function ImageField({ id, property, value, onCommit, services }) {
4693
4358
  setBusy(false);
4694
4359
  }
4695
4360
  };
4696
- const addon = upload ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
4697
- /* @__PURE__ */ jsx13(
4361
+ const addon = upload ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
4362
+ /* @__PURE__ */ jsx12(
4698
4363
  "input",
4699
4364
  {
4700
4365
  ref: inputRef,
@@ -4707,7 +4372,7 @@ function ImageField({ id, property, value, onCommit, services }) {
4707
4372
  onChange: pick
4708
4373
  }
4709
4374
  ),
4710
- /* @__PURE__ */ jsxs12(
4375
+ /* @__PURE__ */ jsxs11(
4711
4376
  Button,
4712
4377
  {
4713
4378
  type: "button",
@@ -4716,13 +4381,13 @@ function ImageField({ id, property, value, onCommit, services }) {
4716
4381
  disabled: busy,
4717
4382
  onClick: () => inputRef.current?.click(),
4718
4383
  children: [
4719
- busy ? /* @__PURE__ */ jsx13(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx13(Upload2, {}),
4384
+ busy ? /* @__PURE__ */ jsx12(Loader24, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx12(Upload2, {}),
4720
4385
  t("Upload")
4721
4386
  ]
4722
4387
  }
4723
4388
  )
4724
4389
  ] }) : null;
4725
- return /* @__PURE__ */ jsx13(TextField, { id, property, value, onCommit, addon, error });
4390
+ return /* @__PURE__ */ jsx12(TextField, { id, property, value, onCommit, addon, error });
4726
4391
  }
4727
4392
  var asNumber = (v) => {
4728
4393
  const n = Number.parseFloat(String(v || ""));
@@ -4744,10 +4409,10 @@ function SizeField({ id, property, value, fallback = "", onCommit }) {
4744
4409
  const next = `${n}${property.unit || "px"}`;
4745
4410
  if (next !== value) onCommit(next);
4746
4411
  };
4747
- return /* @__PURE__ */ jsxs12("div", { ref, className: "eb:space-y-1.5", children: [
4748
- /* @__PURE__ */ jsx13(FieldLabel, { id, children: t(property.label) }),
4749
- /* @__PURE__ */ jsxs12("div", { className: "eb:relative", children: [
4750
- /* @__PURE__ */ jsx13(
4412
+ return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
4413
+ /* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
4414
+ /* @__PURE__ */ jsxs11("div", { className: "eb:relative", children: [
4415
+ /* @__PURE__ */ jsx12(
4751
4416
  Input,
4752
4417
  {
4753
4418
  id,
@@ -4766,17 +4431,17 @@ function SizeField({ id, property, value, fallback = "", onCommit }) {
4766
4431
  className: "eb:pr-9 eb:tabular-nums"
4767
4432
  }
4768
4433
  ),
4769
- /* @__PURE__ */ jsx13("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" })
4434
+ /* @__PURE__ */ jsx12("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" })
4770
4435
  ] })
4771
4436
  ] });
4772
4437
  }
4773
4438
  function ColorField({ id, property, value, fallback = "", onCommit }) {
4774
4439
  const t = useT();
4775
4440
  const [draft, setDraft, ref] = useDraft(value, asText);
4776
- const timer = useRef6(null);
4777
- const latest = useRef6({ value, onCommit });
4441
+ const timer = useRef5(null);
4442
+ const latest = useRef5({ value, onCommit });
4778
4443
  latest.current = { value, onCommit };
4779
- useEffect7(() => () => clearTimeout(timer.current), []);
4444
+ useEffect6(() => () => clearTimeout(timer.current), []);
4780
4445
  const change = (next) => {
4781
4446
  setDraft(next);
4782
4447
  clearTimeout(timer.current);
@@ -4789,9 +4454,9 @@ function ColorField({ id, property, value, fallback = "", onCommit }) {
4789
4454
  }
4790
4455
  }, COLOR_DEBOUNCE_MS);
4791
4456
  };
4792
- return /* @__PURE__ */ jsxs12("div", { ref, className: "eb:space-y-1.5", children: [
4793
- /* @__PURE__ */ jsx13(FieldLabel, { id, children: t(property.label) }),
4794
- /* @__PURE__ */ jsx13(ColorPicker, { id, value: draft, placeholder: emailColor(fallback) || "", onChange: change })
4457
+ return /* @__PURE__ */ jsxs11("div", { ref, className: "eb:space-y-1.5", children: [
4458
+ /* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
4459
+ /* @__PURE__ */ jsx12(ColorPicker, { id, value: draft, placeholder: emailColor(fallback) || "", onChange: change })
4795
4460
  ] });
4796
4461
  }
4797
4462
  var DEFAULT_OPTION = "__default__";
@@ -4801,9 +4466,9 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
4801
4466
  const words = (v) => String(v || "").trim().split(/\s+/).sort().join(" ");
4802
4467
  const fallbackOption = (property.options || []).find((o) => o.value && words(o.value) === words(fallback));
4803
4468
  const defaultLabel = fallbackOption ? t("Default ({value})", { value: t(fallbackOption.label) }) : t("Default");
4804
- return /* @__PURE__ */ jsxs12("div", { className: "eb:space-y-1.5", children: [
4805
- /* @__PURE__ */ jsx13(FieldLabel, { id, children: t(property.label) }),
4806
- /* @__PURE__ */ jsxs12(
4469
+ return /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1.5", children: [
4470
+ /* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
4471
+ /* @__PURE__ */ jsxs11(
4807
4472
  Select,
4808
4473
  {
4809
4474
  value: current,
@@ -4812,10 +4477,10 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
4812
4477
  if (v !== (value || "")) onCommit(v || null);
4813
4478
  },
4814
4479
  children: [
4815
- /* @__PURE__ */ jsx13(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx13(SelectValue, { placeholder: defaultLabel }) }),
4816
- /* @__PURE__ */ jsxs12(SelectContent, { children: [
4817
- !(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx13(SelectItem, { value: DEFAULT_OPTION, children: defaultLabel }),
4818
- (property.options || []).map((o) => /* @__PURE__ */ jsx13(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
4480
+ /* @__PURE__ */ jsx12(SelectTrigger, { id, className: "eb:w-full", children: /* @__PURE__ */ jsx12(SelectValue, { placeholder: defaultLabel }) }),
4481
+ /* @__PURE__ */ jsxs11(SelectContent, { children: [
4482
+ !(property.options || []).some((o) => !o.value) && /* @__PURE__ */ jsx12(SelectItem, { value: DEFAULT_OPTION, children: defaultLabel }),
4483
+ (property.options || []).map((o) => /* @__PURE__ */ jsx12(SelectItem, { value: o.value || DEFAULT_OPTION, children: t(o.label) }, o.value || DEFAULT_OPTION))
4819
4484
  ] })
4820
4485
  ]
4821
4486
  }
@@ -4825,9 +4490,9 @@ function SelectField({ id, property, value, fallback = "", onCommit }) {
4825
4490
  var ALIGN_ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify: AlignJustify };
4826
4491
  function AlignField({ id, property, value, fallback = "", onCommit }) {
4827
4492
  const t = useT();
4828
- return /* @__PURE__ */ jsxs12("div", { className: "eb:space-y-1.5", children: [
4829
- /* @__PURE__ */ jsx13(FieldLabel, { id, children: t(property.label) }),
4830
- /* @__PURE__ */ jsx13(
4493
+ return /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1.5", children: [
4494
+ /* @__PURE__ */ jsx12(FieldLabel, { id, children: t(property.label) }),
4495
+ /* @__PURE__ */ jsx12(
4831
4496
  "div",
4832
4497
  {
4833
4498
  id,
@@ -4837,7 +4502,7 @@ function AlignField({ id, property, value, fallback = "", onCommit }) {
4837
4502
  children: (property.options || []).map((option) => {
4838
4503
  const Icon = ALIGN_ICONS[option.value] || AlignLeft;
4839
4504
  const active = (value || fallback) === option.value;
4840
- return /* @__PURE__ */ jsx13(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx13(
4505
+ return /* @__PURE__ */ jsx12(TooltipWrap, { label: t(option.label), children: /* @__PURE__ */ jsx12(
4841
4506
  "button",
4842
4507
  {
4843
4508
  type: "button",
@@ -4849,7 +4514,7 @@ function AlignField({ id, property, value, fallback = "", onCommit }) {
4849
4514
  "eb:inline-flex eb:h-7 eb:w-7 eb:items-center eb:justify-center eb:rounded-lg2 eb:transition-colors",
4850
4515
  active ? "eb:bg-primary eb:text-primary-foreground" : "eb:text-foreground-lighter eb:hover:bg-surface-200 eb:hover:text-foreground"
4851
4516
  ),
4852
- children: /* @__PURE__ */ jsx13(Icon, { className: "eb:h-3.5 eb:w-3.5" })
4517
+ children: /* @__PURE__ */ jsx12(Icon, { className: "eb:h-3.5 eb:w-3.5" })
4853
4518
  }
4854
4519
  ) }, option.value);
4855
4520
  })
@@ -4878,10 +4543,10 @@ function SpacingField({ id, property, value, fallback = "", onCommit }) {
4878
4543
  );
4879
4544
  if (next !== (value || "")) onCommit(next);
4880
4545
  };
4881
- return /* @__PURE__ */ jsxs12("fieldset", { ref, className: "eb:space-y-1.5", children: [
4882
- /* @__PURE__ */ jsx13("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
4883
- /* @__PURE__ */ jsx13("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs12("div", { className: "eb:space-y-1", children: [
4884
- /* @__PURE__ */ jsx13(
4546
+ return /* @__PURE__ */ jsxs11("fieldset", { ref, className: "eb:space-y-1.5", children: [
4547
+ /* @__PURE__ */ jsx12("legend", { className: "eb:mb-1.5 eb:text-[12px] eb:text-foreground-lighter", children: t(property.label) }),
4548
+ /* @__PURE__ */ jsx12("div", { className: "eb:grid eb:grid-cols-4 eb:gap-1.5", children: SIDE_LABELS.map(([side, label], i) => /* @__PURE__ */ jsxs11("div", { className: "eb:space-y-1", children: [
4549
+ /* @__PURE__ */ jsx12(
4885
4550
  Input,
4886
4551
  {
4887
4552
  id: `${id}-${side}`,
@@ -4900,7 +4565,7 @@ function SpacingField({ id, property, value, fallback = "", onCommit }) {
4900
4565
  className: "eb:px-2 eb:text-center eb:tabular-nums"
4901
4566
  }
4902
4567
  ),
4903
- /* @__PURE__ */ jsx13("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
4568
+ /* @__PURE__ */ jsx12("span", { className: "eb:block eb:text-center eb:text-[10.5px] eb:text-foreground-muted", children: t(label) })
4904
4569
  ] }, side)) })
4905
4570
  ] });
4906
4571
  }
@@ -4918,15 +4583,15 @@ var FIELDS = {
4918
4583
  function PropertyField({ property, value, fallback = "", onCommit, idPrefix = "eb", services = null }) {
4919
4584
  const Field = FIELDS[property.type] || TextField;
4920
4585
  const id = `${idPrefix}-${property.key}`;
4921
- return /* @__PURE__ */ jsx13(Field, { id, property, value, fallback, onCommit, services });
4586
+ return /* @__PURE__ */ jsx12(Field, { id, property, value, fallback, onCommit, services });
4922
4587
  }
4923
4588
 
4924
4589
  // src/features/variables/VariableInsertButton.jsx
4925
- import { useState as useState11 } from "react";
4590
+ import { useState as useState10 } from "react";
4926
4591
  import { Braces as Braces3 } from "lucide-react";
4927
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
4592
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4928
4593
  function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
4929
- const [open, setOpen] = useState11(false);
4594
+ const [open, setOpen] = useState10(false);
4930
4595
  const insert = (variable) => {
4931
4596
  const input = inputRef.current;
4932
4597
  const text = String(value || "");
@@ -4942,228 +4607,14 @@ function VariableInsertButton({ variables, inputRef, value, onChange, label }) {
4942
4607
  input.setSelectionRange(start + token.length, start + token.length);
4943
4608
  });
4944
4609
  };
4945
- return /* @__PURE__ */ jsxs13(Popover, { open, onOpenChange: setOpen, children: [
4946
- /* @__PURE__ */ jsx14(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx14(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx14(Braces3, {}) }) }),
4947
- /* @__PURE__ */ jsx14(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx14(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
4948
- ] });
4949
- }
4950
-
4951
- // src/features/generate/RewriteText.jsx
4952
- import { useEffect as useEffect8, useRef as useRef7, useState as useState12 } from "react";
4953
- import { ChevronDown, Languages, Loader2 as Loader26, Sparkles as Sparkles2, TriangleAlert as TriangleAlert3 } from "lucide-react";
4954
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
4955
- function nativeName(code) {
4956
- try {
4957
- const name = new Intl.DisplayNames([code], { type: "language" }).of(code) || code;
4958
- return name.charAt(0).toLocaleUpperCase(code) + name.slice(1);
4959
- } catch {
4960
- return code;
4961
- }
4962
- }
4963
- function RewriteText({ editor, selection, rewrite }) {
4964
- const { t, notify } = useUi();
4965
- const [running, setRunning] = useState12(
4966
- /** @type {string|null} */
4967
- null
4968
- );
4969
- const [error, setError] = useState12("");
4970
- const [instruction, setInstruction] = useState12("");
4971
- const abortRef = useRef7(
4972
- /** @type {AbortController|null} */
4973
- null
4974
- );
4975
- const label = selection.tagName === "mj-button";
4976
- useEffect8(() => () => abortRef.current?.abort(), []);
4977
- const currentText = () => {
4978
- const element = editor.getElement(selection.id);
4979
- return (label ? element?.content : element?.html) ?? "";
4980
- };
4981
- const run = async (action, extra = {}) => {
4982
- if (running) return;
4983
- const text = currentText();
4984
- setError("");
4985
- setRunning(action);
4986
- const controller = new AbortController();
4987
- abortRef.current = controller;
4988
- try {
4989
- const result = await rewrite({ action, text, format: label ? "plain" : "html", ...extra }, { signal: controller.signal });
4990
- if (controller.signal.aborted) return;
4991
- if (currentText() !== text) {
4992
- setError(t("The text changed in the meantime, so it was left as it is."));
4993
- return;
4994
- }
4995
- editor.setContent(label ? result.text : cleanRichText(result.text, document), selection.id);
4996
- if (action === "custom") setInstruction("");
4997
- notify("success", t("Text rewritten. Ctrl+Z brings back the previous one."));
4998
- } catch (err) {
4999
- if (!controller.signal.aborted) setError(t(err?.message || "The text could not be rewritten."));
5000
- } finally {
5001
- if (abortRef.current === controller) abortRef.current = null;
5002
- setRunning(null);
5003
- }
5004
- };
5005
- const cancel = () => {
5006
- abortRef.current?.abort();
5007
- setRunning(null);
5008
- };
5009
- const busy = Boolean(running);
5010
- const spinner = /* @__PURE__ */ jsx15(Loader26, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
5011
- return /* @__PURE__ */ jsxs14("section", { className: "eb:space-y-2.5", "aria-label": t("Rewrite with AI"), "data-testid": "eb-rewrite", children: [
5012
- /* @__PURE__ */ jsxs14(SectionLabel, { className: "eb:flex eb:items-center eb:gap-1.5", children: [
5013
- /* @__PURE__ */ jsx15(Sparkles2, { className: "eb:size-3", "aria-hidden": "true" }),
5014
- t("Rewrite with AI")
5015
- ] }),
5016
- /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:flex-wrap eb:gap-1.5", children: [
5017
- REWRITE_ACTIONS.map((action) => /* @__PURE__ */ jsxs14("button", { type: "button", className: chipClassName(), disabled: busy, onClick: () => run(action.id), children: [
5018
- running === action.id && spinner,
5019
- t(action.label)
5020
- ] }, action.id)),
5021
- /* @__PURE__ */ jsxs14(DropdownMenu, { children: [
5022
- /* @__PURE__ */ jsx15(DropdownMenuTrigger, { asChild: true, disabled: busy, children: /* @__PURE__ */ jsxs14("button", { type: "button", className: chipClassName(), children: [
5023
- running === "translate" ? spinner : /* @__PURE__ */ jsx15(Languages, { className: "eb:size-3", "aria-hidden": "true" }),
5024
- t("Translate"),
5025
- /* @__PURE__ */ jsx15(ChevronDown, { className: "eb:size-3", "aria-hidden": "true" })
5026
- ] }) }),
5027
- /* @__PURE__ */ jsx15(DropdownMenuContent, { align: "start", "aria-label": t("Translate into"), children: REWRITE_LANGUAGES.map((code) => /* @__PURE__ */ jsx15(DropdownMenuItem, { lang: code, onSelect: () => run("translate", { targetLanguage: code }), children: nativeName(code) }, code)) })
5028
- ] })
5029
- ] }),
5030
- /* @__PURE__ */ jsxs14(
5031
- "form",
5032
- {
5033
- className: "eb:flex eb:gap-1.5",
5034
- onSubmit: (event) => {
5035
- event.preventDefault();
5036
- if (instruction.trim()) run("custom", { instruction: instruction.trim() });
5037
- },
5038
- children: [
5039
- /* @__PURE__ */ jsx15(
5040
- Input,
5041
- {
5042
- value: instruction,
5043
- onChange: (event) => setInstruction(event.target.value),
5044
- maxLength: MAX_REWRITE_INSTRUCTION,
5045
- placeholder: t("Or say what to change\u2026"),
5046
- "aria-label": t("What to change"),
5047
- disabled: busy
5048
- }
5049
- ),
5050
- /* @__PURE__ */ jsx15(Button, { type: "submit", variant: "outline", size: "icon-sm", "aria-label": t("Rewrite"), disabled: busy || !instruction.trim(), children: running === "custom" ? /* @__PURE__ */ jsx15(Loader26, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx15(Sparkles2, {}) })
5051
- ]
5052
- }
5053
- ),
5054
- running && /* @__PURE__ */ jsxs14("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
5055
- t("Rewriting\u2026"),
5056
- /* @__PURE__ */ jsx15(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
5057
- ] }),
5058
- error && /* @__PURE__ */ jsxs14("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
5059
- /* @__PURE__ */ jsx15(TriangleAlert3, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
5060
- error
5061
- ] })
5062
- ] });
5063
- }
5064
-
5065
- // src/features/generate/SubjectIdeas.jsx
5066
- import { useEffect as useEffect9, useRef as useRef8, useState as useState13 } from "react";
5067
- import { Check as Check3, Loader2 as Loader27, Sparkles as Sparkles3, TriangleAlert as TriangleAlert4 } from "lucide-react";
5068
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
5069
- function SubjectIdeas({ editor, meta, onSubject, suggest, variables = null }) {
5070
- const { t, locale } = useUi();
5071
- const [running, setRunning] = useState13(false);
5072
- const [error, setError] = useState13("");
5073
- const [ideas, setIdeas] = useState13(
5074
- /** @type {string[]|null} */
5075
- null
5076
- );
5077
- const [original, setOriginal] = useState13("");
5078
- const abortRef = useRef8(
5079
- /** @type {AbortController|null} */
5080
- null
5081
- );
5082
- useEffect9(() => () => abortRef.current?.abort(), []);
5083
- const run = async () => {
5084
- if (running) return;
5085
- const yours = ideas ? original : meta.subject.trim();
5086
- const controller = new AbortController();
5087
- abortRef.current = controller;
5088
- setError("");
5089
- setRunning(true);
5090
- try {
5091
- const result = await suggest(
5092
- {
5093
- subject: yours,
5094
- preheader: meta.preheader,
5095
- text: emailWords(editor.exportMjml()),
5096
- language: locale,
5097
- variables: variables?.map(({ key, label }) => ({ key, label })),
5098
- avoid: ideas || []
5099
- },
5100
- { signal: controller.signal }
5101
- );
5102
- if (controller.signal.aborted) return;
5103
- setOriginal(yours);
5104
- setIdeas(result.subjects);
5105
- } catch (err) {
5106
- if (!controller.signal.aborted) setError(err?.message ? t(err.message) : t("No subject could be suggested."));
5107
- } finally {
5108
- if (abortRef.current === controller) abortRef.current = null;
5109
- setRunning(false);
5110
- }
5111
- };
5112
- const cancel = () => {
5113
- abortRef.current?.abort();
5114
- setRunning(false);
5115
- };
5116
- const options = ideas ? [...original ? [{ text: original, yours: true }] : [], ...ideas.map((text) => ({ text, yours: false }))] : [];
5117
- const spinner = /* @__PURE__ */ jsx16(Loader27, { className: "eb:size-3 eb:animate-spin", "aria-hidden": "true" });
5118
- return /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-2", "data-testid": "eb-subject-ideas", children: [
5119
- !ideas && /* @__PURE__ */ jsxs15("button", { type: "button", className: chipClassName(), disabled: running, onClick: run, children: [
5120
- running ? spinner : /* @__PURE__ */ jsx16(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
5121
- t("Suggest subjects")
5122
- ] }),
5123
- ideas && /* @__PURE__ */ jsxs15("div", { className: "eb:space-y-2 eb:rounded-md eb:border eb:border-border eb:p-1.5", children: [
5124
- /* @__PURE__ */ jsx16("div", { role: "radiogroup", "aria-label": t("Subject ideas"), className: "eb:space-y-0.5", children: options.map((option) => {
5125
- const chosen = meta.subject === option.text;
5126
- return /* @__PURE__ */ jsxs15(
5127
- "button",
5128
- {
5129
- type: "button",
5130
- role: "radio",
5131
- "aria-checked": chosen,
5132
- className: cn(
5133
- "eb:flex eb:w-full eb:items-start eb:gap-2 eb:rounded eb:px-2 eb:py-1.5 eb:text-left eb:text-[12.5px] eb:leading-snug eb:transition-colors eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50",
5134
- chosen ? "eb:bg-primary/10 eb:text-foreground" : "eb:text-foreground-lighter eb:hover:bg-muted"
5135
- ),
5136
- onClick: () => onSubject(option.text),
5137
- children: [
5138
- /* @__PURE__ */ jsx16(Check3, { className: cn("eb:mt-0.5 eb:size-3.5 eb:shrink-0", chosen ? "eb:text-primary" : "eb:invisible"), "aria-hidden": "true" }),
5139
- /* @__PURE__ */ jsx16("span", { className: "eb:min-w-0 eb:flex-1 eb:break-words", children: option.text }),
5140
- option.yours && /* @__PURE__ */ jsx16("span", { className: "eb:shrink-0 eb:text-[11px] eb:text-foreground-muted", children: t("yours") })
5141
- ]
5142
- },
5143
- `${option.yours ? "yours" : "idea"}:${option.text}`
5144
- );
5145
- }) }),
5146
- /* @__PURE__ */ jsxs15("div", { className: "eb:flex eb:items-center eb:gap-1.5 eb:px-1 eb:pb-0.5", children: [
5147
- /* @__PURE__ */ jsxs15("button", { type: "button", className: chipClassName(), disabled: running, onClick: run, children: [
5148
- running ? spinner : /* @__PURE__ */ jsx16(Sparkles3, { className: "eb:size-3", "aria-hidden": "true" }),
5149
- t("More ideas")
5150
- ] }),
5151
- /* @__PURE__ */ jsx16(Button, { type: "button", variant: "ghost", size: "xs", className: "eb:ml-auto", disabled: running, onClick: () => setIdeas(null), children: t("Done") })
5152
- ] })
5153
- ] }),
5154
- running && /* @__PURE__ */ jsxs15("p", { className: "eb:flex eb:items-center eb:gap-2 eb:text-[12px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: [
5155
- t("Reading the email\u2026"),
5156
- /* @__PURE__ */ jsx16(Button, { type: "button", variant: "link", size: "xs", className: "eb:h-auto eb:px-0", onClick: cancel, children: t("Cancel") })
5157
- ] }),
5158
- error && /* @__PURE__ */ jsxs15("p", { role: "alert", className: "eb:flex eb:items-start eb:gap-1.5 eb:text-[12px] eb:text-destructive", children: [
5159
- /* @__PURE__ */ jsx16(TriangleAlert4, { className: "eb:mt-px eb:size-3.5 eb:shrink-0", "aria-hidden": "true" }),
5160
- error
5161
- ] })
4610
+ return /* @__PURE__ */ jsxs12(Popover, { open, onOpenChange: setOpen, children: [
4611
+ /* @__PURE__ */ jsx13(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(Button, { variant: "ghost", size: "icon-xs", "aria-label": label, title: label, children: /* @__PURE__ */ jsx13(Braces3, {}) }) }),
4612
+ /* @__PURE__ */ jsx13(PopoverContent, { align: "end", className: "eb:w-72 eb:p-0", onCloseAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx13(VariablePicker, { variables, onPick: insert, filter: (v) => v.kind !== "link" }) })
5162
4613
  ] });
5163
4614
  }
5164
4615
 
5165
4616
  // src/ui/PropertiesPanel.jsx
5166
- import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
4617
+ import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
5167
4618
  var GROUP_ORDER = Object.values(GROUPS);
5168
4619
  function grouped(properties) {
5169
4620
  const map = /* @__PURE__ */ new Map();
@@ -5177,9 +4628,9 @@ function grouped(properties) {
5177
4628
  }
5178
4629
  function PropertyGroups({ editor, element, block, idPrefix, services = null }) {
5179
4630
  const t = useT();
5180
- return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
5181
- /* @__PURE__ */ jsx17(SectionLabel, { children: t(group) }),
5182
- properties.map((property) => /* @__PURE__ */ jsx17(
4631
+ return grouped(block?.properties || []).map(([group, properties]) => /* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
4632
+ /* @__PURE__ */ jsx14(SectionLabel, { children: t(group) }),
4633
+ properties.map((property) => /* @__PURE__ */ jsx14(
5183
4634
  PropertyField,
5184
4635
  {
5185
4636
  idPrefix,
@@ -5197,31 +4648,31 @@ var LABEL_PROPERTY = { key: "content", label: "Label", type: "text", group: GROU
5197
4648
  var REWRITABLE_TAGS = /* @__PURE__ */ new Set(["mj-text", "mj-button"]);
5198
4649
  function PanelHeader({ title, onClose, closeLabel = "Deselect" }) {
5199
4650
  const t = useT();
5200
- 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: [
5201
- /* @__PURE__ */ jsx17("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx17(TruncatedText, { children: title }) }),
5202
- onClose && /* @__PURE__ */ jsx17(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t(closeLabel), children: /* @__PURE__ */ jsx17(X, {}) })
4651
+ return /* @__PURE__ */ jsxs13("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: [
4652
+ /* @__PURE__ */ jsx14("h3", { className: "eb:min-w-0 eb:flex-1 eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx14(TruncatedText, { children: title }) }),
4653
+ onClose && /* @__PURE__ */ jsx14(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t(closeLabel), children: /* @__PURE__ */ jsx14(X, {}) })
5203
4654
  ] });
5204
4655
  }
5205
- function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, services = null, onDismiss = null }) {
4656
+ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, variables = null, ai = null, onDismiss = null }) {
5206
4657
  const t = useT();
5207
- const subjectRef = useRef9(null);
5208
- const preheaderRef = useRef9(null);
4658
+ const subjectRef = useRef6(null);
4659
+ const preheaderRef = useRef6(null);
5209
4660
  const rootId = editor.getRootId();
5210
- const root = useMemo5(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
4661
+ const root = useMemo4(() => rootId ? editor.getElement(rootId) : null, [editor, rootId, changes]);
5211
4662
  const bodyBlock2 = root?.blockId ? editor.getBlock(root.blockId) : null;
5212
4663
  const field = (key) => (e) => onMetaChange({ ...meta, [key]: e.target.value });
5213
- return /* @__PURE__ */ jsxs16(Fragment7, { children: [
5214
- /* @__PURE__ */ jsx17(PanelHeader, { title: t("Email"), onClose: onDismiss, closeLabel: "Close" }),
5215
- /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
5216
- /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
5217
- /* @__PURE__ */ jsx17(SectionLabel, { children: t("Template") }),
5218
- /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
5219
- /* @__PURE__ */ jsxs16(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
4664
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
4665
+ /* @__PURE__ */ jsx14(PanelHeader, { title: t("Email"), onClose: onDismiss, closeLabel: "Close" }),
4666
+ /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-5 eb:p-4", children: [
4667
+ /* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
4668
+ /* @__PURE__ */ jsx14(SectionLabel, { children: t("Template") }),
4669
+ /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
4670
+ /* @__PURE__ */ jsxs13(Label, { htmlFor: "eb-meta-name", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: [
5220
4671
  t("Name"),
5221
4672
  " ",
5222
- /* @__PURE__ */ jsx17("span", { className: "eb:text-destructive", children: "*" })
4673
+ /* @__PURE__ */ jsx14("span", { className: "eb:text-destructive", children: "*" })
5223
4674
  ] }),
5224
- /* @__PURE__ */ jsx17(
4675
+ /* @__PURE__ */ jsx14(
5225
4676
  Input,
5226
4677
  {
5227
4678
  id: "eb-meta-name",
@@ -5231,12 +4682,12 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
5231
4682
  "aria-invalid": showErrors && !meta.name.trim() || void 0
5232
4683
  }
5233
4684
  ),
5234
- showErrors && !meta.name.trim() && /* @__PURE__ */ jsx17("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
4685
+ showErrors && !meta.name.trim() && /* @__PURE__ */ jsx14("p", { className: "eb:text-[12px] eb:text-destructive", children: t("Name is required") })
5235
4686
  ] }),
5236
- /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
5237
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
5238
- /* @__PURE__ */ jsx17(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
5239
- variables && /* @__PURE__ */ jsx17(
4687
+ /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
4688
+ /* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
4689
+ /* @__PURE__ */ jsx14(Label, { htmlFor: "eb-meta-subject", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Subject") }),
4690
+ variables && /* @__PURE__ */ jsx14(
5240
4691
  VariableInsertButton,
5241
4692
  {
5242
4693
  variables,
@@ -5247,22 +4698,22 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
5247
4698
  }
5248
4699
  )
5249
4700
  ] }),
5250
- /* @__PURE__ */ jsx17(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") }),
5251
- services?.suggestSubjects && /* @__PURE__ */ jsx17(
5252
- SubjectIdeas,
4701
+ /* @__PURE__ */ jsx14(Input, { ref: subjectRef, id: "eb-meta-subject", maxLength: 250, value: meta.subject, onChange: field("subject") }),
4702
+ ai?.suggestSubjects && /* @__PURE__ */ jsx14(
4703
+ ai.components.Subjects,
5253
4704
  {
5254
4705
  editor,
5255
4706
  meta,
5256
4707
  onSubject: (subject) => onMetaChange({ ...meta, subject }),
5257
- suggest: services.suggestSubjects,
4708
+ suggest: ai.suggestSubjects,
5258
4709
  variables
5259
4710
  }
5260
4711
  )
5261
4712
  ] }),
5262
- /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-1.5", children: [
5263
- /* @__PURE__ */ jsxs16("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
5264
- /* @__PURE__ */ jsx17(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
5265
- variables && /* @__PURE__ */ jsx17(
4713
+ /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-1.5", children: [
4714
+ /* @__PURE__ */ jsxs13("div", { className: "eb:flex eb:h-6 eb:items-center eb:justify-between", children: [
4715
+ /* @__PURE__ */ jsx14(Label, { htmlFor: "eb-meta-preheader", className: "eb:text-[12px] eb:font-normal eb:text-foreground-lighter", children: t("Preview text") }),
4716
+ variables && /* @__PURE__ */ jsx14(
5266
4717
  VariableInsertButton,
5267
4718
  {
5268
4719
  variables,
@@ -5273,7 +4724,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
5273
4724
  }
5274
4725
  )
5275
4726
  ] }),
5276
- /* @__PURE__ */ jsx17(
4727
+ /* @__PURE__ */ jsx14(
5277
4728
  Textarea,
5278
4729
  {
5279
4730
  ref: preheaderRef,
@@ -5287,7 +4738,7 @@ function TemplateSettings({ editor, meta, onMetaChange, changes, showErrors, var
5287
4738
  )
5288
4739
  ] })
5289
4740
  ] }),
5290
- root && bodyBlock2 && /* @__PURE__ */ jsx17(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
4741
+ root && bodyBlock2 && /* @__PURE__ */ jsx14(PropertyGroups, { editor, element: root, block: bodyBlock2, idPrefix: "eb-page" })
5291
4742
  ] })
5292
4743
  ] });
5293
4744
  }
@@ -5300,12 +4751,13 @@ function PropertiesPanel({
5300
4751
  actions = null,
5301
4752
  showErrors = false,
5302
4753
  services = null,
4754
+ ai = null,
5303
4755
  variables = null,
5304
4756
  onDismiss = null
5305
4757
  }) {
5306
4758
  const t = useT();
5307
4759
  const block = selection?.blockId ? editor.getBlock(selection.blockId) : null;
5308
- return /* @__PURE__ */ jsx17("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__ */ jsx17(
4760
+ return /* @__PURE__ */ jsx14("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__ */ jsx14(
5309
4761
  TemplateSettings,
5310
4762
  {
5311
4763
  editor,
@@ -5314,11 +4766,11 @@ function PropertiesPanel({
5314
4766
  changes,
5315
4767
  showErrors,
5316
4768
  variables,
5317
- services,
4769
+ ai,
5318
4770
  onDismiss
5319
4771
  }
5320
- ) : /* @__PURE__ */ jsxs16(Fragment7, { children: [
5321
- /* @__PURE__ */ jsx17(
4772
+ ) : /* @__PURE__ */ jsxs13(Fragment6, { children: [
4773
+ /* @__PURE__ */ jsx14(
5322
4774
  PanelHeader,
5323
4775
  {
5324
4776
  title: t(selection.label),
@@ -5327,12 +4779,12 @@ function PropertiesPanel({
5327
4779
  }
5328
4780
  ),
5329
4781
  actions,
5330
- selection.multiple ? /* @__PURE__ */ jsx17("p", { className: "eb:p-4 eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter", "data-testid": "eb-select-all", children: t("{count} sections selected. Press Delete to remove them (Ctrl+Z brings them back).", {
4782
+ selection.multiple ? /* @__PURE__ */ jsx14("p", { className: "eb:p-4 eb:text-[13px] eb:leading-relaxed eb:text-foreground-lighter", "data-testid": "eb-select-all", children: t("{count} sections selected. Press Delete to remove them (Ctrl+Z brings them back).", {
5331
4783
  count: selection.multiple
5332
- }) }) : /* @__PURE__ */ jsxs16("div", { className: "eb:space-y-5 eb:p-4", children: [
5333
- selection.content !== null && selection.content !== void 0 && /* @__PURE__ */ jsxs16("section", { className: "eb:space-y-3", children: [
5334
- /* @__PURE__ */ jsx17(SectionLabel, { children: t(GROUPS.content) }),
5335
- /* @__PURE__ */ jsx17(
4784
+ }) }) : /* @__PURE__ */ jsxs13("div", { className: "eb:space-y-5 eb:p-4", children: [
4785
+ selection.content !== null && selection.content !== void 0 && /* @__PURE__ */ jsxs13("section", { className: "eb:space-y-3", children: [
4786
+ /* @__PURE__ */ jsx14(SectionLabel, { children: t(GROUPS.content) }),
4787
+ /* @__PURE__ */ jsx14(
5336
4788
  PropertyField,
5337
4789
  {
5338
4790
  idPrefix: "eb-prop",
@@ -5343,16 +4795,16 @@ function PropertiesPanel({
5343
4795
  `${selection.id}:content`
5344
4796
  )
5345
4797
  ] }),
5346
- selection.tagName === "mj-text" && /* @__PURE__ */ jsx17("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
5347
- services?.rewrite && REWRITABLE_TAGS.has(selection.tagName) && /* @__PURE__ */ jsx17(RewriteText, { editor, selection, rewrite: services.rewrite }, selection.id),
5348
- /* @__PURE__ */ jsx17(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
4798
+ selection.tagName === "mj-text" && /* @__PURE__ */ jsx14("p", { className: "eb:text-[12px] eb:text-foreground-muted", children: t("Double-click the text to edit it.") }),
4799
+ ai?.rewrite && REWRITABLE_TAGS.has(selection.tagName) && /* @__PURE__ */ jsx14(ai.components.Rewrite, { editor, selection, rewrite: ai.rewrite }, selection.id),
4800
+ /* @__PURE__ */ jsx14(PropertyGroups, { editor, element: selection, block, idPrefix: "eb-prop", services })
5349
4801
  ] })
5350
4802
  ] }) });
5351
4803
  }
5352
4804
 
5353
4805
  // src/ui/Toolbar.jsx
5354
4806
  import { ArrowLeft as ArrowLeft2, Monitor, Redo2, Smartphone, Undo2 } from "lucide-react";
5355
- import { Fragment as Fragment8, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
4807
+ import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
5356
4808
  function Toolbar({
5357
4809
  title,
5358
4810
  status,
@@ -5369,8 +4821,8 @@ function Toolbar({
5369
4821
  compact = false
5370
4822
  }) {
5371
4823
  const t = useT();
5372
- const identity2 = /* @__PURE__ */ jsxs17(Fragment8, { children: [
5373
- /* @__PURE__ */ jsxs17(
4824
+ const identity2 = /* @__PURE__ */ jsxs14(Fragment7, { children: [
4825
+ /* @__PURE__ */ jsxs14(
5374
4826
  Button,
5375
4827
  {
5376
4828
  variant: "ghost",
@@ -5378,50 +4830,50 @@ function Toolbar({
5378
4830
  onClick: onBack,
5379
4831
  "aria-label": compact ? t("Back") : void 0,
5380
4832
  children: [
5381
- /* @__PURE__ */ jsx18(ArrowLeft2, {}),
4833
+ /* @__PURE__ */ jsx15(ArrowLeft2, {}),
5382
4834
  !compact && t("Back")
5383
4835
  ]
5384
4836
  }
5385
4837
  ),
5386
- /* @__PURE__ */ jsx18(Separator, { orientation: "vertical", className: "eb:h-5" }),
5387
- /* @__PURE__ */ jsxs17("div", { className: cn("eb:min-w-0 eb:flex-1", !compact && "eb:max-w-[320px]"), children: [
5388
- /* @__PURE__ */ jsx18("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx18(TruncatedText, { children: title || t("New template") }) }),
5389
- /* @__PURE__ */ jsx18("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
4838
+ /* @__PURE__ */ jsx15(Separator, { orientation: "vertical", className: "eb:h-5" }),
4839
+ /* @__PURE__ */ jsxs14("div", { className: cn("eb:min-w-0 eb:flex-1", !compact && "eb:max-w-[320px]"), children: [
4840
+ /* @__PURE__ */ jsx15("p", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: /* @__PURE__ */ jsx15(TruncatedText, { children: title || t("New template") }) }),
4841
+ /* @__PURE__ */ jsx15("p", { className: "eb:text-[11px] eb:text-foreground-muted", role: "status", "aria-live": "polite", children: status })
5390
4842
  ] })
5391
4843
  ] });
5392
- const editing = /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: [
5393
- /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
5394
- /* @__PURE__ */ jsx18(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx18(Undo2, {}) }) }),
5395
- /* @__PURE__ */ jsx18(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx18(Redo2, {}) }) })
4844
+ const editing = /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: [
4845
+ /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:items-center eb:gap-0.5", children: [
4846
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Undo (Ctrl+Z)"), children: /* @__PURE__ */ jsx15(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onUndo, disabled: !ready || !canUndo, "aria-label": t("Undo"), children: /* @__PURE__ */ jsx15(Undo2, {}) }) }),
4847
+ /* @__PURE__ */ jsx15(TooltipWrap, { label: t("Redo (Ctrl+Y)"), children: /* @__PURE__ */ jsx15(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onRedo, disabled: !ready || !canRedo, "aria-label": t("Redo"), children: /* @__PURE__ */ jsx15(Redo2, {}) }) })
5396
4848
  ] }),
5397
- /* @__PURE__ */ jsx18(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs17(TabsList, { "aria-label": t("Device"), children: [
5398
- /* @__PURE__ */ jsxs17(TabsTrigger, { value: "desktop", disabled: !ready, "aria-label": compact ? t("Desktop") : void 0, children: [
5399
- /* @__PURE__ */ jsx18(Monitor, {}),
4849
+ /* @__PURE__ */ jsx15(Tabs, { value: device, onValueChange: onDeviceChange, children: /* @__PURE__ */ jsxs14(TabsList, { "aria-label": t("Device"), children: [
4850
+ /* @__PURE__ */ jsxs14(TabsTrigger, { value: "desktop", disabled: !ready, "aria-label": compact ? t("Desktop") : void 0, children: [
4851
+ /* @__PURE__ */ jsx15(Monitor, {}),
5400
4852
  !compact && t("Desktop")
5401
4853
  ] }),
5402
- /* @__PURE__ */ jsxs17(TabsTrigger, { value: "mobile", disabled: !ready, "aria-label": compact ? t("Mobile") : void 0, children: [
5403
- /* @__PURE__ */ jsx18(Smartphone, {}),
4854
+ /* @__PURE__ */ jsxs14(TabsTrigger, { value: "mobile", disabled: !ready, "aria-label": compact ? t("Mobile") : void 0, children: [
4855
+ /* @__PURE__ */ jsx15(Smartphone, {}),
5404
4856
  !compact && t("Mobile")
5405
4857
  ] })
5406
4858
  ] }) })
5407
4859
  ] });
5408
4860
  if (compact) {
5409
- return /* @__PURE__ */ jsxs17("div", { className: "eb:shrink-0 eb:border-b eb:border-border", "data-layout": "compact", children: [
5410
- /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:h-12 eb:items-center eb:gap-2 eb:px-2", children: [
4861
+ return /* @__PURE__ */ jsxs14("div", { className: "eb:shrink-0 eb:border-b eb:border-border", "data-layout": "compact", children: [
4862
+ /* @__PURE__ */ jsxs14("div", { className: "eb:flex eb:h-12 eb:items-center eb:gap-2 eb:px-2", children: [
5411
4863
  identity2,
5412
4864
  primary
5413
4865
  ] }),
5414
- /* @__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: [
4866
+ /* @__PURE__ */ jsxs14("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: [
5415
4867
  editing,
5416
- /* @__PURE__ */ jsx18(Separator, { orientation: "vertical", className: "eb:h-5 eb:shrink-0" }),
5417
- /* @__PURE__ */ jsx18("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: actions })
4868
+ /* @__PURE__ */ jsx15(Separator, { orientation: "vertical", className: "eb:h-5 eb:shrink-0" }),
4869
+ /* @__PURE__ */ jsx15("div", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-2", children: actions })
5418
4870
  ] })
5419
4871
  ] });
5420
4872
  }
5421
- 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: [
4873
+ return /* @__PURE__ */ jsxs14("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: [
5422
4874
  identity2,
5423
4875
  editing,
5424
- /* @__PURE__ */ jsxs17("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", "data-testid": "eb-toolbar-actions", children: [
4876
+ /* @__PURE__ */ jsxs14("div", { className: "eb:ml-auto eb:flex eb:items-center eb:gap-2", "data-testid": "eb-toolbar-actions", children: [
5425
4877
  actions,
5426
4878
  primary
5427
4879
  ] })
@@ -5429,12 +4881,12 @@ function Toolbar({
5429
4881
  }
5430
4882
 
5431
4883
  // src/ui/Drawer.jsx
5432
- import { useEffect as useEffect10 } from "react";
4884
+ import { useEffect as useEffect7 } from "react";
5433
4885
  import { X as X2 } from "lucide-react";
5434
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
4886
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
5435
4887
  function Drawer({ open, side = "left", title, onClose, children, testId, header = true }) {
5436
4888
  const t = useT();
5437
- useEffect10(() => {
4889
+ useEffect7(() => {
5438
4890
  if (!open) return void 0;
5439
4891
  const onKey = (e) => {
5440
4892
  if (e.key === "Escape") onClose();
@@ -5442,9 +4894,9 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
5442
4894
  window.addEventListener("keydown", onKey);
5443
4895
  return () => window.removeEventListener("keydown", onKey);
5444
4896
  }, [open, onClose]);
5445
- 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: [
5446
- /* @__PURE__ */ jsx19("div", { className: "eb:absolute eb:inset-0 eb:bg-black/30", onClick: onClose, "aria-hidden": "true" }),
5447
- /* @__PURE__ */ jsxs18(
4897
+ return /* @__PURE__ */ jsxs15("div", { className: cn("eb:absolute eb:inset-0 eb:z-20", !open && "eb:hidden"), "data-testid": testId, "data-open": open ? "true" : "false", children: [
4898
+ /* @__PURE__ */ jsx16("div", { className: "eb:absolute eb:inset-0 eb:bg-black/30", onClick: onClose, "aria-hidden": "true" }),
4899
+ /* @__PURE__ */ jsxs15(
5448
4900
  "section",
5449
4901
  {
5450
4902
  role: "dialog",
@@ -5454,11 +4906,11 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
5454
4906
  side === "left" ? "eb:left-0 eb:border-r eb:border-border" : "eb:right-0 eb:border-l eb:border-border"
5455
4907
  ),
5456
4908
  children: [
5457
- header && /* @__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: [
5458
- /* @__PURE__ */ jsx19("h3", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: title }),
5459
- /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx19(X2, {}) })
4909
+ header && /* @__PURE__ */ jsxs15("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: [
4910
+ /* @__PURE__ */ jsx16("h3", { className: "eb:text-[13px] eb:font-medium eb:text-foreground", children: title }),
4911
+ /* @__PURE__ */ jsx16(Button, { variant: "ghost", size: "icon-sm-compact", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx16(X2, {}) })
5460
4912
  ] }),
5461
- /* @__PURE__ */ jsx19("div", { className: "eb:grid eb:min-h-0 eb:flex-1", children })
4913
+ /* @__PURE__ */ jsx16("div", { className: "eb:grid eb:min-h-0 eb:flex-1", children })
5462
4914
  ]
5463
4915
  }
5464
4916
  )
@@ -5466,10 +4918,10 @@ function Drawer({ open, side = "left", title, onClose, children, testId, header
5466
4918
  }
5467
4919
 
5468
4920
  // src/ui/useEmailEditorState.js
5469
- import { useCallback, useEffect as useEffect11, useRef as useRef10, useState as useState14 } from "react";
4921
+ import { useCallback, useEffect as useEffect8, useRef as useRef7, useState as useState11 } from "react";
5470
4922
  var DIRTY_CHECK_MS = 150;
5471
4923
  function useEmailEditorState(editor) {
5472
- const [state, setState] = useState14(() => ({
4924
+ const [state, setState] = useState11(() => ({
5473
4925
  status: editor?.status || "idle",
5474
4926
  selection: null,
5475
4927
  canUndo: false,
@@ -5478,9 +4930,9 @@ function useEmailEditorState(editor) {
5478
4930
  changes: 0,
5479
4931
  dirty: false
5480
4932
  }));
5481
- const clean = useRef10(null);
5482
- const forced = useRef10(false);
5483
- const timer = useRef10(null);
4933
+ const clean = useRef7(null);
4934
+ const forced = useRef7(false);
4935
+ const timer = useRef7(null);
5484
4936
  const snapshot = useCallback(() => {
5485
4937
  try {
5486
4938
  return editor.status === "ready" ? editor.exportMjml() : null;
@@ -5503,7 +4955,7 @@ function useEmailEditorState(editor) {
5503
4955
  forced.current = true;
5504
4956
  setState((s) => ({ ...s, dirty: true }));
5505
4957
  }, []);
5506
- useEffect11(() => {
4958
+ useEffect8(() => {
5507
4959
  if (!editor) return void 0;
5508
4960
  const patch = (values) => setState((s) => ({ ...s, ...values }));
5509
4961
  const measure = () => {
@@ -5550,7 +5002,7 @@ function useEmailEditorState(editor) {
5550
5002
  }
5551
5003
 
5552
5004
  // src/ui/EmailBuilder.jsx
5553
- import { Fragment as Fragment9, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
5005
+ import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
5554
5006
  var EMPTY_META = { name: "", subject: "", preheader: "", description: "" };
5555
5007
  function metaOf(template) {
5556
5008
  return {
@@ -5576,19 +5028,20 @@ function Builder({
5576
5028
  onSaved,
5577
5029
  onDirtyChange,
5578
5030
  dirtyCheckRef,
5031
+ ai,
5579
5032
  className
5580
5033
  }) {
5581
5034
  const { t, locale, notify, confirm } = useUi();
5582
- const tRef = useRef11(t);
5035
+ const tRef = useRef8(t);
5583
5036
  tRef.current = t;
5584
- const editor = useMemo6(
5037
+ const editor = useMemo5(
5585
5038
  () => injected || createDefaultEmailEditor({ locale, t: (key, values) => tRef.current(key, values) }),
5586
5039
  [injected, locale]
5587
5040
  );
5588
- const storage = useMemo6(() => injectedStorage || createHttpStorage(), [injectedStorage]);
5589
- const systemVariables = useMemo6(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
5590
- const [customVariables, setCustomVariables] = useState15([]);
5591
- const variables = useMemo6(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
5041
+ const storage = useMemo5(() => injectedStorage || createHttpStorage(), [injectedStorage]);
5042
+ const systemVariables = useMemo5(() => defineVariables(variablesProp || DEFAULT_VARIABLES), [variablesProp]);
5043
+ const [customVariables, setCustomVariables] = useState12([]);
5044
+ const variables = useMemo5(() => mergeVariables(systemVariables, customVariables), [systemVariables, customVariables]);
5592
5045
  const canManageVariables = typeof storage.saveVariable === "function" && typeof storage.listVariables === "function";
5593
5046
  const reloadVariables = useCallback2(async () => {
5594
5047
  if (typeof storage.listVariables !== "function") return;
@@ -5598,52 +5051,45 @@ function Builder({
5598
5051
  notify("error", error.message);
5599
5052
  }
5600
5053
  }, [storage, notify]);
5601
- useEffect12(() => {
5054
+ useEffect9(() => {
5602
5055
  reloadVariables();
5603
5056
  }, [reloadVariables]);
5604
5057
  const { brand, setBrand } = useBrand(storage);
5605
- const brandSender = useMemo6(() => brandedSender(sender, brand), [sender, brand]);
5606
- const starter = useMemo6(
5058
+ const brandSender = useMemo5(() => brandedSender(sender, brand), [sender, brand]);
5059
+ const starter = useMemo5(
5607
5060
  () => brandTemplate(starterMjml || createStarterMjml({ sender: brandSender, t }), brand, { sender, t }),
5608
5061
  [starterMjml, brandSender, brand, sender, t]
5609
5062
  );
5610
- const templates = useMemo6(
5063
+ const templates = useMemo5(
5611
5064
  () => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: brandSender, t }).map((template2) => ({
5612
5065
  ...template2,
5613
5066
  mjml: brandTemplate(template2.mjml, brand, { sender, t })
5614
5067
  })),
5615
5068
  [galleryProp, brandSender, brand, sender, t]
5616
5069
  );
5617
- useEffect12(() => {
5070
+ useEffect9(() => {
5618
5071
  editor.setBrand?.(hasBrand(brand) ? brand : null);
5619
5072
  }, [editor, brand]);
5620
- const services = useMemo6(
5621
- () => ({
5622
- uploadImage: (file) => storage.uploadImage(file),
5623
- rewrite: typeof storage.rewrite === "function" ? (input, options) => storage.rewrite(input, options) : null,
5624
- suggestSubjects: typeof storage.suggestSubjects === "function" ? (input, options) => storage.suggestSubjects(input, options) : null
5625
- }),
5626
- [storage]
5627
- );
5073
+ const services = useMemo5(() => ({ uploadImage: (file) => storage.uploadImage(file) }), [storage]);
5628
5074
  const state = useEmailEditorState(editor);
5629
5075
  const autosaveOn = isFeatureEnabled("autosave");
5630
5076
  const autosaveOptions = emailBuilderFeatures.get("autosave")?.options || {};
5631
- const [template, setTemplate] = useState15({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
5632
- const [boot, setBoot] = useState15({ status: "loading", document: null, mjml: null, draft: false, message: null });
5633
- const [meta, setMeta] = useState15(EMPTY_META);
5634
- const [savedMeta, setSavedMeta] = useState15(EMPTY_META);
5635
- const [saving, setSaving] = useState15(null);
5636
- const [showErrors, setShowErrors] = useState15(false);
5637
- const [autosaveBlocked, setAutosaveBlocked] = useState15(null);
5638
- const [engineError, setEngineError] = useState15(null);
5639
- const [attempt, setAttempt] = useState15(0);
5640
- const rootRef = useRef11(null);
5077
+ const [template, setTemplate] = useState12({ id: initialTemplateId, revision: null, savedAt: null, auto: false });
5078
+ const [boot, setBoot] = useState12({ status: "loading", document: null, mjml: null, draft: false, message: null });
5079
+ const [meta, setMeta] = useState12(EMPTY_META);
5080
+ const [savedMeta, setSavedMeta] = useState12(EMPTY_META);
5081
+ const [saving, setSaving] = useState12(null);
5082
+ const [showErrors, setShowErrors] = useState12(false);
5083
+ const [autosaveBlocked, setAutosaveBlocked] = useState12(null);
5084
+ const [engineError, setEngineError] = useState12(null);
5085
+ const [attempt, setAttempt] = useState12(0);
5086
+ const rootRef = useRef8(null);
5641
5087
  const layout2 = useLayoutWidth(rootRef);
5642
5088
  const compact = layout2 !== "wide";
5643
5089
  const blocksInDrawer = layout2 === "narrow";
5644
- const [drawer, setDrawer] = useState15(null);
5090
+ const [drawer, setDrawer] = useState12(null);
5645
5091
  const closeDrawer = useCallback2(() => setDrawer(null), []);
5646
- useEffect12(() => {
5092
+ useEffect9(() => {
5647
5093
  if (!compact) setDrawer(null);
5648
5094
  else if (!blocksInDrawer) setDrawer((d) => d === "blocks" ? null : d);
5649
5095
  }, [compact, blocksInDrawer]);
@@ -5654,8 +5100,8 @@ function Builder({
5654
5100
  (date) => date ? new Date(date).toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" }) : "",
5655
5101
  [locale]
5656
5102
  );
5657
- const booted = useRef11(false);
5658
- useEffect12(() => {
5103
+ const booted = useRef8(false);
5104
+ useEffect9(() => {
5659
5105
  if (booted.current) return;
5660
5106
  booted.current = true;
5661
5107
  (async () => {
@@ -5715,26 +5161,26 @@ function Builder({
5715
5161
  if (!boot.document) editor.loadMjml(boot.mjml || initialMjml || starter);
5716
5162
  }, [editor, boot.document, boot.mjml, starter, initialMjml]);
5717
5163
  const { markDirty, markClean, checkDirty } = state;
5718
- useEffect12(() => {
5164
+ useEffect9(() => {
5719
5165
  if (ready && boot.draft) markDirty();
5720
5166
  }, [ready, boot.draft, markDirty]);
5721
5167
  const isDirty = useCallback2(
5722
5168
  () => editor.status === "ready" && checkDirty() || metaDirty,
5723
5169
  [editor, checkDirty, metaDirty]
5724
5170
  );
5725
- const reportDirty = useRef11(onDirtyChange);
5171
+ const reportDirty = useRef8(onDirtyChange);
5726
5172
  reportDirty.current = onDirtyChange;
5727
- useEffect12(() => {
5173
+ useEffect9(() => {
5728
5174
  reportDirty.current?.(dirty);
5729
5175
  }, [dirty]);
5730
- useEffect12(() => {
5176
+ useEffect9(() => {
5731
5177
  if (!dirtyCheckRef) return void 0;
5732
5178
  dirtyCheckRef.current = isDirty;
5733
5179
  return () => {
5734
5180
  dirtyCheckRef.current = null;
5735
5181
  };
5736
5182
  }, [dirtyCheckRef, isDirty]);
5737
- useEffect12(() => {
5183
+ useEffect9(() => {
5738
5184
  if (!dirty) return void 0;
5739
5185
  const warn = (e) => {
5740
5186
  e.preventDefault();
@@ -5743,8 +5189,8 @@ function Builder({
5743
5189
  window.addEventListener("beforeunload", warn);
5744
5190
  return () => window.removeEventListener("beforeunload", warn);
5745
5191
  }, [dirty]);
5746
- const savingRef = useRef11(false);
5747
- const latest = useRef11({});
5192
+ const savingRef = useRef8(false);
5193
+ const latest = useRef8({});
5748
5194
  latest.current = { template, meta, onSaved, compact };
5749
5195
  const save = useCallback2(
5750
5196
  async ({ auto = false } = {}) => {
@@ -5759,9 +5205,9 @@ function Builder({
5759
5205
  }
5760
5206
  return false;
5761
5207
  }
5762
- const document2 = editor.getDocument();
5208
+ const document = editor.getDocument();
5763
5209
  const mjml6 = editor.exportMjml();
5764
- const payload = { ...currentMeta, document: document2, mjml: mjml6 };
5210
+ const payload = { ...currentMeta, document, mjml: mjml6 };
5765
5211
  savingRef.current = true;
5766
5212
  setSaving(auto ? "auto" : "manual");
5767
5213
  try {
@@ -5774,11 +5220,19 @@ function Builder({
5774
5220
  if (!current.id) clearLocalDraft();
5775
5221
  latest.current.onSaved?.(result.template);
5776
5222
  const warnings = result.warnings || [];
5223
+ const withoutPicture = auto ? 0 : countImagesWithoutPicture(mjml6);
5777
5224
  if (warnings.length) {
5778
5225
  notify(
5779
5226
  "warning",
5780
5227
  t("Saved, with {count} MJML warning(s): {message}", { count: warnings.length, message: warnings[0].message })
5781
5228
  );
5229
+ } else if (withoutPicture) {
5230
+ notify(
5231
+ "warning",
5232
+ t("Saved. {count} image(s) have no picture yet: they are left out of the email until you add one.", {
5233
+ count: withoutPicture
5234
+ })
5235
+ );
5782
5236
  } else if (!auto) {
5783
5237
  notify(
5784
5238
  "success",
@@ -5827,7 +5281,7 @@ function Builder({
5827
5281
  },
5828
5282
  [editor, storage, markClean, notify, confirm, t]
5829
5283
  );
5830
- useEffect12(() => {
5284
+ useEffect9(() => {
5831
5285
  const onKey = (e) => {
5832
5286
  if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "s") {
5833
5287
  e.preventDefault();
@@ -5837,11 +5291,11 @@ function Builder({
5837
5291
  window.addEventListener("keydown", onKey, true);
5838
5292
  return () => window.removeEventListener("keydown", onKey, true);
5839
5293
  }, [save]);
5840
- useEffect12(() => {
5294
+ useEffect9(() => {
5841
5295
  if (!ready) return void 0;
5842
5296
  return editor.addShortcut("save", "mod+s", () => save());
5843
5297
  }, [ready, editor, save]);
5844
- const scheduler = useMemo6(
5298
+ const scheduler = useMemo5(
5845
5299
  () => createAutosaveScheduler({
5846
5300
  run: () => save({ auto: true }),
5847
5301
  idleMs: autosaveOptions.idleMs,
@@ -5849,13 +5303,13 @@ function Builder({
5849
5303
  }),
5850
5304
  [save, autosaveOptions.idleMs, autosaveOptions.maxWaitMs]
5851
5305
  );
5852
- useEffect12(() => () => scheduler.dispose(), [scheduler]);
5853
- useEffect12(() => {
5306
+ useEffect9(() => () => scheduler.dispose(), [scheduler]);
5307
+ useEffect9(() => {
5854
5308
  if (!autosaveOn || !ready || !template.id || autosaveBlocked) return;
5855
5309
  if (dirty) scheduler.notify();
5856
5310
  else scheduler.reset();
5857
5311
  }, [autosaveOn, ready, template.id, autosaveBlocked, dirty, state.changes, meta, scheduler]);
5858
- useEffect12(() => {
5312
+ useEffect9(() => {
5859
5313
  if (!autosaveOn || !ready || template.id || !dirty) return void 0;
5860
5314
  const timer = setTimeout(() => {
5861
5315
  if (editor.status === "ready") saveLocalDraft({ document: editor.getDocument(), meta });
@@ -5896,12 +5350,13 @@ function Builder({
5896
5350
  sender: brandSender,
5897
5351
  hostSender: sender,
5898
5352
  brand,
5899
- setBrand
5353
+ setBrand,
5354
+ ai
5900
5355
  };
5901
5356
  const toolbarFeatures = emailBuilderFeatures.list(
5902
5357
  (f) => f.enabled !== false && Boolean(f.toolbar) && (f.isAvailable ? f.isAvailable(featureContext) : true)
5903
5358
  );
5904
- const properties = /* @__PURE__ */ jsx20(
5359
+ const properties = /* @__PURE__ */ jsx17(
5905
5360
  PropertiesPanel,
5906
5361
  {
5907
5362
  onDismiss: compact ? closeDrawer : null,
@@ -5911,12 +5366,13 @@ function Builder({
5911
5366
  onMetaChange: setMeta,
5912
5367
  changes: state.changes,
5913
5368
  showErrors,
5914
- actions: /* @__PURE__ */ jsx20(ElementActions, { editor, selection: state.selection }),
5369
+ actions: /* @__PURE__ */ jsx17(ElementActions, { editor, selection: state.selection }),
5915
5370
  services,
5371
+ ai,
5916
5372
  variables
5917
5373
  }
5918
5374
  );
5919
- return /* @__PURE__ */ jsxs19(
5375
+ return /* @__PURE__ */ jsxs16(
5920
5376
  "div",
5921
5377
  {
5922
5378
  ref: rootRef,
@@ -5926,7 +5382,7 @@ function Builder({
5926
5382
  "data-layout": layout2,
5927
5383
  "data-template-id": template.id || "",
5928
5384
  children: [
5929
- /* @__PURE__ */ jsx20(
5385
+ /* @__PURE__ */ jsx17(
5930
5386
  Toolbar,
5931
5387
  {
5932
5388
  title: meta.name,
@@ -5940,17 +5396,21 @@ function Builder({
5940
5396
  onDeviceChange: (device) => editor.setDevice(device),
5941
5397
  onBack: onClose ? back : void 0,
5942
5398
  compact,
5943
- actions: toolbarFeatures.map((feature) => {
5944
- const Action = feature.toolbar;
5945
- return /* @__PURE__ */ jsx20(Action, { context: featureContext }, feature.id);
5946
- }),
5947
- primary: /* @__PURE__ */ jsxs19(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
5948
- saving === "manual" ? /* @__PURE__ */ jsx20(Loader28, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx20(Save, {}),
5399
+ actions: [
5400
+ // The AI module's button first, when the host gave the module.
5401
+ ai?.generate && /* @__PURE__ */ jsx17(ai.components.Generate, { context: featureContext, generate: ai.generate }, "ai-generate"),
5402
+ ...toolbarFeatures.map((feature) => {
5403
+ const Action = feature.toolbar;
5404
+ return /* @__PURE__ */ jsx17(Action, { context: featureContext }, feature.id);
5405
+ })
5406
+ ].filter(Boolean),
5407
+ primary: /* @__PURE__ */ jsxs16(Button, { size: "sm-compact", onClick: () => save(), disabled: !ready || saving !== null, children: [
5408
+ saving === "manual" ? /* @__PURE__ */ jsx17(Loader25, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx17(Save, {}),
5949
5409
  t("Save")
5950
5410
  ] })
5951
5411
  }
5952
5412
  ),
5953
- /* @__PURE__ */ jsxs19(
5413
+ /* @__PURE__ */ jsxs16(
5954
5414
  "div",
5955
5415
  {
5956
5416
  className: cn(
@@ -5960,12 +5420,12 @@ function Builder({
5960
5420
  layout2 === "narrow" && "eb:grid-cols-1"
5961
5421
  ),
5962
5422
  children: [
5963
- !blocksInDrawer && /* @__PURE__ */ jsx20(BlockSidebar, { editor, disabled: !ready, onAdded: compact ? () => setDrawer("properties") : void 0 }),
5964
- /* @__PURE__ */ jsx20("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: [
5965
- /* @__PURE__ */ jsx20(AlertTriangle, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
5966
- /* @__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.") }),
5967
- /* @__PURE__ */ jsx20("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
5968
- engineError && /* @__PURE__ */ jsx20(
5423
+ !blocksInDrawer && /* @__PURE__ */ jsx17(BlockSidebar, { editor, disabled: !ready, onAdded: compact ? () => setDrawer("properties") : void 0 }),
5424
+ /* @__PURE__ */ jsx17("div", { className: "eb:relative eb:min-w-0 eb:bg-surface-75", "aria-busy": !ready, children: boot.status === "error" || engineError ? /* @__PURE__ */ jsxs16("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: [
5425
+ /* @__PURE__ */ jsx17(AlertTriangle, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
5426
+ /* @__PURE__ */ jsx17("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.") }),
5427
+ /* @__PURE__ */ jsx17("p", { className: "eb:max-w-sm eb:text-[12px] eb:text-foreground-muted", children: boot.message || engineError }),
5428
+ engineError && /* @__PURE__ */ jsx17(
5969
5429
  Button,
5970
5430
  {
5971
5431
  variant: "outline",
@@ -5977,8 +5437,8 @@ function Builder({
5977
5437
  children: t("Retry")
5978
5438
  }
5979
5439
  )
5980
- ] }) : /* @__PURE__ */ jsxs19(Fragment9, { children: [
5981
- boot.status === "ready" && /* @__PURE__ */ jsx20(
5440
+ ] }) : /* @__PURE__ */ jsxs16(Fragment8, { children: [
5441
+ boot.status === "ready" && /* @__PURE__ */ jsx17(
5982
5442
  EmailCanvas,
5983
5443
  {
5984
5444
  editor,
@@ -5988,14 +5448,14 @@ function Builder({
5988
5448
  },
5989
5449
  attempt
5990
5450
  ),
5991
- !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: [
5992
- /* @__PURE__ */ jsx20(Loader28, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
5993
- /* @__PURE__ */ jsx20("span", { className: "eb:sr-only", children: t("Loading the builder") })
5451
+ !ready && /* @__PURE__ */ jsxs16("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: [
5452
+ /* @__PURE__ */ jsx17(Loader25, { className: "eb:size-6 eb:animate-spin eb:text-foreground-muted", "aria-hidden": "true" }),
5453
+ /* @__PURE__ */ jsx17("span", { className: "eb:sr-only", children: t("Loading the builder") })
5994
5454
  ] })
5995
5455
  ] }) }),
5996
5456
  !compact && properties,
5997
- blocksInDrawer && /* @__PURE__ */ jsx20(Drawer, { open: drawer === "blocks", side: "left", title: t("Add a block"), onClose: closeDrawer, testId: "eb-blocks-drawer", children: /* @__PURE__ */ jsx20(BlockSidebar, { editor, disabled: !ready, onAdded: () => setDrawer("properties") }) }),
5998
- compact && /* @__PURE__ */ jsx20(
5457
+ blocksInDrawer && /* @__PURE__ */ jsx17(Drawer, { open: drawer === "blocks", side: "left", title: t("Add a block"), onClose: closeDrawer, testId: "eb-blocks-drawer", children: /* @__PURE__ */ jsx17(BlockSidebar, { editor, disabled: !ready, onAdded: () => setDrawer("properties") }) }),
5458
+ compact && /* @__PURE__ */ jsx17(
5999
5459
  Drawer,
6000
5460
  {
6001
5461
  open: drawer === "properties",
@@ -6010,12 +5470,12 @@ function Builder({
6010
5470
  ]
6011
5471
  }
6012
5472
  ),
6013
- 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: [
6014
- blocksInDrawer && /* @__PURE__ */ jsxs19(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setDrawer("blocks"), children: [
6015
- /* @__PURE__ */ jsx20(Plus2, {}),
5473
+ compact && /* @__PURE__ */ jsxs16("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: [
5474
+ blocksInDrawer && /* @__PURE__ */ jsxs16(Button, { variant: "outline", size: "sm-compact", disabled: !ready, onClick: () => setDrawer("blocks"), children: [
5475
+ /* @__PURE__ */ jsx17(Plus2, {}),
6016
5476
  t("Add a block")
6017
5477
  ] }),
6018
- /* @__PURE__ */ jsxs19(
5478
+ /* @__PURE__ */ jsxs16(
6019
5479
  Button,
6020
5480
  {
6021
5481
  variant: state.selection ? "default" : "outline",
@@ -6024,25 +5484,44 @@ function Builder({
6024
5484
  disabled: !ready,
6025
5485
  onClick: () => setDrawer("properties"),
6026
5486
  children: [
6027
- /* @__PURE__ */ jsx20(SlidersHorizontal, {}),
6028
- /* @__PURE__ */ jsx20("span", { className: "eb:truncate", children: ready && state.selection ? t("Edit: {element}", { element: t(state.selection.label) }) : t("Email settings") })
5487
+ /* @__PURE__ */ jsx17(SlidersHorizontal, {}),
5488
+ /* @__PURE__ */ jsx17("span", { className: "eb:truncate", children: ready && state.selection ? t("Edit: {element}", { element: t(state.selection.label) }) : t("Email settings") })
6029
5489
  ]
6030
5490
  }
6031
5491
  )
6032
5492
  ] }),
6033
- /* @__PURE__ */ jsx20(NoticeArea, {})
5493
+ /* @__PURE__ */ jsx17(NoticeArea, {}),
5494
+ ai?.key && ai.components.Connect && /* @__PURE__ */ jsx17(ai.components.Connect, { aiKey: ai.key })
6034
5495
  ]
6035
5496
  }
6036
5497
  );
6037
5498
  }
6038
- function EmailBuilder({ locale = "en", messages, onNotify, ...props }) {
6039
- return /* @__PURE__ */ jsx20(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx20(Builder, { ...props, templateId: props.templateId ?? null }) });
5499
+ function EmailBuilder({ locale = "en", messages, onNotify, ai: aiProp = null, ...props }) {
5500
+ const ai = useAiModule(aiProp);
5501
+ const allMessages = useMemo5(
5502
+ () => ai ? { ...messagesOf(ai.messages, locale), ...messages } : messages,
5503
+ [ai, locale, messages]
5504
+ );
5505
+ return /* @__PURE__ */ jsx17(UiProvider, { locale, messages: allMessages, onNotify, children: /* @__PURE__ */ jsx17(Builder, { ...props, ai, templateId: props.templateId ?? null }) });
5506
+ }
5507
+ function useAiModule(value) {
5508
+ const valid = Boolean(value && typeof value === "object" && /** @type {any} */
5509
+ value.components);
5510
+ useEffect9(() => {
5511
+ if (value && !valid) {
5512
+ console.warn("EmailBuilder: `ai` must be made by createAi() from best-email-builder/ai \u2014 the AI stays off.");
5513
+ }
5514
+ }, [value, valid]);
5515
+ return valid ? (
5516
+ /** @type {import('../ai/createAi.js').EmailBuilderAi} */
5517
+ value
5518
+ ) : null;
6040
5519
  }
6041
5520
 
6042
5521
  // src/ui/EmailBuilderBoundary.jsx
6043
5522
  import { Component } from "react";
6044
5523
  import { AlertTriangle as AlertTriangle2, ArrowLeft as ArrowLeft3, RefreshCw } from "lucide-react";
6045
- import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
5524
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
6046
5525
  var isChunkError = (error) => error?.name === "ChunkLoadError" || /Loading (CSS )?chunk .* failed/i.test(String(error?.message || ""));
6047
5526
  var english = (text) => text;
6048
5527
  var EmailBuilderBoundary = class extends Component {
@@ -6067,23 +5546,23 @@ var EmailBuilderBoundary = class extends Component {
6067
5546
  const { error, attempt } = this.state;
6068
5547
  const t = this.props.t || english;
6069
5548
  if (!error) {
6070
- return /* @__PURE__ */ jsx21("div", { style: { height: "100%" }, children: this.props.children }, attempt);
5549
+ return /* @__PURE__ */ jsx18("div", { style: { height: "100%" }, children: this.props.children }, attempt);
6071
5550
  }
6072
- return /* @__PURE__ */ jsxs20(
5551
+ return /* @__PURE__ */ jsxs17(
6073
5552
  "div",
6074
5553
  {
6075
5554
  role: "alert",
6076
5555
  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",
6077
5556
  children: [
6078
- /* @__PURE__ */ jsx21(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
6079
- /* @__PURE__ */ jsx21("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
6080
- /* @__PURE__ */ jsxs20("div", { className: "eb:flex eb:gap-2", children: [
6081
- this.props.onClose && /* @__PURE__ */ jsxs20(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
6082
- /* @__PURE__ */ jsx21(ArrowLeft3, {}),
5557
+ /* @__PURE__ */ jsx18(AlertTriangle2, { className: "eb:size-6 eb:text-destructive", "aria-hidden": "true" }),
5558
+ /* @__PURE__ */ jsx18("p", { className: "eb:text-[13px] eb:text-foreground", children: t("The builder could not load.") }),
5559
+ /* @__PURE__ */ jsxs17("div", { className: "eb:flex eb:gap-2", children: [
5560
+ this.props.onClose && /* @__PURE__ */ jsxs17(Button, { variant: "outline", size: "sm", onClick: this.props.onClose, children: [
5561
+ /* @__PURE__ */ jsx18(ArrowLeft3, {}),
6083
5562
  t("Back")
6084
5563
  ] }),
6085
- /* @__PURE__ */ jsxs20(Button, { size: "sm", onClick: this.retry, children: [
6086
- /* @__PURE__ */ jsx21(RefreshCw, {}),
5564
+ /* @__PURE__ */ jsxs17(Button, { size: "sm", onClick: this.retry, children: [
5565
+ /* @__PURE__ */ jsx18(RefreshCw, {}),
6087
5566
  t("Retry")
6088
5567
  ] })
6089
5568
  ] })
@@ -6107,4 +5586,4 @@ export {
6107
5586
  EmailBuilder,
6108
5587
  EmailBuilderBoundary
6109
5588
  };
6110
- //# sourceMappingURL=chunk-MY6WPDT4.js.map
5589
+ //# sourceMappingURL=chunk-7QQH6MTV.js.map