@samva/markup 0.13.1 → 0.15.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.
@@ -1,273 +0,0 @@
1
- import { i as escapeText, r as escapeAttribute } from "./html-eOZfDwVf.js";
2
- import { a as jsx, c as rawMarkup, l as reportEmailDiagnostic, n as Fragment, s as normalizeChildren, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
3
- //#region src/email/components.ts
4
- /** Marks the hidden preview block so the renderer can read it back and the text lane can keep it. */
5
- const PREHEADER_ATTRIBUTE = "data-samva-preheader";
6
- const PREHEADER_PADDING_ATTRIBUTE = "data-samva-preheader-padding";
7
- const PREHEADER_TARGET_LENGTH = 200;
8
- const PREHEADER_PADDING = "\xA0‌​‍‎‏";
9
- const presentation = (table, row, rowProps = {}) => jsx("table", {
10
- role: "presentation",
11
- border: 0,
12
- cellpadding: 0,
13
- cellspacing: 0,
14
- ...table,
15
- children: jsx("tbody", { children: jsx("tr", {
16
- ...rowProps,
17
- children: row
18
- }) })
19
- });
20
- /** The hidden inbox preview block. Padding characters stop the client borrowing body copy. */
21
- const preheaderNode = (text) => jsx("div", {
22
- [PREHEADER_ATTRIBUTE]: text,
23
- style: {
24
- display: "none",
25
- overflow: "hidden",
26
- lineHeight: "1px",
27
- opacity: 0,
28
- maxHeight: 0,
29
- maxWidth: 0
30
- },
31
- children: [text, text.length >= PREHEADER_TARGET_LENGTH ? null : jsx("div", {
32
- [PREHEADER_PADDING_ATTRIBUTE]: true,
33
- children: PREHEADER_PADDING.repeat(Math.ceil((PREHEADER_TARGET_LENGTH - text.length) / 7))
34
- })]
35
- });
36
- /** Document shell: the head every target client needs, plus the body content cell. */
37
- const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor, style, children }) => jsx("html", {
38
- lang,
39
- dir,
40
- xmlns: "http://www.w3.org/1999/xhtml",
41
- "xmlns:o": "urn:schemas-microsoft-com:office:office",
42
- children: [jsx("head", { children: [
43
- jsx("meta", { charset: "utf-8" }),
44
- jsx("meta", {
45
- "http-equiv": "Content-Type",
46
- content: "text/html; charset=UTF-8"
47
- }),
48
- jsx("meta", {
49
- name: "viewport",
50
- content: "width=device-width, initial-scale=1"
51
- }),
52
- jsx("meta", { name: "x-apple-disable-message-reformatting" }),
53
- jsx("meta", {
54
- name: "color-scheme",
55
- content: "light dark"
56
- }),
57
- jsx("meta", {
58
- name: "supported-color-schemes",
59
- content: "light dark"
60
- }),
61
- title === void 0 ? null : jsx("title", { children: title }),
62
- rawMarkup("<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->")
63
- ] }), jsx("body", {
64
- style: {
65
- margin: 0,
66
- padding: 0,
67
- ...backgroundColor === void 0 ? {} : { backgroundColor }
68
- },
69
- children: [presentation({
70
- align: "center",
71
- width: "100%",
72
- ...backgroundColor === void 0 ? {} : { bgcolor: backgroundColor }
73
- }, jsx("td", {
74
- className,
75
- ...style === void 0 ? {} : { style },
76
- children
77
- }))]
78
- })]
79
- });
80
- /** A full-width band of content, as the single-cell table every client agrees on. */
81
- const Section = ({ className, align = "center", width = "100%", bgcolor, style, tableStyle, children }) => presentation({
82
- align,
83
- width,
84
- ...bgcolor === void 0 ? {} : { bgcolor },
85
- ...tableStyle === void 0 ? {} : { style: tableStyle }
86
- }, jsx("td", {
87
- className,
88
- ...style === void 0 ? {} : { style },
89
- children
90
- }));
91
- /** The equal share of a row that each of `count` cells takes, whole percent rounded down. */
92
- const equalColumnWidth = (count) => `${Math.floor(100 / count)}%`;
93
- /** Give a `Column` that states no width its equal share, keeping the primitive's attribute order. */
94
- const withColumnWidth = (node, width) => {
95
- if (node.type === "Fragment") return EmailNode.Fragment({ children: node.children.map((child) => withColumnWidth(child, width)) });
96
- if (node.type !== "Element" || node.tag !== "td" || node.props.width !== void 0) return node;
97
- const props = {};
98
- for (const [key, value] of Object.entries(node.props)) {
99
- if (key === "style") props.width = width;
100
- props[key] = value;
101
- }
102
- if (props.width === void 0) props.width = width;
103
- return EmailNode.Element({
104
- ...node,
105
- props
106
- });
107
- };
108
- /**
109
- * A row of side-by-side `Column`s. No flex or grid conversion happens here. With `each` it repeats
110
- * its cells over a list, `per` to a row, and splits the row equally between cells that state no
111
- * width.
112
- */
113
- const Columns = ({ className, align = "center", width = "100%", style, each, per, children }) => {
114
- const table = {
115
- className,
116
- align,
117
- width,
118
- ...style === void 0 ? {} : { style }
119
- };
120
- if (each === void 0 || typeof children !== "function") return presentation(table, children, { style: { width: "100%" } });
121
- if (per !== void 0 && (!Number.isInteger(per) || per < 1)) reportEmailDiagnostic({
122
- code: "invalid-columns-per",
123
- severity: "error",
124
- message: "<Columns per> must be a positive integer."
125
- });
126
- const size = per !== void 0 && Number.isInteger(per) && per >= 1 ? per : void 0;
127
- const chunks = [];
128
- if (size === void 0) chunks.push(each);
129
- else for (let at = 0; at < each.length; at += size) chunks.push(each.slice(at, at + size));
130
- const share = equalColumnWidth(size ?? Math.max(each.length, 1));
131
- return jsx(Fragment, { children: chunks.map((chunk) => presentation(table, chunk.flatMap((item, index) => normalizeChildren(children(item, index)).map((cell) => withColumnWidth(cell, share))), { style: { width: "100%" } })) });
132
- };
133
- /** One cell of a `Columns` row. */
134
- const Column = ({ className, align, valign, width, style, children }) => jsx("td", {
135
- className,
136
- ...align === void 0 ? {} : { align },
137
- ...valign === void 0 ? {} : { valign },
138
- ...width === void 0 ? {} : { width },
139
- ...style === void 0 ? {} : { style },
140
- children
141
- });
142
- const DEFAULT_BUTTON_FONT = "Helvetica, Arial, sans-serif";
143
- /** Average advance of a bold sans-serif glyph, as a fraction of the font size. */
144
- const BOLD_GLYPH_ADVANCE = .6;
145
- const invalidButtonNumber = (name) => reportEmailDiagnostic({
146
- code: "invalid-button-number",
147
- severity: "error",
148
- message: `<Button ${name}> must be a finite number.`
149
- });
150
- const buttonNumber = (name, value, fallback) => {
151
- if (typeof value === "number" && Number.isFinite(value)) return value;
152
- invalidButtonNumber(name);
153
- return fallback;
154
- };
155
- const optionalButtonNumber = (name, value) => {
156
- if (value === void 0) return void 0;
157
- if (typeof value === "number" && Number.isFinite(value)) return value;
158
- invalidButtonNumber(name);
159
- };
160
- const optionalPositiveButtonNumber = (name, value) => {
161
- if (value === void 0) return void 0;
162
- if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
163
- reportEmailDiagnostic({
164
- code: "invalid-button-number",
165
- severity: "error",
166
- message: `<Button ${name}> must be a positive finite number.`
167
- });
168
- };
169
- /** A link styled as a button, with a VML fallback so classic Outlook draws the shape. */
170
- const Button = ({ className, href, width, height, paddingX = 16, backgroundColor = "#000000", color = "#ffffff", borderRadius = 0, borderColor, borderWidth = 0, fontSize = 16, fontFamily = DEFAULT_BUTTON_FONT, align = "left", style, children }) => {
171
- const safeWidth = optionalButtonNumber("width", width);
172
- const safeHeight = optionalPositiveButtonNumber("height", height);
173
- const safePaddingX = buttonNumber("paddingX", paddingX, 16);
174
- const safeBorderRadius = buttonNumber("borderRadius", borderRadius, 0);
175
- const safeBorderWidth = buttonNumber("borderWidth", borderWidth, 0);
176
- const safeFontSize = buttonNumber("fontSize", fontSize, 16);
177
- const label = typeof children === "string" ? children : void 0;
178
- const fallback = safeHeight !== void 0 && label !== void 0;
179
- if (!fallback) reportEmailDiagnostic({
180
- code: "button-outlook-fallback",
181
- severity: "warning",
182
- message: "<Button> draws its classic Outlook shape from VML, which needs a height and a plain string label. Without both, classic Windows Outlook shows an unstyled link.",
183
- clients: ["outlook-windows"]
184
- });
185
- const arcsize = safeHeight === void 0 ? 0 : Math.round(safeBorderRadius / safeHeight * 100);
186
- const fallbackWidth = safeWidth ?? Math.ceil((label?.length ?? 0) * safeFontSize * BOLD_GLYPH_ADVANCE) + 2 * safePaddingX;
187
- return presentation({
188
- width: "100%",
189
- style: { borderCollapse: "collapse" }
190
- }, jsx("td", {
191
- align,
192
- children: [fallback ? rawMarkup(`<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${escapeAttribute(href)}" style="height:${safeHeight}px;v-text-anchor:middle;width:${fallbackWidth}px;" arcsize="${arcsize}%" ${borderColor === void 0 ? "stroke=\"false\"" : `strokecolor="${escapeAttribute(borderColor)}" strokeweight="${safeBorderWidth}px"`} fillcolor="${escapeAttribute(backgroundColor)}"><w:anchorlock/><center style="color:${escapeAttribute(color)};font-family:${escapeAttribute(fontFamily)};font-size:${safeFontSize}px;font-weight:bold;">${escapeText(label)}</center></v:roundrect><![endif]-->`) : null, jsx("a", {
193
- className,
194
- href,
195
- target: "_blank",
196
- rel: "noopener noreferrer",
197
- style: {
198
- backgroundColor,
199
- color,
200
- borderRadius: `${safeBorderRadius}px`,
201
- display: "inline-block",
202
- fontFamily,
203
- fontSize: `${safeFontSize}px`,
204
- fontWeight: "bold",
205
- lineHeight: safeHeight === void 0 ? "120%" : `${safeHeight - 2 * safeBorderWidth}px`,
206
- textAlign: "center",
207
- textDecoration: "none",
208
- ...safeWidth === void 0 ? { padding: `0 ${safePaddingX}px` } : {
209
- width: `${safeWidth}px`,
210
- maxWidth: "100%"
211
- },
212
- ...borderColor === void 0 ? {} : { border: `${safeBorderWidth}px solid ${borderColor}` },
213
- ...fallback ? { msoHide: "all" } : {},
214
- ...style
215
- },
216
- children
217
- })]
218
- }));
219
- };
220
- /** Vertical space that survives clients which collapse margins. */
221
- const Spacer = ({ height }) => presentation({ width: "100%" }, jsx("td", {
222
- height,
223
- style: {
224
- height: `${height}px`,
225
- lineHeight: `${height}px`,
226
- fontSize: "1px"
227
- },
228
- children: "​"
229
- }));
230
- /** A horizontal rule drawn as a cell border, which every target client renders. */
231
- const Divider = ({ className, color = "#e5e7eb", thickness = 1, style }) => presentation({ width: "100%" }, jsx("td", {
232
- className,
233
- style: {
234
- borderTop: `${thickness}px solid ${color}`,
235
- fontSize: "1px",
236
- lineHeight: "1px",
237
- ...style
238
- },
239
- children: "​"
240
- }));
241
- /** An image with the width/height attributes Outlook sizes from before CSS applies. */
242
- const Image = ({ className, src, alt, width, height, align, style }) => jsx("img", {
243
- className,
244
- src,
245
- alt,
246
- border: 0,
247
- ...width === void 0 ? {} : { width },
248
- ...height === void 0 ? {} : { height },
249
- ...align === void 0 ? {} : { align },
250
- style: {
251
- display: "block",
252
- outline: "none",
253
- border: "none",
254
- textDecoration: "none",
255
- maxWidth: "100%",
256
- ...style
257
- }
258
- });
259
- /** A text link that opens outside the client and cannot reach back at the opener. */
260
- const Link = ({ className, href, target = "_blank", rel = "noopener noreferrer", style, children }) => jsx("a", {
261
- className,
262
- href,
263
- target,
264
- rel,
265
- style: {
266
- color: "#067df7",
267
- textDecoration: "none",
268
- ...style
269
- },
270
- children
271
- });
272
- //#endregion
273
- export { Email as a, PREHEADER_ATTRIBUTE as c, equalColumnWidth as d, preheaderNode as f, Divider as i, Section as l, Column as n, Image as o, Columns as r, Link as s, Button as t, Spacer as u };