@samva/markup 0.10.0 → 0.11.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,7 +1,52 @@
1
1
  import { n as isObject } from "./guards-eV1qLbf2.js";
2
- import { i as UNSUBSCRIBE_URL_PLACEHOLDER$1, n as RESERVED_FOOTER_ATTRIBUTES, r as UNSUBSCRIBE_LINK, t as BRAND_FOOTER_MARKER } from "./unsubscribe-link-CoesnQDU.js";
3
2
  import { callFormatter, createFmt } from "./fmt.js";
4
- 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/internal/unsubscribe-link.ts
4
+ /**
5
+ * Marks the one link the serializer lets carry the brand plugin's unsubscribe
6
+ * placeholder as its href. Only `BrandFooter` sets it; nothing public exports
7
+ * it, so no author-written link can claim the exemption.
8
+ */
9
+ const UNSUBSCRIBE_LINK = Symbol("samva.unsubscribe-link");
10
+ /**
11
+ * Marks the elements `BrandFooter` writes the brand plugin's footer markers
12
+ * on. The serializer emits those reserved attributes only for an element
13
+ * carrying it, so an author cannot claim a footer the send path would then
14
+ * not append.
15
+ */
16
+ const BRAND_FOOTER_MARKER = Symbol("samva.brand-footer-marker");
17
+ //#endregion
18
+ //#region src/email/samva-brand-plugin.ts
19
+ /**
20
+ * Samva's brand plugin, the compiler's default. A template imports its
21
+ * organization's brand from `samva:brand`, and Samva's send path fills
22
+ * `%samva:unsubscribe-url%` per delivery and turns `data-samva-no-track` into
23
+ * its email provider's own no-track marker.
24
+ */
25
+ const samvaBrandPlugin = {
26
+ specifier: "samva:brand",
27
+ footerAttribute: "data-samva-footer",
28
+ unsubscribeRowAttribute: "data-samva-unsubscribe",
29
+ unsubscribeUrlPlaceholder: "%samva:unsubscribe-url%",
30
+ noTrackAttribute: "data-samva-no-track"
31
+ };
32
+ //#endregion
33
+ //#region src/email/brand-plugin.ts
34
+ const SLUG = "[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?";
35
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
36
+ /** `<specifier>` or `<specifier>/<slug>`, as a regular expression source with no groups. */
37
+ const brandSpecifierPattern = (plugin) => `${escapeRegExp(plugin.specifier)}(?:\\/${SLUG})?`;
38
+ /** The brand a specifier names: `undefined` slug means the default brand; `undefined` when it names none. */
39
+ const parseBrandSpecifier = (specifier, plugin = samvaBrandPlugin) => {
40
+ const match = new RegExp(`^${escapeRegExp(plugin.specifier)}(?:\\/(${SLUG}))?$`).exec(specifier);
41
+ return match === null ? void 0 : { slug: match[1] };
42
+ };
43
+ /** The attributes only `BrandFooter` writes; an authored element carrying one is refused. */
44
+ const reservedBrandAttributes = (plugin) => /* @__PURE__ */ new Set([
45
+ plugin.footerAttribute,
46
+ plugin.unsubscribeRowAttribute,
47
+ plugin.noTrackAttribute
48
+ ]);
49
+ //#endregion
5
50
  //#region src/email/elements.ts
6
51
  /** Attributes every supported element accepts. `class`/`className`/`style` are handled separately. */
7
52
  const EMAIL_GLOBAL_ATTRIBUTES = {
@@ -317,10 +362,8 @@ const MAX_ITERATIONS = 1e4;
317
362
  const MAX_DEPTH = 64;
318
363
  /** Classic Outlook lays a document out correctly only under a transitional doctype. */
319
364
  const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
320
- /** The per-recipient unsubscribe URL the send path substitutes; SES must not rewrite it. */
321
- const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
322
- const PREHEADER_TARGET_LENGTH$1 = 200;
323
- const PREHEADER_PADDING$1 = "\xA0‌​‍‎‏";
365
+ const PREHEADER_TARGET_LENGTH = 200;
366
+ const PREHEADER_PADDING = "\xA0‌​‍‎‏";
324
367
  const escapeText$1 = (value) => value.replace(/[&<>]/g, (character) => ({
325
368
  "&": "&amp;",
326
369
  "<": "&lt;",
@@ -589,9 +632,9 @@ const buildAttributes = (ctx, scope, tag, spec, node, props, depth) => {
589
632
  const name = key === "className" ? "class" : key;
590
633
  const value = evalValue(ctx, scope, raw, depth + 1);
591
634
  props.set(name, value);
592
- if (tag === "a" && name === "href" && raw === UNSUBSCRIBE_URL_PLACEHOLDER) {
635
+ if (tag === "a" && name === "href" && raw === ctx.brandPlugin.unsubscribeUrlPlaceholder) {
593
636
  present.add(name);
594
- emitted.push(` href="${UNSUBSCRIBE_URL_PLACEHOLDER}" ses:no-track`);
637
+ emitted.push(` href="${escapeAttribute$1(ctx.brandPlugin.unsubscribeUrlPlaceholder)}" ${ctx.brandPlugin.noTrackAttribute}`);
595
638
  continue;
596
639
  }
597
640
  if (name === "class") {
@@ -620,7 +663,7 @@ const buildAttributes = (ctx, scope, tag, spec, node, props, depth) => {
620
663
  report$1(ctx, "event-handler", `<${tag} ${name}> is an event handler; email clients strip scripting and Samva refuses it.`);
621
664
  continue;
622
665
  }
623
- if ((name === "data-samva-footer" || name === "data-samva-unsubscribe") && node.reserved !== true) {
666
+ if (ctx.reservedAttributes.has(name) && node.reserved !== true) {
624
667
  report$1(ctx, "reserved-attribute", `<${tag} ${name}> is reserved for the brand footer.`);
625
668
  continue;
626
669
  }
@@ -800,10 +843,10 @@ const PREHEADER_STYLE = [
800
843
  /** The hidden inbox-preview block: the text, then padding so the client cannot borrow body copy. */
801
844
  const preheaderBlock = (ctx, text) => {
802
845
  const children = [text];
803
- if (text.length < PREHEADER_TARGET_LENGTH$1) children.push({
846
+ if (text.length < PREHEADER_TARGET_LENGTH) children.push({
804
847
  el: "div",
805
848
  attrs: { "data-samva-preheader-padding": true },
806
- children: [PREHEADER_PADDING$1.repeat(Math.ceil((PREHEADER_TARGET_LENGTH$1 - text.length) / 7))]
849
+ children: [PREHEADER_PADDING.repeat(Math.ceil((PREHEADER_TARGET_LENGTH - text.length) / 7))]
807
850
  });
808
851
  const out = [];
809
852
  evalElement(ctx, /* @__PURE__ */ new Map(), {
@@ -976,17 +1019,22 @@ const derive = (node, context) => {
976
1019
  }
977
1020
  };
978
1021
  const normalize = (value) => value.replace(/\r\n?/g, "\n").split("\n").map((line) => line.replace(/[^\S\n]+$/, "")).join("\n").replace(/\n{3,}/g, PARAGRAPH).trim();
979
- const createContext = (ir, input, options, plain) => ({
980
- input,
981
- locale: options.locale ?? ir.locale,
982
- timeZone: options.timeZone,
983
- plain,
984
- diagnostics: [],
985
- iterations: 0,
986
- fmt: void 0,
987
- preheader: void 0,
988
- markerUsed: false
989
- });
1022
+ const createContext = (ir, input, options, plain) => {
1023
+ const brandPlugin = options.brandPlugin ?? samvaBrandPlugin;
1024
+ return {
1025
+ input,
1026
+ locale: options.locale ?? ir.locale,
1027
+ timeZone: options.timeZone,
1028
+ plain,
1029
+ brandPlugin,
1030
+ reservedAttributes: reservedBrandAttributes(brandPlugin),
1031
+ diagnostics: [],
1032
+ iterations: 0,
1033
+ fmt: void 0,
1034
+ preheader: void 0,
1035
+ markerUsed: false
1036
+ };
1037
+ };
990
1038
  const settle = (ctx) => {
991
1039
  if (ctx.diagnostics.length > 0) throw new IrRenderError(ctx.diagnostics);
992
1040
  };
@@ -1392,12 +1440,13 @@ const attributes = (sink, tag, spec, props, origins) => {
1392
1440
  const present = /* @__PURE__ */ new Set();
1393
1441
  let classes;
1394
1442
  const unsubscribeLink = tag === "a" && props[UNSUBSCRIBE_LINK] === true;
1443
+ const { unsubscribeUrlPlaceholder, noTrackAttribute } = sink.brandPlugin;
1395
1444
  for (const [key, value] of Object.entries(props)) {
1396
1445
  if (key === "key") continue;
1397
1446
  const name = key === "className" ? "class" : key;
1398
- if (unsubscribeLink && name === "href" && value === "%samva:unsubscribe-url%") {
1447
+ if (unsubscribeLink && name === "href" && value === unsubscribeUrlPlaceholder) {
1399
1448
  present.add(name);
1400
- emitted.push(` href="${UNSUBSCRIBE_URL_PLACEHOLDER$1}" ses:no-track`);
1449
+ emitted.push(` href="${escapeAttribute(unsubscribeUrlPlaceholder)}" ${noTrackAttribute}`);
1401
1450
  continue;
1402
1451
  }
1403
1452
  if (name === "class") {
@@ -1428,8 +1477,8 @@ const attributes = (sink, tag, spec, props, origins) => {
1428
1477
  report(sink, "event-handler", `<${tag} ${name}> is an event handler; email clients strip scripting and Samva refuses it.`, origins);
1429
1478
  continue;
1430
1479
  }
1431
- if (RESERVED_FOOTER_ATTRIBUTES.has(name) && props[BRAND_FOOTER_MARKER] !== true) {
1432
- report(sink, "reserved-attribute", `<${tag} ${name}> is written only by BrandFooter from samva:brand; render BrandFooter instead of marking an element as the footer.`, origins);
1480
+ if (sink.reservedAttributes.has(name) && props[BRAND_FOOTER_MARKER] !== true) {
1481
+ report(sink, "reserved-attribute", `<${tag} ${name}> is written only by BrandFooter from ${sink.brandPlugin.specifier}; render BrandFooter instead of marking an element as the footer.`, origins);
1433
1482
  continue;
1434
1483
  }
1435
1484
  if (/^(data|aria)-[a-z0-9-]+$/.test(name)) {
@@ -1530,15 +1579,21 @@ const record = (sink, start, node, instancePath, text) => {
1530
1579
  });
1531
1580
  return text;
1532
1581
  };
1533
- /** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
1534
- const serializeEmailHtml = (node, options = {}) => {
1535
- const sink = {
1582
+ const serializationSink = (options) => {
1583
+ const brandPlugin = options.brandPlugin ?? samvaBrandPlugin;
1584
+ return {
1536
1585
  diagnostics: [],
1537
1586
  positions: [],
1538
1587
  written: 0,
1539
1588
  relativeUrls: options.relativeUrls === true,
1540
- instancePaths: options.instancePaths === true
1589
+ instancePaths: options.instancePaths === true,
1590
+ brandPlugin,
1591
+ reservedAttributes: reservedBrandAttributes(brandPlugin)
1541
1592
  };
1593
+ };
1594
+ /** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
1595
+ const serializeEmailHtml = (node, options = {}) => {
1596
+ const sink = serializationSink(options);
1542
1597
  return {
1543
1598
  html: serialize(sink, node, "", { next: 0 }),
1544
1599
  diagnostics: sink.diagnostics,
@@ -1547,13 +1602,7 @@ const serializeEmailHtml = (node, options = {}) => {
1547
1602
  };
1548
1603
  /** An authored style object as a declaration list, checked by the same rules as a serialized tree. */
1549
1604
  const serializeStyle = (style, origins) => {
1550
- const sink = {
1551
- diagnostics: [],
1552
- positions: [],
1553
- written: 0,
1554
- relativeUrls: false,
1555
- instancePaths: false
1556
- };
1605
+ const sink = serializationSink({});
1557
1606
  return {
1558
1607
  css: declarations(sink, style, origins),
1559
1608
  diagnostics: sink.diagnostics
@@ -1564,13 +1613,7 @@ const serializeStyle = (style, origins) => {
1564
1613
  * the text that would be emitted, or `undefined` when the attribute is dropped or invalid.
1565
1614
  */
1566
1615
  const checkAttribute = (tag, name, value, origins) => {
1567
- const sink = {
1568
- diagnostics: [],
1569
- positions: [],
1570
- written: 0,
1571
- relativeUrls: false,
1572
- instancePaths: false
1573
- };
1616
+ const sink = serializationSink({});
1574
1617
  const element = emailElement(tag);
1575
1618
  const globals = EMAIL_GLOBAL_ATTRIBUTES;
1576
1619
  const attributeSpec = () => {
@@ -1589,276 +1632,6 @@ const checkAttribute = (tag, name, value, origins) => {
1589
1632
  };
1590
1633
  };
1591
1634
  //#endregion
1592
- //#region src/email/components.ts
1593
- /** Marks the hidden preview block so the renderer can read it back and the text lane can keep it. */
1594
- const PREHEADER_ATTRIBUTE = "data-samva-preheader";
1595
- const PREHEADER_PADDING_ATTRIBUTE = "data-samva-preheader-padding";
1596
- const PREHEADER_TARGET_LENGTH = 200;
1597
- const PREHEADER_PADDING = "\xA0‌​‍‎‏";
1598
- const presentation = (table, row, rowProps = {}) => jsx("table", {
1599
- role: "presentation",
1600
- border: 0,
1601
- cellpadding: 0,
1602
- cellspacing: 0,
1603
- ...table,
1604
- children: jsx("tbody", { children: jsx("tr", {
1605
- ...rowProps,
1606
- children: row
1607
- }) })
1608
- });
1609
- /** The hidden inbox preview block. Padding characters stop the client borrowing body copy. */
1610
- const preheaderNode = (text) => jsx("div", {
1611
- [PREHEADER_ATTRIBUTE]: text,
1612
- style: {
1613
- display: "none",
1614
- overflow: "hidden",
1615
- lineHeight: "1px",
1616
- opacity: 0,
1617
- maxHeight: 0,
1618
- maxWidth: 0
1619
- },
1620
- children: [text, text.length >= PREHEADER_TARGET_LENGTH ? null : jsx("div", {
1621
- [PREHEADER_PADDING_ATTRIBUTE]: true,
1622
- children: PREHEADER_PADDING.repeat(Math.ceil((PREHEADER_TARGET_LENGTH - text.length) / 7))
1623
- })]
1624
- });
1625
- /** Document shell: the head every target client needs, plus the body content cell. */
1626
- const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor, style, children }) => jsx("html", {
1627
- lang,
1628
- dir,
1629
- xmlns: "http://www.w3.org/1999/xhtml",
1630
- "xmlns:o": "urn:schemas-microsoft-com:office:office",
1631
- children: [jsx("head", { children: [
1632
- jsx("meta", { charset: "utf-8" }),
1633
- jsx("meta", {
1634
- "http-equiv": "Content-Type",
1635
- content: "text/html; charset=UTF-8"
1636
- }),
1637
- jsx("meta", {
1638
- name: "viewport",
1639
- content: "width=device-width, initial-scale=1"
1640
- }),
1641
- jsx("meta", { name: "x-apple-disable-message-reformatting" }),
1642
- jsx("meta", {
1643
- name: "color-scheme",
1644
- content: "light dark"
1645
- }),
1646
- jsx("meta", {
1647
- name: "supported-color-schemes",
1648
- content: "light dark"
1649
- }),
1650
- title === void 0 ? null : jsx("title", { children: title }),
1651
- rawMarkup("<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->")
1652
- ] }), jsx("body", {
1653
- style: {
1654
- margin: 0,
1655
- padding: 0,
1656
- ...backgroundColor === void 0 ? {} : { backgroundColor }
1657
- },
1658
- children: [presentation({
1659
- align: "center",
1660
- width: "100%",
1661
- ...backgroundColor === void 0 ? {} : { bgcolor: backgroundColor }
1662
- }, jsx("td", {
1663
- className,
1664
- ...style === void 0 ? {} : { style },
1665
- children
1666
- }))]
1667
- })]
1668
- });
1669
- /** A full-width band of content, as the single-cell table every client agrees on. */
1670
- const Section = ({ className, align = "center", width = "100%", bgcolor, style, tableStyle, children }) => presentation({
1671
- align,
1672
- width,
1673
- ...bgcolor === void 0 ? {} : { bgcolor },
1674
- ...tableStyle === void 0 ? {} : { style: tableStyle }
1675
- }, jsx("td", {
1676
- className,
1677
- ...style === void 0 ? {} : { style },
1678
- children
1679
- }));
1680
- /** The equal share of a row that each of `count` cells takes, whole percent rounded down. */
1681
- const equalColumnWidth = (count) => `${Math.floor(100 / count)}%`;
1682
- /** Give a `Column` that states no width its equal share, keeping the primitive's attribute order. */
1683
- const withColumnWidth = (node, width) => {
1684
- if (node.type === "Fragment") return EmailNode.Fragment({ children: node.children.map((child) => withColumnWidth(child, width)) });
1685
- if (node.type !== "Element" || node.tag !== "td" || node.props.width !== void 0) return node;
1686
- const props = {};
1687
- for (const [key, value] of Object.entries(node.props)) {
1688
- if (key === "style") props.width = width;
1689
- props[key] = value;
1690
- }
1691
- if (props.width === void 0) props.width = width;
1692
- return EmailNode.Element({
1693
- ...node,
1694
- props
1695
- });
1696
- };
1697
- /**
1698
- * A row of side-by-side `Column`s. No flex or grid conversion happens here. With `each` it repeats
1699
- * its cells over a list, `per` to a row, and splits the row equally between cells that state no
1700
- * width.
1701
- */
1702
- const Columns = ({ className, align = "center", width = "100%", style, each, per, children }) => {
1703
- const table = {
1704
- className,
1705
- align,
1706
- width,
1707
- ...style === void 0 ? {} : { style }
1708
- };
1709
- if (each === void 0 || typeof children !== "function") return presentation(table, children, { style: { width: "100%" } });
1710
- if (per !== void 0 && (!Number.isInteger(per) || per < 1)) reportEmailDiagnostic({
1711
- code: "invalid-columns-per",
1712
- severity: "error",
1713
- message: "<Columns per> must be a positive integer."
1714
- });
1715
- const size = per !== void 0 && Number.isInteger(per) && per >= 1 ? per : void 0;
1716
- const chunks = [];
1717
- if (size === void 0) chunks.push(each);
1718
- else for (let at = 0; at < each.length; at += size) chunks.push(each.slice(at, at + size));
1719
- const share = equalColumnWidth(size ?? Math.max(each.length, 1));
1720
- 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%" } })) });
1721
- };
1722
- /** One cell of a `Columns` row. */
1723
- const Column = ({ className, align, valign, width, style, children }) => jsx("td", {
1724
- className,
1725
- ...align === void 0 ? {} : { align },
1726
- ...valign === void 0 ? {} : { valign },
1727
- ...width === void 0 ? {} : { width },
1728
- ...style === void 0 ? {} : { style },
1729
- children
1730
- });
1731
- const DEFAULT_BUTTON_FONT = "Helvetica, Arial, sans-serif";
1732
- /** Average advance of a bold sans-serif glyph, as a fraction of the font size. */
1733
- const BOLD_GLYPH_ADVANCE = .6;
1734
- const invalidButtonNumber = (name) => reportEmailDiagnostic({
1735
- code: "invalid-button-number",
1736
- severity: "error",
1737
- message: `<Button ${name}> must be a finite number.`
1738
- });
1739
- const buttonNumber = (name, value, fallback) => {
1740
- if (typeof value === "number" && Number.isFinite(value)) return value;
1741
- invalidButtonNumber(name);
1742
- return fallback;
1743
- };
1744
- const optionalButtonNumber = (name, value) => {
1745
- if (value === void 0) return void 0;
1746
- if (typeof value === "number" && Number.isFinite(value)) return value;
1747
- invalidButtonNumber(name);
1748
- };
1749
- const optionalPositiveButtonNumber = (name, value) => {
1750
- if (value === void 0) return void 0;
1751
- if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
1752
- reportEmailDiagnostic({
1753
- code: "invalid-button-number",
1754
- severity: "error",
1755
- message: `<Button ${name}> must be a positive finite number.`
1756
- });
1757
- };
1758
- /** A link styled as a button, with a VML fallback so classic Outlook draws the shape. */
1759
- 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 }) => {
1760
- const safeWidth = optionalButtonNumber("width", width);
1761
- const safeHeight = optionalPositiveButtonNumber("height", height);
1762
- const safePaddingX = buttonNumber("paddingX", paddingX, 16);
1763
- const safeBorderRadius = buttonNumber("borderRadius", borderRadius, 0);
1764
- const safeBorderWidth = buttonNumber("borderWidth", borderWidth, 0);
1765
- const safeFontSize = buttonNumber("fontSize", fontSize, 16);
1766
- const label = typeof children === "string" ? children : void 0;
1767
- const fallback = safeHeight !== void 0 && label !== void 0;
1768
- if (!fallback) reportEmailDiagnostic({
1769
- code: "button-outlook-fallback",
1770
- severity: "warning",
1771
- 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.",
1772
- clients: ["outlook-windows"]
1773
- });
1774
- const arcsize = safeHeight === void 0 ? 0 : Math.round(safeBorderRadius / safeHeight * 100);
1775
- const fallbackWidth = safeWidth ?? Math.ceil((label?.length ?? 0) * safeFontSize * BOLD_GLYPH_ADVANCE) + 2 * safePaddingX;
1776
- return presentation({
1777
- width: "100%",
1778
- style: { borderCollapse: "collapse" }
1779
- }, jsx("td", {
1780
- align,
1781
- 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", {
1782
- className,
1783
- href,
1784
- target: "_blank",
1785
- rel: "noopener noreferrer",
1786
- style: {
1787
- backgroundColor,
1788
- color,
1789
- borderRadius: `${safeBorderRadius}px`,
1790
- display: "inline-block",
1791
- fontFamily,
1792
- fontSize: `${safeFontSize}px`,
1793
- fontWeight: "bold",
1794
- lineHeight: safeHeight === void 0 ? "120%" : `${safeHeight - 2 * safeBorderWidth}px`,
1795
- textAlign: "center",
1796
- textDecoration: "none",
1797
- ...safeWidth === void 0 ? { padding: `0 ${safePaddingX}px` } : {
1798
- width: `${safeWidth}px`,
1799
- maxWidth: "100%"
1800
- },
1801
- ...borderColor === void 0 ? {} : { border: `${safeBorderWidth}px solid ${borderColor}` },
1802
- ...fallback ? { msoHide: "all" } : {},
1803
- ...style
1804
- },
1805
- children
1806
- })]
1807
- }));
1808
- };
1809
- /** Vertical space that survives clients which collapse margins. */
1810
- const Spacer = ({ height }) => presentation({ width: "100%" }, jsx("td", {
1811
- height,
1812
- style: {
1813
- height: `${height}px`,
1814
- lineHeight: `${height}px`,
1815
- fontSize: "1px"
1816
- },
1817
- children: "​"
1818
- }));
1819
- /** A horizontal rule drawn as a cell border, which every target client renders. */
1820
- const Divider = ({ className, color = "#e5e7eb", thickness = 1, style }) => presentation({ width: "100%" }, jsx("td", {
1821
- className,
1822
- style: {
1823
- borderTop: `${thickness}px solid ${color}`,
1824
- fontSize: "1px",
1825
- lineHeight: "1px",
1826
- ...style
1827
- },
1828
- children: "​"
1829
- }));
1830
- /** An image with the width/height attributes Outlook sizes from before CSS applies. */
1831
- const Image = ({ className, src, alt, width, height, align, style }) => jsx("img", {
1832
- className,
1833
- src,
1834
- alt,
1835
- border: 0,
1836
- ...width === void 0 ? {} : { width },
1837
- ...height === void 0 ? {} : { height },
1838
- ...align === void 0 ? {} : { align },
1839
- style: {
1840
- display: "block",
1841
- outline: "none",
1842
- border: "none",
1843
- textDecoration: "none",
1844
- maxWidth: "100%",
1845
- ...style
1846
- }
1847
- });
1848
- /** A text link that opens outside the client and cannot reach back at the opener. */
1849
- const Link = ({ className, href, target = "_blank", rel = "noopener noreferrer", style, children }) => jsx("a", {
1850
- className,
1851
- href,
1852
- target,
1853
- rel,
1854
- style: {
1855
- color: "#067df7",
1856
- textDecoration: "none",
1857
- ...style
1858
- },
1859
- children
1860
- });
1861
- //#endregion
1862
- export { renderIrSms as A, emailSelectionAncestry as C, renderIrPreview as D, emailSelections as E, isUrlAttribute as F, EMAIL_ELEMENTS as M, EMAIL_GLOBAL_ATTRIBUTES as N, IrRenderError as O, emailElement as P, emailDefinitionInstances as S, emailSelectionSiblings as T, kebab as _, Email as a, INSTANCE_PATH_ATTRIBUTE as b, PREHEADER_ATTRIBUTE as c, equalColumnWidth as d, preheaderNode as f, escapeText as g, escapeAttribute as h, Divider as i, renderIrWhatsApp as j, renderIr as k, Section as l, declarationValue as m, Column as n, Image as o, checkAttribute as p, Columns as r, Link as s, Button as t, Spacer as u, serializeEmailHtml as v, emailSelectionIndex as w, emailAuthoringTarget as x, serializeStyle as y };
1635
+ export { isUrlAttribute as C, samvaBrandPlugin as D, reservedBrandAttributes as E, BRAND_FOOTER_MARKER as O, emailElement as S, parseBrandSpecifier as T, renderIr as _, kebab as a, EMAIL_ELEMENTS as b, INSTANCE_PATH_ATTRIBUTE as c, emailSelectionAncestry as d, emailSelectionIndex as f, IrRenderError as g, renderIrPreview as h, escapeText as i, UNSUBSCRIBE_LINK as k, emailAuthoringTarget as l, emailSelections as m, declarationValue as n, serializeEmailHtml as o, emailSelectionSiblings as p, escapeAttribute as r, serializeStyle as s, checkAttribute as t, emailDefinitionInstances as u, renderIrSms as v, brandSpecifierPattern as w, EMAIL_GLOBAL_ATTRIBUTES as x, renderIrWhatsApp as y };
1863
1636
 
1864
- //# sourceMappingURL=components-BZCqegS8.js.map
1637
+ //# sourceMappingURL=html-DjPxwjLi.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as isObject } from "./guards-eV1qLbf2.js";
2
- import { _ as kebab, h as escapeAttribute, m as declarationValue } from "./components-BZCqegS8.js";
2
+ import { a as kebab, n as declarationValue, r as escapeAttribute } from "./html-DjPxwjLi.js";
3
3
  import { t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
4
4
  //#region src/email/font-stacks.ts
5
5
  /** Generic families every email client understands as the last resort in a stack. */
@@ -580,4 +580,4 @@ const SML_IR_VERSION = 1;
580
580
  //#endregion
581
581
  export { downlevelMediaQuery as a, renderHeadCss as c, matchesSelector as i, resolveVariables as l, applyStylesheet as n, finalizeValue as o, classListOf as r, foldCalc as s, SML_IR_VERSION as t, unquote as u };
582
582
 
583
- //# sourceMappingURL=ir-CLhlklKV.js.map
583
+ //# sourceMappingURL=ir-De6pTH51.js.map