@pagenflow/email 1.3.7 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1564,7 +1564,7 @@ function getBorderStyle$6(border) {
1564
1564
  }
1565
1565
  return style;
1566
1566
  }
1567
- function getBorderStyleString$1(border) {
1567
+ function getBorderStyleString$2(border) {
1568
1568
  if (!border)
1569
1569
  return "";
1570
1570
  const styles = [];
@@ -1600,16 +1600,16 @@ function getBorderStyleString$1(border) {
1600
1600
  }
1601
1601
  function Button({ config, devMode }) {
1602
1602
  const { href, children, backgroundColor = "#007bff", // Default blue
1603
- color = "#ffffff", padding = "12px 24px", borderRadius = "3px", border, width, maxWidth, justifyContent = "center", textAlign = "center", fontSize = "16px", fontWeight = "500", fontStyle, lineHeight = "1.2", letterSpacing, textTransform, textDecoration = "none", fontFamily = "Arial, sans-serif", whiteSpace = "normal", } = config;
1604
- // 1. Link (A) Tag Styles (Fallback for Webmail/Mobile)
1605
- const linkStyle = {
1606
- display: "block",
1607
- wordBreak: "break-word"};
1603
+ color = "#ffffff", padding = "12px 24px", borderRadius = "3px", border, width, maxWidth, justifyContent = "center", textAlign = "center", fontSize = "16px", fontWeight = "500", fontStyle, fontFamily = "Arial, sans-serif", lineHeight = "1.2", letterSpacing, textTransform, textDecoration = "none", direction, verticalAlign, opacity, whiteSpace = "normal", wordBreak = "break-word", } = config;
1604
+ // Sanitize fontFamily early so safeFontFamily is available for all paths below.
1605
+ const safeFontFamily = fontFamily
1606
+ ? fontFamily.replace(/['"]/g, "")
1607
+ : fontFamily;
1608
1608
  // 2. Outer TD Style for Background and Border Radius (no border)
1609
1609
  const backgroundTdStyle = Object.assign(Object.assign({ backgroundColor: backgroundColor, borderRadius: borderRadius, width: width || "auto" }, (maxWidth && { maxWidth: maxWidth })), (borderRadius && { overflow: "hidden" }));
1610
1610
  // 3. Border styles
1611
1611
  getBorderStyle$6(border);
1612
- const borderStyleString = getBorderStyleString$1(border);
1612
+ const borderStyleString = getBorderStyleString$2(border);
1613
1613
  // --- Determine Button Approach Based on Width ---
1614
1614
  // Check if width is percentage-based or not defined
1615
1615
  const isPercentageWidth = !width || width.includes("%");
@@ -1663,6 +1663,8 @@ function Button({ config, devMode }) {
1663
1663
  ? `text-decoration:${textDecoration};`
1664
1664
  : "";
1665
1665
  const vmlWhiteSpace = whiteSpace !== "normal" ? `white-space:${whiteSpace};` : "";
1666
+ const vmlDirection = direction ? `direction:${direction};` : "";
1667
+ const vmlOpacity = opacity !== undefined ? `opacity:${opacity};` : "";
1666
1668
  // VML code uses MSO conditional comments to render only in Outlook
1667
1669
  // Use table with explicit MSO height for vertical centering
1668
1670
  const horizontalPaddingValue = padding.split(" ")[1]
@@ -1684,7 +1686,7 @@ function Button({ config, devMode }) {
1684
1686
  <v:textbox inset="${horizontalPaddingValue}px,${numericPadding}px,${horizontalPaddingValue}px,${numericPadding}px">
1685
1687
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse;">
1686
1688
  <tr>
1687
- <td ${vmlAlignAttr} valign="middle" style="${vmlAlignStyle}color:${color};font-family:${fontFamily};font-size:${fontSize};font-weight:${vmlFontWeight};${vmlFontStyle}${vmlLetterSpacing}${vmlTextTransform}${vmlTextDecoration}${vmlWhiteSpace}line-height:${lineHeight};mso-line-height-rule:exactly;">
1689
+ <td ${vmlAlignAttr} valign="middle" style="${vmlAlignStyle}color:${color};font-family:${safeFontFamily};font-size:${fontSize};font-weight:${vmlFontWeight};${vmlFontStyle}${vmlLetterSpacing}${vmlTextTransform}${vmlTextDecoration}${vmlWhiteSpace}${vmlDirection}${vmlOpacity}line-height:${lineHeight};mso-line-height-rule:exactly;">
1688
1690
  ${typeof children === "string" ? children : ""}
1689
1691
  </td>
1690
1692
  </tr>
@@ -1698,11 +1700,20 @@ function Button({ config, devMode }) {
1698
1700
  let simpleOutlookButton = "";
1699
1701
  if (useSimpleOutlookApproach) {
1700
1702
  // Build consistent inline styles for text properties
1701
- const textDecorationStyle = textDecoration && textDecoration !== "none" ? `text-decoration: ${textDecoration};` : "";
1703
+ const textDecorationStyle = textDecoration && textDecoration !== "none"
1704
+ ? `text-decoration: ${textDecoration};`
1705
+ : "";
1702
1706
  const fontStyleProp = fontStyle ? `font-style: ${fontStyle};` : "";
1703
- const letterSpacingProp = letterSpacing ? `letter-spacing: ${letterSpacing};` : "";
1704
- const textTransformProp = textTransform ? `text-transform: ${textTransform};` : "";
1707
+ const letterSpacingProp = letterSpacing
1708
+ ? `letter-spacing: ${letterSpacing};`
1709
+ : "";
1710
+ const textTransformProp = textTransform
1711
+ ? `text-transform: ${textTransform};`
1712
+ : "";
1705
1713
  const whiteSpaceProp = whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "";
1714
+ const directionProp = direction ? `direction: ${direction};` : "";
1715
+ const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
1716
+ const wordBreakProp = wordBreak !== "break-word" ? `word-break: ${wordBreak};` : "";
1706
1717
  simpleOutlookButton = `
1707
1718
  <!--[if mso]>
1708
1719
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse: collapse;">
@@ -1711,7 +1722,7 @@ function Button({ config, devMode }) {
1711
1722
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="${width || "auto"}" style="border-collapse: collapse;">
1712
1723
  <tr>
1713
1724
  <td bgcolor="${backgroundColor}" align="${textAlign}" style="padding: ${padding}; text-align: ${textAlign}; border-radius: ${borderRadius}; ${borderStyleString}">
1714
- <a href="${href}" target="_blank" rel="noopener noreferrer" style="color: ${color}; ${textDecorationStyle} display: block; font-family: ${fontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyleProp} line-height: ${lineHeight}; ${letterSpacingProp} ${textTransformProp} text-align: ${textAlign}; ${whiteSpaceProp} mso-line-height-rule: exactly;">
1725
+ <a href="${href}" target="_blank" rel="noopener noreferrer" style="color: ${color}; ${textDecorationStyle} display: block; font-family: ${safeFontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyleProp} line-height: ${lineHeight}; ${letterSpacingProp} ${textTransformProp} text-align: ${textAlign}; ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1715
1726
  ${typeof children === "string" ? children : ""}
1716
1727
  </a>
1717
1728
  </td>
@@ -1723,6 +1734,27 @@ function Button({ config, devMode }) {
1723
1734
  <![endif]-->
1724
1735
  `;
1725
1736
  }
1737
+ // Build shared inline style fragments for the non-MSO path.
1738
+ // fontFamily uses the sanitized value so embedded quotes never break the
1739
+ // style attribute string (which is always wrapped in double quotes).
1740
+ const sharedTextStyles = [
1741
+ `color: ${color};`,
1742
+ safeFontFamily ? `font-family: ${safeFontFamily};` : "",
1743
+ fontSize ? `font-size: ${fontSize};` : "",
1744
+ fontWeight ? `font-weight: ${fontWeight};` : "",
1745
+ fontStyle ? `font-style: ${fontStyle};` : "",
1746
+ lineHeight ? `line-height: ${lineHeight};` : "",
1747
+ letterSpacing ? `letter-spacing: ${letterSpacing};` : "",
1748
+ textTransform ? `text-transform: ${textTransform};` : "",
1749
+ textDecoration && textDecoration !== "none"
1750
+ ? `text-decoration: ${textDecoration};`
1751
+ : "",
1752
+ direction ? `direction: ${direction};` : "",
1753
+ opacity !== undefined ? `opacity: ${opacity};` : "",
1754
+ whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "",
1755
+ ]
1756
+ .filter(Boolean)
1757
+ .join(" ");
1726
1758
  return (
1727
1759
  // Wrapper table for alignment - maintains proper positioning for hover indicators
1728
1760
  jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
@@ -1753,11 +1785,11 @@ function Button({ config, devMode }) {
1753
1785
  <tr>
1754
1786
  <td style="padding: 0;">
1755
1787
  ${devMode
1756
- ? `<span style="color: ${color}; font-family: ${fontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyle ? `font-style: ${fontStyle};` : ""} line-height: ${lineHeight}; ${letterSpacing ? `letter-spacing: ${letterSpacing};` : ""} ${textTransform ? `text-transform: ${textTransform};` : ""} ${textDecoration && textDecoration !== "none" ? `text-decoration: ${textDecoration};` : ""} ${whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : ""} display: ${linkStyle.display}; text-align: ${textAlign}; word-break: ${linkStyle.wordBreak}; padding: ${padding};">
1788
+ ? `<span style="${sharedTextStyles} display: block; text-align: ${textAlign}; word-break: ${wordBreak}; padding: ${padding};">
1757
1789
  ${typeof children === "string" ? children : ""}
1758
1790
  </span>`
1759
- : `<a href="${href}" target="_blank" rel="noopener noreferrer" style="color: ${color}; ${textDecoration && textDecoration !== "none" ? `text-decoration: ${textDecoration};` : "text-decoration: none;"} display: ${linkStyle.display}; word-break: ${linkStyle.wordBreak}; font-family: ${fontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyle ? `font-style: ${fontStyle};` : ""} line-height: ${lineHeight}; ${letterSpacing ? `letter-spacing: ${letterSpacing};` : ""} ${textTransform ? `text-transform: ${textTransform};` : ""} text-align: ${textAlign}; ${whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : ""} padding: ${padding};">
1760
- <span style="color: ${color}; font-family: ${fontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyle ? `font-style: ${fontStyle};` : ""} line-height: ${lineHeight}; ${letterSpacing ? `letter-spacing: ${letterSpacing};` : ""} ${textTransform ? `text-transform: ${textTransform};` : ""} ${textDecoration && textDecoration !== "none" ? `text-decoration: ${textDecoration};` : ""} ${whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : ""}">
1791
+ : `<a href="${href}" target="_blank" rel="noopener noreferrer" style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; word-break: ${wordBreak}; text-align: ${textAlign}; padding: ${padding};">
1792
+ <span style="${sharedTextStyles}">
1761
1793
  ${typeof children === "string" ? children : ""}
1762
1794
  </span>
1763
1795
  </a>`}
@@ -2021,7 +2053,7 @@ function Container({ children, config, devMode, devNode }) {
2021
2053
  fontSize: "0",
2022
2054
  lineHeight: "0",
2023
2055
  height: config.gap,
2024
- }, children: "\u00A0" }))] }, `child-${index}`), jsx("td", { className: "desktop-gap-column", width: config.gap, style: gapTdStyle, children: "\u00A0" }, `gap-${index}`)] }, `ctn:${index}`));
2056
+ }, children: "\u00A0" }))] }, `child-${index}`), jsx("td", { className: isStacking ? "desktop-gap-column" : undefined, width: config.gap, style: gapTdStyle, children: "\u00A0" }, `gap-${index}`)] }, `ctn:${index}`));
2025
2057
  }
2026
2058
  return (jsx("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: child }, `child-${index}`));
2027
2059
  });
@@ -2077,24 +2109,58 @@ function Divider({ config, devNode }) {
2077
2109
  }
2078
2110
  var Divider_default = memo(Divider, arePropsEqual);
2079
2111
 
2080
- function Head({ children, backgroundColor = "#ffffff", title = "Email Preview", rowGaps = [], }) {
2081
- // Outlook (MSO) Styles and Reset
2112
+ // ── Helpers ───────────────────────────────────────────────────────────────────
2113
+ function normaliseFallbacks(fallbackFontFamily) {
2114
+ if (!fallbackFontFamily)
2115
+ return [];
2116
+ return Array.isArray(fallbackFontFamily)
2117
+ ? fallbackFontFamily
2118
+ : [fallbackFontFamily];
2119
+ }
2120
+ /**
2121
+ * Build the CSS font-stack string from the primary family + fallbacks.
2122
+ * Wraps names that contain spaces in single quotes.
2123
+ */
2124
+ function buildFontStack(primary, fallbacks) {
2125
+ const quote = (name) => (name.includes(" ") ? `'${name}'` : name);
2126
+ return [primary, ...fallbacks].map(quote).join(", ");
2127
+ }
2128
+ // ── Component ─────────────────────────────────────────────────────────────────
2129
+ function Font({ fontFamily, fallbackFontFamily, webFont, fontWeight = 400, fontStyle = "normal", }) {
2130
+ var _a;
2131
+ const fallbacks = normaliseFallbacks(fallbackFontFamily);
2132
+ const fontFaceCss = webFont
2133
+ ? `
2134
+ @font-face {
2135
+ font-family: '${fontFamily}';
2136
+ font-style: ${fontStyle};
2137
+ font-weight: ${fontWeight};
2138
+ font-display: swap;
2139
+ src: url('${webFont.url}') format('${webFont.format}');
2140
+ mso-font-alt: '${(_a = fallbacks[0]) !== null && _a !== void 0 ? _a : "sans-serif"}';
2141
+ }`.trim()
2142
+ : null;
2143
+ const msoComment = fallbacks.length > 0
2144
+ ? `<!--[if mso]>\n<style type="text/css">\n .${cssClassName(fontFamily)} {\n font-family: ${buildFontStack(fallbacks[0], fallbacks.slice(1))}, sans-serif !important;\n }\n</style>\n<![endif]-->`
2145
+ : null;
2146
+ return (jsxs(Fragment$1, { children: [fontFaceCss && (jsx("style", { type: "text/css", dangerouslySetInnerHTML: { __html: fontFaceCss } })), msoComment && (jsx("style", { type: "text/css", dangerouslySetInnerHTML: {
2147
+ __html: `</style>${msoComment}<style type="text/css">`,
2148
+ } }))] }));
2149
+ }
2150
+ // ── Utility: generate a stable CSS class name from a font family name ─────────
2151
+ // Used in the MSO conditional comment to scope the fallback rule.
2152
+ function cssClassName(fontFamily) {
2153
+ return `font-${fontFamily.toLowerCase().replace(/\s+/g, "-")}`;
2154
+ }
2155
+
2156
+ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview", rowGaps = [], fonts = [], }) {
2082
2157
  const msoResetStyles = `
2083
- /* Forces Outlook to render 100% width and prevents line-height issues */
2084
2158
  .ExternalClass { width: 100%; line-height: 100%; }
2085
2159
  .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div { line-height: 100%; }
2086
-
2087
- /* Reset tables for MSO and border issues */
2088
2160
  table { mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-collapse: collapse; border-spacing: 0; }
2089
2161
  td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
2090
-
2091
- /* Reset images */
2092
2162
  img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }
2093
-
2094
- /* Fix for Gmail image wrapping and blue links */
2095
2163
  #MessageViewBody img { min-width: 100%; }
2096
-
2097
- /* --- APPLE BLUE LINK FIX --- */
2098
2164
  a[x-apple-data-detectors] {
2099
2165
  color: inherit !important;
2100
2166
  text-decoration: none !important;
@@ -2103,14 +2169,8 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2103
2169
  font-weight: inherit !important;
2104
2170
  line-height: inherit !important;
2105
2171
  }
2106
-
2107
- /* 🔒 NEW: Set global background color via CSS for clients that respect it */
2108
2172
  body { background-color: ${backgroundColor} !important; }
2109
-
2110
- /* Disable browser default margin */
2111
- p {
2112
- margin: 0;
2113
- }
2173
+ p { margin: 0; }
2114
2174
  `;
2115
2175
  const globalStyles = `
2116
2176
  @media screen and (max-width: 768px) {
@@ -2119,7 +2179,6 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2119
2179
  max-width: 100% !important;
2120
2180
  }
2121
2181
  }
2122
-
2123
2182
  @media screen and (max-width: 768px) {
2124
2183
  .hide-on-mobile {
2125
2184
  display: none !important;
@@ -2128,7 +2187,14 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2128
2187
  mso-hide: all;
2129
2188
  }
2130
2189
  }
2131
-
2190
+ @media screen and (min-width: 769px) {
2191
+ .hide-on-desktop {
2192
+ display: none !important;
2193
+ max-height: 0 !important;
2194
+ overflow: hidden !important;
2195
+ mso-hide: all;
2196
+ }
2197
+ }
2132
2198
  @media screen and (max-width: 768px) {
2133
2199
  .stack-td {
2134
2200
  width: 100% !important;
@@ -2138,12 +2204,7 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2138
2204
  padding-left: 0 !important;
2139
2205
  padding-right: 0 !important;
2140
2206
  }
2141
-
2142
- .desktop-gap-column {
2143
- width: 0 !important;
2144
- display: none !important;
2145
- }
2146
-
2207
+ .desktop-gap-column { width: 0 !important; display: none !important; }
2147
2208
  .mobile-gap-spacer {
2148
2209
  display: block !important;
2149
2210
  width: 100% !important;
@@ -2152,66 +2213,28 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2152
2213
  mso-line-height-rule: exactly;
2153
2214
  }
2154
2215
  }
2155
-
2156
2216
  @media only screen and (max-width: 768px) {
2157
- /* 1. Handling Mobile Alignment (Justify) */
2158
- .row-content-table[data-mobile-justify="center"] {
2159
- margin: 0 auto !important;
2160
- float: none !important;
2161
- }
2162
- .row-content-table[data-mobile-justify="start"] {
2163
- margin: 0 !important;
2164
- float: left !important;
2165
- }
2166
- .row-content-table[data-mobile-justify="end"] {
2167
- margin: 0 0 0 auto !important;
2168
- float: right !important;
2169
- }
2170
-
2171
- /* 2. Handling Mobile Vertical Alignment (Align Items) */
2172
- .row-content-table[data-mobile-align="center"] .child-cell {
2173
- vertical-align: middle !important;
2174
- }
2175
- .row-content-table[data-mobile-align="start"] .child-cell {
2176
- vertical-align: top !important;
2177
- }
2178
- .row-content-table[data-mobile-align="end"] .child-cell {
2179
- vertical-align: bottom !important;
2180
- }
2181
-
2182
- /* 3. Handling Mobile Wrap - Pure CSS Solution */
2183
-
2184
- /* Force table to be full width */
2185
- .row-content-table[data-mobile-wrap="true"] {
2186
- width: 100% !important;
2187
- max-width: 100% !important;
2188
- }
2189
-
2190
- /* Force table row to stack cells */
2191
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr {
2192
- display: block !important;
2193
- }
2194
-
2195
- /* Force each child cell to be full width block */
2217
+ .row-content-table[data-mobile-justify="center"] { margin: 0 auto !important; float: none !important; }
2218
+ .row-content-table[data-mobile-justify="start"] { margin: 0 !important; float: left !important; }
2219
+ .row-content-table[data-mobile-justify="end"] { margin: 0 0 0 auto !important; float: right !important; }
2220
+ .row-content-table[data-mobile-align="center"] .child-cell { vertical-align: middle !important; }
2221
+ .row-content-table[data-mobile-align="start"] .child-cell { vertical-align: top !important; }
2222
+ .row-content-table[data-mobile-align="end"] .child-cell { vertical-align: bottom !important; }
2223
+ .row-content-table[data-mobile-wrap="true"] { width: 100% !important; max-width: 100% !important; }
2224
+ .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr { display: block !important; }
2196
2225
  .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell {
2197
2226
  display: block !important;
2198
2227
  width: 100% !important;
2199
2228
  box-sizing: border-box !important;
2200
2229
  }
2201
-
2202
- /* Hide horizontal gap cells */
2203
2230
  .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .row-gap-td {
2204
2231
  display: none !important;
2205
2232
  width: 0 !important;
2206
2233
  height: 0 !important;
2207
2234
  }
2208
-
2209
- /* Add vertical spacing between stacked cells using margin */
2210
2235
  .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell:not(:last-child) {
2211
2236
  margin-bottom: 20px !important;
2212
2237
  }
2213
-
2214
- /* Dynamic gap support - common values */
2215
2238
  ${["10px", "15px", "20px", "24px", "30px", "40px", ...rowGaps]
2216
2239
  .filter((gap, index, self) => self.indexOf(gap) === index)
2217
2240
  .map((gap) => `
@@ -2220,25 +2243,8 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2220
2243
  }`)
2221
2244
  .join("\n")}
2222
2245
  }
2223
-
2224
- /* ================================================= */
2225
- /* 🔒 UNIVERSAL LINK RESET */
2226
- a {
2227
- color: inherit;
2228
- text-decoration: none;
2229
- }
2230
- /* ================================================= */
2231
-
2232
- /* ================================================= */
2233
- /* 🔒 LIST STYLE ENFORCEMENT */
2234
-
2235
- /* Reset all lists and list items */
2236
- ol, ul {
2237
- margin: 0px;
2238
- padding: 0px;
2239
- list-style: none;
2240
- }
2241
-
2246
+ a { color: inherit; text-decoration: none; }
2247
+ ol, ul { margin: 0px; padding: 0px; list-style: none; }
2242
2248
  li {
2243
2249
  list-style-type: none !important;
2244
2250
  list-style: none !important;
@@ -2247,8 +2253,6 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2247
2253
  margin: 0px;
2248
2254
  display: block !important;
2249
2255
  }
2250
-
2251
- /* 🔒 FORCE HIDE ::marker pseudo-element for non-list items */
2252
2256
  li::marker {
2253
2257
  content: "" !important;
2254
2258
  font-size: 0px !important;
@@ -2256,24 +2260,18 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2256
2260
  color: transparent !important;
2257
2261
  width: 0px !important;
2258
2262
  }
2259
-
2260
- /* Apply bullet styles only to items with data-list="bullet" */
2261
2263
  li[data-list="bullet"] {
2262
2264
  list-style-type: disc !important;
2263
2265
  list-style-position: inside !important;
2264
2266
  padding-left: 1.5em;
2265
2267
  display: list-item !important;
2266
2268
  }
2267
-
2268
- /* Apply ordered styles only to items with data-list="ordered" */
2269
2269
  li[data-list="ordered"] {
2270
2270
  list-style-type: decimal !important;
2271
2271
  list-style-position: inside !important;
2272
2272
  padding-left: 1.5em;
2273
2273
  display: list-item !important;
2274
2274
  }
2275
-
2276
- /* Ensure marker only takes its natural size with no extra spacing */
2277
2275
  li[data-list="bullet"]::marker,
2278
2276
  li[data-list="ordered"]::marker {
2279
2277
  content: normal !important;
@@ -2283,22 +2281,13 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2283
2281
  padding: 0 !important;
2284
2282
  margin: 0 !important;
2285
2283
  }
2286
- /* ================================================= */
2287
-
2288
- /* ================================================= */
2289
- /* 🔒 HEADING STYLE RESET */
2290
- h1, h2, h3, h4, h5, h6 {
2291
- margin: 0;
2292
- padding: 0;
2293
- font-weight: inherit; /* Disables browser defaults */
2294
- }
2295
- /* ================================================= */
2284
+ h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; font-weight: inherit; }
2296
2285
  `;
2297
- 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 }), jsx("style", { type: "text/css", dangerouslySetInnerHTML: { __html: msoResetStyles } }), jsx("style", { type: "text/css", dangerouslySetInnerHTML: { __html: globalStyles } }), children] }));
2286
+ 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, Object.assign({}, 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 } })] }));
2298
2287
  }
2299
2288
 
2300
2289
  function Heading({ config, devMode, children }) {
2301
- const { text, level = "h1", padding, color, textAlign, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2290
+ const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2302
2291
  // Determine the content to render
2303
2292
  const content = text !== null && text !== void 0 ? text : children;
2304
2293
  const isString = typeof content === "string";
@@ -2313,6 +2302,7 @@ function Heading({ config, devMode, children }) {
2313
2302
  const headingStyle = {
2314
2303
  color: color,
2315
2304
  textAlign: textAlign,
2305
+ fontFamily: fontFamily || "Arial, Helvetica, sans-serif",
2316
2306
  fontSize: fontSize,
2317
2307
  fontWeight: fontWeight,
2318
2308
  fontStyle: fontStyle,
@@ -2326,8 +2316,6 @@ function Heading({ config, devMode, children }) {
2326
2316
  // Critical: Remove default top/bottom margin from HTML heading tags
2327
2317
  margin: "0",
2328
2318
  padding: "0",
2329
- // Ensures compatibility with MSO/general font rendering
2330
- fontFamily: "Arial, Helvetica, sans-serif",
2331
2319
  // Outlook specific fixes (using string indexing)
2332
2320
  ["msoLineHeightRule"]: "exactly",
2333
2321
  };
@@ -2362,92 +2350,196 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2362
2350
  );
2363
2351
  }
2364
2352
 
2353
+ /**
2354
+ * Content rendered by Outlook Classic only.
2355
+ * Outputs: <!--[if mso]> ... <![endif]-->
2356
+ */
2357
+ function MsoOnly({ html }) {
2358
+ return (jsx("td", { dangerouslySetInnerHTML: {
2359
+ __html: `<!--[if mso]>${html}<![endif]-->`,
2360
+ } }));
2361
+ }
2362
+ /**
2363
+ * Content hidden from Outlook Classic, visible in all other clients.
2364
+ * Outputs: <!--[if !mso]><!--> ... <!--<![endif]-->
2365
+ */
2366
+ function NonMso({ html }) {
2367
+ return (jsx("td", { dangerouslySetInnerHTML: {
2368
+ __html: `<!--[if !mso]><!-->${html}<!--<![endif]-->`,
2369
+ } }));
2370
+ }
2371
+
2372
+ // ---------------------------------------------------------------------------
2373
+ // Helpers
2374
+ // ---------------------------------------------------------------------------
2365
2375
  function getBorderStyle$3(border) {
2366
2376
  if (!border)
2367
2377
  return {};
2368
2378
  const style = {};
2369
- // If a full border is specified, apply it
2370
2379
  if (border.width && border.style && border.color) {
2371
2380
  style.border = `${border.width} ${border.style} ${border.color}`;
2372
2381
  }
2373
2382
  else {
2374
- // If only individual borders are specified, explicitly set others to 'none'
2375
- // to prevent Outlook Classic from showing black borders
2376
- const hasIndividualBorders = border.top || border.right || border.bottom || border.left;
2377
- if (hasIndividualBorders) {
2378
- // Default all borders to none
2383
+ const hasIndividual = border.top || border.right || border.bottom || border.left;
2384
+ if (hasIndividual) {
2379
2385
  style.borderTop = "none";
2380
2386
  style.borderRight = "none";
2381
2387
  style.borderBottom = "none";
2382
2388
  style.borderLeft = "none";
2383
2389
  }
2384
2390
  }
2385
- // Override with specific borders if provided
2386
- if (border.top) {
2391
+ if (border.top)
2387
2392
  style.borderTop = `${border.top.width} ${border.top.style} ${border.top.color}`;
2388
- }
2389
- if (border.right) {
2393
+ if (border.right)
2390
2394
  style.borderRight = `${border.right.width} ${border.right.style} ${border.right.color}`;
2391
- }
2392
- if (border.bottom) {
2395
+ if (border.bottom)
2393
2396
  style.borderBottom = `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}`;
2394
- }
2395
- if (border.left) {
2397
+ if (border.left)
2396
2398
  style.borderLeft = `${border.left.width} ${border.left.style} ${border.left.color}`;
2397
- }
2398
2399
  return style;
2399
2400
  }
2400
- function Image({ config, devNode, devMode }) {
2401
- const { src, alt, width, height, maxHeight, maxWidth, backgroundColor, padding, borderRadius, border, href, target, } = config;
2402
- // Get border styles
2401
+ function getBorderStyleString$1(border) {
2402
+ if (!border)
2403
+ return "";
2404
+ const styles = [];
2405
+ if (border.width && border.style && border.color) {
2406
+ styles.push(`border:${border.width} ${border.style} ${border.color};`);
2407
+ }
2408
+ else {
2409
+ const hasIndividual = border.top || border.right || border.bottom || border.left;
2410
+ if (hasIndividual) {
2411
+ styles.push("border-top:none;", "border-right:none;", "border-bottom:none;", "border-left:none;");
2412
+ }
2413
+ }
2414
+ if (border.top)
2415
+ styles.push(`border-top:${border.top.width} ${border.top.style} ${border.top.color};`);
2416
+ if (border.right)
2417
+ styles.push(`border-right:${border.right.width} ${border.right.style} ${border.right.color};`);
2418
+ if (border.bottom)
2419
+ styles.push(`border-bottom:${border.bottom.width} ${border.bottom.style} ${border.bottom.color};`);
2420
+ if (border.left)
2421
+ styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color};`);
2422
+ return styles.join(" ");
2423
+ }
2424
+ // ---------------------------------------------------------------------------
2425
+ // Merged styles helper — applies mobile overrides on top of desktop values
2426
+ // ---------------------------------------------------------------------------
2427
+ function mergeConfig(config, overrides) {
2428
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2429
+ return {
2430
+ width: (_a = overrides === null || overrides === void 0 ? void 0 : overrides.width) !== null && _a !== void 0 ? _a : config.width,
2431
+ height: (_b = overrides === null || overrides === void 0 ? void 0 : overrides.height) !== null && _b !== void 0 ? _b : config.height,
2432
+ maxWidth: (_c = overrides === null || overrides === void 0 ? void 0 : overrides.maxWidth) !== null && _c !== void 0 ? _c : config.maxWidth,
2433
+ maxHeight: (_d = overrides === null || overrides === void 0 ? void 0 : overrides.maxHeight) !== null && _d !== void 0 ? _d : config.maxHeight,
2434
+ backgroundColor: (_e = overrides === null || overrides === void 0 ? void 0 : overrides.backgroundColor) !== null && _e !== void 0 ? _e : config.backgroundColor,
2435
+ padding: (_f = overrides === null || overrides === void 0 ? void 0 : overrides.padding) !== null && _f !== void 0 ? _f : config.padding,
2436
+ borderRadius: (_g = overrides === null || overrides === void 0 ? void 0 : overrides.borderRadius) !== null && _g !== void 0 ? _g : config.borderRadius,
2437
+ border: (_h = overrides === null || overrides === void 0 ? void 0 : overrides.border) !== null && _h !== void 0 ? _h : config.border,
2438
+ };
2439
+ }
2440
+ // ---------------------------------------------------------------------------
2441
+ // Desktop table — JSX (same as original)
2442
+ // ---------------------------------------------------------------------------
2443
+ function renderDesktopTable({ config, className, devNode, devMode, }) {
2444
+ const { src, alt, href, target } = config;
2445
+ const { width, height, maxWidth, maxHeight, backgroundColor, padding, borderRadius, border, } = mergeConfig(config);
2403
2446
  const borderStyle = getBorderStyle$3(border);
2404
- // 1. Image Style: Critical for compatibility, especially display: block
2405
- const imgStyle = Object.assign({
2406
- // Basic image properties
2407
- display: "block", objectFit: "cover",
2408
- // Dimensions (using CSS fallback)
2409
- width: width || "100%", height: height || "auto", maxWidth: maxWidth || "100%", maxHeight: maxHeight,
2410
- // Styling
2411
- border: "0", borderRadius: borderRadius }, borderStyle);
2412
- // 2. Link Style: Ensure no underline or color changes
2447
+ const imgStyle = Object.assign({ display: "block", objectFit: "cover", width: width || "100%", height: height || "auto", maxWidth: maxWidth || "100%", maxHeight: maxHeight, border: "0", borderRadius: borderRadius }, borderStyle);
2413
2448
  const linkStyle = {
2414
2449
  display: "block",
2415
2450
  textDecoration: "none",
2416
2451
  border: "0",
2417
2452
  outline: "none",
2418
2453
  };
2419
- // 3. TD Style: Where padding and background are reliably applied
2420
2454
  const tdStyle = {
2421
2455
  padding: padding,
2422
2456
  backgroundColor: backgroundColor,
2423
- fontSize: "0", // CRITICAL: Collapses extra space from Outlook/Gmail
2424
- lineHeight: "0", // CRITICAL: Collapses extra space from Outlook/Gmail
2457
+ fontSize: "0",
2458
+ lineHeight: "0",
2425
2459
  };
2426
- // Image element with proper attributes for email compatibility
2427
- const imageElement = (jsx("img", { draggable: false, src: src, alt: alt, style: imgStyle,
2428
- // For Outlook: Use the smaller of width or maxWidth for the HTML attribute
2429
- width: (() => {
2430
- const widthPx = (width === null || width === void 0 ? void 0 : width.endsWith("px")) ? parseInt(width, 10) : undefined;
2431
- const maxWidthPx = (maxWidth === null || maxWidth === void 0 ? void 0 : maxWidth.endsWith("px"))
2432
- ? parseInt(maxWidth, 10)
2433
- : undefined;
2434
- if (widthPx && maxWidthPx) {
2435
- return Math.min(widthPx, maxWidthPx);
2436
- }
2437
- return widthPx || maxWidthPx;
2438
- })(), height: (height === null || height === void 0 ? void 0 : height.endsWith("px")) ? parseInt(height, 10) : undefined, border: 0 }));
2439
- // Wrap image in link if href is provided and not in dev mode
2460
+ const widthNum = (width === null || width === void 0 ? void 0 : width.endsWith("px")) ? parseInt(width, 10) : undefined;
2461
+ const maxWidthNum = (maxWidth === null || maxWidth === void 0 ? void 0 : maxWidth.endsWith("px"))
2462
+ ? parseInt(maxWidth, 10)
2463
+ : undefined;
2464
+ const heightNum = (height === null || height === void 0 ? void 0 : height.endsWith("px")) ? parseInt(height, 10) : undefined;
2465
+ const imageElement = (jsx("img", { draggable: false, src: src, alt: alt, style: imgStyle, width: widthNum && maxWidthNum
2466
+ ? Math.min(widthNum, maxWidthNum)
2467
+ : widthNum || maxWidthNum, height: heightNum, border: 0 }));
2440
2468
  const content = href && !devMode ? (jsx("a", Object.assign({ href: href, target: target, style: linkStyle }, (target === "_blank" ? { rel: "noopener noreferrer" } : {}), { children: imageElement }))) : (imageElement);
2441
- return (
2442
- // We wrap the image in a table to reliably apply padding, background, and alignment.
2443
- jsxs("table", { "aria-label": `Image Wrapper for: ${alt}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2444
- // --- Start dev
2469
+ return (jsxs("table", { "aria-label": `Image Wrapper for: ${alt}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: className, style: {
2445
2470
  position: "relative",
2446
- // --- End dev
2447
2471
  width: width || "100%",
2448
2472
  borderCollapse: "collapse",
2449
2473
  }, onClick: devMode ? (e) => e.preventDefault() : undefined, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: "center", children: content }) }) }), devMode && !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
2450
2474
  }
2475
+ // ---------------------------------------------------------------------------
2476
+ // Mobile table — HTML string (injected via NonMso, same pattern as Icon VML)
2477
+ // ---------------------------------------------------------------------------
2478
+ function buildMobileTableHTML({ config, overrides, className, }) {
2479
+ const { src, alt, href, target } = config;
2480
+ const { width, height, maxWidth, maxHeight, backgroundColor, padding, borderRadius, border, } = mergeConfig(config, overrides);
2481
+ const borderStyleStr = getBorderStyleString$1(border);
2482
+ const widthNum = (width === null || width === void 0 ? void 0 : width.endsWith("px")) ? parseInt(width, 10) : undefined;
2483
+ const maxWidthNum = (maxWidth === null || maxWidth === void 0 ? void 0 : maxWidth.endsWith("px"))
2484
+ ? parseInt(maxWidth, 10)
2485
+ : undefined;
2486
+ const heightNum = (height === null || height === void 0 ? void 0 : height.endsWith("px")) ? parseInt(height, 10) : undefined;
2487
+ const resolvedWidth = widthNum && maxWidthNum
2488
+ ? Math.min(widthNum, maxWidthNum)
2489
+ : widthNum || maxWidthNum;
2490
+ const imgTag = `<img
2491
+ draggable="false"
2492
+ src="${src}"
2493
+ alt="${alt}"
2494
+ ${resolvedWidth ? `width="${resolvedWidth}"` : ""}
2495
+ ${heightNum ? `height="${heightNum}"` : ""}
2496
+ border="0"
2497
+ style="display:block;object-fit:cover;width:${width || "100%"};height:${height || "auto"};max-width:${maxWidth || "100%"};${maxHeight ? `max-height:${maxHeight};` : ""}border:0;${borderRadius ? `border-radius:${borderRadius};` : ""}${borderStyleStr}"
2498
+ />`;
2499
+ const content = href
2500
+ ? `<a href="${href}" target="${target || "_self"}" style="display:block;text-decoration:none;border:0;outline:none;"${target === "_blank" ? ' rel="noopener noreferrer"' : ""}>${imgTag}</a>`
2501
+ : imgTag;
2502
+ return `
2503
+ <table
2504
+ aria-label="Image Wrapper for: ${alt}"
2505
+ role="presentation"
2506
+ cellpadding="0"
2507
+ cellspacing="0"
2508
+ border="0"
2509
+ class="${className}"
2510
+ style="position:relative;width:${width || "100%"};border-collapse:collapse;"
2511
+ >
2512
+ <tbody>
2513
+ <tr>
2514
+ <td
2515
+ align="center"
2516
+ style="padding:${padding || ""};background-color:${backgroundColor || ""};font-size:0;line-height:0;"
2517
+ >
2518
+ ${content}
2519
+ </td>
2520
+ </tr>
2521
+ </tbody>
2522
+ </table>
2523
+ `;
2524
+ }
2525
+ // ---------------------------------------------------------------------------
2526
+ // Component
2527
+ // ---------------------------------------------------------------------------
2528
+ function Image({ config, devNode, devMode }) {
2529
+ const { mobile } = config;
2530
+ const hasMobileOverrides = !!mobile && !mobile.hidden;
2531
+ const isHiddenOnMobile = !!(mobile === null || mobile === void 0 ? void 0 : mobile.hidden);
2532
+ return (jsxs(Fragment$1, { children: [renderDesktopTable({
2533
+ config,
2534
+ className: hasMobileOverrides || isHiddenOnMobile ? "hide-on-mobile" : undefined,
2535
+ devNode,
2536
+ devMode,
2537
+ }), hasMobileOverrides && !devMode && (jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: { width: "100%", borderCollapse: "collapse" }, children: jsx("tbody", { children: jsx("tr", { children: jsx(NonMso, { html: buildMobileTableHTML({
2538
+ config,
2539
+ overrides: mobile,
2540
+ className: "hide-on-desktop",
2541
+ }) }) }) }) }))] }));
2542
+ }
2451
2543
  var Image_default = memo(Image, arePropsEqual);
2452
2544
 
2453
2545
  const justifyMap$1 = {
@@ -2678,7 +2770,7 @@ function Spacer({ config, devNode }) {
2678
2770
  var Spacer_default = memo(Spacer, arePropsEqual);
2679
2771
 
2680
2772
  function Text({ config, devMode, children }) {
2681
- const { text, padding, color, textAlign, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", } = config;
2773
+ const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", } = config;
2682
2774
  // 1. TD Style: Where padding and background are reliably applied.
2683
2775
  const tdStyle = {
2684
2776
  padding: padding,
@@ -2690,6 +2782,7 @@ function Text({ config, devMode, children }) {
2690
2782
  const contentStyle = {
2691
2783
  color: color,
2692
2784
  textAlign: textAlign,
2785
+ fontFamily: fontFamily || "Arial, Helvetica, sans-serif",
2693
2786
  fontSize: fontSize,
2694
2787
  fontWeight: fontWeight,
2695
2788
  fontStyle: fontStyle,
@@ -2704,7 +2797,6 @@ function Text({ config, devMode, children }) {
2704
2797
  wordBreak: wordBreak,
2705
2798
  margin: "0",
2706
2799
  padding: "0",
2707
- fontFamily: "Arial, Helvetica, sans-serif",
2708
2800
  };
2709
2801
  // Determine content to render
2710
2802
  const content = text !== null && text !== void 0 ? text : children;
@@ -2984,5 +3076,5 @@ function Icon({ config, devNode, devMode, children }) {
2984
3076
  }
2985
3077
  var Icon_default = memo(Icon, arePropsEqual);
2986
3078
 
2987
- export { Body, Button_default as Button, Column_default as Column, Container_default as Container, Divider_default as Divider, Head, Heading_default as Heading, Html, Icon_default as Icon, Image_default as Image, Row_default as Row, Section_default as Section, Spacer_default as Spacer, Text_default as Text };
3079
+ export { Body, Button_default as Button, Column_default as Column, Container_default as Container, Divider_default as Divider, Font, Head, Heading_default as Heading, Html, Icon_default as Icon, Image_default as Image, MsoOnly, NonMso, Row_default as Row, Section_default as Section, Spacer_default as Spacer, Text_default as Text };
2988
3080
  //# sourceMappingURL=index.esm.js.map