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