@pagenflow/email 1.4.5 → 1.4.7

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.js CHANGED
@@ -6,16 +6,16 @@ var React = require('react');
6
6
  function Body({ children, config = {} }) {
7
7
  var _a, _b, _c, _d;
8
8
  // Extract config values with fallbacks
9
- const globalColor = config.color || "#000000";
10
- const globalFontSize = config.fontSize || "16px";
11
- const globalBackgroundColor = config.backgroundColor || "#ffffff";
12
- const globalLineHeight = config.lineHeight || "1.4";
13
- const globalFontFamily = config.fontFamily || "Arial, Helvetica, sans-serif";
9
+ const globalColor = config.color || "";
10
+ const globalFontSize = config.fontSize || "";
11
+ const globalBackgroundColor = config.backgroundColor || "";
12
+ const globalLineHeight = config.lineHeight || "";
13
+ const globalFontFamily = config.fontFamily || "";
14
14
  // Background image properties
15
15
  const bgImage = ((_a = config.backgroundImage) === null || _a === void 0 ? void 0 : _a.src) || "";
16
- const bgRepeat = ((_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat) || "no-repeat";
17
- const bgSize = ((_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size) || "cover";
18
- const bgPosition = ((_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position) || "center";
16
+ const bgRepeat = ((_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat) || "";
17
+ const bgSize = ((_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size) || "";
18
+ const bgPosition = ((_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position) || "";
19
19
  // 1. Style for the <body> tag inline
20
20
  const bodyStyle = {
21
21
  backgroundColor: globalBackgroundColor,
@@ -1614,135 +1614,40 @@ function Button({ config, devMode }) {
1614
1614
  };
1615
1615
  // Border styles
1616
1616
  const borderStyleString = getBorderStyleString$2(border);
1617
- // --- Determine Button Approach Based on Width ---
1618
- // Check if width is percentage-based or not defined
1619
- const isPercentageWidth = !width || width.includes("%");
1620
- const useSimpleOutlookApproach = isPercentageWidth;
1621
1617
  const align = justifyContent ? justifyMap$3[justifyContent] : undefined;
1622
- // --- VML Calculation and Code for Outlook Compatibility (Fixed Width Only) ---
1623
- let vmlButton = "";
1624
- if (!useSimpleOutlookApproach) {
1625
- // VML needs fixed pixel height. We estimate it based on padding and potential wrapping.
1626
- const numericPadding = padding
1627
- ? parseInt(padding.split(" ")[0] || "12", 10)
1628
- : 12;
1629
- const numericFontSize = fontSize ? parseInt(fontSize, 10) : 0;
1630
- const numericLineHeight = lineHeight
1631
- ? lineHeight.includes("px")
1632
- ? parseInt(lineHeight, 10)
1633
- : numericFontSize * parseFloat(lineHeight)
1634
- : numericFontSize;
1635
- // Trust user's explicit pixel width - no calculation needed
1636
- const vmlWidth = parseInt(width, 10);
1637
- // Calculate VML height - trust user's padding and let text wrap naturally
1638
- // VML v:textbox will handle text wrapping automatically
1639
- const textContent = typeof children === "string" ? children : "";
1640
- // Estimate number of lines based on text length and button width
1641
- const horizontalPadding = (padding === null || padding === void 0 ? void 0 : padding.split(" ")[1])
1642
- ? parseInt(padding.split(" ")[1], 10) * 2
1643
- : numericPadding * 2;
1644
- const availableTextWidth = vmlWidth - horizontalPadding;
1645
- const charWidthMultiplier = fontWeight && parseInt(fontWeight) >= 500 ? 0.7 : 0.6;
1646
- const avgCharWidth = numericFontSize * charWidthMultiplier;
1647
- const charsPerLine = Math.max(Math.floor(availableTextWidth / avgCharWidth), 1);
1648
- const numberOfLines = Math.max(Math.ceil(textContent.length / charsPerLine), 1);
1649
- // Calculate height: vertical padding + (lines * line height) + extra buffer for VML
1650
- const textHeight = numberOfLines * numericLineHeight;
1651
- // Add extra 4px buffer to prevent bottom cropping in VML
1652
- const vmlHeight = Math.max(numericPadding * 2 + textHeight + 4, 40);
1653
- // VML colors must use the full hex format (e.g., #000000)
1654
- const vmlFillColor = backgroundColor
1655
- ? backgroundColor.startsWith("#")
1656
- ? backgroundColor
1657
- : `#${backgroundColor}`
1658
- : undefined;
1659
- // VML stroke color for border
1660
- const vmlStrokeColor = (border === null || border === void 0 ? void 0 : border.color) || vmlFillColor;
1661
- const vmlStrokeWeight = (border === null || border === void 0 ? void 0 : border.width) ? parseInt(border.width, 10) : 0;
1662
- const hasVmlStroke = vmlStrokeWeight > 0;
1663
- // Build VML font styles - consistent with other rendering paths
1664
- const vmlFontWeight = fontWeight;
1665
- const vmlFontStyle = fontStyle === "italic" ? "font-style:italic;" : "";
1666
- const vmlLetterSpacing = letterSpacing
1667
- ? `letter-spacing:${letterSpacing};`
1668
- : "";
1669
- const vmlTextTransform = textTransform
1670
- ? `text-transform:${textTransform};`
1671
- : "";
1672
- const vmlTextDecoration = textDecoration && textDecoration !== "none"
1673
- ? `text-decoration:${textDecoration};`
1674
- : "";
1675
- const vmlWhiteSpace = whiteSpace ? `white-space:${whiteSpace};` : "";
1676
- const vmlDirection = direction ? `direction:${direction};` : "";
1677
- const vmlOpacity = opacity !== undefined ? `opacity:${opacity};` : "";
1678
- // VML code uses MSO conditional comments to render only in Outlook
1679
- // Use table with explicit MSO height for vertical centering
1680
- const horizontalPaddingValue = (padding === null || padding === void 0 ? void 0 : padding.split(" ")[1])
1681
- ? parseInt(padding.split(" ")[1], 10)
1682
- : numericPadding;
1683
- // For VML, we need to use a table inside to properly apply padding and centering
1684
- let vmlAlignAttr = "";
1685
- let vmlAlignStyle = "";
1686
- if (textAlign === "center") {
1687
- vmlAlignAttr = 'align="center"';
1688
- }
1689
- else if (textAlign) {
1690
- vmlAlignStyle = `text-align:${textAlign};`;
1691
- }
1692
- // Border radius is intentionally omitted (arcsize="0%") for Outlook Classic.
1693
- // Outlook Classic does not reliably support rounded corners and the result
1694
- // is inconsistent, so we render sharp corners there instead.
1695
- vmlButton = `
1696
- <!--[if mso]>
1697
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" ${href ? `href="${href}"` : ""} style="height:${vmlHeight}px;width:${vmlWidth}px;" arcsize="0%" ${vmlStrokeColor ? `strokecolor="${vmlStrokeColor}"` : ""} ${hasVmlStroke ? `strokeweight="${vmlStrokeWeight}px"` : 'stroke="f"'} ${vmlFillColor ? `fillcolor="${vmlFillColor}"` : ""}>
1698
- <w:anchorlock/>
1699
- <v:textbox inset="${horizontalPaddingValue}px,${numericPadding}px,${horizontalPaddingValue}px,${numericPadding}px">
1700
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse;">
1701
- <tr>
1702
- <td ${vmlAlignAttr} valign="middle" style="${vmlAlignStyle}${color ? `color:${color};` : ""}${safeFontFamily ? `font-family:${safeFontFamily};` : ""}${fontSize ? `font-size:${fontSize};` : ""}${vmlFontWeight ? `font-weight:${vmlFontWeight};` : ""}${vmlFontStyle}${vmlLetterSpacing}${vmlTextTransform}${vmlTextDecoration}${vmlWhiteSpace}${vmlDirection}${vmlOpacity}${lineHeight ? `line-height:${lineHeight};` : ""}mso-line-height-rule:exactly;">
1703
- ${typeof children === "string" ? children : ""}
1704
- </td>
1705
- </tr>
1706
- </table>
1707
- </v:textbox>
1708
- </v:roundrect>
1709
- <![endif]-->
1710
- `;
1711
- }
1712
1618
  // --- Simple Outlook Approach for Percentage Widths ---
1713
1619
  let simpleOutlookButton = "";
1714
- if (useSimpleOutlookApproach) {
1715
- // Build consistent inline styles for text properties
1716
- const textDecorationStyle = textDecoration && textDecoration !== "none"
1717
- ? `text-decoration: ${textDecoration};`
1718
- : "";
1719
- const fontStyleProp = fontStyle ? `font-style: ${fontStyle};` : "";
1720
- const letterSpacingProp = letterSpacing
1721
- ? `letter-spacing: ${letterSpacing};`
1722
- : "";
1723
- const textTransformProp = textTransform
1724
- ? `text-transform: ${textTransform};`
1725
- : "";
1726
- const whiteSpaceProp = whiteSpace ? `white-space: ${whiteSpace};` : "";
1727
- const directionProp = direction ? `direction: ${direction};` : "";
1728
- const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
1729
- const wordBreakProp = wordBreak ? `word-break: ${wordBreak};` : "";
1730
- // Border radius is intentionally omitted from the Outlook Classic table cell.
1731
- // Outlook Classic ignores border-radius on table cells anyway, and including it
1732
- // can cause unexpected rendering artifacts, so we explicitly leave it out.
1733
- simpleOutlookButton = `
1620
+ // Build consistent inline styles for text properties
1621
+ const textDecorationStyle = textDecoration && textDecoration !== "none"
1622
+ ? `text-decoration: ${textDecoration};`
1623
+ : "";
1624
+ const fontStyleProp = fontStyle ? `font-style: ${fontStyle};` : "";
1625
+ const letterSpacingProp = letterSpacing
1626
+ ? `letter-spacing: ${letterSpacing};`
1627
+ : "";
1628
+ const textTransformProp = textTransform
1629
+ ? `text-transform: ${textTransform};`
1630
+ : "";
1631
+ const whiteSpaceProp = whiteSpace ? `white-space: ${whiteSpace};` : "";
1632
+ const directionProp = direction ? `direction: ${direction};` : "";
1633
+ const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
1634
+ const wordBreakProp = wordBreak ? `word-break: ${wordBreak};` : "";
1635
+ // Border radius is intentionally omitted from the Outlook Classic table cell.
1636
+ // Outlook Classic ignores border-radius on table cells anyway, and including it
1637
+ // can cause unexpected rendering artifacts, so we explicitly leave it out.
1638
+ simpleOutlookButton = `
1734
1639
  <!--[if mso]>
1735
1640
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse: collapse;">
1736
1641
  <tr>
1737
1642
  <td ${align ? `align="${align}"` : ""} style="padding: 0;">
1738
1643
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="${width || "auto"}" style="border-collapse: collapse;">
1739
1644
  <tr>
1740
- <td ${backgroundColor ? `bgcolor="${backgroundColor}"` : ""} ${textAlign ? `align="${textAlign}"` : ""} style="${padding ? `padding: ${padding};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${borderStyleString}">
1645
+ <td ${backgroundColor ? `bgcolor="${backgroundColor}"` : ""} ${textAlign ? `align="${textAlign}"` : ""} style="${padding ? `padding: ${padding};` : ""} ${href ? "cursor: pointer;" : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${borderStyleString}">
1741
1646
  ${href
1742
- ? `<a href="${href}" target="${target}" rel="noopener noreferrer" style="${color ? `color: ${color};` : ""} ${textDecorationStyle} display: block; ${safeFontFamily ? `font-family: ${safeFontFamily};` : ""} ${fontSize ? `font-size: ${fontSize};` : ""} ${fontWeight ? `font-weight: ${fontWeight};` : ""} ${fontStyleProp} ${lineHeight ? `line-height: ${lineHeight};` : ""} ${letterSpacingProp} ${textTransformProp} ${textAlign ? `text-align: ${textAlign};` : ""} ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1647
+ ? `<a href="${href}" target="${target}" rel="noopener noreferrer" style="${color ? `color: ${color};` : ""} ${textDecorationStyle} display: block; ${safeFontFamily ? `font-family: ${safeFontFamily};` : ""} ${fontSize ? `font-size: ${fontSize};` : ""} ${fontWeight ? `font-weight: ${fontWeight};` : ""} ${fontStyleProp} ${lineHeight ? `line-height: ${lineHeight};` : ""} ${letterSpacingProp} ${textTransformProp} ${textAlign ? `text-align: ${textAlign};` : ""} ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1743
1648
  ${typeof children === "string" ? children : ""}
1744
1649
  </a>`
1745
- : `<span style="${color ? `color: ${color};` : ""} ${textDecorationStyle} display: block; ${safeFontFamily ? `font-family: ${safeFontFamily};` : ""} ${fontSize ? `font-size: ${fontSize};` : ""} ${fontWeight ? `font-weight: ${fontWeight};` : ""} ${fontStyleProp} ${lineHeight ? `line-height: ${lineHeight};` : ""} ${letterSpacingProp} ${textTransformProp} ${textAlign ? `text-align: ${textAlign};` : ""} ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1650
+ : `<span style="${color ? `color: ${color};` : ""} ${textDecorationStyle} display: block; ${safeFontFamily ? `font-family: ${safeFontFamily};` : ""} ${fontSize ? `font-size: ${fontSize};` : ""} ${fontWeight ? `font-weight: ${fontWeight};` : ""} ${fontStyleProp} ${lineHeight ? `line-height: ${lineHeight};` : ""} ${letterSpacingProp} ${textTransformProp} ${textAlign ? `text-align: ${textAlign};` : ""} ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1746
1651
  ${typeof children === "string" ? children : ""}
1747
1652
  </span>`}
1748
1653
  </td>
@@ -1753,7 +1658,6 @@ function Button({ config, devMode }) {
1753
1658
  </table>
1754
1659
  <![endif]-->
1755
1660
  `;
1756
- }
1757
1661
  // Build shared inline style fragments for the non-MSO path.
1758
1662
  // fontFamily uses the sanitized value so embedded quotes never break the
1759
1663
  // style attribute string (which is always wrapped in double quotes).
@@ -1800,7 +1704,7 @@ function Button({ config, devMode }) {
1800
1704
  padding: 0,
1801
1705
  }, onClick: devMode ? (e) => e.preventDefault() : undefined, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { dangerouslySetInnerHTML: {
1802
1706
  __html: `
1803
- ${useSimpleOutlookApproach ? simpleOutlookButton : vmlButton}
1707
+ ${simpleOutlookButton}
1804
1708
  <!--[if !mso]><!-->
1805
1709
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; width: 100%;">
1806
1710
  <tbody>
@@ -1815,7 +1719,7 @@ function Button({ config, devMode }) {
1815
1719
  ${typeof children === "string" ? children : ""}
1816
1720
  </span>`
1817
1721
  : href
1818
- ? `<a href="${href}" target="${target}" rel="noopener noreferrer" style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; ${wordBreak ? `word-break: ${wordBreak};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${padding ? `padding: ${padding};` : ""}">
1722
+ ? `<a href="${href}" target="${target}" rel="noopener noreferrer" style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; cursor: pointer; ${wordBreak ? `word-break: ${wordBreak};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${padding ? `padding: ${padding};` : ""}">
1819
1723
  <span>
1820
1724
  ${typeof children === "string" ? children : ""}
1821
1725
  </span>
@@ -1899,6 +1803,9 @@ function Column({ children, config, devNode }) {
1899
1803
  };
1900
1804
  // 2. Outer TD style: Background and Border Radius (no border here).
1901
1805
  // height is set so the TD occupies the full declared height.
1806
+ // When maxWidth is set, the outer TD stays at its normal width so it
1807
+ // always fills its parent — the inner maxWidth table (see below) does
1808
+ // the actual capping.
1902
1809
  const outerTdStyle = {
1903
1810
  width: config.width,
1904
1811
  height: config.height,
@@ -1942,6 +1849,18 @@ function Column({ children, config, devNode }) {
1942
1849
  fontSize: "1px",
1943
1850
  width: "100%",
1944
1851
  };
1852
+ // 5. maxWidth constraining table style (modern clients).
1853
+ // The `width` HTML attribute on this table is what Outlook Classic
1854
+ // (Word engine) reads — it has no concept of max-width, but it does
1855
+ // honour the `width` attribute as a hard column cap.
1856
+ // The CSS max-width here handles modern web/email clients correctly.
1857
+ // <center> around it ensures the constrained block stays horizontally
1858
+ // centred in both the Word engine and standards-based renderers.
1859
+ const maxWidthTableStyle = {
1860
+ width: "100%",
1861
+ maxWidth: config.maxWidth,
1862
+ borderCollapse: "collapse",
1863
+ };
1945
1864
  // Main content rendering
1946
1865
  const renderContent = () => (jsxRuntime.jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap$2[config.alignItems] : "left", children: config.gap && numChildren > 1 ? (jsxRuntime.jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1947
1866
  width: "100%",
@@ -1958,7 +1877,24 @@ function Column({ children, config, devNode }) {
1958
1877
  return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1959
1878
  position: "relative",
1960
1879
  ...outerTableStyle,
1961
- }, ...(config.height && { height: config.height }), children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: outerTdStyle, ...(config.width && { width: config.width }), ...(config.height && { height: config.height }), children: renderContent() }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
1880
+ }, ...(config.height && { height: config.height }), children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: outerTdStyle, ...(config.width && { width: config.width }), ...(config.height && { height: config.height }), children: config.maxWidth ? (
1881
+ /*
1882
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
1883
+ *
1884
+ * <center> instructs the Word rendering engine to horizontally
1885
+ * centre its child block, equivalent to margin: 0 auto in CSS.
1886
+ *
1887
+ * The inner table carries the `width` HTML attribute set to the
1888
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
1889
+ * cap; it has no concept of max-width so this is the only lever
1890
+ * available. Modern clients receive the CSS max-width on the
1891
+ * same table and behave correctly.
1892
+ *
1893
+ * The outer column remains at its normal width so it always
1894
+ * fills its parent cell in every client — only the inner
1895
+ * content is capped.
1896
+ */
1897
+ jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Column Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: config.maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: renderContent() }) }) }) }) })) : (renderContent()) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
1962
1898
  }
1963
1899
  var Column_default = React.memo(Column, arePropsEqual);
1964
1900
 
@@ -2290,36 +2226,7 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2290
2226
  mso-line-height-rule: exactly;
2291
2227
  }
2292
2228
  }
2293
- @media only screen and (max-width: 768px) {
2294
- .row-content-table[data-mobile-justify="center"] { margin: 0 auto !important; float: none !important; }
2295
- .row-content-table[data-mobile-justify="start"] { margin: 0 !important; float: left !important; }
2296
- .row-content-table[data-mobile-justify="end"] { margin: 0 0 0 auto !important; float: right !important; }
2297
- .row-content-table[data-mobile-align="center"] .child-cell { vertical-align: middle !important; }
2298
- .row-content-table[data-mobile-align="start"] .child-cell { vertical-align: top !important; }
2299
- .row-content-table[data-mobile-align="end"] .child-cell { vertical-align: bottom !important; }
2300
- .row-content-table[data-mobile-wrap="true"] { width: 100% !important; max-width: 100% !important; }
2301
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr { display: block !important; }
2302
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell {
2303
- display: block !important;
2304
- width: 100% !important;
2305
- box-sizing: border-box !important;
2306
- }
2307
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .row-gap-td {
2308
- display: none !important;
2309
- width: 0 !important;
2310
- height: 0 !important;
2311
- }
2312
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell:not(:last-child) {
2313
- margin-bottom: 20px !important;
2314
- }
2315
- ${["10px", "15px", "20px", "24px", "30px", "40px", ...rowGaps]
2316
- .filter((gap, index, self) => self.indexOf(gap) === index)
2317
- .map((gap) => `
2318
- .row-content-table[data-mobile-wrap="true"][data-gap="${gap}"] > tbody > .content-tr > .child-cell:not(:last-child) {
2319
- margin-bottom: ${gap} !important;
2320
- }`)
2321
- .join("\n")}
2322
- }
2229
+
2323
2230
  a { color: inherit; text-decoration: none; }
2324
2231
  ol, ul { margin: 0px; padding: 0px; list-style: none; }
2325
2232
  li {
@@ -2363,6 +2270,141 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2363
2270
  return (jsxRuntime.jsxs("head", { children: [jsxRuntime.jsx("meta", { httpEquiv: "Content-Type", content: "text/html; charset=utf-8" }), jsxRuntime.jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }), jsxRuntime.jsx("meta", { httpEquiv: "X-UA-Compatible", content: "IE=edge" }), jsxRuntime.jsx("title", { children: title }), fonts.flatMap((resolved) => resolved.fontProps.map((props, i) => (jsxRuntime.jsx(Font, { ...props }, `${resolved.family}-${props.fontWeight}-${props.fontStyle}-${i}`)))), children, jsxRuntime.jsx("style", { type: "text/css", dangerouslySetInnerHTML: { __html: msoResetStyles } }), jsxRuntime.jsx("style", { type: "text/css", dangerouslySetInnerHTML: { __html: globalStyles } })] }));
2364
2271
  }
2365
2272
 
2273
+ class MiniDomParser {
2274
+ constructor(html) {
2275
+ this.html = html;
2276
+ this.root = this.makeNode("root", {}, null);
2277
+ this.parse();
2278
+ }
2279
+ makeNode(tagName, attributes, parent) {
2280
+ var _a;
2281
+ return {
2282
+ tagName,
2283
+ attributes,
2284
+ style: this.parseStyle((_a = attributes["style"]) !== null && _a !== void 0 ? _a : ""),
2285
+ children: [],
2286
+ parent,
2287
+ rawHtml: "",
2288
+ };
2289
+ }
2290
+ parseStyle(styleStr) {
2291
+ const map = {};
2292
+ for (const declaration of styleStr.split(";")) {
2293
+ const [prop, ...rest] = declaration.split(":");
2294
+ if (!prop || !rest.length)
2295
+ continue;
2296
+ const key = prop.trim().toLowerCase();
2297
+ const val = rest.join(":").trim();
2298
+ if (key && val)
2299
+ map[key] = val;
2300
+ }
2301
+ return map;
2302
+ }
2303
+ parseAttributes(attrStr) {
2304
+ var _a, _b, _c;
2305
+ const attrs = {};
2306
+ const pattern = /(\w[\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
2307
+ let m;
2308
+ while ((m = pattern.exec(attrStr)) !== null) {
2309
+ const key = m[1].toLowerCase();
2310
+ const val = (_c = (_b = (_a = m[2]) !== null && _a !== void 0 ? _a : m[3]) !== null && _b !== void 0 ? _b : m[4]) !== null && _c !== void 0 ? _c : "";
2311
+ attrs[key] = val;
2312
+ }
2313
+ return attrs;
2314
+ }
2315
+ parse() {
2316
+ const tokenPattern = /(<\/[\w]+\s*>|<[\w][^>]*>|[^<]+)/gi;
2317
+ const stack = [this.root];
2318
+ let m;
2319
+ while ((m = tokenPattern.exec(this.html)) !== null) {
2320
+ const token = m[0];
2321
+ if (token.startsWith("</")) {
2322
+ if (stack.length > 1)
2323
+ stack.pop();
2324
+ continue;
2325
+ }
2326
+ if (token.startsWith("<")) {
2327
+ const tagMatch = token.match(/^<([\w]+)([\s\S]*)>$/i);
2328
+ if (!tagMatch)
2329
+ continue;
2330
+ const tagName = tagMatch[1].toLowerCase();
2331
+ const attrStr = tagMatch[2].trim();
2332
+ const attrs = this.parseAttributes(attrStr);
2333
+ const current = stack[stack.length - 1];
2334
+ const node = this.makeNode(tagName, attrs, current);
2335
+ node.rawHtml = token;
2336
+ current.children.push(node);
2337
+ const selfClosing = /\/$/.test(attrStr) ||
2338
+ ["br", "hr", "img", "input", "meta", "link"].includes(tagName);
2339
+ if (!selfClosing)
2340
+ stack.push(node);
2341
+ }
2342
+ }
2343
+ }
2344
+ collectByTag(node, tag, results) {
2345
+ if (node.tagName === tag)
2346
+ results.push(node);
2347
+ for (const child of node.children) {
2348
+ this.collectByTag(child, tag, results);
2349
+ }
2350
+ }
2351
+ querySelectorAllByTag(tag) {
2352
+ const results = [];
2353
+ this.collectByTag(this.root, tag.toLowerCase(), results);
2354
+ return results;
2355
+ }
2356
+ }
2357
+
2358
+ function resolveInheritedStyle(node, prop) {
2359
+ let el = node.parent;
2360
+ while (el && el.tagName !== "root") {
2361
+ const val = el.style[prop];
2362
+ if (val && val !== "inherit")
2363
+ return val;
2364
+ el = el.parent;
2365
+ }
2366
+ return undefined;
2367
+ }
2368
+ const BROWSER_LINK_DEFAULTS = ["color", "text-decoration"];
2369
+ function resolveAnchorStyles(anchor, fallback) {
2370
+ const resolved = { ...anchor.style };
2371
+ for (const prop of BROWSER_LINK_DEFAULTS) {
2372
+ if (resolved[prop] && resolved[prop] !== "inherit")
2373
+ continue;
2374
+ const inherited = resolveInheritedStyle(anchor, prop);
2375
+ if (inherited) {
2376
+ resolved[prop] = inherited;
2377
+ }
2378
+ else if (fallback === null || fallback === void 0 ? void 0 : fallback[prop]) {
2379
+ resolved[prop] = fallback[prop];
2380
+ }
2381
+ else if (prop === "text-decoration") {
2382
+ resolved[prop] = "none";
2383
+ }
2384
+ }
2385
+ return Object.entries(resolved)
2386
+ .map(([k, v]) => `${k}:${v}`)
2387
+ .join(";");
2388
+ }
2389
+ function injectLinkStyles(html, fallback) {
2390
+ if (!html || (!html.includes("<a ") && !html.includes("<a>")))
2391
+ return html;
2392
+ const parser = new MiniDomParser(html);
2393
+ const anchors = parser.querySelectorAllByTag("a");
2394
+ let result = html;
2395
+ for (const anchor of anchors) {
2396
+ const resolvedStyle = resolveAnchorStyles(anchor, fallback !== null && fallback !== void 0 ? fallback : {});
2397
+ const cleanAttrs = anchor.rawHtml
2398
+ .replace(/^<a\s*/i, "")
2399
+ .replace(/>$/, "")
2400
+ .replace(/style\s*=\s*(?:"[^"]*"|'[^']*')/gi, "")
2401
+ .trim();
2402
+ const newTag = `<a ${cleanAttrs} style="${resolvedStyle}">`.replace(/\s+/g, " ");
2403
+ result = result.replace(anchor.rawHtml, newTag);
2404
+ }
2405
+ return result;
2406
+ }
2407
+
2366
2408
  function Heading({ config, devMode, children }) {
2367
2409
  const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2368
2410
  // Determine the content to render
@@ -2379,7 +2421,7 @@ function Heading({ config, devMode, children }) {
2379
2421
  const headingStyle = {
2380
2422
  color: color,
2381
2423
  textAlign: textAlign,
2382
- fontFamily: fontFamily || "Arial, Helvetica, sans-serif",
2424
+ fontFamily: fontFamily,
2383
2425
  fontSize: fontSize,
2384
2426
  fontWeight: fontWeight,
2385
2427
  fontStyle: fontStyle,
@@ -2396,6 +2438,9 @@ function Heading({ config, devMode, children }) {
2396
2438
  // Outlook specific fixes (using string indexing)
2397
2439
  ["msoLineHeightRule"]: "exactly",
2398
2440
  };
2441
+ const processedHtml = isString
2442
+ ? injectLinkStyles(content, headingStyle)
2443
+ : "";
2399
2444
  // Dynamically create the Heading element
2400
2445
  const HeadingTag = level;
2401
2446
  return (
@@ -2403,7 +2448,7 @@ function Heading({ config, devMode, children }) {
2403
2448
  jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2404
2449
  width: "100%",
2405
2450
  borderCollapse: "collapse",
2406
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: content } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
2451
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
2407
2452
  }
2408
2453
  var Heading_default = React.memo(Heading, arePropsEqual);
2409
2454
 
@@ -2675,11 +2720,16 @@ function getHrefFromInnerLink(innerLink) {
2675
2720
  }
2676
2721
  }
2677
2722
  function Row({ children, config, devNode, devMode }) {
2678
- var _a, _b, _c, _d, _e, _f, _g;
2723
+ var _a, _b, _c, _d, _e, _f;
2679
2724
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2680
2725
  const numChildren = childrenArray.length;
2681
2726
  const href = getHrefFromInnerLink(config.innerLink);
2682
2727
  const target = ((_a = config.innerLink) === null || _a === void 0 ? void 0 : _a.target) || "_blank";
2728
+ // Whether children should stack on mobile.
2729
+ // Mirrors Container's isStacking pattern: drives stack-td / desktop-gap-column
2730
+ // / mobile-gap-spacer class names so that stacking works via non-@media CSS
2731
+ // rules that survive Gmail's stylesheet stripping.
2732
+ const isStacking = ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.wrap) === true && numChildren > 1;
2683
2733
  // 1. Outer TD: Background, Border Radius, Width, Height.
2684
2734
  const backgroundTdStyle = {
2685
2735
  backgroundColor: config.backgroundColor,
@@ -2689,9 +2739,9 @@ function Row({ children, config, devNode, devMode }) {
2689
2739
  backgroundImage: config.backgroundImage
2690
2740
  ? `url(${config.backgroundImage.src})`
2691
2741
  : undefined,
2692
- backgroundRepeat: (_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat,
2693
- backgroundSize: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size,
2694
- backgroundPosition: (_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position,
2742
+ backgroundRepeat: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.repeat,
2743
+ backgroundSize: (_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.size,
2744
+ backgroundPosition: (_e = config.backgroundImage) === null || _e === void 0 ? void 0 : _e.position,
2695
2745
  ...(config.borderRadius && { overflow: "hidden" }),
2696
2746
  };
2697
2747
  // 2. Inner Table: Border and Border Radius.
@@ -2746,13 +2796,30 @@ function Row({ children, config, devNode, devMode }) {
2746
2796
  width: "100%",
2747
2797
  height: "100%",
2748
2798
  borderCollapse: "collapse",
2749
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsxRuntime.jsx("table", { "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle, ...(config.height && { height: config.height }), className: "content-table row-content-table", "data-mobile-justify": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.justifyContent, "data-mobile-align": (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.alignItems, "data-mobile-wrap": ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("td", { valign: tdValign, style: {
2799
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsxRuntime.jsx("table", { "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle, ...(config.height && { height: config.height }), className: "content-table row-content-table", "data-mobile-wrap": ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { align: tdAlign, style: {
2750
2800
  verticalAlign: tdValign,
2751
- textAlign: "left",
2801
+ textAlign: tdAlign,
2752
2802
  padding: "0",
2753
2803
  margin: "0",
2754
- }, className: "child-cell", children: child }), index < numChildren - 1 &&
2755
- config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle, className: "row-gap-td", children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2804
+ },
2805
+ // Mirror of Container's stack-td pattern: when isStacking,
2806
+ // the non-@media .stack-td rule forces display:block +
2807
+ // width:100% on each child, which survives Gmail's
2808
+ // @media stripping and achieves true mobile stacking.
2809
+ className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2810
+ index < numChildren - 1 &&
2811
+ config.gap && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2812
+ display: "none",
2813
+ fontSize: "0",
2814
+ lineHeight: "0",
2815
+ height: config.gap,
2816
+ }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2817
+ config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle,
2818
+ // Mirror of Container's desktop-gap-column pattern:
2819
+ // when isStacking, the non-@media .desktop-gap-column
2820
+ // rule collapses the between-column gap td so it does
2821
+ // not create phantom space while children are stacked.
2822
+ className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2756
2823
  if (href && !devMode) {
2757
2824
  return (jsxRuntime.jsx("a", { href: href, ...({ target }), style: {
2758
2825
  textDecoration: "none",
@@ -2868,17 +2935,23 @@ var Spacer_default = React.memo(Spacer, arePropsEqual);
2868
2935
  function Text({ config, devMode, children }) {
2869
2936
  const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, } = config;
2870
2937
  // 1. TD Style: Where padding and background are reliably applied.
2938
+ // When maxWidth is set, this TD stays at width: 100% so it always fills
2939
+ // its parent — the inner maxWidth table (see below) does the actual
2940
+ // capping, keeping the outer layout intact in all clients.
2871
2941
  const tdStyle = {
2872
2942
  padding: padding,
2873
2943
  backgroundColor: backgroundColor,
2874
2944
  width: "100%",
2875
2945
  verticalAlign: "top",
2876
2946
  };
2877
- // 2. Content Style: Applied directly to a wrapper element
2947
+ // 2. Content Style: Applied directly to the inner div wrapper.
2948
+ // maxWidth is intentionally omitted here — putting it on a div has no
2949
+ // effect in Outlook Classic (Word engine ignores CSS on div elements).
2950
+ // The constraint is enforced at the table level instead (see below).
2878
2951
  const contentStyle = {
2879
2952
  color: color,
2880
2953
  textAlign: textAlign,
2881
- fontFamily: fontFamily || "Arial, Helvetica, sans-serif",
2954
+ fontFamily: fontFamily,
2882
2955
  fontSize: fontSize,
2883
2956
  fontWeight: fontWeight,
2884
2957
  fontStyle: fontStyle,
@@ -2893,15 +2966,46 @@ function Text({ config, devMode, children }) {
2893
2966
  wordBreak: wordBreak,
2894
2967
  margin: "0",
2895
2968
  padding: "0",
2896
- maxWidth,
2969
+ };
2970
+ // 3. maxWidth constraining table style (modern clients).
2971
+ // The `width` HTML attribute on this table is what Outlook Classic
2972
+ // (Word engine) reads — it has no concept of max-width, but it does
2973
+ // honour the `width` attribute as a hard column cap.
2974
+ // The CSS max-width here handles modern web/email clients correctly.
2975
+ // <center> around it ensures the constrained block stays horizontally
2976
+ // centred in both the Word engine and standards-based renderers.
2977
+ const maxWidthTableStyle = {
2978
+ width: "100%",
2979
+ maxWidth: maxWidth,
2980
+ borderCollapse: "collapse",
2897
2981
  };
2898
2982
  // Determine content to render
2899
2983
  const content = text !== null && text !== void 0 ? text : children;
2900
2984
  const isString = typeof content === "string";
2985
+ const processedHtml = isString
2986
+ ? injectLinkStyles(content, contentStyle)
2987
+ : "";
2988
+ const innerContent = isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content }));
2901
2989
  return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2902
2990
  width: "100%",
2903
2991
  borderCollapse: "collapse",
2904
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: content } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content })) }) }) }) }));
2992
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: maxWidth ? (
2993
+ /*
2994
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
2995
+ *
2996
+ * <center> instructs the Word rendering engine to horizontally
2997
+ * centre its child block, equivalent to margin: 0 auto in CSS.
2998
+ *
2999
+ * The inner table carries the `width` HTML attribute set to the
3000
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
3001
+ * cap; it has no concept of max-width so this is the only lever
3002
+ * available. Modern clients receive the CSS max-width on the
3003
+ * same table and behave correctly.
3004
+ *
3005
+ * The outer TD remains at width: 100% so it always fills its
3006
+ * parent cell in every client — only the inner content is capped.
3007
+ */
3008
+ jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: innerContent }) }) }) }) })) : (innerContent) }) }) }) }));
2905
3009
  }
2906
3010
  var Text_default = React.memo(Text, arePropsEqual);
2907
3011