best-email-builder 0.3.0 → 0.5.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 (112) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +290 -20
  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-2RZHGAH5.js +64 -0
  9. package/dist/chunk-2RZHGAH5.js.map +1 -0
  10. package/dist/chunk-3LKF4QBL.js +595 -0
  11. package/dist/chunk-3LKF4QBL.js.map +1 -0
  12. package/dist/chunk-5SASKCUC.js +251 -0
  13. package/dist/chunk-5SASKCUC.js.map +1 -0
  14. package/dist/{chunk-JOC752I3.js → chunk-6D3SLT5W.js} +27 -29
  15. package/dist/chunk-6D3SLT5W.js.map +1 -0
  16. package/dist/chunk-7U6BAKM7.js +119 -0
  17. package/dist/chunk-7U6BAKM7.js.map +1 -0
  18. package/dist/chunk-DFNHICTF.js +1359 -0
  19. package/dist/chunk-DFNHICTF.js.map +1 -0
  20. package/dist/chunk-ECHGJZ2W.js +327 -0
  21. package/dist/chunk-ECHGJZ2W.js.map +1 -0
  22. package/dist/chunk-KXJJSVN5.js +98 -0
  23. package/dist/chunk-KXJJSVN5.js.map +1 -0
  24. package/dist/{chunk-ZSUHOCCE.js → chunk-LXR4QTGP.js} +123 -579
  25. package/dist/chunk-LXR4QTGP.js.map +1 -0
  26. package/dist/{chunk-BVOVAEEJ.js → chunk-LYMJ2TBX.js} +2 -2
  27. package/dist/chunk-MPX5CWTZ.js +1687 -0
  28. package/dist/chunk-MPX5CWTZ.js.map +1 -0
  29. package/dist/chunk-PB7YZ3WP.js +5635 -0
  30. package/dist/chunk-PB7YZ3WP.js.map +1 -0
  31. package/dist/chunk-UFKCLU2D.js +255 -0
  32. package/dist/chunk-UFKCLU2D.js.map +1 -0
  33. package/dist/index.js +51 -20
  34. package/dist/index.js.map +1 -1
  35. package/dist/library.js +12 -7
  36. package/dist/server.js +513 -214
  37. package/dist/server.js.map +1 -1
  38. package/dist/styles.css +1 -1
  39. package/dist/types/ai/ConnectAi.d.ts +12 -0
  40. package/dist/types/ai/GenerateButton.d.ts +4 -0
  41. package/dist/types/ai/RewriteText.d.ts +5 -0
  42. package/dist/types/ai/SubjectIdeas.d.ts +25 -0
  43. package/dist/types/ai/chip.d.ts +1 -0
  44. package/dist/types/ai/claude.d.ts +21 -0
  45. package/dist/types/ai/createAi.d.ts +66 -0
  46. package/dist/types/ai/draft.d.ts +163 -0
  47. package/dist/types/ai/generator.d.ts +123 -0
  48. package/dist/types/ai/i18n/fr.d.ts +2 -0
  49. package/dist/types/ai/i18n/index.d.ts +2 -0
  50. package/dist/types/ai/index.d.ts +9 -0
  51. package/dist/types/ai/keys.d.ts +46 -0
  52. package/dist/types/ai/prompt.d.ts +76 -0
  53. package/dist/types/ai/providers.d.ts +67 -0
  54. package/dist/types/ai/purposes.d.ts +48 -0
  55. package/dist/types/ai/rewrite.d.ts +83 -0
  56. package/dist/types/ai/server.d.ts +9 -0
  57. package/dist/types/ai/subjects.d.ts +97 -0
  58. package/dist/types/blocks/shared.d.ts +20 -0
  59. package/dist/types/core/EmailEditor.d.ts +72 -19
  60. package/dist/types/core/constants.d.ts +1 -1
  61. package/dist/types/core/mjmlDefaults.d.ts +13 -0
  62. package/dist/types/core/scrollbars.d.ts +1 -1
  63. package/dist/types/defaultEditor.d.ts +4 -1
  64. package/dist/types/engine/NativeAdapter.d.ts +28 -0
  65. package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
  66. package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
  67. package/dist/types/engine/canvas/icons.d.ts +14 -0
  68. package/dist/types/engine/canvas/morph.d.ts +21 -0
  69. package/dist/types/engine/canvasMjml.d.ts +30 -0
  70. package/dist/types/engine/mjml.d.ts +31 -0
  71. package/dist/types/engine/model.d.ts +130 -0
  72. package/dist/types/engine/placement.d.ts +69 -0
  73. package/dist/types/engine/richText.d.ts +15 -0
  74. package/dist/types/engine/unwrapModule.d.ts +29 -0
  75. package/dist/types/features/brand/BrandButton.d.ts +4 -0
  76. package/dist/types/features/brand/brand.d.ts +108 -0
  77. package/dist/types/features/brand/extract.d.ts +61 -0
  78. package/dist/types/features/brand/index.d.ts +2 -0
  79. package/dist/types/features/brand/messages.d.ts +13 -0
  80. package/dist/types/features/brand/useBrand.d.ts +22 -0
  81. package/dist/types/features/preview/PreviewButton.d.ts +1 -1
  82. package/dist/types/features/preview/darkMode.d.ts +65 -0
  83. package/dist/types/features/variables/VariablePicker.d.ts +3 -2
  84. package/dist/types/i18n/index.d.ts +8 -0
  85. package/dist/types/index.d.ts +5 -2
  86. package/dist/types/server/brand.d.ts +32 -0
  87. package/dist/types/server/brandImport.d.ts +93 -0
  88. package/dist/types/server/compileMjml.d.ts +2 -0
  89. package/dist/types/server/handler.d.ts +26 -0
  90. package/dist/types/server/index.d.ts +5 -1
  91. package/dist/types/storage/client.d.ts +57 -3
  92. package/dist/types/ui/EmailBuilder.d.ts +5 -1
  93. package/dist/types/ui/EmailStudio.d.ts +6 -1
  94. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  95. package/dist/types/ui/fields.d.ts +2 -1
  96. package/dist/types/ui/primitives/button.d.ts +1 -1
  97. package/dist/types/ui/primitives/color-picker.d.ts +2 -1
  98. package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
  99. package/dist/types/ui/useLayoutWidth.d.ts +1 -1
  100. package/package.json +27 -7
  101. package/dist/chunk-3M36VVXS.js +0 -665
  102. package/dist/chunk-3M36VVXS.js.map +0 -1
  103. package/dist/chunk-57CTQNTK.js +0 -56
  104. package/dist/chunk-57CTQNTK.js.map +0 -1
  105. package/dist/chunk-ILRAQTHS.js +0 -3677
  106. package/dist/chunk-ILRAQTHS.js.map +0 -1
  107. package/dist/chunk-JOC752I3.js.map +0 -1
  108. package/dist/chunk-ZSUHOCCE.js.map +0 -1
  109. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
  110. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
  111. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
  112. /package/dist/{chunk-BVOVAEEJ.js.map → chunk-LYMJ2TBX.js.map} +0 -0
@@ -0,0 +1,255 @@
1
+ // src/features/preview/darkMode.js
2
+ var DARK_PAGE = "#121212";
3
+ var DARK_TEXT = "#e8e8e8";
4
+ var NAMED = {
5
+ white: "#ffffff",
6
+ black: "#000000",
7
+ snow: "#fffafa",
8
+ ivory: "#fffff0",
9
+ whitesmoke: "#f5f5f5",
10
+ ghostwhite: "#f8f8ff",
11
+ gainsboro: "#dcdcdc",
12
+ lightgray: "#d3d3d3",
13
+ lightgrey: "#d3d3d3",
14
+ silver: "#c0c0c0",
15
+ gray: "#808080",
16
+ grey: "#808080",
17
+ navy: "#000080"
18
+ };
19
+ var amount = (v, max) => v.endsWith("%") ? parseFloat(v) * max / 100 : parseFloat(v);
20
+ function parseCssColor(token) {
21
+ const value = token.trim().toLowerCase();
22
+ const hex = NAMED[
23
+ /** @type {keyof typeof NAMED} */
24
+ value
25
+ ] || value;
26
+ let match = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.exec(hex);
27
+ if (match) {
28
+ let digits = match[1];
29
+ if (digits.length <= 4) digits = [...digits].map((d) => d + d).join("");
30
+ const n = (i) => parseInt(digits.slice(i, i + 2), 16);
31
+ return { r: n(0), g: n(2), b: n(4), a: digits.length === 8 ? n(6) / 255 : 1 };
32
+ }
33
+ match = /^rgba?\(\s*([\d.]+%?)[\s,]+([\d.]+%?)[\s,]+([\d.]+%?)(?:[\s,/]+([\d.]+%?))?\s*\)$/.exec(value);
34
+ if (match) {
35
+ const channel = (v) => Math.min(255, amount(v, 255));
36
+ const alpha = match[4] === void 0 ? 1 : amount(match[4], 1);
37
+ return { r: channel(match[1]), g: channel(match[2]), b: channel(match[3]), a: Math.min(1, alpha) };
38
+ }
39
+ match = /^hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%(?:[\s,/]+([\d.]+%?))?\s*\)$/.exec(value);
40
+ if (match) {
41
+ const alpha = match[4] === void 0 ? 1 : Math.min(1, amount(match[4], 1));
42
+ const s = Math.min(100, parseFloat(match[2])) / 100;
43
+ const l = Math.min(100, parseFloat(match[3])) / 100;
44
+ return { ...rgbOfHsl(parseFloat(match[1]) % 360, s, l), a: alpha };
45
+ }
46
+ return null;
47
+ }
48
+ function luminance({ r, g, b }) {
49
+ const linear = (c) => {
50
+ const v = c / 255;
51
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
52
+ };
53
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
54
+ }
55
+ function contrast(a, b) {
56
+ const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x);
57
+ return (light + 0.05) / (dark + 0.05);
58
+ }
59
+ function toHsl({ r, g, b }) {
60
+ const [R, G, B] = [r / 255, g / 255, b / 255];
61
+ const max = Math.max(R, G, B);
62
+ const min = Math.min(R, G, B);
63
+ const l = (max + min) / 2;
64
+ if (max === min) return [0, 0, l];
65
+ const d = max - min;
66
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
67
+ const h = max === R ? (G - B) / d + (G < B ? 6 : 0) : max === G ? (B - R) / d + 2 : (R - G) / d + 4;
68
+ return [h * 60, s, l];
69
+ }
70
+ function rgbOfHsl(h, s, l) {
71
+ const k = (n) => (n + h / 30) % 12;
72
+ const q = s * Math.min(l, 1 - l);
73
+ const f = (n) => Math.round(255 * (l - q * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1))));
74
+ return { r: f(0), g: f(8), b: f(4) };
75
+ }
76
+ function fromHsl(h, s, l, a) {
77
+ const { r, g, b } = rgbOfHsl(h, s, l);
78
+ if (a < 1) return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 100) / 100})`;
79
+ return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
80
+ }
81
+ function over(top, bottom) {
82
+ const mix = (c) => top[c] * top.a + bottom[c] * (1 - top.a);
83
+ return { r: mix("r"), g: mix("g"), b: mix("b"), a: 1 };
84
+ }
85
+ var PAGE = (
86
+ /** @type {Rgba} */
87
+ parseCssColor(DARK_PAGE)
88
+ );
89
+ function darkSurface(token, color) {
90
+ const lum = luminance(color);
91
+ const [h, s] = toHsl(color);
92
+ return lum > 0.45 ? fromHsl(h, s * 0.5, 0.07 + (1 - lum) * 0.35, color.a) : token;
93
+ }
94
+ function darkText(token, color, background) {
95
+ if (luminance(color) >= 0.3) return token;
96
+ const [h, s] = toHsl(color);
97
+ const lightened = fromHsl(h, s * 0.7, 0.91 - luminance(color) * 0.5, color.a);
98
+ const solid = over(color, background);
99
+ return contrast(solid, background) >= contrast(over(
100
+ /** @type {Rgba} */
101
+ parseCssColor(lightened),
102
+ background
103
+ ), background) ? token : lightened;
104
+ }
105
+ function darkColor(token, role, background = PAGE) {
106
+ const color = parseCssColor(token);
107
+ if (!color) return token;
108
+ if (role === "surface") return darkSurface(token, color);
109
+ const behind = typeof background === "string" ? parseCssColor(background) || PAGE : background;
110
+ if (role === "line" && luminance(color) > 0.45) return darkSurface(token, color);
111
+ return darkText(token, color, behind);
112
+ }
113
+ var COLOR = `#[0-9a-f]{3,8}\\b|(?:rgba?|hsla?)\\([^)]*\\)|\\b(?:${Object.keys(NAMED).join("|")})\\b`;
114
+ var COLOR_TOKEN = new RegExp(`url\\([^)]*\\)|(${COLOR})`, "gi");
115
+ var COLOR_PROPERTY = /(^|[;{\s])(color|background(?:-color)?|border(?:-(?:top|right|bottom|left))?(?:-color)?|outline(?:-color)?)(\s*:\s*)([^;}]*)/gi;
116
+ var roleOf = (property) => {
117
+ const name = property.toLowerCase();
118
+ return name === "color" ? "text" : name.startsWith("background") ? "surface" : "line";
119
+ };
120
+ function darkDeclarations(css, behind) {
121
+ let painted = null;
122
+ for (const match of css.matchAll(COLOR_PROPERTY)) {
123
+ if (roleOf(match[2]) !== "surface") continue;
124
+ for (const token of match[4].matchAll(COLOR_TOKEN)) {
125
+ const color = token[1] && parseCssColor(darkColor(token[1], "surface"));
126
+ if (color) {
127
+ painted = over(color, behind);
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ const background = painted || behind;
133
+ const out = css.replace(COLOR_PROPERTY, (_, before, property, colon, value) => {
134
+ const role = roleOf(property);
135
+ const changed = value.replace(
136
+ COLOR_TOKEN,
137
+ (whole, color) => color ? darkColor(color, role, background) : whole
138
+ );
139
+ return `${before}${property}${colon}${changed}`;
140
+ });
141
+ return { css: out, background: painted };
142
+ }
143
+ function darkCss(css, background = PAGE) {
144
+ const behind = typeof background === "string" ? parseCssColor(background) || PAGE : background;
145
+ if (!css.includes("{")) return darkDeclarations(css, behind).css;
146
+ return css.replace(/\{([^{}]*)\}/g, (_, body) => `{${darkDeclarations(body, behind).css}}`);
147
+ }
148
+ var DARK_SHEET = `<style data-eb-dark>:root{color-scheme:dark}html,body{background-color:${DARK_PAGE};color:${DARK_TEXT}}</style>`;
149
+ var TOKEN = /<!--[\s\S]*?(?:-->|$)|<!doctype[^>]*>|<(\/?)([a-z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/gi;
150
+ var ATTRIBUTE = /(\s)([^\s=/>"']+)(\s*=\s*)("[^"]*"|'[^']*'|[^\s"'>]+)/g;
151
+ var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
152
+ var CELL = { closes: ["td", "th"], within: ["tr", "table"] };
153
+ var IMPLIED_END = {
154
+ td: CELL,
155
+ th: CELL,
156
+ tr: { closes: ["tr"], within: ["table", "tbody", "thead", "tfoot"] },
157
+ li: { closes: ["li"], within: ["ul", "ol"] },
158
+ p: { closes: ["p"], within: ["div", "td", "th", "li", "table", "body"] },
159
+ option: { closes: ["option"], within: ["select"] }
160
+ };
161
+ var TEXT_ATTRIBUTES = /* @__PURE__ */ new Set(["color", "text", "link", "vlink", "alink"]);
162
+ var attributeColor = (value) => {
163
+ const trimmed = value.trim();
164
+ return /^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(trimmed) ? `#${trimmed}` : trimmed;
165
+ };
166
+ function darkTag(attributes, behind) {
167
+ const parsed = [];
168
+ for (const match of attributes.matchAll(ATTRIBUTE)) {
169
+ const raw = match[4];
170
+ const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : "";
171
+ parsed.push({ name: match[2].toLowerCase(), value: quote ? raw.slice(1, -1) : raw, quote });
172
+ }
173
+ const bgcolor = parsed.find((a) => a.name === "bgcolor");
174
+ const legacy = bgcolor && parseCssColor(darkColor(attributeColor(bgcolor.value), "surface"));
175
+ const base = legacy ? over(legacy, behind) : behind;
176
+ const style = parsed.find((a) => a.name === "style");
177
+ const declared = style ? darkDeclarations(style.value, base) : null;
178
+ const painted = declared?.background || (legacy ? base : null);
179
+ const background = painted || behind;
180
+ const out = attributes.replace(ATTRIBUTE, (whole, space, name, equals, raw) => {
181
+ const key = name.toLowerCase();
182
+ const quote = raw[0] === '"' || raw[0] === "'" ? raw[0] : "";
183
+ const value = quote ? raw.slice(1, -1) : raw;
184
+ let next = value;
185
+ if (key === "style") next = darkDeclarations(value, base).css;
186
+ else if (key === "bgcolor") next = recolorAttribute(value, "surface", behind);
187
+ else if (TEXT_ATTRIBUTES.has(key)) next = recolorAttribute(value, "text", background);
188
+ if (next === value) return whole;
189
+ return `${space}${name}${equals}${quote}${quote ? next : next.replace(/\s+/g, "")}${quote}`;
190
+ });
191
+ return { attributes: out, background: painted };
192
+ }
193
+ function recolorAttribute(value, role, background) {
194
+ const color = attributeColor(value);
195
+ if (!parseCssColor(color)) return value;
196
+ const changed = darkColor(color, role, background);
197
+ return changed === color ? value : changed;
198
+ }
199
+ function simulateDarkMode(html) {
200
+ if (!html) return "";
201
+ const source = String(html);
202
+ const stack = [];
203
+ const behind = () => stack.at(-1)?.background || PAGE;
204
+ let sheetAt = -1;
205
+ let sheetRank = 0;
206
+ let out = "";
207
+ let last = 0;
208
+ TOKEN.lastIndex = 0;
209
+ for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {
210
+ const [whole, closing, rawName, attributes] = match;
211
+ out += source.slice(last, match.index);
212
+ last = match.index + whole.length;
213
+ if (!rawName) {
214
+ out += whole;
215
+ if (/^<!doctype/i.test(whole) && sheetRank < 1) [sheetAt, sheetRank] = [out.length, 1];
216
+ continue;
217
+ }
218
+ const tag = rawName.toLowerCase();
219
+ if (closing) {
220
+ const at = stack.findLastIndex((entry) => entry.tag === tag);
221
+ if (at !== -1) stack.length = at;
222
+ out += whole;
223
+ continue;
224
+ }
225
+ const implied = IMPLIED_END[
226
+ /** @type {keyof typeof IMPLIED_END} */
227
+ tag
228
+ ];
229
+ if (implied) {
230
+ const open = stack.findLastIndex((entry) => implied.closes.includes(entry.tag));
231
+ if (open > stack.findLastIndex((entry) => implied.within.includes(entry.tag))) stack.length = open;
232
+ }
233
+ const { attributes: changed, background } = darkTag(attributes, behind());
234
+ out += `<${rawName}${changed}>`;
235
+ if (tag === "head" && sheetRank < 3) [sheetAt, sheetRank] = [out.length, 3];
236
+ if (tag === "html" && sheetRank < 2) [sheetAt, sheetRank] = [out.length, 2];
237
+ if (tag === "style" || tag === "script" || tag === "title" || tag === "textarea") {
238
+ const end = source.slice(last).search(new RegExp(`</${tag}\\s*>`, "i"));
239
+ const body = end === -1 ? source.slice(last) : source.slice(last, last + end);
240
+ out += tag === "style" ? darkCss(body) : body;
241
+ last += body.length;
242
+ TOKEN.lastIndex = last;
243
+ continue;
244
+ }
245
+ if (!VOID.has(tag) && !/\/\s*$/.test(attributes)) stack.push({ tag, background: background || behind() });
246
+ }
247
+ out += source.slice(last);
248
+ return sheetAt === -1 ? DARK_SHEET + out : out.slice(0, sheetAt) + DARK_SHEET + out.slice(sheetAt);
249
+ }
250
+
251
+ export {
252
+ DARK_PAGE,
253
+ simulateDarkMode
254
+ };
255
+ //# sourceMappingURL=chunk-UFKCLU2D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/features/preview/darkMode.js"],"sourcesContent":["// @ts-check\n/**\n * DARK MODE, simulated — an email as the mail apps that repaint it in dark\n * mode show it (Gmail and Outlook apps, Outlook.com): light backgrounds\n * turned dark, dark text turned light where it would no longer read,\n * everything else left as it is.\n *\n * surfaces background colors (`background`, `bgcolor`…): a LIGHT one\n * becomes dark, same hue; a darker one (a brand button, an\n * orange banner) stays.\n * text `color`, `<font color>`, `<body text>`: decided against the\n * background it sits on, once that one is repainted. Dark text\n * on what became dark turns light, same hue; dark text on a\n * background that stayed light enough (black on an orange\n * button) stays; light text stays.\n * lines border and outline colors: a light one darkens like a\n * surface, a dark one is decided like text (a black divider on\n * the dark page turns light).\n * images untouched: a dark logo on a transparent background disappears\n * here as it does in those apps — what the preview is for.\n *\n * Works on the compiled HTML as a string, tag by tag, keeping which\n * background each element sits on: the colors of `style` attributes, of\n * `bgcolor`/`color`/`text` attributes and of `<style>` sheets (where the\n * background is not known: the dark page). Comments are left alone (Outlook's\n * conditional blocks). No script, no DOM: the preview frame stays sandboxed.\n * Only color values change; tags and attribute names stay as they are.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\n/** The page behind the email, and its default text, in dark apps. */\nexport const DARK_PAGE = '#121212';\nconst DARK_TEXT = '#e8e8e8';\n\n/** A few named colors emails use. */\nconst NAMED = {\n white: '#ffffff',\n black: '#000000',\n snow: '#fffafa',\n ivory: '#fffff0',\n whitesmoke: '#f5f5f5',\n ghostwhite: '#f8f8ff',\n gainsboro: '#dcdcdc',\n lightgray: '#d3d3d3',\n lightgrey: '#d3d3d3',\n silver: '#c0c0c0',\n gray: '#808080',\n grey: '#808080',\n navy: '#000080',\n};\n\n/** @typedef {{r: number, g: number, b: number, a: number}} Rgba channels 0–255, alpha 0–1 */\n\n/** @param {string} v a number or a percentage of `max` */\nconst amount = (v, max) => (v.endsWith('%') ? (parseFloat(v) * max) / 100 : parseFloat(v));\n\n/** @param {string} token @returns {Rgba|null} */\nexport function parseCssColor(token) {\n const value = token.trim().toLowerCase();\n const hex = NAMED[/** @type {keyof typeof NAMED} */ (value)] || value;\n let match = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.exec(hex);\n if (match) {\n let digits = match[1];\n if (digits.length <= 4) digits = [...digits].map((d) => d + d).join('');\n const n = (/** @type {number} */ i) => parseInt(digits.slice(i, i + 2), 16);\n return { r: n(0), g: n(2), b: n(4), a: digits.length === 8 ? n(6) / 255 : 1 };\n }\n match = /^rgba?\\(\\s*([\\d.]+%?)[\\s,]+([\\d.]+%?)[\\s,]+([\\d.]+%?)(?:[\\s,/]+([\\d.]+%?))?\\s*\\)$/.exec(value);\n if (match) {\n const channel = (/** @type {string} */ v) => Math.min(255, amount(v, 255));\n const alpha = match[4] === undefined ? 1 : amount(match[4], 1);\n return { r: channel(match[1]), g: channel(match[2]), b: channel(match[3]), a: Math.min(1, alpha) };\n }\n match = /^hsla?\\(\\s*([\\d.]+)(?:deg)?[\\s,]+([\\d.]+)%[\\s,]+([\\d.]+)%(?:[\\s,/]+([\\d.]+%?))?\\s*\\)$/.exec(value);\n if (match) {\n const alpha = match[4] === undefined ? 1 : Math.min(1, amount(match[4], 1));\n const s = Math.min(100, parseFloat(match[2])) / 100;\n const l = Math.min(100, parseFloat(match[3])) / 100;\n return { ...rgbOfHsl(parseFloat(match[1]) % 360, s, l), a: alpha };\n }\n return null;\n}\n\n/** WCAG relative luminance, 0 (black) to 1 (white). @param {Rgba} color */\nexport function luminance({ r, g, b }) {\n const linear = (/** @type {number} */ c) => {\n const v = c / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/** WCAG contrast ratio, 1 to 21. @param {Rgba} a @param {Rgba} b */\nexport function contrast(a, b) {\n const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x);\n return (light + 0.05) / (dark + 0.05);\n}\n\n/** @param {Rgba} color @returns {[number, number, number]} hue 0–360, saturation and lightness 0–1 */\nfunction toHsl({ r, g, b }) {\n const [R, G, B] = [r / 255, g / 255, b / 255];\n const max = Math.max(R, G, B);\n const min = Math.min(R, G, B);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n const h = max === R ? (G - B) / d + (G < B ? 6 : 0) : max === G ? (B - R) / d + 2 : (R - G) / d + 4;\n return [h * 60, s, l];\n}\n\n/** @param {number} h @param {number} s @param {number} l */\nfunction rgbOfHsl(h, s, l) {\n const k = (/** @type {number} */ n) => (n + h / 30) % 12;\n const q = s * Math.min(l, 1 - l);\n const f = (/** @type {number} */ n) => Math.round(255 * (l - q * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1))));\n return { r: f(0), g: f(8), b: f(4) };\n}\n\n/** @param {number} h @param {number} s @param {number} l @param {number} a */\nfunction fromHsl(h, s, l, a) {\n const { r, g, b } = rgbOfHsl(h, s, l);\n if (a < 1) return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 100) / 100})`;\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;\n}\n\n/** `top` painted over `bottom`. @param {Rgba} top @param {Rgba} bottom @returns {Rgba} */\nfunction over(top, bottom) {\n const mix = (/** @type {'r'|'g'|'b'} */ c) => top[c] * top.a + bottom[c] * (1 - top.a);\n return { r: mix('r'), g: mix('g'), b: mix('b'), a: 1 };\n}\n\nconst PAGE = /** @type {Rgba} */ (parseCssColor(DARK_PAGE));\n\n/** A light surface in dark mode, or the color as given. @param {string} token @param {Rgba} color */\nfunction darkSurface(token, color) {\n const lum = luminance(color);\n const [h, s] = toHsl(color);\n // White becomes the dark apps' page; a pale tint a dark one of its hue.\n return lum > 0.45 ? fromHsl(h, s * 0.5, 0.07 + (1 - lum) * 0.35, color.a) : token;\n}\n\n/** A text color in dark mode, on `background` (already repainted). @param {string} token @param {Rgba} color @param {Rgba} background */\nfunction darkText(token, color, background) {\n if (luminance(color) >= 0.3) return token;\n const [h, s] = toHsl(color);\n const lightened = fromHsl(h, s * 0.7, 0.91 - luminance(color) * 0.5, color.a);\n // Kept where it already reads better than the light one would (black on an orange button).\n const solid = over(color, background);\n return contrast(solid, background) >= contrast(over(/** @type {Rgba} */ (parseCssColor(lightened)), background), background) ? token : lightened;\n}\n\n/**\n * One color in dark mode. `surface`: a background; `text`: a text color;\n * `line`: a border. Text and lines are decided against `background` (the\n * repainted one behind them; the dark page by default). Unknown values\n * (`transparent`, `inherit`…) come back as given.\n * @param {string} token @param {'surface'|'text'|'line'} role @param {string|Rgba} [background]\n */\nexport function darkColor(token, role, background = PAGE) {\n const color = parseCssColor(token);\n if (!color) return token;\n if (role === 'surface') return darkSurface(token, color);\n const behind = typeof background === 'string' ? parseCssColor(background) || PAGE : background;\n if (role === 'line' && luminance(color) > 0.45) return darkSurface(token, color);\n return darkText(token, color, behind);\n}\n\nconst COLOR = `#[0-9a-f]{3,8}\\\\b|(?:rgba?|hsla?)\\\\([^)]*\\\\)|\\\\b(?:${Object.keys(NAMED).join('|')})\\\\b`;\nconst COLOR_TOKEN = new RegExp(`url\\\\([^)]*\\\\)|(${COLOR})`, 'gi');\nconst COLOR_PROPERTY =\n /(^|[;{\\s])(color|background(?:-color)?|border(?:-(?:top|right|bottom|left))?(?:-color)?|outline(?:-color)?)(\\s*:\\s*)([^;}]*)/gi;\n\n/** @param {string} property @returns {'surface'|'text'|'line'} */\nconst roleOf = (property) => {\n const name = property.toLowerCase();\n return name === 'color' ? 'text' : name.startsWith('background') ? 'surface' : 'line';\n};\n\n/**\n * The declarations of one rule or `style` attribute in dark mode, and the\n * background they paint (repainted, over `behind`), or null.\n * @param {string} css @param {Rgba} behind\n */\nfunction darkDeclarations(css, behind) {\n /** @type {Rgba|null} */\n let painted = null;\n for (const match of css.matchAll(COLOR_PROPERTY)) {\n if (roleOf(match[2]) !== 'surface') continue;\n for (const token of match[4].matchAll(COLOR_TOKEN)) {\n const color = token[1] && parseCssColor(darkColor(token[1], 'surface'));\n if (color) {\n painted = over(color, behind);\n break;\n }\n }\n }\n const background = painted || behind;\n const out = css.replace(COLOR_PROPERTY, (_, before, property, colon, value) => {\n const role = roleOf(property);\n const changed = value.replace(COLOR_TOKEN, (/** @type {string} */ whole, /** @type {string} */ color) =>\n color ? darkColor(color, role, background) : whole\n );\n return `${before}${property}${colon}${changed}`;\n });\n return { css: out, background: painted };\n}\n\n/**\n * The colors of a CSS text in dark mode: a `style` attribute's declarations,\n * or a sheet (each rule on its own; behind it, the dark page).\n * @param {string} css @param {string|Rgba} [background]\n */\nexport function darkCss(css, background = PAGE) {\n const behind = typeof background === 'string' ? parseCssColor(background) || PAGE : background;\n if (!css.includes('{')) return darkDeclarations(css, behind).css;\n return css.replace(/\\{([^{}]*)\\}/g, (_, body) => `{${darkDeclarations(body, behind).css}}`);\n}\n\nconst DARK_SHEET = `<style data-eb-dark>:root{color-scheme:dark}html,body{background-color:${DARK_PAGE};color:${DARK_TEXT}}</style>`;\n\nconst TOKEN = /<!--[\\s\\S]*?(?:-->|$)|<!doctype[^>]*>|<(\\/?)([a-z][\\w:-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi;\nconst ATTRIBUTE = /(\\s)([^\\s=/>\"']+)(\\s*=\\s*)(\"[^\"]*\"|'[^']*'|[^\\s\"'>]+)/g;\nconst VOID = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr']);\n/** Elements whose end tag HTML lets a next one imply: what it closes, and up to which container. */\nconst CELL = { closes: ['td', 'th'], within: ['tr', 'table'] };\nconst IMPLIED_END = {\n td: CELL,\n th: CELL,\n tr: { closes: ['tr'], within: ['table', 'tbody', 'thead', 'tfoot'] },\n li: { closes: ['li'], within: ['ul', 'ol'] },\n p: { closes: ['p'], within: ['div', 'td', 'th', 'li', 'table', 'body'] },\n option: { closes: ['option'], within: ['select'] },\n};\n/** Legacy attributes holding a text color. */\nconst TEXT_ATTRIBUTES = new Set(['color', 'text', 'link', 'vlink', 'alink']);\n\n/** A legacy attribute color: `FFFFFF` (no #) is still a color there. @param {string} value */\nconst attributeColor = (value) => {\n const trimmed = value.trim();\n return /^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(trimmed) ? `#${trimmed}` : trimmed;\n};\n\n/**\n * One opening tag in dark mode, on `behind`; and the background it paints.\n * @param {string} attributes @param {Rgba} behind\n */\nfunction darkTag(attributes, behind) {\n /** @type {{name: string, value: string, quote: string}[]} */\n const parsed = [];\n for (const match of attributes.matchAll(ATTRIBUTE)) {\n const raw = match[4];\n const quote = raw[0] === '\"' || raw[0] === \"'\" ? raw[0] : '';\n parsed.push({ name: match[2].toLowerCase(), value: quote ? raw.slice(1, -1) : raw, quote });\n }\n const bgcolor = parsed.find((a) => a.name === 'bgcolor');\n const legacy = bgcolor && parseCssColor(darkColor(attributeColor(bgcolor.value), 'surface'));\n // `style` paints over `bgcolor`, which paints over what is behind.\n const base = legacy ? over(legacy, behind) : behind;\n const style = parsed.find((a) => a.name === 'style');\n const declared = style ? darkDeclarations(style.value, base) : null;\n const painted = declared?.background || (legacy ? base : null);\n const background = painted || behind;\n\n const out = attributes.replace(ATTRIBUTE, (whole, space, name, equals, raw) => {\n const key = name.toLowerCase();\n const quote = raw[0] === '\"' || raw[0] === \"'\" ? raw[0] : '';\n const value = quote ? raw.slice(1, -1) : raw;\n let next = value;\n if (key === 'style') next = darkDeclarations(value, base).css;\n else if (key === 'bgcolor') next = recolorAttribute(value, 'surface', behind);\n else if (TEXT_ATTRIBUTES.has(key)) next = recolorAttribute(value, 'text', background);\n if (next === value) return whole;\n // An unquoted value gets no space (`rgba(…)` written without one).\n return `${space}${name}${equals}${quote}${quote ? next : next.replace(/\\s+/g, '')}${quote}`;\n });\n return { attributes: out, background: painted };\n}\n\n/** @param {string} value @param {'surface'|'text'} role @param {Rgba} background */\nfunction recolorAttribute(value, role, background) {\n const color = attributeColor(value);\n if (!parseCssColor(color)) return value;\n const changed = darkColor(color, role, background);\n return changed === color ? value : changed;\n}\n\n/**\n * A compiled email as a dark-mode app would show it.\n * @param {string} html\n */\nexport function simulateDarkMode(html) {\n if (!html) return '';\n const source = String(html);\n /** @type {{tag: string, background: Rgba}[]} */\n const stack = [];\n const behind = () => stack.at(-1)?.background || PAGE;\n /** Where the apps' sheet goes: after <head>, else after <html>, else after the doctype. */\n let sheetAt = -1;\n let sheetRank = 0;\n let out = '';\n let last = 0;\n TOKEN.lastIndex = 0;\n for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {\n const [whole, closing, rawName, attributes] = match;\n out += source.slice(last, match.index);\n last = match.index + whole.length;\n if (!rawName) {\n // A comment or the doctype: as it is.\n out += whole;\n if (/^<!doctype/i.test(whole) && sheetRank < 1) [sheetAt, sheetRank] = [out.length, 1];\n continue;\n }\n const tag = rawName.toLowerCase();\n if (closing) {\n const at = stack.findLastIndex((entry) => entry.tag === tag);\n if (at !== -1) stack.length = at;\n out += whole;\n continue;\n }\n const implied = IMPLIED_END[/** @type {keyof typeof IMPLIED_END} */ (tag)];\n if (implied) {\n const open = stack.findLastIndex((entry) => implied.closes.includes(entry.tag));\n if (open > stack.findLastIndex((entry) => implied.within.includes(entry.tag))) stack.length = open;\n }\n const { attributes: changed, background } = darkTag(attributes, behind());\n out += `<${rawName}${changed}>`;\n if (tag === 'head' && sheetRank < 3) [sheetAt, sheetRank] = [out.length, 3];\n if (tag === 'html' && sheetRank < 2) [sheetAt, sheetRank] = [out.length, 2];\n if (tag === 'style' || tag === 'script' || tag === 'title' || tag === 'textarea') {\n // Raw text up to its end tag: a sheet is recolored, the rest kept.\n const end = source.slice(last).search(new RegExp(`</${tag}\\\\s*>`, 'i'));\n const body = end === -1 ? source.slice(last) : source.slice(last, last + end);\n out += tag === 'style' ? darkCss(body) : body;\n last += body.length;\n TOKEN.lastIndex = last;\n continue;\n }\n if (!VOID.has(tag) && !/\\/\\s*$/.test(attributes)) stack.push({ tag, background: background || behind() });\n }\n out += source.slice(last);\n // The apps' own page and text, under whatever the email sets itself.\n return sheetAt === -1 ? DARK_SHEET + out : out.slice(0, sheetAt) + DARK_SHEET + out.slice(sheetAt);\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,66 +1,86 @@
1
1
  import {
2
+ EMPTY_MJML,
3
+ ENGINE_NAME,
2
4
  EmailBuilder,
3
5
  EmailBuilderBoundary,
4
6
  EmailCanvas,
5
7
  createDefaultEmailEditor,
6
8
  createEmailEditor,
7
- createGrapesJsAdapter,
9
+ createNativeAdapter,
8
10
  createRegistry,
9
11
  emailBlocks,
10
12
  emailBuilderFeatures,
11
13
  isFeatureEnabled
12
- } from "./chunk-ILRAQTHS.js";
14
+ } from "./chunk-PB7YZ3WP.js";
13
15
  import {
14
16
  TemplateLibrary
15
- } from "./chunk-JOC752I3.js";
17
+ } from "./chunk-6D3SLT5W.js";
16
18
  import {
17
19
  STARTER_MJML,
18
- StorageError,
19
20
  TEMPLATE_GALLERY,
20
- UiProvider,
21
21
  buildGallery,
22
- createHttpStorage,
23
- createMemoryStorage,
24
22
  createStarterMjml,
25
- useT,
26
- useUi,
27
23
  withPlaceholderImages
28
- } from "./chunk-ZSUHOCCE.js";
24
+ } from "./chunk-3LKF4QBL.js";
29
25
  import {
30
26
  applyVariables,
31
27
  applyVariablesToText,
32
28
  unknownVariables,
33
29
  usedVariables
34
- } from "./chunk-BVOVAEEJ.js";
35
- import "./chunk-57CTQNTK.js";
30
+ } from "./chunk-LYMJ2TBX.js";
31
+ import "./chunk-ECHGJZ2W.js";
32
+ import {
33
+ simulateDarkMode
34
+ } from "./chunk-UFKCLU2D.js";
35
+ import "./chunk-KXJJSVN5.js";
36
+ import {
37
+ StorageError,
38
+ UiProvider,
39
+ createHttpStorage,
40
+ createMemoryStorage,
41
+ useT,
42
+ useUi
43
+ } from "./chunk-LXR4QTGP.js";
36
44
  import {
37
- CUSTOM_GROUP,
38
- DEFAULT_ASSETS_PATH,
39
- DEFAULT_VARIABLES,
40
45
  EMAIL_BUILDER_VERSION,
41
46
  EMAIL_DEVICES,
42
47
  EMAIL_DOCUMENT_SCHEMA_VERSION,
43
48
  EmailDocumentError,
49
+ createDocument,
50
+ readDocument,
51
+ serializeDocument
52
+ } from "./chunk-7U6BAKM7.js";
53
+ import "./chunk-2RZHGAH5.js";
54
+ import "./chunk-5SASKCUC.js";
55
+ import {
56
+ CUSTOM_GROUP,
57
+ DEFAULT_ASSETS_PATH,
58
+ DEFAULT_VARIABLES,
44
59
  LOCALES,
45
60
  absoluteAssetUrls,
46
- createDocument,
61
+ applyBrand,
62
+ brandedSender,
63
+ cleanBrand,
47
64
  createTranslator,
48
65
  defineVariables,
49
66
  isAssetPath,
50
67
  keyFromLabel,
51
68
  mergeVariables,
52
- readDocument,
53
- serializeDocument,
69
+ messagesOf,
70
+ parseMjml,
71
+ parseMjmlFragment,
72
+ serializeMjml,
54
73
  tokenOf,
55
74
  translatorFor,
56
75
  validateCustomVariable
57
- } from "./chunk-3M36VVXS.js";
76
+ } from "./chunk-DFNHICTF.js";
58
77
 
59
78
  // src/ui/EmailStudio.jsx
60
79
  import { useMemo, useState } from "react";
61
80
  import { jsx } from "react/jsx-runtime";
62
81
  function EmailStudio({
63
82
  storage: injectedStorage,
83
+ ai,
64
84
  locale = "en",
65
85
  messages,
66
86
  onNotify,
@@ -80,6 +100,7 @@ function EmailStudio({
80
100
  EmailBuilder,
81
101
  {
82
102
  ...shared,
103
+ ai,
83
104
  templates: [],
84
105
  ...builderProps,
85
106
  templateId: session.templateId,
@@ -119,6 +140,8 @@ export {
119
140
  EMAIL_BUILDER_VERSION,
120
141
  EMAIL_DEVICES,
121
142
  EMAIL_DOCUMENT_SCHEMA_VERSION,
143
+ EMPTY_MJML,
144
+ ENGINE_NAME,
122
145
  EmailBuilder,
123
146
  EmailBuilderBoundary,
124
147
  EmailCanvas,
@@ -131,15 +154,18 @@ export {
131
154
  TemplateLibrary,
132
155
  UiProvider,
133
156
  absoluteAssetUrls,
157
+ applyBrand,
134
158
  applyVariables,
135
159
  applyVariablesToText,
160
+ brandedSender,
136
161
  buildGallery,
162
+ cleanBrand,
137
163
  createDefaultEmailEditor,
138
164
  createDocument,
139
165
  createEmailEditor,
140
- createGrapesJsAdapter,
141
166
  createHttpStorage,
142
167
  createMemoryStorage,
168
+ createNativeAdapter,
143
169
  createRegistry,
144
170
  createStarterMjml,
145
171
  createTranslator,
@@ -150,8 +176,13 @@ export {
150
176
  isFeatureEnabled,
151
177
  keyFromLabel,
152
178
  mergeVariables,
179
+ messagesOf,
180
+ parseMjml,
181
+ parseMjmlFragment,
153
182
  readDocument,
154
183
  serializeDocument,
184
+ serializeMjml,
185
+ simulateDarkMode,
155
186
  tokenOf,
156
187
  translatorFor,
157
188
  unknownVariables,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ui/EmailStudio.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { createHttpStorage } from '../storage/client.js';\nimport EmailBuilder from './EmailBuilder.jsx';\nimport TemplateLibrary from './library/TemplateLibrary.jsx';\n\n/**\n * EMAIL STUDIO — the two screens wired together.\n *\n * TemplateLibrary ── Edit / New template / Start from this base ──▶ EmailBuilder\n * ▲ │\n * └──────────────────────────── Back ───────────────────────────┘\n *\n * Nothing here that a host could not write itself: both screens are exported\n * on their own, and an application that only wants the editor mounts\n * `EmailBuilder` alone (or the library alone, opening its own editor).\n *\n * Shared props go to both screens — except `templates` (the bases), which\n * only the library shows: the editor gets `[]`, so no second gallery replaces\n * the email being edited (`builderProps.templates` brings it back).\n * `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was\n * just edited or created.\n *\n * @param {{\n * storage?: import('../storage/client.js').TemplateStorage,\n * locale?: string,\n * messages?: Record<string, string>,\n * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,\n * sender?: import('../features/templates/starter.js').StarterSender,\n * templates?: import('../features/templates/gallery.js').GalleryTemplate[],\n * onClose?: () => void,\n * libraryProps?: object,\n * builderProps?: object,\n * className?: string,\n * }} props `onClose`: shows the library's close button.\n */\nexport default function EmailStudio({\n storage: injectedStorage,\n locale = 'en',\n messages,\n onNotify,\n sender,\n templates,\n onClose,\n libraryProps,\n builderProps,\n className,\n}) {\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n /** null = library; else the editor session: {key, templateId, start}. */\n const [session, setSession] = useState(null);\n const [lastId, setLastId] = useState(null);\n const shared = { storage, locale, messages, onNotify, sender, templates, className };\n\n if (session) {\n return (\n <EmailBuilder\n key={session.key}\n {...shared}\n // The bases are picked in the bank: no second gallery in the editor,\n // where picking one would replace the email being edited.\n templates={[]}\n {...builderProps}\n templateId={session.templateId}\n initialMjml={session.start?.mjml}\n initialMeta={session.start?.meta}\n onSaved={(template) => {\n if (template?.id != null) setLastId(template.id);\n builderProps?.onSaved?.(template);\n }}\n onClose={() => {\n setSession(null);\n builderProps?.onClose?.();\n }}\n />\n );\n }\n\n return (\n <TemplateLibrary\n {...shared}\n {...libraryProps}\n selectedId={lastId}\n onClose={onClose}\n onEdit={(template) => {\n setLastId(template.id);\n setSession({ key: Date.now(), templateId: template.id, start: null });\n }}\n onCreate={(start) => setSession({ key: Date.now(), templateId: null, start })}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AAuD5B;AApBS,SAAR,YAA6B;AAAA,EAClC,SAAS;AAAA,EACT,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAG;AACD,QAAM,UAAU,QAAQ,MAAM,mBAAmB,kBAAkB,GAAG,CAAC,eAAe,CAAC;AAEvF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,QAAM,SAAS,EAAE,SAAS,QAAQ,UAAU,UAAU,QAAQ,WAAW,UAAU;AAEnF,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QAEE,GAAG;AAAA,QAGJ,WAAW,CAAC;AAAA,QACX,GAAG;AAAA,QACJ,YAAY,QAAQ;AAAA,QACpB,aAAa,QAAQ,OAAO;AAAA,QAC5B,aAAa,QAAQ,OAAO;AAAA,QAC5B,SAAS,CAAC,aAAa;AACrB,cAAI,UAAU,MAAM,KAAM,WAAU,SAAS,EAAE;AAC/C,wBAAc,UAAU,QAAQ;AAAA,QAClC;AAAA,QACA,SAAS,MAAM;AACb,qBAAW,IAAI;AACf,wBAAc,UAAU;AAAA,QAC1B;AAAA;AAAA,MAhBK,QAAQ;AAAA,IAiBf;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA,MACJ,YAAY;AAAA,MACZ;AAAA,MACA,QAAQ,CAAC,aAAa;AACpB,kBAAU,SAAS,EAAE;AACrB,mBAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,SAAS,IAAI,OAAO,KAAK,CAAC;AAAA,MACtE;AAAA,MACA,UAAU,CAAC,UAAU,WAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,MAAM,MAAM,CAAC;AAAA;AAAA,EAC9E;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/ui/EmailStudio.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { createHttpStorage } from '../storage/client.js';\nimport EmailBuilder from './EmailBuilder.jsx';\nimport TemplateLibrary from './library/TemplateLibrary.jsx';\n\n/**\n * EMAIL STUDIO — the two screens wired together.\n *\n * TemplateLibrary ── Edit / New template / Start from this base ──▶ EmailBuilder\n * ▲ │\n * └──────────────────────────── Back ───────────────────────────┘\n *\n * Nothing here that a host could not write itself: both screens are exported\n * on their own, and an application that only wants the editor mounts\n * `EmailBuilder` alone (or the library alone, opening its own editor).\n *\n * Shared props go to both screens — except `templates` (the bases), which\n * only the library shows: the editor gets `[]`, so no second gallery replaces\n * the email being edited (`builderProps.templates` brings it back).\n * `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was\n * just edited or created.\n *\n * `ai` (`createAi()` from `best-email-builder/ai`) goes to the editor only:\n * without it, the editor has no AI button at all.\n *\n * @param {{\n * storage?: import('../storage/client.js').TemplateStorage,\n * ai?: import('../ai/createAi.js').EmailBuilderAi|null,\n * locale?: string,\n * messages?: Record<string, string>,\n * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,\n * sender?: import('../features/templates/starter.js').StarterSender,\n * templates?: import('../features/templates/gallery.js').GalleryTemplate[],\n * onClose?: () => void,\n * libraryProps?: object,\n * builderProps?: object,\n * className?: string,\n * }} props `onClose`: shows the library's close button.\n */\nexport default function EmailStudio({\n storage: injectedStorage,\n ai,\n locale = 'en',\n messages,\n onNotify,\n sender,\n templates,\n onClose,\n libraryProps,\n builderProps,\n className,\n}) {\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n /** null = library; else the editor session: {key, templateId, start}. */\n const [session, setSession] = useState(null);\n const [lastId, setLastId] = useState(null);\n const shared = { storage, locale, messages, onNotify, sender, templates, className };\n\n if (session) {\n return (\n <EmailBuilder\n key={session.key}\n {...shared}\n ai={ai}\n // The bases are picked in the bank: no second gallery in the editor,\n // where picking one would replace the email being edited.\n templates={[]}\n {...builderProps}\n templateId={session.templateId}\n initialMjml={session.start?.mjml}\n initialMeta={session.start?.meta}\n onSaved={(template) => {\n if (template?.id != null) setLastId(template.id);\n builderProps?.onSaved?.(template);\n }}\n onClose={() => {\n setSession(null);\n builderProps?.onClose?.();\n }}\n />\n );\n }\n\n return (\n <TemplateLibrary\n {...shared}\n {...libraryProps}\n selectedId={lastId}\n onClose={onClose}\n onEdit={(template) => {\n setLastId(template.id);\n setSession({ key: Date.now(), templateId: template.id, start: null });\n }}\n onCreate={(start) => setSession({ key: Date.now(), templateId: null, start })}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AA4D5B;AArBS,SAAR,YAA6B;AAAA,EAClC,SAAS;AAAA,EACT;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAG;AACD,QAAM,UAAU,QAAQ,MAAM,mBAAmB,kBAAkB,GAAG,CAAC,eAAe,CAAC;AAEvF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,QAAM,SAAS,EAAE,SAAS,QAAQ,UAAU,UAAU,QAAQ,WAAW,UAAU;AAEnF,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QAEE,GAAG;AAAA,QACJ;AAAA,QAGA,WAAW,CAAC;AAAA,QACX,GAAG;AAAA,QACJ,YAAY,QAAQ;AAAA,QACpB,aAAa,QAAQ,OAAO;AAAA,QAC5B,aAAa,QAAQ,OAAO;AAAA,QAC5B,SAAS,CAAC,aAAa;AACrB,cAAI,UAAU,MAAM,KAAM,WAAU,SAAS,EAAE;AAC/C,wBAAc,UAAU,QAAQ;AAAA,QAClC;AAAA,QACA,SAAS,MAAM;AACb,qBAAW,IAAI;AACf,wBAAc,UAAU;AAAA,QAC1B;AAAA;AAAA,MAjBK,QAAQ;AAAA,IAkBf;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA,MACJ,YAAY;AAAA,MACZ;AAAA,MACA,QAAQ,CAAC,aAAa;AACpB,kBAAU,SAAS,EAAE;AACrB,mBAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,SAAS,IAAI,OAAO,KAAK,CAAC;AAAA,MACtE;AAAA,MACA,UAAU,CAAC,UAAU,WAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,MAAM,MAAM,CAAC;AAAA;AAAA,EAC9E;AAEJ;","names":[]}
package/dist/library.js CHANGED
@@ -1,20 +1,25 @@
1
1
  import {
2
2
  TemplateLibrary
3
- } from "./chunk-JOC752I3.js";
3
+ } from "./chunk-6D3SLT5W.js";
4
4
  import {
5
- StorageError,
6
5
  TEMPLATE_GALLERY,
7
6
  buildGallery,
8
- createHttpStorage,
9
- createMemoryStorage,
10
7
  withPlaceholderImages
11
- } from "./chunk-ZSUHOCCE.js";
8
+ } from "./chunk-3LKF4QBL.js";
9
+ import "./chunk-UFKCLU2D.js";
10
+ import {
11
+ StorageError,
12
+ createHttpStorage,
13
+ createMemoryStorage
14
+ } from "./chunk-LXR4QTGP.js";
15
+ import {
16
+ EMAIL_BUILDER_VERSION
17
+ } from "./chunk-7U6BAKM7.js";
12
18
  import {
13
- EMAIL_BUILDER_VERSION,
14
19
  LOCALES,
15
20
  createTranslator,
16
21
  translatorFor
17
- } from "./chunk-3M36VVXS.js";
22
+ } from "./chunk-DFNHICTF.js";
18
23
  export {
19
24
  EMAIL_BUILDER_VERSION,
20
25
  LOCALES,