@pagenflow/email 1.4.3 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import { ReactNode } from "react";
2
2
  import { BorderConfig } from "../types";
3
+ import IInnerLink from "../types/IInnerLink";
3
4
  export interface ButtonConfig {
4
- /** The destination URL for the button. Required. */
5
- href: string;
5
+ /** Link configuration for the button destination. Required. */
6
+ innerLink?: IInnerLink;
6
7
  /** Button text. */
7
8
  children: ReactNode;
8
9
  /** Background color. Required for VML compatibility. */
@@ -1,14 +1,9 @@
1
1
  import { ReactNode } from "react";
2
2
  export interface DividerConfig {
3
- /** Thickness of the line (e.g., "1px"). */
4
3
  height?: string;
5
- /** Color of the line. */
6
4
  color?: string;
7
- /** Width of the line (e.g., "100%" or "300px"). */
8
5
  width?: string;
9
- /** Spacing above and below the divider (e.g., "20px 0"). */
10
6
  margin?: string;
11
- /** Horizontal alignment of the divider. */
12
7
  align?: "left" | "center" | "right";
13
8
  hideOnMobile?: boolean;
14
9
  }
@@ -1,5 +1,11 @@
1
1
  import { CSSProperties, ReactNode } from "react";
2
2
  import { BorderConfig } from "../types";
3
+ import IInnerLink from "../types/IInnerLink";
4
+ /**
5
+ * RULES NOT TO BE REMOVED
6
+ * -------------------
7
+ * - Image should not be draggable in dev mode as we don't want to disrupt the builder dnd behavior
8
+ */
3
9
  export interface ImageMobileConfig {
4
10
  width?: string;
5
11
  height?: string;
@@ -24,8 +30,7 @@ export interface ImageConfig {
24
30
  padding?: string;
25
31
  borderRadius?: string;
26
32
  border?: BorderConfig;
27
- href?: string;
28
- target?: string;
33
+ innerLink?: IInnerLink;
29
34
  objectFit?: CSSProperties["objectFit"];
30
35
  objectPosition?: string;
31
36
  mobile?: ImageMobileConfig;
package/dist/index.cjs.js CHANGED
@@ -1539,6 +1539,27 @@ const justifyMap$3 = {
1539
1539
  center: "center",
1540
1540
  end: "right",
1541
1541
  };
1542
+ // Helper to build link href based on innerLink type (mirrors Icon component)
1543
+ function buildLinkHref$2(innerLink) {
1544
+ if (!innerLink || innerLink.type === "none")
1545
+ return null;
1546
+ switch (innerLink.type) {
1547
+ case "url":
1548
+ return innerLink.url || null;
1549
+ case "email":
1550
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
1551
+ case "phone":
1552
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
1553
+ case "anchor":
1554
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
1555
+ case "page_top":
1556
+ return "#top";
1557
+ case "page_bottom":
1558
+ return "#bottom";
1559
+ default:
1560
+ return null;
1561
+ }
1562
+ }
1542
1563
  function getBorderStyleString$2(border) {
1543
1564
  if (!border)
1544
1565
  return "";
@@ -1574,8 +1595,10 @@ function getBorderStyleString$2(border) {
1574
1595
  return styles.join(" ");
1575
1596
  }
1576
1597
  function Button({ config, devMode }) {
1577
- const { href, children, backgroundColor = "#007bff", // Default blue
1578
- 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;
1598
+ const { innerLink, children, backgroundColor, color, padding, borderRadius, border, width, maxWidth, justifyContent, textAlign, fontSize, fontWeight, fontStyle, fontFamily, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, opacity, whiteSpace, wordBreak, } = config;
1599
+ // Resolve href from innerLink
1600
+ const href = buildLinkHref$2(innerLink);
1601
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
1579
1602
  // Sanitize fontFamily early so safeFontFamily is available for all paths below.
1580
1603
  const safeFontFamily = fontFamily
1581
1604
  ? fontFamily.replace(/['"]/g, "")
@@ -1595,23 +1618,25 @@ function Button({ config, devMode }) {
1595
1618
  // Check if width is percentage-based or not defined
1596
1619
  const isPercentageWidth = !width || width.includes("%");
1597
1620
  const useSimpleOutlookApproach = isPercentageWidth;
1598
- const align = justifyMap$3[justifyContent];
1621
+ const align = justifyContent ? justifyMap$3[justifyContent] : undefined;
1599
1622
  // --- VML Calculation and Code for Outlook Compatibility (Fixed Width Only) ---
1600
1623
  let vmlButton = "";
1601
1624
  if (!useSimpleOutlookApproach) {
1602
1625
  // VML needs fixed pixel height. We estimate it based on padding and potential wrapping.
1603
- const numericPadding = parseInt(padding.split(" ")[0] || "12", 10);
1604
- const numericFontSize = parseInt(fontSize, 10);
1605
- const numericLineHeight = lineHeight.includes("px")
1606
- ? parseInt(lineHeight, 10)
1607
- : numericFontSize * parseFloat(lineHeight);
1626
+ const numericPadding = padding ? parseInt(padding.split(" ")[0] || "12", 10) : 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;
1608
1633
  // Trust user's explicit pixel width - no calculation needed
1609
1634
  const vmlWidth = parseInt(width, 10);
1610
1635
  // Calculate VML height - trust user's padding and let text wrap naturally
1611
1636
  // VML v:textbox will handle text wrapping automatically
1612
1637
  const textContent = typeof children === "string" ? children : "";
1613
1638
  // Estimate number of lines based on text length and button width
1614
- const horizontalPadding = padding.split(" ")[1]
1639
+ const horizontalPadding = (padding === null || padding === void 0 ? void 0 : padding.split(" ")[1])
1615
1640
  ? parseInt(padding.split(" ")[1], 10) * 2
1616
1641
  : numericPadding * 2;
1617
1642
  const availableTextWidth = vmlWidth - horizontalPadding;
@@ -1624,15 +1649,17 @@ function Button({ config, devMode }) {
1624
1649
  // Add extra 4px buffer to prevent bottom cropping in VML
1625
1650
  const vmlHeight = Math.max(numericPadding * 2 + textHeight + 4, 40);
1626
1651
  // VML colors must use the full hex format (e.g., #000000)
1627
- const vmlFillColor = backgroundColor.startsWith("#")
1628
- ? backgroundColor
1629
- : `#${backgroundColor}`;
1652
+ const vmlFillColor = backgroundColor
1653
+ ? backgroundColor.startsWith("#")
1654
+ ? backgroundColor
1655
+ : `#${backgroundColor}`
1656
+ : undefined;
1630
1657
  // VML stroke color for border
1631
1658
  const vmlStrokeColor = (border === null || border === void 0 ? void 0 : border.color) || vmlFillColor;
1632
1659
  const vmlStrokeWeight = (border === null || border === void 0 ? void 0 : border.width) ? parseInt(border.width, 10) : 0;
1633
1660
  const hasVmlStroke = vmlStrokeWeight > 0;
1634
1661
  // Build VML font styles - consistent with other rendering paths
1635
- const vmlFontWeight = fontWeight || "500";
1662
+ const vmlFontWeight = fontWeight;
1636
1663
  const vmlFontStyle = fontStyle === "italic" ? "font-style:italic;" : "";
1637
1664
  const vmlLetterSpacing = letterSpacing
1638
1665
  ? `letter-spacing:${letterSpacing};`
@@ -1643,12 +1670,12 @@ function Button({ config, devMode }) {
1643
1670
  const vmlTextDecoration = textDecoration && textDecoration !== "none"
1644
1671
  ? `text-decoration:${textDecoration};`
1645
1672
  : "";
1646
- const vmlWhiteSpace = whiteSpace !== "normal" ? `white-space:${whiteSpace};` : "";
1673
+ const vmlWhiteSpace = whiteSpace ? `white-space:${whiteSpace};` : "";
1647
1674
  const vmlDirection = direction ? `direction:${direction};` : "";
1648
1675
  const vmlOpacity = opacity !== undefined ? `opacity:${opacity};` : "";
1649
1676
  // VML code uses MSO conditional comments to render only in Outlook
1650
1677
  // Use table with explicit MSO height for vertical centering
1651
- const horizontalPaddingValue = padding.split(" ")[1]
1678
+ const horizontalPaddingValue = (padding === null || padding === void 0 ? void 0 : padding.split(" ")[1])
1652
1679
  ? parseInt(padding.split(" ")[1], 10)
1653
1680
  : numericPadding;
1654
1681
  // For VML, we need to use a table inside to properly apply padding and centering
@@ -1657,7 +1684,7 @@ function Button({ config, devMode }) {
1657
1684
  if (textAlign === "center") {
1658
1685
  vmlAlignAttr = 'align="center"';
1659
1686
  }
1660
- else {
1687
+ else if (textAlign) {
1661
1688
  vmlAlignStyle = `text-align:${textAlign};`;
1662
1689
  }
1663
1690
  // Border radius is intentionally omitted (arcsize="0%") for Outlook Classic.
@@ -1665,12 +1692,12 @@ function Button({ config, devMode }) {
1665
1692
  // is inconsistent, so we render sharp corners there instead.
1666
1693
  vmlButton = `
1667
1694
  <!--[if mso]>
1668
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:${vmlHeight}px;width:${vmlWidth}px;" arcsize="0%" strokecolor="${vmlStrokeColor}" ${hasVmlStroke ? `strokeweight="${vmlStrokeWeight}px"` : 'stroke="f"'} fillcolor="${vmlFillColor}">
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}"` : ""}>
1669
1696
  <w:anchorlock/>
1670
1697
  <v:textbox inset="${horizontalPaddingValue}px,${numericPadding}px,${horizontalPaddingValue}px,${numericPadding}px">
1671
1698
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse;">
1672
1699
  <tr>
1673
- <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;">
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;">
1674
1701
  ${typeof children === "string" ? children : ""}
1675
1702
  </td>
1676
1703
  </tr>
@@ -1694,10 +1721,10 @@ function Button({ config, devMode }) {
1694
1721
  const textTransformProp = textTransform
1695
1722
  ? `text-transform: ${textTransform};`
1696
1723
  : "";
1697
- const whiteSpaceProp = whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "";
1724
+ const whiteSpaceProp = whiteSpace ? `white-space: ${whiteSpace};` : "";
1698
1725
  const directionProp = direction ? `direction: ${direction};` : "";
1699
1726
  const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
1700
- const wordBreakProp = wordBreak !== "break-word" ? `word-break: ${wordBreak};` : "";
1727
+ const wordBreakProp = wordBreak ? `word-break: ${wordBreak};` : "";
1701
1728
  // Border radius is intentionally omitted from the Outlook Classic table cell.
1702
1729
  // Outlook Classic ignores border-radius on table cells anyway, and including it
1703
1730
  // can cause unexpected rendering artifacts, so we explicitly leave it out.
@@ -1705,13 +1732,17 @@ function Button({ config, devMode }) {
1705
1732
  <!--[if mso]>
1706
1733
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse: collapse;">
1707
1734
  <tr>
1708
- <td align="${align}" style="padding: 0;">
1735
+ <td ${align ? `align="${align}"` : ""} style="padding: 0;">
1709
1736
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="${width || "auto"}" style="border-collapse: collapse;">
1710
1737
  <tr>
1711
- <td bgcolor="${backgroundColor}" align="${textAlign}" style="padding: ${padding}; text-align: ${textAlign}; ${borderStyleString}">
1712
- <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;">
1713
- ${typeof children === "string" ? children : ""}
1714
- </a>
1738
+ <td ${backgroundColor ? `bgcolor="${backgroundColor}"` : ""} ${textAlign ? `align="${textAlign}"` : ""} style="${padding ? `padding: ${padding};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${borderStyleString}">
1739
+ ${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;">
1741
+ ${typeof children === "string" ? children : ""}
1742
+ </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;">
1744
+ ${typeof children === "string" ? children : ""}
1745
+ </span>`}
1715
1746
  </td>
1716
1747
  </tr>
1717
1748
  </table>
@@ -1725,7 +1756,7 @@ function Button({ config, devMode }) {
1725
1756
  // fontFamily uses the sanitized value so embedded quotes never break the
1726
1757
  // style attribute string (which is always wrapped in double quotes).
1727
1758
  const sharedTextStyles = [
1728
- `color: ${color};`,
1759
+ color ? `color: ${color};` : "",
1729
1760
  safeFontFamily ? `font-family: ${safeFontFamily};` : "",
1730
1761
  fontSize ? `font-size: ${fontSize};` : "",
1731
1762
  fontWeight ? `font-weight: ${fontWeight};` : "",
@@ -1738,7 +1769,7 @@ function Button({ config, devMode }) {
1738
1769
  : "",
1739
1770
  direction ? `direction: ${direction};` : "",
1740
1771
  opacity !== undefined ? `opacity: ${opacity};` : "",
1741
- whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "",
1772
+ whiteSpace ? `white-space: ${whiteSpace};` : "",
1742
1773
  ]
1743
1774
  .filter(Boolean)
1744
1775
  .join(" ");
@@ -1772,20 +1803,24 @@ function Button({ config, devMode }) {
1772
1803
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; width: 100%;">
1773
1804
  <tbody>
1774
1805
  <tr>
1775
- <td style="background-color: ${backgroundTdStyle.backgroundColor}; border-radius: ${backgroundTdStyle.borderRadius}; width: ${backgroundTdStyle.width}; ${maxWidth ? `max-width: ${maxWidth};` : ""} ${borderRadius ? "overflow: hidden;" : ""}">
1776
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: separate; border-spacing: 0; border-radius: ${borderRadius}; width: 100%; ${borderStyleString}">
1806
+ <td style="${backgroundTdStyle.backgroundColor ? `background-color: ${backgroundTdStyle.backgroundColor};` : ""} ${backgroundTdStyle.borderRadius ? `border-radius: ${backgroundTdStyle.borderRadius};` : ""} width: ${backgroundTdStyle.width}; ${maxWidth ? `max-width: ${maxWidth};` : ""} ${borderRadius ? "overflow: hidden;" : ""}">
1807
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: separate; border-spacing: 0; ${borderRadius ? `border-radius: ${borderRadius};` : ""} width: 100%; ${borderStyleString}">
1777
1808
  <tbody>
1778
1809
  <tr>
1779
1810
  <td style="padding: 0;">
1780
1811
  ${devMode
1781
- ? `<span style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; word-break: ${wordBreak}; text-align: ${textAlign}; padding: ${padding};">
1812
+ ? `<span style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; ${wordBreak ? `word-break: ${wordBreak};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${padding ? `padding: ${padding};` : ""}">
1782
1813
  ${typeof children === "string" ? children : ""}
1783
1814
  </span>`
1784
- : `<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};">
1785
- <span>
1815
+ : 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};` : ""}">
1817
+ <span>
1818
+ ${typeof children === "string" ? children : ""}
1819
+ </span>
1820
+ </a>`
1821
+ : `<span style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; ${wordBreak ? `word-break: ${wordBreak};` : ""} ${textAlign ? `text-align: ${textAlign};` : ""} ${padding ? `padding: ${padding};` : ""}">
1786
1822
  ${typeof children === "string" ? children : ""}
1787
- </span>
1788
- </a>`}
1823
+ </span>`}
1789
1824
  </td>
1790
1825
  </tr>
1791
1826
  </tbody>
@@ -2104,40 +2139,48 @@ var Container_default = React.memo(Container, arePropsEqual);
2104
2139
 
2105
2140
  function Divider({ config, devNode }) {
2106
2141
  const { height = "1px", color = "#cccccc", width = "100%", margin = "20px 0", align = "center", hideOnMobile, } = config;
2107
- // 1. Outer TD Style: Applies the vertical spacing (margin)
2108
- const outerTdStyle = {
2109
- padding: margin,
2110
- fontSize: "0",
2111
- lineHeight: "0",
2112
- width: "100%",
2113
- };
2114
- // 2. Divider Table Style: Applies the line properties
2115
- const dividerTableStyle = {
2116
- width: width,
2117
- height: height,
2118
- backgroundColor: color,
2119
- borderCollapse: "collapse",
2120
- border: "0",
2121
- // ✅ FIX 1: Use string literal indexing for MSO properties
2122
- // ["mso-table-lspace" as string]: "0pt",
2123
- ["msoTableLspace"]: "0pt",
2124
- // ["mso-table-rspace" as string]: "0pt",
2125
- ["msoTableRspace"]: "0pt",
2126
- };
2127
- // Parse height for the HTML attribute
2128
- const dividerHeightAttribute = parseInt(height, 10) || 1;
2129
- return (jsxRuntime.jsxs("table", { "aria-label": "Divider Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2130
- // --- Start dev
2131
- position: "relative",
2132
- // --- End dev
2142
+ const heightPx = parseInt(height, 10) || 1;
2143
+ // Parse margin into paddingTop / paddingBottom for the outer TD.
2144
+ // Outlook ignores shorthand "20px 0" on TDs — must be explicit.
2145
+ const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw] = margin.trim().split(/\s+/);
2146
+ const marginTop = marginTopRaw;
2147
+ const marginBottom = marginBottomRaw !== null && marginBottomRaw !== void 0 ? marginBottomRaw : marginTopRaw; // "20px 0" → top=20px, bottom=20px
2148
+ // Outlook requires align on the outer TD to correctly position
2149
+ // a fixed-width inner table (e.g. width="300px").
2150
+ const alignAttr = align === "left" ? "left" : align === "right" ? "right" : "center";
2151
+ return (jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2152
+ position: "relative", // dev overlay anchor
2133
2153
  width: "100%",
2134
2154
  borderCollapse: "collapse",
2135
- }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: outerTdStyle, align: align, children: jsxRuntime.jsx("table", { "aria-label": "Divider Line", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, style: dividerTableStyle, ...{ height: dividerHeightAttribute }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
2155
+ border: "0",
2156
+ }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: alignAttr, style: {
2157
+ paddingTop: marginTop,
2158
+ paddingBottom: marginBottom,
2159
+ paddingLeft: "0",
2160
+ paddingRight: "0",
2161
+ fontSize: "0",
2162
+ lineHeight: "0",
2163
+ }, children: jsxRuntime.jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: alignAttr, style: {
2164
+ width: width,
2165
+ borderCollapse: "collapse",
2166
+ border: "0",
2167
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { ...{ height: heightPx }, ref: (el) => {
2168
+ if (!el)
2169
+ return;
2170
+ el.setAttribute("style", `height:${height};` +
2171
+ `line-height:${height};` +
2172
+ `font-size:0;` +
2173
+ `padding:0;` +
2174
+ `background-color:${color};` +
2175
+ `mso-line-height-rule:exactly;`);
2176
+ }, style: {
2177
+ // Fallback for non-Outlook clients (React-rendered style object).
2136
2178
  height: height,
2179
+ lineHeight: height,
2137
2180
  fontSize: "0",
2138
- lineHeight: "0",
2139
2181
  padding: "0",
2140
- }, children: "\u00A0" }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2182
+ backgroundColor: color,
2183
+ } }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2141
2184
  }
2142
2185
  var Divider_default = React.memo(Divider, arePropsEqual);
2143
2186
 
@@ -2382,6 +2425,27 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2382
2425
  );
2383
2426
  }
2384
2427
 
2428
+ // Helper to build link href based on innerLink type
2429
+ function buildLinkHref$1(innerLink) {
2430
+ if (!innerLink || innerLink.type === "none")
2431
+ return null;
2432
+ switch (innerLink.type) {
2433
+ case "url":
2434
+ return innerLink.url || null;
2435
+ case "email":
2436
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
2437
+ case "phone":
2438
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
2439
+ case "anchor":
2440
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
2441
+ case "page_top":
2442
+ return "#top";
2443
+ case "page_bottom":
2444
+ return "#bottom";
2445
+ default:
2446
+ return null;
2447
+ }
2448
+ }
2385
2449
  function getBorderStyle$3(border) {
2386
2450
  if (!border)
2387
2451
  return {};
@@ -2424,7 +2488,10 @@ function getBorderStyleString$1(border) {
2424
2488
  }
2425
2489
  function Image({ config, devNode, devMode }) {
2426
2490
  var _a, _b;
2427
- const { src, alt, href, target, mobile } = config;
2491
+ const { src, alt, innerLink, mobile } = config;
2492
+ // Resolve href and target from innerLink
2493
+ const href = buildLinkHref$1(innerLink);
2494
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2428
2495
  const seed = src + (alt || "");
2429
2496
  const instanceId = seed
2430
2497
  .split("")
@@ -2454,32 +2521,56 @@ function Image({ config, devNode, devMode }) {
2454
2521
  ? parseInt(config.maxWidth, 10)
2455
2522
  : undefined;
2456
2523
  const imgWidthAttr = isPercent ? (maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined) : widthAttr;
2457
- // 2. Mobile Overrides (Every property used)
2524
+ // 2. Mobile Overrides only emit CSS properties that are explicitly set,
2525
+ // so unspecified properties are left untouched (no forced defaults).
2458
2526
  let mobileCss = "";
2459
2527
  if (mobile) {
2528
+ // .wrap-${imgClass} rules
2529
+ const wrapRules = [
2530
+ // Always reset min-width so the px lock from desktop can be overridden
2531
+ "min-width: 0 !important;",
2532
+ ];
2533
+ if (mobile.width !== undefined)
2534
+ wrapRules.push(`width: ${mobile.width} !important;`);
2535
+ if (mobile.maxWidth !== undefined)
2536
+ wrapRules.push(`max-width: ${mobile.maxWidth} !important;`);
2537
+ // .td-${imgClass} rules
2538
+ const tdRules = [];
2539
+ if (mobile.padding !== undefined)
2540
+ tdRules.push(`padding: ${mobile.padding} !important;`);
2541
+ if (mobile.backgroundColor !== undefined)
2542
+ tdRules.push(`background-color: ${mobile.backgroundColor} !important;`);
2543
+ // .${imgClass} rules
2544
+ const imgRules = [];
2545
+ if (mobile.width !== undefined)
2546
+ imgRules.push(`width: ${mobile.width} !important;`);
2547
+ if (mobile.height !== undefined)
2548
+ imgRules.push(`height: ${mobile.height} !important;`);
2549
+ if (mobile.maxWidth !== undefined)
2550
+ imgRules.push(`max-width: ${mobile.maxWidth} !important;`);
2551
+ if (mobile.maxHeight !== undefined)
2552
+ imgRules.push(`max-height: ${mobile.maxHeight} !important;`);
2553
+ if (mobile.borderRadius !== undefined)
2554
+ imgRules.push(`border-radius: ${mobile.borderRadius} !important;`);
2555
+ if (mobile.hidden !== undefined)
2556
+ imgRules.push(`display: ${mobile.hidden ? "none" : "block"} !important;`);
2557
+ if (mobile.objectFit !== undefined)
2558
+ imgRules.push(`object-fit: ${mobile.objectFit} !important;`);
2559
+ if (mobile.objectPosition !== undefined)
2560
+ imgRules.push(`object-position: ${mobile.objectPosition} !important;`);
2561
+ if (mobile.border !== undefined)
2562
+ imgRules.push(getBorderStyleString$1(mobile.border));
2460
2563
  mobileCss = `
2461
2564
  @media screen and (max-width: 768px) {
2462
2565
  .wrap-${imgClass} {
2463
2566
  /* This breaks the px lock from desktop and makes it fluid */
2464
- width: ${mobile.width || "100%"} !important;
2465
- max-width: ${mobile.maxWidth || "100%"} !important;
2466
- min-width: 0 !important;
2567
+ ${wrapRules.join("\n ")}
2467
2568
  }
2468
2569
  .td-${imgClass} {
2469
- padding: ${mobile.padding || "0"} !important;
2470
- background-color: ${mobile.backgroundColor || "transparent"} !important;
2471
- width: 100% !important;
2570
+ ${tdRules.join("\n ")}
2472
2571
  }
2473
2572
  .${imgClass} {
2474
- width: ${mobile.width || "100%"} !important;
2475
- height: ${mobile.height || "auto"} !important;
2476
- max-width: ${mobile.maxWidth || "100%"} !important;
2477
- max-height: ${mobile.maxHeight || "none"} !important;
2478
- border-radius: ${mobile.borderRadius || "0"} !important;
2479
- display: ${mobile.hidden ? "none" : "block"} !important;
2480
- object-fit: ${mobile.objectFit || "fill"} !important;
2481
- object-position: ${mobile.objectPosition || "center"} !important;
2482
- ${getBorderStyleString$1(mobile.border)}
2573
+ ${imgRules.join("\n ")}
2483
2574
  }
2484
2575
  }
2485
2576
  `;
@@ -2497,7 +2588,7 @@ function Image({ config, devNode, devMode }) {
2497
2588
  objectFit: config.objectFit,
2498
2589
  objectPosition: config.objectPosition,
2499
2590
  };
2500
- const imageElement = (jsxRuntime.jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle }));
2591
+ const imageElement = (jsxRuntime.jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle, draggable: !devMode }));
2501
2592
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [mobile && jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: mobileCss } }), jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: `wrap-${imgClass}`, align: "center" // Ensures a 300px image stays centered in its parent
2502
2593
  , style: {
2503
2594
  width: tableWidth, // Fixed px here prevents the 100% "ghost space"
@@ -2510,7 +2601,9 @@ function Image({ config, devNode, devMode }) {
2510
2601
  fontSize: "0",
2511
2602
  lineHeight: "0",
2512
2603
  width: tableWidth, // Lock the cell as well
2513
- }, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2604
+ }, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank"
2605
+ ? { rel: "noopener noreferrer" }
2606
+ : {}), style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2514
2607
  }
2515
2608
  var Image_default = React.memo(Image, arePropsEqual);
2516
2609
 
@@ -2584,7 +2677,7 @@ function Row({ children, config, devNode, devMode }) {
2584
2677
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2585
2678
  const numChildren = childrenArray.length;
2586
2679
  const href = getHrefFromInnerLink(config.innerLink);
2587
- const target = (_a = config.innerLink) === null || _a === void 0 ? void 0 : _a.target;
2680
+ const target = ((_a = config.innerLink) === null || _a === void 0 ? void 0 : _a.target) || "_blank";
2588
2681
  // 1. Outer TD: Background, Border Radius, Width, Height.
2589
2682
  const backgroundTdStyle = {
2590
2683
  backgroundColor: config.backgroundColor,
@@ -2659,7 +2752,7 @@ function Row({ children, config, devNode, devMode }) {
2659
2752
  }, className: "child-cell", children: child }), index < numChildren - 1 &&
2660
2753
  config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle, className: "row-gap-td", children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2661
2754
  if (href && !devMode) {
2662
- return (jsxRuntime.jsx("a", { href: href, ...(target && { target }), style: {
2755
+ return (jsxRuntime.jsx("a", { href: href, ...({ target }), style: {
2663
2756
  textDecoration: "none",
2664
2757
  color: "inherit",
2665
2758
  display: "block",
@@ -2905,7 +2998,7 @@ function buildIconifyUrl(config) {
2905
2998
  const template = process.env.ICONIFY_API_IMAGE_URI ||
2906
2999
  "https://iconify.pagenflow.com/api/image/{{height}}/{{color}}/{{rotate}}-{{rotate-orientation}}/{{icon-full-name}}.png";
2907
3000
  return template
2908
- .replace("{{height}}", String(numericHeight * 2))
3001
+ .replace("{{height}}", String(numericHeight * 4))
2909
3002
  .replace("{{color}}", cleanColor)
2910
3003
  .replace("{{rotate}}", String(rotate))
2911
3004
  .replace("{{rotate-orientation}}", rotateOrientation)
@@ -2939,7 +3032,7 @@ function Icon({ config, devNode, devMode, children }) {
2939
3032
  // Determine icon source
2940
3033
  const iconSrc = buildIconifyUrl(config);
2941
3034
  const href = buildLinkHref(innerLink);
2942
- const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_self";
3035
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2943
3036
  const align = justifyMap[justifyContent];
2944
3037
  // Get border styles
2945
3038
  const borderStyle = getBorderStyle(border);