best-email-builder 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +214 -19
  3. package/dist/builder.js +4 -4
  4. package/dist/{chunk-ZSUHOCCE.js → chunk-AT73Y7JA.js} +159 -83
  5. package/dist/chunk-AT73Y7JA.js.map +1 -0
  6. package/dist/chunk-HHLOE6OZ.js +1682 -0
  7. package/dist/chunk-HHLOE6OZ.js.map +1 -0
  8. package/dist/chunk-JNHFZXDL.js +1776 -0
  9. package/dist/chunk-JNHFZXDL.js.map +1 -0
  10. package/dist/chunk-MY6WPDT4.js +6110 -0
  11. package/dist/chunk-MY6WPDT4.js.map +1 -0
  12. package/dist/{chunk-JOC752I3.js → chunk-NAEGU6PR.js} +13 -4
  13. package/dist/{chunk-JOC752I3.js.map → chunk-NAEGU6PR.js.map} +1 -1
  14. package/dist/{chunk-BVOVAEEJ.js → chunk-TGDZ4NFI.js} +44 -3
  15. package/dist/chunk-TGDZ4NFI.js.map +1 -0
  16. package/dist/index.js +45 -8
  17. package/dist/index.js.map +1 -1
  18. package/dist/library.js +3 -3
  19. package/dist/server.js +544 -204
  20. package/dist/server.js.map +1 -1
  21. package/dist/styles.css +1 -1
  22. package/dist/types/blocks/shared.d.ts +20 -0
  23. package/dist/types/core/EmailEditor.d.ts +72 -19
  24. package/dist/types/core/constants.d.ts +1 -1
  25. package/dist/types/core/mjmlDefaults.d.ts +13 -0
  26. package/dist/types/core/scrollbars.d.ts +1 -1
  27. package/dist/types/defaultEditor.d.ts +4 -1
  28. package/dist/types/engine/NativeAdapter.d.ts +28 -0
  29. package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
  30. package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
  31. package/dist/types/engine/canvas/icons.d.ts +14 -0
  32. package/dist/types/engine/canvas/morph.d.ts +21 -0
  33. package/dist/types/engine/canvasMjml.d.ts +30 -0
  34. package/dist/types/engine/mjml.d.ts +31 -0
  35. package/dist/types/engine/model.d.ts +130 -0
  36. package/dist/types/engine/placement.d.ts +69 -0
  37. package/dist/types/engine/richText.d.ts +15 -0
  38. package/dist/types/engine/unwrapModule.d.ts +29 -0
  39. package/dist/types/features/brand/BrandButton.d.ts +4 -0
  40. package/dist/types/features/brand/brand.d.ts +108 -0
  41. package/dist/types/features/brand/extract.d.ts +61 -0
  42. package/dist/types/features/brand/index.d.ts +2 -0
  43. package/dist/types/features/brand/messages.d.ts +13 -0
  44. package/dist/types/features/brand/useBrand.d.ts +22 -0
  45. package/dist/types/features/generate/GenerateButton.d.ts +3 -0
  46. package/dist/types/features/generate/RewriteText.d.ts +5 -0
  47. package/dist/types/features/generate/SubjectIdeas.d.ts +16 -0
  48. package/dist/types/features/generate/chip.d.ts +1 -0
  49. package/dist/types/features/generate/claude.d.ts +21 -0
  50. package/dist/types/features/generate/draft.d.ts +122 -0
  51. package/dist/types/features/generate/generator.d.ts +118 -0
  52. package/dist/types/features/generate/index.d.ts +2 -0
  53. package/dist/types/features/generate/prompt.d.ts +76 -0
  54. package/dist/types/features/generate/purposes.d.ts +48 -0
  55. package/dist/types/features/generate/rewrite.d.ts +83 -0
  56. package/dist/types/features/generate/subjects.d.ts +83 -0
  57. package/dist/types/features/preview/PreviewButton.d.ts +1 -1
  58. package/dist/types/features/preview/darkMode.d.ts +48 -0
  59. package/dist/types/index.d.ts +10 -1
  60. package/dist/types/server/brand.d.ts +32 -0
  61. package/dist/types/server/brandImport.d.ts +93 -0
  62. package/dist/types/server/compileMjml.d.ts +2 -0
  63. package/dist/types/server/handler.d.ts +25 -0
  64. package/dist/types/server/index.d.ts +11 -0
  65. package/dist/types/storage/client.d.ts +75 -3
  66. package/dist/types/ui/fields.d.ts +2 -1
  67. package/dist/types/ui/primitives/button.d.ts +1 -1
  68. package/dist/types/ui/primitives/color-picker.d.ts +2 -1
  69. package/dist/types/ui/useLayoutWidth.d.ts +1 -1
  70. package/package.json +13 -7
  71. package/dist/chunk-3M36VVXS.js +0 -665
  72. package/dist/chunk-3M36VVXS.js.map +0 -1
  73. package/dist/chunk-57CTQNTK.js +0 -56
  74. package/dist/chunk-57CTQNTK.js.map +0 -1
  75. package/dist/chunk-BVOVAEEJ.js.map +0 -1
  76. package/dist/chunk-ILRAQTHS.js +0 -3677
  77. package/dist/chunk-ILRAQTHS.js.map +0 -1
  78. package/dist/chunk-ZSUHOCCE.js.map +0 -1
  79. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
  80. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
  81. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
@@ -1,14 +1,23 @@
1
1
  import {
2
+ DARK_PAGE,
2
3
  DEFAULT_ASSETS_PATH,
4
+ DEFAULT_BUTTON_URL,
3
5
  DEFAULT_VARIABLES,
4
6
  EMAIL_MOBILE_WIDTH,
7
+ FONT_STACK,
8
+ GROUPS,
5
9
  IMAGE_TYPES,
10
+ PALETTE,
11
+ cleanBrand,
6
12
  identity,
7
13
  imageUploadError,
14
+ inWhiteSection,
15
+ prop,
8
16
  readDocument,
17
+ simulateDarkMode,
9
18
  translatorFor,
10
19
  validateCustomVariable
11
- } from "./chunk-3M36VVXS.js";
20
+ } from "./chunk-HHLOE6OZ.js";
12
21
 
13
22
  // src/ui/context.jsx
14
23
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
@@ -117,6 +126,40 @@ function PopoverContent({ className, align = "center", sideOffset = 4, ...props
117
126
  }
118
127
  ) });
119
128
  }
129
+ function DropdownMenu(props) {
130
+ return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
131
+ }
132
+ function DropdownMenuTrigger(props) {
133
+ return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
134
+ }
135
+ function DropdownMenuContent({ className, sideOffset = 4, ...props }) {
136
+ return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
137
+ DropdownMenuPrimitive.Content,
138
+ {
139
+ "data-slot": "dropdown-menu-content",
140
+ sideOffset,
141
+ className: cn(
142
+ "eb-scope eb:z-50 eb:max-h-(--radix-dropdown-menu-content-available-height) eb:min-w-[8rem] eb:origin-(--radix-dropdown-menu-content-transform-origin) eb:overflow-x-hidden eb:overflow-y-auto eb:rounded-lg2 eb:border eb:border-border eb:bg-popover eb:p-1 eb:text-popover-foreground eb:shadow-md",
143
+ FADE_ZOOM,
144
+ className
145
+ ),
146
+ ...props
147
+ }
148
+ ) });
149
+ }
150
+ function DropdownMenuItem({ className, ...props }) {
151
+ return /* @__PURE__ */ jsx(
152
+ DropdownMenuPrimitive.Item,
153
+ {
154
+ "data-slot": "dropdown-menu-item",
155
+ className: cn(
156
+ "eb:relative eb:flex eb:cursor-default eb:items-center eb:gap-2 eb:rounded-sm eb:px-2 eb:py-1.5 eb:text-[13px] eb:outline-hidden eb:select-none eb:focus:bg-accent eb:focus:text-accent-foreground eb:data-[disabled]:pointer-events-none eb:data-[disabled]:opacity-50 eb:[&_svg]:pointer-events-none eb:[&_svg]:shrink-0 eb:[&_svg:not([class*='size-'])]:size-4",
157
+ className
158
+ ),
159
+ ...props
160
+ }
161
+ );
162
+ }
120
163
  function Select(props) {
121
164
  return /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
122
165
  }
@@ -409,7 +452,14 @@ function UiProvider({ locale = "en", messages, onNotify, children }) {
409
452
  return;
410
453
  }
411
454
  const id = nextId.current += 1;
412
- setNotices((list) => [...list.slice(-3), { id, level, message }]);
455
+ setNotices((list) => {
456
+ const same = list.filter((n) => n.level === level && n.message === message);
457
+ for (const notice of same) {
458
+ clearTimeout(timers.current.get(notice.id));
459
+ timers.current.delete(notice.id);
460
+ }
461
+ return [...list.filter((n) => !same.includes(n)).slice(-3), { id, level, message }];
462
+ });
413
463
  timers.current.set(
414
464
  id,
415
465
  setTimeout(() => dismiss(id), NOTICE_TTL[level] || 4e3)
@@ -470,65 +520,6 @@ function UiProvider({ locale = "en", messages, onNotify, children }) {
470
520
  ] }) });
471
521
  }
472
522
 
473
- // src/blocks/shared.js
474
- var PALETTE = {
475
- ink: "#2b3038",
476
- inkSoft: "#41506b",
477
- navy: "#212e5a",
478
- brand: "#2977bc",
479
- border: "#dfe6f0",
480
- canvas: "#eceff4",
481
- surface: "#ffffff",
482
- white: "#ffffff"
483
- };
484
- var DEFAULT_BUTTON_URL = "https://example.com";
485
- var FONT_STACK = "Arial, Helvetica, sans-serif";
486
- var FONT_OPTIONS = [
487
- { value: "Arial, Helvetica, sans-serif", label: "Arial" },
488
- { value: "Verdana, Geneva, sans-serif", label: "Verdana" },
489
- { value: "Tahoma, Geneva, sans-serif", label: "Tahoma" },
490
- { value: "'Trebuchet MS', Helvetica, sans-serif", label: "Trebuchet" },
491
- { value: "Georgia, Times, serif", label: "Georgia" },
492
- { value: "'Times New Roman', Times, serif", label: "Times" },
493
- { value: "'Courier New', Courier, monospace", label: "Courier" }
494
- ];
495
- var ALIGN_OPTIONS = [
496
- { value: "left", label: "Left" },
497
- { value: "center", label: "Center" },
498
- { value: "right", label: "Right" }
499
- ];
500
- var GROUPS = {
501
- content: "Content",
502
- link: "Link",
503
- typography: "Typography",
504
- colors: "Colors",
505
- layout: "Layout",
506
- spacing: "Spacing"
507
- };
508
- var prop = {
509
- /** @returns {BlockProperty} */
510
- fontFamily: () => ({ key: "font-family", label: "Font", type: "select", group: GROUPS.typography, options: FONT_OPTIONS }),
511
- /** @returns {BlockProperty} */
512
- fontSize: (min = 10, max = 48) => ({ key: "font-size", label: "Size", type: "size", group: GROUPS.typography, min, max, unit: "px" }),
513
- /** @returns {BlockProperty} */
514
- lineHeight: () => ({ key: "line-height", label: "Line height", type: "size", group: GROUPS.typography, min: 10, max: 80, unit: "px" }),
515
- /** @returns {BlockProperty} */
516
- color: (label = "Text") => ({ key: "color", label, type: "color", group: GROUPS.colors }),
517
- /** @returns {BlockProperty} */
518
- background: (key = "background-color", label = "Background") => ({ key, label, type: "color", group: GROUPS.colors }),
519
- /** @returns {BlockProperty} */
520
- align: (options = ALIGN_OPTIONS) => ({ key: "align", label: "Alignment", type: "align", group: GROUPS.layout, options }),
521
- /** @returns {BlockProperty} */
522
- padding: (key = "padding", label = "Padding") => ({ key, label, type: "spacing", group: GROUPS.spacing }),
523
- /** @returns {BlockProperty} */
524
- href: (label = "Link URL") => ({ key: "href", label, type: "url", group: GROUPS.link, placeholder: "https://" }),
525
- /** @returns {BlockProperty} */
526
- radius: (max = 40) => ({ key: "border-radius", label: "Corner radius", type: "size", group: GROUPS.layout, min: 0, max, unit: "px" })
527
- };
528
- function inWhiteSection(inner) {
529
- return `<mj-section background-color="${PALETTE.surface}" padding-top="0px" padding-bottom="0px"><mj-column>${inner}</mj-column></mj-section>`;
530
- }
531
-
532
523
  // src/features/templates/starter.js
533
524
  function escapeXml(text) {
534
525
  return String(text ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -834,7 +825,7 @@ function withDocument(template) {
834
825
  if (!template || !template.document) return template;
835
826
  return { ...template, document: readDocument(template.document) };
836
827
  }
837
- function createHttpStorage({ baseUrl = "/api/email-builder", headers = {}, fetch: fetchImpl } = {}) {
828
+ function createHttpStorage({ baseUrl = "/api/email-builder", headers = {}, fetch: fetchImpl, generate = false, brand = false } = {}) {
838
829
  const root = baseUrl.replace(/\/+$/, "");
839
830
  const doFetch = fetchImpl || ((input, init) => fetch(input, init));
840
831
  const extraHeaders = () => typeof headers === "function" ? headers() : headers;
@@ -873,6 +864,18 @@ function createHttpStorage({ baseUrl = "/api/email-builder", headers = {}, fetch
873
864
  deleteVariable: async (key) => {
874
865
  await call(`/variables/${encodeURIComponent(key)}`, { method: "DELETE" });
875
866
  },
867
+ ...generate ? {
868
+ generate: async (input, { signal } = {}) => call("/generate", { method: "POST", body: JSON.stringify(input), signal }),
869
+ rewrite: async (input, { signal } = {}) => call("/rewrite", { method: "POST", body: JSON.stringify(input), signal }),
870
+ suggestSubjects: async (input, { signal } = {}) => call("/subjects", { method: "POST", body: JSON.stringify(input), signal })
871
+ } : {},
872
+ ...brand ? {
873
+ loadBrand: async () => call("/brand"),
874
+ saveBrand: async (value) => call("/brand", { method: "PUT", body: JSON.stringify(value) }),
875
+ ...brand === true || brand.import !== false ? {
876
+ importBrand: async (url, { signal } = {}) => call("/brand/import", { method: "POST", body: JSON.stringify({ url }), signal })
877
+ } : {}
878
+ } : {},
876
879
  async uploadImage(file) {
877
880
  assertUploadable(file);
878
881
  const form = new FormData();
@@ -883,11 +886,22 @@ function createHttpStorage({ baseUrl = "/api/email-builder", headers = {}, fetch
883
886
  }
884
887
  };
885
888
  }
886
- function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PATH, systemVariables = DEFAULT_VARIABLES } = {}) {
889
+ function createMemoryStorage({
890
+ render,
891
+ sendTest,
892
+ generate,
893
+ rewrite,
894
+ suggestSubjects,
895
+ importBrand,
896
+ brand = null,
897
+ assetsPath = DEFAULT_ASSETS_PATH,
898
+ systemVariables = DEFAULT_VARIABLES
899
+ } = {}) {
887
900
  const rows = /* @__PURE__ */ new Map();
888
901
  const versions = /* @__PURE__ */ new Map();
889
902
  const images = /* @__PURE__ */ new Map();
890
903
  const customVariables = /* @__PURE__ */ new Map();
904
+ let savedBrand = cleanBrand(brand);
891
905
  let nextId = 1;
892
906
  let tick = Date.parse("2026-01-01T00:00:00.000Z");
893
907
  const now = () => new Date(tick += 1e3).toISOString();
@@ -1025,13 +1039,25 @@ function createMemoryStorage({ render, sendTest, assetsPath = DEFAULT_ASSETS_PAT
1025
1039
  async deleteVariable(key) {
1026
1040
  if (!customVariables.delete(String(key))) throw new StorageError("Variable not found", 404, "NOT_FOUND");
1027
1041
  },
1042
+ async loadBrand() {
1043
+ return { ...savedBrand };
1044
+ },
1045
+ async saveBrand(value) {
1046
+ savedBrand = cleanBrand(value);
1047
+ return { ...savedBrand };
1048
+ },
1049
+ ...importBrand ? { importBrand } : {},
1028
1050
  ...render ? { render } : {},
1029
1051
  ...sendTest ? { sendTest } : {},
1052
+ ...generate ? { generate } : {},
1053
+ ...rewrite ? { rewrite } : {},
1054
+ ...suggestSubjects ? { suggestSubjects } : {},
1030
1055
  dump: () => ({
1031
1056
  rows: [...rows.values()],
1032
1057
  versions: Object.fromEntries(versions),
1033
1058
  images: [...images.keys()],
1034
- variables: [...customVariables.values()]
1059
+ variables: [...customVariables.values()],
1060
+ brand: savedBrand
1035
1061
  })
1036
1062
  };
1037
1063
  return storage;
@@ -1088,7 +1114,7 @@ function Label({ className, ...props }) {
1088
1114
 
1089
1115
  // src/features/preview/PreviewButton.jsx
1090
1116
  import { useMemo as useMemo2, useState as useState2 } from "react";
1091
- import { AlertTriangle, CheckCircle2 as CheckCircle22, Eye, Info as Info2, Loader2, Monitor, RefreshCw, Smartphone } from "lucide-react";
1117
+ import { AlertTriangle, CheckCircle2 as CheckCircle22, Eye, Info as Info2, Loader2, Monitor, Moon, RefreshCw, Smartphone, Sun } from "lucide-react";
1092
1118
 
1093
1119
  // src/core/scrollbars.js
1094
1120
  var FRAME_SCROLLBAR_CSS = `
@@ -1145,9 +1171,11 @@ var withPreviewScrollbars = (html) => !html ? "" : /<\/head>/i.test(html) ? html
1145
1171
  function EmailPreview({ html, loading = false, error = null, device = "desktop", onDeviceChange, onRefresh, hint = true }) {
1146
1172
  const { t } = useUi();
1147
1173
  const isMobile = device === "mobile";
1148
- const srcDoc = useMemo2(() => withPreviewScrollbars(html), [html]);
1174
+ const [scheme, setScheme] = useState2("light");
1175
+ const dark = scheme === "dark";
1176
+ const srcDoc = useMemo2(() => withPreviewScrollbars(dark ? simulateDarkMode(html) : html), [html, dark]);
1149
1177
  return /* @__PURE__ */ jsxs4("div", { className: "eb:flex eb:h-full eb:min-h-0 eb:flex-col", children: [
1150
- /* @__PURE__ */ jsxs4("div", { className: "eb:mb-2 eb:flex eb:shrink-0 eb:items-center eb:justify-between eb:gap-2", children: [
1178
+ /* @__PURE__ */ jsxs4("div", { className: "eb:mb-2 eb:flex eb:shrink-0 eb:flex-wrap eb:items-center eb:gap-2", children: [
1151
1179
  /* @__PURE__ */ jsx6(Tabs, { value: device, onValueChange: (value) => onDeviceChange?.(value), children: /* @__PURE__ */ jsxs4(TabsList, { "aria-label": t("Device"), children: [
1152
1180
  /* @__PURE__ */ jsxs4(TabsTrigger, { value: "desktop", children: [
1153
1181
  /* @__PURE__ */ jsx6(Monitor, {}),
@@ -1158,36 +1186,42 @@ function EmailPreview({ html, loading = false, error = null, device = "desktop",
1158
1186
  t("Mobile")
1159
1187
  ] })
1160
1188
  ] }) }),
1189
+ /* @__PURE__ */ jsx6(Tabs, { value: scheme, onValueChange: setScheme, children: /* @__PURE__ */ jsxs4(TabsList, { "aria-label": t("Color scheme"), children: [
1190
+ /* @__PURE__ */ jsx6(TabsTrigger, { value: "light", "aria-label": t("Light mode"), title: t("Light mode"), children: /* @__PURE__ */ jsx6(Sun, {}) }),
1191
+ /* @__PURE__ */ jsx6(TabsTrigger, { value: "dark", "aria-label": t("Dark mode"), title: t("Dark mode"), children: /* @__PURE__ */ jsx6(Moon, {}) })
1192
+ ] }) }),
1193
+ /* @__PURE__ */ jsx6("span", { className: "eb:flex-1" }),
1161
1194
  onRefresh && /* @__PURE__ */ jsxs4(Button, { variant: "outline", size: "sm-compact", onClick: onRefresh, disabled: loading, children: [
1162
1195
  loading ? /* @__PURE__ */ jsx6(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx6(RefreshCw, {}),
1163
1196
  t("Refresh")
1164
1197
  ] })
1165
1198
  ] }),
1166
1199
  /* @__PURE__ */ jsx6("div", { className: "eb:min-h-0 eb:flex-1 eb:overflow-auto eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:p-3", children: error ? /* @__PURE__ */ jsx6("div", { className: "eb:flex eb:h-full eb:items-center eb:justify-center eb:p-6 eb:text-center eb:text-[13px] eb:text-destructive", children: error }) : !srcDoc ? /* @__PURE__ */ jsx6("div", { className: "eb:flex eb:h-full eb:items-center eb:justify-center eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter", children: loading ? t("Rendering\u2026") : t("Nothing to show yet.") }) : (
1167
- // Narrows and widens like the editor's canvas (GrapesJS: width .35s ease).
1200
+ // Narrows and widens like the editor's canvas (width .35s ease).
1168
1201
  /* @__PURE__ */ jsx6(
1169
1202
  "div",
1170
1203
  {
1171
1204
  className: cn(
1172
- "eb:mx-auto eb:max-w-full eb:bg-white eb:transition-[width,box-shadow] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
1205
+ "eb:mx-auto eb:max-w-full eb:transition-[width,box-shadow] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
1173
1206
  isMobile && "eb:shadow-sm"
1174
1207
  ),
1175
- style: { width: isMobile ? EMAIL_MOBILE_WIDTH : "100%" },
1208
+ style: { width: isMobile ? EMAIL_MOBILE_WIDTH : "100%", background: dark ? DARK_PAGE : "#ffffff" },
1176
1209
  "data-device": device,
1210
+ "data-scheme": scheme,
1177
1211
  children: /* @__PURE__ */ jsx6(
1178
1212
  "iframe",
1179
1213
  {
1180
1214
  sandbox: "",
1181
1215
  srcDoc,
1182
1216
  title: t("Email preview"),
1183
- className: "eb:block eb:w-full eb:border-0 eb:bg-white eb:transition-[height] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
1184
- style: { height: isMobile ? 760 : 900 }
1217
+ className: "eb:block eb:w-full eb:border-0 eb:transition-[height] eb:duration-[350ms] eb:ease-in-out eb:motion-reduce:transition-none",
1218
+ style: { height: isMobile ? 760 : 900, background: dark ? DARK_PAGE : "#ffffff" }
1185
1219
  }
1186
1220
  )
1187
1221
  }
1188
1222
  )
1189
1223
  ) }),
1190
- hint && /* @__PURE__ */ jsx6("p", { className: "eb:mt-2 eb:shrink-0 eb:text-[11px] eb:leading-relaxed eb:text-foreground-lighter", children: t("Indicative preview \u2014 check it with \u201CSend test\u201D.") })
1224
+ dark ? /* @__PURE__ */ jsx6("p", { className: "eb:mt-2 eb:shrink-0 eb:text-[11px] eb:leading-relaxed eb:text-foreground-lighter", "data-testid": "eb-dark-hint", children: t("Dark mode as Gmail and Outlook apps show it: light backgrounds turn dark, dark text turns light, images stay as they are.") }) : hint && /* @__PURE__ */ jsx6("p", { className: "eb:mt-2 eb:shrink-0 eb:text-[11px] eb:leading-relaxed eb:text-foreground-lighter", children: t("Indicative preview \u2014 check it with \u201CSend test\u201D.") })
1191
1225
  ] });
1192
1226
  }
1193
1227
  function PreviewButton({ context }) {
@@ -1270,8 +1304,50 @@ function PreviewButton({ context }) {
1270
1304
  ] });
1271
1305
  }
1272
1306
 
1307
+ // src/features/brand/useBrand.js
1308
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState3 } from "react";
1309
+ var entries = /* @__PURE__ */ new WeakMap();
1310
+ function brandOf(storage) {
1311
+ let entry = entries.get(storage);
1312
+ if (entry) return entry;
1313
+ const created = { value: {}, loaded: Promise.resolve({}), listeners: /* @__PURE__ */ new Set() };
1314
+ created.loaded = typeof storage?.loadBrand === "function" ? Promise.resolve().then(() => storage.loadBrand()).then(
1315
+ (brand) => cleanBrand(brand),
1316
+ () => (
1317
+ /** @type {Brand} */
1318
+ {}
1319
+ )
1320
+ ).then((brand) => {
1321
+ created.value = brand;
1322
+ created.listeners.forEach((listener) => listener(brand));
1323
+ return brand;
1324
+ }) : created.loaded;
1325
+ if (storage && typeof storage === "object") entries.set(storage, created);
1326
+ return created;
1327
+ }
1328
+ function useBrand(storage) {
1329
+ const [brand, setState] = useState3(() => brandOf(storage).value);
1330
+ useEffect2(() => {
1331
+ const entry = brandOf(storage);
1332
+ setState(entry.value);
1333
+ entry.listeners.add(setState);
1334
+ return () => {
1335
+ entry.listeners.delete(setState);
1336
+ };
1337
+ }, [storage]);
1338
+ const setBrand = useCallback2(
1339
+ (value) => {
1340
+ const entry = brandOf(storage);
1341
+ entry.value = cleanBrand(value);
1342
+ entry.listeners.forEach((listener) => listener(entry.value));
1343
+ },
1344
+ [storage]
1345
+ );
1346
+ return { brand, setBrand };
1347
+ }
1348
+
1273
1349
  // src/ui/useLayoutWidth.js
1274
- import { useEffect as useEffect2, useState as useState3 } from "react";
1350
+ import { useEffect as useEffect3, useState as useState4 } from "react";
1275
1351
  var LAYOUT_BREAKPOINTS = { wide: 1180, medium: 760 };
1276
1352
  function layoutFor(width) {
1277
1353
  if (width >= LAYOUT_BREAKPOINTS.wide) return "wide";
@@ -1279,11 +1355,11 @@ function layoutFor(width) {
1279
1355
  return "narrow";
1280
1356
  }
1281
1357
  function useLayoutWidth(ref) {
1282
- const [layout, setLayout] = useState3(
1358
+ const [layout, setLayout] = useState4(
1283
1359
  /** @type {'wide'|'medium'|'narrow'} */
1284
1360
  "wide"
1285
1361
  );
1286
- useEffect2(() => {
1362
+ useEffect3(() => {
1287
1363
  const el = ref.current;
1288
1364
  if (!el || typeof ResizeObserver === "undefined") return void 0;
1289
1365
  const measure = () => setLayout(layoutFor(el.getBoundingClientRect().width));
@@ -1296,12 +1372,6 @@ function useLayoutWidth(ref) {
1296
1372
  }
1297
1373
 
1298
1374
  export {
1299
- PALETTE,
1300
- DEFAULT_BUTTON_URL,
1301
- FONT_STACK,
1302
- GROUPS,
1303
- prop,
1304
- inWhiteSection,
1305
1375
  socialBlock,
1306
1376
  socialElementBlock,
1307
1377
  cn,
@@ -1326,6 +1396,10 @@ export {
1326
1396
  Popover,
1327
1397
  PopoverTrigger,
1328
1398
  PopoverContent,
1399
+ DropdownMenu,
1400
+ DropdownMenuTrigger,
1401
+ DropdownMenuContent,
1402
+ DropdownMenuItem,
1329
1403
  Select,
1330
1404
  SelectValue,
1331
1405
  SelectTrigger,
@@ -1343,9 +1417,11 @@ export {
1343
1417
  buildGallery,
1344
1418
  withThumbnailImages,
1345
1419
  withPlaceholderImages,
1420
+ brandOf,
1421
+ useBrand,
1346
1422
  StorageError,
1347
1423
  createHttpStorage,
1348
1424
  createMemoryStorage,
1349
1425
  useLayoutWidth
1350
1426
  };
1351
- //# sourceMappingURL=chunk-ZSUHOCCE.js.map
1427
+ //# sourceMappingURL=chunk-AT73Y7JA.js.map