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