@matthesketh/utopia-email 0.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,776 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ EmailButton: () => EmailButton,
24
+ EmailCard: () => EmailCard,
25
+ EmailColumns: () => EmailColumns,
26
+ EmailDivider: () => EmailDivider,
27
+ EmailHeading: () => EmailHeading,
28
+ EmailImage: () => EmailImage,
29
+ EmailLayout: () => EmailLayout,
30
+ EmailSpacer: () => EmailSpacer,
31
+ EmailText: () => EmailText,
32
+ createMailer: () => createMailer,
33
+ htmlToText: () => htmlToText,
34
+ inlineCSS: () => inlineCSS,
35
+ renderEmail: () => renderEmail
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/render-email.ts
40
+ var import_utopia_server = require("@matthesketh/utopia-server");
41
+
42
+ // src/css-inliner.ts
43
+ function parseCSS(css) {
44
+ const rules = [];
45
+ const cleaned = css.replace(/\/\*[\s\S]*?\*\//g, "");
46
+ let i = 0;
47
+ while (i < cleaned.length) {
48
+ while (i < cleaned.length && /\s/.test(cleaned[i])) i++;
49
+ if (i >= cleaned.length) break;
50
+ if (cleaned[i] === "@") {
51
+ let depth = 0;
52
+ while (i < cleaned.length) {
53
+ if (cleaned[i] === "{") depth++;
54
+ if (cleaned[i] === "}") {
55
+ depth--;
56
+ if (depth <= 0) {
57
+ i++;
58
+ break;
59
+ }
60
+ }
61
+ i++;
62
+ }
63
+ continue;
64
+ }
65
+ const selectorStart = i;
66
+ while (i < cleaned.length && cleaned[i] !== "{") i++;
67
+ if (i >= cleaned.length) break;
68
+ const selector = cleaned.slice(selectorStart, i).trim();
69
+ i++;
70
+ const declStart = i;
71
+ while (i < cleaned.length && cleaned[i] !== "}") i++;
72
+ const declarations = cleaned.slice(declStart, i).trim();
73
+ i++;
74
+ if (selector && declarations) {
75
+ for (const sel of selector.split(",")) {
76
+ const trimmed = sel.trim();
77
+ if (trimmed) {
78
+ rules.push({ selector: trimmed, declarations });
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return rules;
84
+ }
85
+ function calculateSpecificity(selector) {
86
+ let ids = 0;
87
+ let classes = 0;
88
+ let types = 0;
89
+ const parts = selector.replace(/\s*>\s*/g, " ").trim();
90
+ const idMatches = parts.match(/#[a-zA-Z_-][\w-]*/g);
91
+ if (idMatches) ids = idMatches.length;
92
+ const classMatches = parts.match(/\.[a-zA-Z_-][\w-]*/g);
93
+ if (classMatches) classes += classMatches.length;
94
+ const attrMatches = parts.match(/\[[^\]]+\]/g);
95
+ if (attrMatches) classes += attrMatches.length;
96
+ const segments = parts.split(/[\s>+~]+/);
97
+ for (const seg of segments) {
98
+ const stripped = seg.replace(/#[a-zA-Z_-][\w-]*/g, "").replace(/\.[a-zA-Z_-][\w-]*/g, "").replace(/\[[^\]]+\]/g, "").replace(/:[\w-]+(\([^)]*\))?/g, "").trim();
99
+ if (stripped && stripped !== "*") {
100
+ types++;
101
+ }
102
+ }
103
+ return { ids, classes, types };
104
+ }
105
+ function compareSpecificity(a, b) {
106
+ if (a.ids !== b.ids) return a.ids - b.ids;
107
+ if (a.classes !== b.classes) return a.classes - b.classes;
108
+ return a.types - b.types;
109
+ }
110
+ function matchesSimpleSelector(selector, tag, classes, id, attrs) {
111
+ let remaining = selector;
112
+ const tagMatch = remaining.match(/^([a-zA-Z][\w-]*)/);
113
+ if (tagMatch) {
114
+ if (tag.toLowerCase() !== tagMatch[1].toLowerCase()) return false;
115
+ remaining = remaining.slice(tagMatch[1].length);
116
+ }
117
+ while (remaining.length > 0) {
118
+ if (remaining[0] === "#") {
119
+ const idMatch = remaining.match(/^#([a-zA-Z_-][\w-]*)/);
120
+ if (!idMatch) return false;
121
+ if (id !== idMatch[1]) return false;
122
+ remaining = remaining.slice(idMatch[0].length);
123
+ } else if (remaining[0] === ".") {
124
+ const classMatch = remaining.match(/^\.([a-zA-Z_-][\w-]*)/);
125
+ if (!classMatch) return false;
126
+ if (!classes.includes(classMatch[1])) return false;
127
+ remaining = remaining.slice(classMatch[0].length);
128
+ } else if (remaining[0] === "[") {
129
+ const attrMatch = remaining.match(/^\[([^\]]+)\]/);
130
+ if (!attrMatch) return false;
131
+ const attrExpr = attrMatch[1];
132
+ const eqIdx = attrExpr.indexOf("=");
133
+ if (eqIdx === -1) {
134
+ if (!(attrExpr.trim() in attrs)) return false;
135
+ } else {
136
+ const attrName = attrExpr.slice(0, eqIdx).replace(/[~|^$*]$/, "").trim();
137
+ const attrValue = attrExpr.slice(eqIdx + 1).replace(/^["']|["']$/g, "").trim();
138
+ if (attrs[attrName] !== attrValue) return false;
139
+ }
140
+ remaining = remaining.slice(attrMatch[0].length);
141
+ } else if (remaining[0] === ":") {
142
+ const pseudoMatch = remaining.match(/^:[\w-]+(\([^)]*\))?/);
143
+ if (!pseudoMatch) return false;
144
+ remaining = remaining.slice(pseudoMatch[0].length);
145
+ } else if (remaining[0] === "*") {
146
+ remaining = remaining.slice(1);
147
+ } else {
148
+ return false;
149
+ }
150
+ }
151
+ return true;
152
+ }
153
+ function selectorMatches(selector, element) {
154
+ if (selector.includes(">")) {
155
+ const parts2 = selector.split(/\s*>\s*/);
156
+ const targetSelector2 = parts2[parts2.length - 1].trim();
157
+ if (!matchesSimpleSelector(targetSelector2, element.tag, element.classes, element.id, element.attrs)) {
158
+ return false;
159
+ }
160
+ let ancestors = element.ancestors;
161
+ for (let i = parts2.length - 2; i >= 0; i--) {
162
+ const parentSelector = parts2[i].trim();
163
+ if (ancestors.length === 0) return false;
164
+ const parent = ancestors[ancestors.length - 1];
165
+ if (!matchesSimpleSelector(parentSelector, parent.tag, parent.classes, parent.id, parent.attrs)) {
166
+ return false;
167
+ }
168
+ ancestors = ancestors.slice(0, -1);
169
+ }
170
+ return true;
171
+ }
172
+ const parts = selector.split(/\s+/);
173
+ if (parts.length === 1) {
174
+ return matchesSimpleSelector(parts[0], element.tag, element.classes, element.id, element.attrs);
175
+ }
176
+ const targetSelector = parts[parts.length - 1];
177
+ if (!matchesSimpleSelector(targetSelector, element.tag, element.classes, element.id, element.attrs)) {
178
+ return false;
179
+ }
180
+ let ancestorIdx = element.ancestors.length - 1;
181
+ for (let i = parts.length - 2; i >= 0; i--) {
182
+ const ancestorSelector = parts[i];
183
+ let found = false;
184
+ while (ancestorIdx >= 0) {
185
+ const ancestor = element.ancestors[ancestorIdx];
186
+ ancestorIdx--;
187
+ if (matchesSimpleSelector(ancestorSelector, ancestor.tag, ancestor.classes, ancestor.id, ancestor.attrs)) {
188
+ found = true;
189
+ break;
190
+ }
191
+ }
192
+ if (!found) return false;
193
+ }
194
+ return true;
195
+ }
196
+ function parseDeclarations(decl) {
197
+ const map = /* @__PURE__ */ new Map();
198
+ for (const part of decl.split(";")) {
199
+ const trimmed = part.trim();
200
+ if (!trimmed) continue;
201
+ const colonIdx = trimmed.indexOf(":");
202
+ if (colonIdx === -1) continue;
203
+ const prop = trimmed.slice(0, colonIdx).trim();
204
+ const value = trimmed.slice(colonIdx + 1).trim();
205
+ if (prop && value) {
206
+ map.set(prop, value);
207
+ }
208
+ }
209
+ return map;
210
+ }
211
+ function mergeStyles(matches, existingStyle) {
212
+ matches.sort((a, b) => {
213
+ const specCmp = compareSpecificity(a.specificity, b.specificity);
214
+ return specCmp !== 0 ? specCmp : a.order - b.order;
215
+ });
216
+ const merged = /* @__PURE__ */ new Map();
217
+ for (const match of matches) {
218
+ const decls = parseDeclarations(match.declarations);
219
+ for (const [prop, value] of decls) {
220
+ merged.set(prop, value);
221
+ }
222
+ }
223
+ if (existingStyle) {
224
+ const existing = parseDeclarations(existingStyle);
225
+ for (const [prop, value] of existing) {
226
+ merged.set(prop, value);
227
+ }
228
+ }
229
+ const parts = [];
230
+ for (const [prop, value] of merged) {
231
+ parts.push(`${prop}: ${value}`);
232
+ }
233
+ return parts.join("; ");
234
+ }
235
+ function inlineCSS(html, css) {
236
+ if (!css.trim()) return html;
237
+ const rules = parseCSS(css);
238
+ if (rules.length === 0) return html;
239
+ const tagRegex = /<([a-zA-Z][\w-]*)(\s[^>]*?)?\s*\/?>/g;
240
+ const elements = [];
241
+ const ancestorStack = [];
242
+ const allTagsRegex = /<\/?([a-zA-Z][\w-]*)(\s[^>]*?)?\s*\/?>/g;
243
+ const voidElements = /* @__PURE__ */ new Set([
244
+ "area",
245
+ "base",
246
+ "br",
247
+ "col",
248
+ "embed",
249
+ "hr",
250
+ "img",
251
+ "input",
252
+ "link",
253
+ "meta",
254
+ "param",
255
+ "source",
256
+ "track",
257
+ "wbr"
258
+ ]);
259
+ let match;
260
+ while ((match = allTagsRegex.exec(html)) !== null) {
261
+ const fullTag = match[0];
262
+ const isClosing = fullTag[1] === "/";
263
+ const tagName = match[1].toLowerCase();
264
+ const attrsStr = match[2] || "";
265
+ if (isClosing) {
266
+ for (let i = ancestorStack.length - 1; i >= 0; i--) {
267
+ if (ancestorStack[i].tag === tagName) {
268
+ ancestorStack.splice(i);
269
+ break;
270
+ }
271
+ }
272
+ continue;
273
+ }
274
+ const attrs = {};
275
+ const attrRegex = /([a-zA-Z_:][\w:.-]*)\s*(?:=\s*"([^"]*)")?/g;
276
+ let attrMatch;
277
+ while ((attrMatch = attrRegex.exec(attrsStr)) !== null) {
278
+ attrs[attrMatch[1]] = attrMatch[2] ?? "";
279
+ }
280
+ const classes = (attrs["class"] || "").split(/\s+/).filter(Boolean);
281
+ const id = attrs["id"] || "";
282
+ const existingStyle = attrs["style"] || "";
283
+ const element = {
284
+ fullTag,
285
+ start: match.index,
286
+ tag: tagName,
287
+ classes,
288
+ id,
289
+ attrs,
290
+ existingStyle,
291
+ ancestors: [...ancestorStack]
292
+ };
293
+ elements.push(element);
294
+ const isSelfClosing = fullTag.endsWith("/>") || voidElements.has(tagName);
295
+ if (!isSelfClosing) {
296
+ ancestorStack.push({ tag: tagName, classes, id, attrs });
297
+ }
298
+ }
299
+ const elementMatches = /* @__PURE__ */ new Map();
300
+ for (let ruleIdx = 0; ruleIdx < rules.length; ruleIdx++) {
301
+ const rule = rules[ruleIdx];
302
+ const specificity = calculateSpecificity(rule.selector);
303
+ for (const element of elements) {
304
+ if (selectorMatches(rule.selector, element)) {
305
+ let matches = elementMatches.get(element);
306
+ if (!matches) {
307
+ matches = [];
308
+ elementMatches.set(element, matches);
309
+ }
310
+ matches.push({
311
+ declarations: rule.declarations,
312
+ specificity,
313
+ order: ruleIdx
314
+ });
315
+ }
316
+ }
317
+ }
318
+ const sortedElements = [...elementMatches.entries()].sort((a, b) => b[0].start - a[0].start);
319
+ let result = html;
320
+ for (const [element, matches] of sortedElements) {
321
+ const mergedStyle = mergeStyles(matches, element.existingStyle);
322
+ if (!mergedStyle) continue;
323
+ const originalTag = element.fullTag;
324
+ let newTag;
325
+ if (element.existingStyle) {
326
+ newTag = originalTag.replace(
327
+ /style="[^"]*"/,
328
+ `style="${mergedStyle}"`
329
+ );
330
+ } else {
331
+ const insertPos = originalTag.endsWith("/>") ? originalTag.length - 2 : originalTag.length - 1;
332
+ newTag = originalTag.slice(0, insertPos) + ` style="${mergedStyle}"` + originalTag.slice(insertPos);
333
+ }
334
+ result = result.slice(0, element.start) + newTag + result.slice(element.start + originalTag.length);
335
+ }
336
+ return result;
337
+ }
338
+
339
+ // src/html-to-text.ts
340
+ var ENTITY_MAP = {
341
+ "&amp;": "&",
342
+ "&lt;": "<",
343
+ "&gt;": ">",
344
+ "&quot;": '"',
345
+ "&#39;": "'",
346
+ "&apos;": "'",
347
+ "&nbsp;": " ",
348
+ "&ndash;": "\u2013",
349
+ "&mdash;": "\u2014",
350
+ "&copy;": "\xA9",
351
+ "&reg;": "\xAE",
352
+ "&trade;": "\u2122",
353
+ "&hellip;": "\u2026",
354
+ "&bull;": "\u2022"
355
+ };
356
+ function htmlToText(html) {
357
+ let text = html;
358
+ text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
359
+ text = text.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, "");
360
+ text = text.replace(/<!--[\s\S]*?-->/g, "");
361
+ text = text.replace(/<a\s[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_, href, content) => {
362
+ const linkText = content.replace(/<[^>]+>/g, "").trim();
363
+ if (linkText && href && linkText !== href) {
364
+ return `${linkText} (${href})`;
365
+ }
366
+ return linkText || href;
367
+ });
368
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, (_, content) => {
369
+ const headingText = content.replace(/<[^>]+>/g, "").trim();
370
+ return `
371
+
372
+ ${headingText.toUpperCase()}
373
+
374
+ `;
375
+ });
376
+ text = text.replace(/<br\s*\/?>/gi, "\n");
377
+ text = text.replace(/<hr\s*\/?>/gi, "\n---\n");
378
+ text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, content) => {
379
+ const itemText = content.replace(/<[^>]+>/g, "").trim();
380
+ return `
381
+ - ${itemText}`;
382
+ });
383
+ text = text.replace(/<\/(p|div|tr|table|blockquote)>/gi, "\n\n");
384
+ text = text.replace(/<\/(td|th)>/gi, " ");
385
+ text = text.replace(/<[^>]+>/g, "");
386
+ text = text.replace(/&[a-zA-Z0-9#]+;/g, (entity) => {
387
+ if (ENTITY_MAP[entity]) return ENTITY_MAP[entity];
388
+ const numMatch = entity.match(/^&#(\d+);$/);
389
+ if (numMatch) return String.fromCharCode(parseInt(numMatch[1], 10));
390
+ const hexMatch = entity.match(/^&#x([a-fA-F0-9]+);$/);
391
+ if (hexMatch) return String.fromCharCode(parseInt(hexMatch[1], 16));
392
+ return entity;
393
+ });
394
+ text = text.replace(/\t/g, " ");
395
+ text = text.replace(/[^\S\n]+/g, " ");
396
+ text = text.replace(/\n{3,}/g, "\n\n");
397
+ text = text.split("\n").map((line) => line.trim()).join("\n");
398
+ text = text.trim();
399
+ return text;
400
+ }
401
+
402
+ // src/email-document.ts
403
+ var EMAIL_RESET = `
404
+ body, #body-table { margin: 0; padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
405
+ table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
406
+ td { border-collapse: collapse; }
407
+ img { border: 0; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }
408
+ a img { border: none; }
409
+ `.trim();
410
+ function wrapEmailDocument(options) {
411
+ const { bodyHtml, css, previewText, headContent, skipStyleBlock } = options;
412
+ const previewHtml = previewText ? `<span style="display: none; font-size: 1px; color: #ffffff; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;">${escapeHtml(previewText)}</span>` : "";
413
+ const styleBlock = !skipStyleBlock && css ? `<style type="text/css">
414
+ ${css}
415
+ </style>` : "";
416
+ return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
417
+ <html xmlns="http://www.w3.org/1999/xhtml">
418
+ <head>
419
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
420
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
421
+ <!--[if !mso]><!-->
422
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
423
+ <!--<![endif]-->
424
+ <style type="text/css">
425
+ ${EMAIL_RESET}
426
+ </style>
427
+ ${styleBlock}
428
+ ${headContent || ""}
429
+ </head>
430
+ <body style="margin: 0; padding: 0;">
431
+ ${previewHtml}
432
+ <table role="presentation" id="body-table" cellpadding="0" cellspacing="0" border="0" width="100%" style="width: 100%;">
433
+ <tr>
434
+ <td align="center" valign="top">
435
+ ${bodyHtml}
436
+ </td>
437
+ </tr>
438
+ </table>
439
+ </body>
440
+ </html>`;
441
+ }
442
+ function escapeHtml(str) {
443
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
444
+ }
445
+
446
+ // src/render-email.ts
447
+ function renderEmail(component, props, options) {
448
+ const {
449
+ subject,
450
+ previewText,
451
+ skipInlining = false,
452
+ skipStyleBlock = false,
453
+ headContent
454
+ } = options ?? {};
455
+ const { html: bodyHtml, css } = (0, import_utopia_server.renderToString)(component, props);
456
+ const inlinedBody = skipInlining ? bodyHtml : inlineCSS(bodyHtml, css);
457
+ const fullHtml = wrapEmailDocument({
458
+ bodyHtml: inlinedBody,
459
+ css,
460
+ previewText,
461
+ headContent,
462
+ skipStyleBlock
463
+ });
464
+ const text = htmlToText(fullHtml);
465
+ return {
466
+ html: fullHtml,
467
+ text,
468
+ subject
469
+ };
470
+ }
471
+
472
+ // src/mailer.ts
473
+ function createMailer(adapter) {
474
+ return {
475
+ async send(options) {
476
+ const {
477
+ to,
478
+ from,
479
+ subject,
480
+ component,
481
+ props,
482
+ renderOptions,
483
+ cc,
484
+ bcc,
485
+ replyTo,
486
+ attachments
487
+ } = options;
488
+ const rendered = renderEmail(component, props, {
489
+ ...renderOptions,
490
+ subject
491
+ });
492
+ const emailSubject = subject ?? rendered.subject ?? "";
493
+ return adapter.send({
494
+ to,
495
+ from,
496
+ subject: emailSubject,
497
+ html: rendered.html,
498
+ text: rendered.text,
499
+ cc,
500
+ bcc,
501
+ replyTo,
502
+ attachments
503
+ });
504
+ }
505
+ };
506
+ }
507
+
508
+ // src/components/email-layout.ts
509
+ var import_ssr_runtime = require("@matthesketh/utopia-server/ssr-runtime");
510
+ var EmailLayout = {
511
+ setup: (props) => ({
512
+ width: props.width ?? 600,
513
+ backgroundColor: props.backgroundColor ?? "#ffffff",
514
+ fontFamily: props.fontFamily ?? "Arial, Helvetica, sans-serif"
515
+ }),
516
+ render: (ctx) => {
517
+ const table = (0, import_ssr_runtime.createElement)("table");
518
+ (0, import_ssr_runtime.setAttr)(table, "role", "presentation");
519
+ (0, import_ssr_runtime.setAttr)(table, "cellpadding", "0");
520
+ (0, import_ssr_runtime.setAttr)(table, "cellspacing", "0");
521
+ (0, import_ssr_runtime.setAttr)(table, "border", "0");
522
+ (0, import_ssr_runtime.setAttr)(table, "width", String(ctx.width));
523
+ (0, import_ssr_runtime.setAttr)(table, "style", `max-width: ${ctx.width}px; width: 100%; margin: 0 auto; background-color: ${ctx.backgroundColor}; font-family: ${ctx.fontFamily}`);
524
+ const tr = (0, import_ssr_runtime.createElement)("tr");
525
+ const td = (0, import_ssr_runtime.createElement)("td");
526
+ (0, import_ssr_runtime.setAttr)(td, "style", "padding: 0");
527
+ if (ctx.$slots.default) {
528
+ (0, import_ssr_runtime.appendChild)(td, ctx.$slots.default());
529
+ }
530
+ (0, import_ssr_runtime.appendChild)(tr, td);
531
+ (0, import_ssr_runtime.appendChild)(table, tr);
532
+ return table;
533
+ }
534
+ };
535
+
536
+ // src/components/email-button.ts
537
+ var import_ssr_runtime2 = require("@matthesketh/utopia-server/ssr-runtime");
538
+ var EmailButton = {
539
+ setup: (props) => ({
540
+ href: props.href ?? "#",
541
+ text: props.text ?? "Click Here",
542
+ color: props.color ?? "#007bff",
543
+ textColor: props.textColor ?? "#ffffff",
544
+ borderRadius: props.borderRadius ?? "4px"
545
+ }),
546
+ render: (ctx) => {
547
+ const table = (0, import_ssr_runtime2.createElement)("table");
548
+ (0, import_ssr_runtime2.setAttr)(table, "role", "presentation");
549
+ (0, import_ssr_runtime2.setAttr)(table, "cellpadding", "0");
550
+ (0, import_ssr_runtime2.setAttr)(table, "cellspacing", "0");
551
+ (0, import_ssr_runtime2.setAttr)(table, "border", "0");
552
+ const tr = (0, import_ssr_runtime2.createElement)("tr");
553
+ const td = (0, import_ssr_runtime2.createElement)("td");
554
+ (0, import_ssr_runtime2.setAttr)(td, "align", "center");
555
+ (0, import_ssr_runtime2.setAttr)(td, "style", `background-color: ${ctx.color}; border-radius: ${ctx.borderRadius}; padding: 12px 24px`);
556
+ const a = (0, import_ssr_runtime2.createElement)("a");
557
+ (0, import_ssr_runtime2.setAttr)(a, "href", ctx.href);
558
+ (0, import_ssr_runtime2.setAttr)(a, "style", `color: ${ctx.textColor}; text-decoration: none; font-size: 16px; font-weight: bold; display: inline-block`);
559
+ (0, import_ssr_runtime2.appendChild)(a, (0, import_ssr_runtime2.createTextNode)(ctx.text));
560
+ (0, import_ssr_runtime2.appendChild)(td, a);
561
+ (0, import_ssr_runtime2.appendChild)(tr, td);
562
+ (0, import_ssr_runtime2.appendChild)(table, tr);
563
+ return table;
564
+ }
565
+ };
566
+
567
+ // src/components/email-card.ts
568
+ var import_ssr_runtime3 = require("@matthesketh/utopia-server/ssr-runtime");
569
+ var EmailCard = {
570
+ setup: (props) => ({
571
+ backgroundColor: props.backgroundColor ?? "#ffffff",
572
+ padding: props.padding ?? "20px",
573
+ borderRadius: props.borderRadius ?? "4px",
574
+ borderColor: props.borderColor ?? "#e0e0e0"
575
+ }),
576
+ render: (ctx) => {
577
+ const table = (0, import_ssr_runtime3.createElement)("table");
578
+ (0, import_ssr_runtime3.setAttr)(table, "role", "presentation");
579
+ (0, import_ssr_runtime3.setAttr)(table, "cellpadding", "0");
580
+ (0, import_ssr_runtime3.setAttr)(table, "cellspacing", "0");
581
+ (0, import_ssr_runtime3.setAttr)(table, "border", "0");
582
+ (0, import_ssr_runtime3.setAttr)(table, "width", "100%");
583
+ const tr = (0, import_ssr_runtime3.createElement)("tr");
584
+ const td = (0, import_ssr_runtime3.createElement)("td");
585
+ (0, import_ssr_runtime3.setAttr)(td, "style", `background-color: ${ctx.backgroundColor}; padding: ${ctx.padding}; border-radius: ${ctx.borderRadius}; border: 1px solid ${ctx.borderColor}`);
586
+ if (ctx.$slots.default) {
587
+ (0, import_ssr_runtime3.appendChild)(td, ctx.$slots.default());
588
+ }
589
+ (0, import_ssr_runtime3.appendChild)(tr, td);
590
+ (0, import_ssr_runtime3.appendChild)(table, tr);
591
+ return table;
592
+ }
593
+ };
594
+
595
+ // src/components/email-divider.ts
596
+ var import_ssr_runtime4 = require("@matthesketh/utopia-server/ssr-runtime");
597
+ var EmailDivider = {
598
+ setup: (props) => ({
599
+ color: props.color ?? "#e0e0e0",
600
+ width: props.width ?? "100%",
601
+ height: props.height ?? "1px"
602
+ }),
603
+ render: (ctx) => {
604
+ const table = (0, import_ssr_runtime4.createElement)("table");
605
+ (0, import_ssr_runtime4.setAttr)(table, "role", "presentation");
606
+ (0, import_ssr_runtime4.setAttr)(table, "cellpadding", "0");
607
+ (0, import_ssr_runtime4.setAttr)(table, "cellspacing", "0");
608
+ (0, import_ssr_runtime4.setAttr)(table, "border", "0");
609
+ (0, import_ssr_runtime4.setAttr)(table, "width", ctx.width);
610
+ const tr = (0, import_ssr_runtime4.createElement)("tr");
611
+ const td = (0, import_ssr_runtime4.createElement)("td");
612
+ (0, import_ssr_runtime4.setAttr)(td, "style", `border-bottom: ${ctx.height} solid ${ctx.color}; font-size: 1px; line-height: 1px; height: ${ctx.height}`);
613
+ const nbsp = (0, import_ssr_runtime4.createElement)("span");
614
+ (0, import_ssr_runtime4.setAttr)(nbsp, "style", "display: block; height: 0; overflow: hidden");
615
+ (0, import_ssr_runtime4.appendChild)(td, nbsp);
616
+ (0, import_ssr_runtime4.appendChild)(tr, td);
617
+ (0, import_ssr_runtime4.appendChild)(table, tr);
618
+ return table;
619
+ }
620
+ };
621
+
622
+ // src/components/email-heading.ts
623
+ var import_ssr_runtime5 = require("@matthesketh/utopia-server/ssr-runtime");
624
+ var HEADING_SIZES = {
625
+ 1: "28px",
626
+ 2: "22px",
627
+ 3: "18px"
628
+ };
629
+ var EmailHeading = {
630
+ setup: (props) => ({
631
+ level: Math.min(3, Math.max(1, props.level ?? 1)),
632
+ color: props.color ?? "#333333",
633
+ align: props.align ?? "left"
634
+ }),
635
+ render: (ctx) => {
636
+ const tag = `h${ctx.level}`;
637
+ const el = (0, import_ssr_runtime5.createElement)(tag);
638
+ const fontSize = HEADING_SIZES[ctx.level] || "28px";
639
+ (0, import_ssr_runtime5.setAttr)(el, "style", `margin: 0 0 10px 0; font-size: ${fontSize}; line-height: 1.3; color: ${ctx.color}; text-align: ${ctx.align}`);
640
+ if (ctx.$slots.default) {
641
+ (0, import_ssr_runtime5.appendChild)(el, ctx.$slots.default());
642
+ }
643
+ return el;
644
+ }
645
+ };
646
+
647
+ // src/components/email-text.ts
648
+ var import_ssr_runtime6 = require("@matthesketh/utopia-server/ssr-runtime");
649
+ var EmailText = {
650
+ setup: (props) => ({
651
+ color: props.color ?? "#333333",
652
+ fontSize: props.fontSize ?? "16px",
653
+ lineHeight: props.lineHeight ?? "1.5",
654
+ align: props.align ?? "left"
655
+ }),
656
+ render: (ctx) => {
657
+ const p = (0, import_ssr_runtime6.createElement)("p");
658
+ (0, import_ssr_runtime6.setAttr)(p, "style", `margin: 0 0 16px 0; font-size: ${ctx.fontSize}; line-height: ${ctx.lineHeight}; color: ${ctx.color}; text-align: ${ctx.align}`);
659
+ if (ctx.$slots.default) {
660
+ (0, import_ssr_runtime6.appendChild)(p, ctx.$slots.default());
661
+ }
662
+ return p;
663
+ }
664
+ };
665
+
666
+ // src/components/email-image.ts
667
+ var import_ssr_runtime7 = require("@matthesketh/utopia-server/ssr-runtime");
668
+ var EmailImage = {
669
+ setup: (props) => ({
670
+ src: props.src ?? "",
671
+ alt: props.alt ?? "",
672
+ width: props.width,
673
+ height: props.height,
674
+ align: props.align ?? "center"
675
+ }),
676
+ render: (ctx) => {
677
+ const img = (0, import_ssr_runtime7.createElement)("img");
678
+ (0, import_ssr_runtime7.setAttr)(img, "src", ctx.src);
679
+ (0, import_ssr_runtime7.setAttr)(img, "alt", ctx.alt);
680
+ let style = "display: block; border: 0; outline: none; text-decoration: none";
681
+ if (ctx.width) {
682
+ (0, import_ssr_runtime7.setAttr)(img, "width", String(ctx.width));
683
+ style += `; max-width: ${ctx.width}px`;
684
+ }
685
+ if (ctx.height) {
686
+ (0, import_ssr_runtime7.setAttr)(img, "height", String(ctx.height));
687
+ }
688
+ (0, import_ssr_runtime7.setAttr)(img, "style", style);
689
+ if (ctx.align !== "left") {
690
+ const wrapper = (0, import_ssr_runtime7.createElement)("div");
691
+ (0, import_ssr_runtime7.setAttr)(wrapper, "style", `text-align: ${ctx.align}`);
692
+ (0, import_ssr_runtime7.appendChild)(wrapper, img);
693
+ return wrapper;
694
+ }
695
+ return img;
696
+ }
697
+ };
698
+
699
+ // src/components/email-columns.ts
700
+ var import_ssr_runtime8 = require("@matthesketh/utopia-server/ssr-runtime");
701
+ var EmailColumns = {
702
+ setup: (props) => ({
703
+ columns: props.columns ?? 2,
704
+ gap: props.gap ?? "20px"
705
+ }),
706
+ render: (ctx) => {
707
+ const table = (0, import_ssr_runtime8.createElement)("table");
708
+ (0, import_ssr_runtime8.setAttr)(table, "role", "presentation");
709
+ (0, import_ssr_runtime8.setAttr)(table, "cellpadding", "0");
710
+ (0, import_ssr_runtime8.setAttr)(table, "cellspacing", "0");
711
+ (0, import_ssr_runtime8.setAttr)(table, "border", "0");
712
+ (0, import_ssr_runtime8.setAttr)(table, "width", "100%");
713
+ const tr = (0, import_ssr_runtime8.createElement)("tr");
714
+ const columnCount = Math.min(4, Math.max(1, ctx.columns));
715
+ const widthPercent = Math.floor(100 / columnCount);
716
+ for (let i = 0; i < columnCount; i++) {
717
+ const td = (0, import_ssr_runtime8.createElement)("td");
718
+ (0, import_ssr_runtime8.setAttr)(td, "width", `${widthPercent}%`);
719
+ (0, import_ssr_runtime8.setAttr)(td, "valign", "top");
720
+ const paddingLeft = i > 0 ? `padding-left: ${ctx.gap}` : "";
721
+ if (paddingLeft) {
722
+ (0, import_ssr_runtime8.setAttr)(td, "style", paddingLeft);
723
+ }
724
+ const slotName = `column-${i}`;
725
+ if (ctx.$slots[slotName]) {
726
+ (0, import_ssr_runtime8.appendChild)(td, ctx.$slots[slotName]());
727
+ } else if (i === 0 && ctx.$slots.default) {
728
+ (0, import_ssr_runtime8.appendChild)(td, ctx.$slots.default());
729
+ }
730
+ (0, import_ssr_runtime8.appendChild)(tr, td);
731
+ }
732
+ (0, import_ssr_runtime8.appendChild)(table, tr);
733
+ return table;
734
+ }
735
+ };
736
+
737
+ // src/components/email-spacer.ts
738
+ var import_ssr_runtime9 = require("@matthesketh/utopia-server/ssr-runtime");
739
+ var EmailSpacer = {
740
+ setup: (props) => ({
741
+ height: props.height ?? "20px"
742
+ }),
743
+ render: (ctx) => {
744
+ const table = (0, import_ssr_runtime9.createElement)("table");
745
+ (0, import_ssr_runtime9.setAttr)(table, "role", "presentation");
746
+ (0, import_ssr_runtime9.setAttr)(table, "cellpadding", "0");
747
+ (0, import_ssr_runtime9.setAttr)(table, "cellspacing", "0");
748
+ (0, import_ssr_runtime9.setAttr)(table, "border", "0");
749
+ (0, import_ssr_runtime9.setAttr)(table, "width", "100%");
750
+ const tr = (0, import_ssr_runtime9.createElement)("tr");
751
+ const td = (0, import_ssr_runtime9.createElement)("td");
752
+ (0, import_ssr_runtime9.setAttr)(td, "style", `height: ${ctx.height}; font-size: 1px; line-height: 1px`);
753
+ const span = (0, import_ssr_runtime9.createElement)("span");
754
+ (0, import_ssr_runtime9.setAttr)(span, "style", "display: block; height: 0; overflow: hidden");
755
+ (0, import_ssr_runtime9.appendChild)(td, span);
756
+ (0, import_ssr_runtime9.appendChild)(tr, td);
757
+ (0, import_ssr_runtime9.appendChild)(table, tr);
758
+ return table;
759
+ }
760
+ };
761
+ // Annotate the CommonJS export names for ESM import in node:
762
+ 0 && (module.exports = {
763
+ EmailButton,
764
+ EmailCard,
765
+ EmailColumns,
766
+ EmailDivider,
767
+ EmailHeading,
768
+ EmailImage,
769
+ EmailLayout,
770
+ EmailSpacer,
771
+ EmailText,
772
+ createMailer,
773
+ htmlToText,
774
+ inlineCSS,
775
+ renderEmail
776
+ });