@pagenflow/email 1.4.7 → 1.4.9

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
@@ -1533,6 +1533,57 @@ function arePropsEqual(prevProps, nextProps) {
1533
1533
  return isEqual(prevProps, nextProps);
1534
1534
  }
1535
1535
 
1536
+ /**
1537
+ * bindingAttribute.ts
1538
+ * -------------------
1539
+ * Serialises DataBindings into discrete HTML attributes:
1540
+ *
1541
+ * data-bind-if → visible condition → stamped on the root element
1542
+ * data-bind-list → repeater (dataList + itemAlias) → stamped on the
1543
+ * direct parent of the children loop, NOT the root
1544
+ * data-bind → propertyMap → stamped on the root element
1545
+ *
1546
+ * Keeping the three concerns in separate attributes lets the post-processor
1547
+ * handle them independently without ambiguity.
1548
+ */
1549
+ // ─── Root element props (visible + propertyMap) ───────────────────────────────
1550
+ /**
1551
+ * Props to spread onto the component's root element.
1552
+ * Carries `data-bind-if` and/or `data-bind` (propertyMap).
1553
+ */
1554
+ function rootBindingProps(bindings) {
1555
+ if (!bindings)
1556
+ return {};
1557
+ const props = {};
1558
+ if (bindings.visible) {
1559
+ props['data-bind-if'] = bindings.visible;
1560
+ }
1561
+ if (bindings.propertyMap && Object.keys(bindings.propertyMap).length) {
1562
+ props['data-bind'] = JSON.stringify({ propertyMap: bindings.propertyMap });
1563
+ }
1564
+ return props;
1565
+ }
1566
+ // ─── List wrapper props (dataList + itemAlias) ────────────────────────────────
1567
+ /**
1568
+ * Props to spread onto the *direct parent* of the children loop.
1569
+ * Carries `data-bind-list` only when a repeater is defined.
1570
+ *
1571
+ * Usage inside a component:
1572
+ * <tbody {...listBindingProps(bindings)}>
1573
+ * {children}
1574
+ * </tbody>
1575
+ */
1576
+ function listBindingProps(bindings) {
1577
+ if (!(bindings === null || bindings === void 0 ? void 0 : bindings.dataList))
1578
+ return {};
1579
+ return {
1580
+ 'data-bind-list': JSON.stringify({
1581
+ dataList: bindings.dataList,
1582
+ ...(bindings.itemAlias && { itemAlias: bindings.itemAlias }),
1583
+ }),
1584
+ };
1585
+ }
1586
+
1536
1587
  // Map alignment to HTML 'align' attribute
1537
1588
  const justifyMap$3 = {
1538
1589
  start: "left",
@@ -1540,7 +1591,7 @@ const justifyMap$3 = {
1540
1591
  end: "right",
1541
1592
  };
1542
1593
  // Helper to build link href based on innerLink type (mirrors Icon component)
1543
- function buildLinkHref$2(innerLink) {
1594
+ function buildLinkHref$4(innerLink) {
1544
1595
  if (!innerLink || innerLink.type === "none")
1545
1596
  return null;
1546
1597
  switch (innerLink.type) {
@@ -1594,10 +1645,10 @@ function getBorderStyleString$2(border) {
1594
1645
  }
1595
1646
  return styles.join(" ");
1596
1647
  }
1597
- function Button({ config, devMode }) {
1648
+ function Button({ config, devMode, bindings }) {
1598
1649
  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
1650
  // Resolve href from innerLink
1600
- const href = buildLinkHref$2(innerLink);
1651
+ const href = buildLinkHref$4(innerLink);
1601
1652
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
1602
1653
  // Sanitize fontFamily early so safeFontFamily is available for all paths below.
1603
1654
  const safeFontFamily = fontFamily
@@ -1681,7 +1732,7 @@ function Button({ config, devMode }) {
1681
1732
  .join(" ");
1682
1733
  return (
1683
1734
  // Wrapper table for alignment - maintains proper positioning for hover indicators
1684
- jsxRuntime.jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1735
+ jsxRuntime.jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1685
1736
  width: "100%",
1686
1737
  borderCollapse: "collapse",
1687
1738
  boxSizing: "border-box",
@@ -1741,6 +1792,15 @@ function Button({ config, devMode }) {
1741
1792
  }
1742
1793
  var Button_default = React.memo(Button, arePropsEqual);
1743
1794
 
1795
+ function isGapZero(gap) {
1796
+ if (!gap)
1797
+ return true;
1798
+ // Remove whitespace and convert to lowercase
1799
+ const trimmedGap = gap.trim().toLowerCase();
1800
+ // Check for exact zero matches (0, 0px, 0%, 0em, 0rem, etc.)
1801
+ return /^0(px|%|em|rem|ex|ch|vw|vh|vmin|vmax)?$/.test(trimmedGap);
1802
+ }
1803
+
1744
1804
  // Helper for vertical alignment
1745
1805
  const vAlignMap = {
1746
1806
  start: "top",
@@ -1789,7 +1849,7 @@ function getBorderStyle$5(border) {
1789
1849
  }
1790
1850
  return style;
1791
1851
  }
1792
- function Column({ children, config, devNode }) {
1852
+ function Column({ children, config, devNode, bindings }) {
1793
1853
  var _a, _b, _c;
1794
1854
  // Process children array for gap support
1795
1855
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1841,6 +1901,7 @@ function Column({ children, config, devNode }) {
1841
1901
  // treat it as content-box height and add padding on top, causing the
1842
1902
  // total to exceed the declared height in preview mode.
1843
1903
  verticalAlign: config.alignItems ? alignMap$2[config.alignItems] : "top",
1904
+ background: "transparent",
1844
1905
  };
1845
1906
  // 4. Gap spacer style (used between children)
1846
1907
  const gapSpacerStyle = {
@@ -1848,6 +1909,7 @@ function Column({ children, config, devNode }) {
1848
1909
  lineHeight: "1px",
1849
1910
  fontSize: "1px",
1850
1911
  width: "100%",
1912
+ background: "transparent",
1851
1913
  };
1852
1914
  // 5. maxWidth constraining table style (modern clients).
1853
1915
  // The `width` HTML attribute on this table is what Outlook Classic
@@ -1862,10 +1924,10 @@ function Column({ children, config, devNode }) {
1862
1924
  borderCollapse: "collapse",
1863
1925
  };
1864
1926
  // Main content rendering
1865
- const renderContent = () => (jsxRuntime.jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap$2[config.alignItems] : "left", children: config.gap && numChildren > 1 ? (jsxRuntime.jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1927
+ const renderContent = () => (jsxRuntime.jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap$2[config.alignItems] : "left", ...(numChildren > 1 ? {} : listBindingProps(bindings)), children: numChildren > 1 ? (jsxRuntime.jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1866
1928
  width: "100%",
1867
1929
  borderCollapse: "collapse",
1868
- }, children: jsxRuntime.jsx("tbody", { children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
1930
+ }, children: jsxRuntime.jsx("tbody", { ...listBindingProps(bindings), children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
1869
1931
  verticalAlign: config.alignItems
1870
1932
  ? alignMap$2[config.alignItems]
1871
1933
  : "top",
@@ -1873,8 +1935,8 @@ function Column({ children, config, devNode }) {
1873
1935
  ? vAlignMap[config.justifyContent]
1874
1936
  : "top", align: config.alignItems
1875
1937
  ? alignMap$2[config.alignItems]
1876
- : "left", children: child }) }), index < numChildren - 1 && (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
1877
- return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1938
+ : "left", children: child }) }), index < numChildren - 1 && !isGapZero(config.gap) && (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
1939
+ return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1878
1940
  position: "relative",
1879
1941
  ...outerTableStyle,
1880
1942
  }, ...(config.height && { height: config.height }), children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: outerTdStyle, ...(config.width && { width: config.width }), ...(config.height && { height: config.height }), children: config.maxWidth ? (
@@ -1943,7 +2005,7 @@ function getBorderStyle$4(border) {
1943
2005
  }
1944
2006
  return style;
1945
2007
  }
1946
- function Container({ children, config, devMode, devNode }) {
2008
+ function Container({ children, config, bindings, devMode, devNode, }) {
1947
2009
  var _a, _b, _c;
1948
2010
  const { widthType, childrenConstraints } = config;
1949
2011
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1961,7 +2023,7 @@ function Container({ children, config, devMode, devNode }) {
1961
2023
  return 0;
1962
2024
  })();
1963
2025
  const getChildWidths = (() => {
1964
- const { widthDistributionType } = childrenConstraints;
2026
+ const { widthDistributionType } = childrenConstraints !== null && childrenConstraints !== void 0 ? childrenConstraints : {};
1965
2027
  const totalGapSpace = gapWidthPx * (numChildren > 1 ? numChildren - 1 : 0);
1966
2028
  const remainingContentSpace = containerWidthPx - totalGapSpace;
1967
2029
  switch (widthDistributionType) {
@@ -2035,6 +2097,7 @@ function Container({ children, config, devMode, devNode }) {
2035
2097
  width: config.gap || "0",
2036
2098
  lineHeight: "1px",
2037
2099
  fontSize: "1px",
2100
+ background: "transparent",
2038
2101
  };
2039
2102
  const justifyAlign = config.justifyContent
2040
2103
  ? justifyMap$2[config.justifyContent]
@@ -2050,19 +2113,20 @@ function Container({ children, config, devMode, devNode }) {
2050
2113
  textAlign: "left",
2051
2114
  };
2052
2115
  if (config.gap && index < numChildren - 1) {
2053
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: [child, isStacking && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2116
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: [child, isStacking && !isGapZero(config.gap) && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2054
2117
  display: "none",
2055
2118
  fontSize: "0",
2056
2119
  lineHeight: "0",
2057
2120
  height: config.gap,
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}`));
2121
+ background: "transparent",
2122
+ }, children: "\u00A0" }))] }, `child-${index}`), !isGapZero(config.gap) && (jsxRuntime.jsx("td", { className: isStacking ? "desktop-gap-column" : undefined, width: config.gap, style: gapTdStyle, children: "\u00A0" }, `gap-${index}`))] }, `ctn:${index}`));
2059
2123
  }
2060
2124
  return (jsxRuntime.jsx("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: child }, `child-${index}`));
2061
2125
  });
2062
2126
  return (jsxRuntime.jsx("table", { "aria-label": `Container | Table Outer`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: {
2063
2127
  position: "relative",
2064
2128
  ...outerTableStyle,
2065
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { align: justifyAlign, children: [jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedWrapper } }), jsxRuntime.jsxs("table", { className: [
2129
+ }, ...rootBindingProps(bindings), children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { align: justifyAlign, children: [jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedWrapper } }), jsxRuntime.jsxs("table", { className: [
2066
2130
  widthType === "fixed" ? "container-fixed-width" : undefined,
2067
2131
  devMode ? "main-wrapper relative" : undefined,
2068
2132
  ]
@@ -2071,22 +2135,22 @@ function Container({ children, config, devMode, devNode }) {
2071
2135
  width: "100%",
2072
2136
  maxWidth: widthType === "fixed" ? config.width || "600px" : undefined,
2073
2137
  borderCollapse: "collapse",
2074
- }, width: containerWidthAttr, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: backgroundTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Border Wrapper`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: borderTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Content Table`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: contentTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: rowElements }) }) }) }) }) }) }) }) }) }), !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }), jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedFooter } })] }) }) }) }));
2138
+ }, width: containerWidthAttr, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: backgroundTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Border Wrapper`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: borderTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Content Table`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: contentTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { ...listBindingProps(bindings), children: rowElements }) }) }) }) }) }) }) }) }) }), !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }), jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedFooter } })] }) }) }) }));
2075
2139
  }
2076
2140
  var Container_default = React.memo(Container, arePropsEqual);
2077
2141
 
2078
- function Divider({ config, devNode }) {
2142
+ function Divider({ config, devNode, bindings }) {
2079
2143
  const { height = "1px", color = "#cccccc", width = "100%", margin = "20px 0", align = "center", hideOnMobile, } = config;
2080
2144
  const heightPx = parseInt(height, 10) || 1;
2081
2145
  // Parse margin into paddingTop / paddingBottom for the outer TD.
2082
2146
  // Outlook ignores shorthand "20px 0" on TDs — must be explicit.
2083
- const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw] = margin.trim().split(/\s+/);
2147
+ const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw,] = margin.trim().split(/\s+/);
2084
2148
  const marginTop = marginTopRaw;
2085
2149
  const marginBottom = marginBottomRaw !== null && marginBottomRaw !== void 0 ? marginBottomRaw : marginTopRaw; // "20px 0" → top=20px, bottom=20px
2086
2150
  // Outlook requires align on the outer TD to correctly position
2087
2151
  // a fixed-width inner table (e.g. width="300px").
2088
2152
  const alignAttr = align === "left" ? "left" : align === "right" ? "right" : "center";
2089
- return (jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2153
+ return (jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2090
2154
  position: "relative", // dev overlay anchor
2091
2155
  width: "100%",
2092
2156
  borderCollapse: "collapse",
@@ -2183,7 +2247,13 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2183
2247
  line-height: inherit !important;
2184
2248
  }
2185
2249
  body { background-color: ${backgroundColor} !important; }
2186
- p { margin: 0; }
2250
+ p {
2251
+ margin: 0;
2252
+ margin-block-start: 0px;
2253
+ margin-block-end: 0px;
2254
+ margin-inline-start: 0px;
2255
+ margin-inline-end: 0px;
2256
+ }
2187
2257
  `;
2188
2258
  const globalStyles = `
2189
2259
  @media screen and (max-width: 768px) {
@@ -2405,8 +2475,56 @@ function injectLinkStyles(html, fallback) {
2405
2475
  return result;
2406
2476
  }
2407
2477
 
2408
- function Heading({ config, devMode, children }) {
2409
- const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2478
+ // injectParagraphReset.ts
2479
+ const P_RESET_STYLE = [
2480
+ "margin:0",
2481
+ "margin-block-start:0px",
2482
+ "margin-block-end:0px",
2483
+ "margin-inline-start:0px",
2484
+ "margin-inline-end:0px",
2485
+ ].join(";");
2486
+ function injectParagraphReset(html) {
2487
+ if (!html || !html.includes("<p"))
2488
+ return html;
2489
+ return html.replace(/<p(\s[^>]*)?>/gi, (_, attrs = "") => {
2490
+ var _a, _b;
2491
+ const existingStyle = (_b = (_a = /style\s*=\s*"([^"]*)"/i.exec(attrs)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
2492
+ const mergedStyle = existingStyle
2493
+ ? `${existingStyle};${P_RESET_STYLE}`
2494
+ : P_RESET_STYLE;
2495
+ const cleanAttrs = attrs.replace(/\s*style\s*=\s*"[^"]*"/i, "").trim();
2496
+ return cleanAttrs
2497
+ ? `<p ${cleanAttrs} style="${mergedStyle}">`
2498
+ : `<p style="${mergedStyle}">`;
2499
+ });
2500
+ }
2501
+
2502
+ // Helper to build link href based on innerLink type
2503
+ function buildLinkHref$3(innerLink) {
2504
+ if (!innerLink || innerLink.type === "none")
2505
+ return null;
2506
+ switch (innerLink.type) {
2507
+ case "url":
2508
+ return innerLink.url || null;
2509
+ case "email":
2510
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
2511
+ case "phone":
2512
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
2513
+ case "anchor":
2514
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
2515
+ case "page_top":
2516
+ return "#top";
2517
+ case "page_bottom":
2518
+ return "#bottom";
2519
+ default:
2520
+ return null;
2521
+ }
2522
+ }
2523
+ function Heading({ config, devMode, children, bindings }) {
2524
+ const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, innerLink, } = config;
2525
+ // Resolve href and target from innerLink
2526
+ const href = buildLinkHref$3(innerLink);
2527
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2410
2528
  // Determine the content to render
2411
2529
  const content = text !== null && text !== void 0 ? text : children;
2412
2530
  const isString = typeof content === "string";
@@ -2439,16 +2557,21 @@ function Heading({ config, devMode, children }) {
2439
2557
  ["msoLineHeightRule"]: "exactly",
2440
2558
  };
2441
2559
  const processedHtml = isString
2442
- ? injectLinkStyles(content, headingStyle)
2560
+ ? injectParagraphReset(injectLinkStyles(content, headingStyle))
2443
2561
  : "";
2444
2562
  // Dynamically create the Heading element
2445
2563
  const HeadingTag = level;
2564
+ const headingElement = isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content }));
2446
2565
  return (
2447
2566
  // Wrap the heading content in a table for padding/width/background management.
2448
- jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2567
+ jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2449
2568
  width: "100%",
2450
2569
  borderCollapse: "collapse",
2451
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
2570
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank" ? { rel: "noopener noreferrer" } : {}), style: {
2571
+ display: "block",
2572
+ textDecoration: "none",
2573
+ color: "inherit",
2574
+ }, children: headingElement })) : (headingElement) }) }) }) }));
2452
2575
  }
2453
2576
  var Heading_default = React.memo(Heading, arePropsEqual);
2454
2577
 
@@ -2473,7 +2596,7 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2473
2596
  }
2474
2597
 
2475
2598
  // Helper to build link href based on innerLink type
2476
- function buildLinkHref$1(innerLink) {
2599
+ function buildLinkHref$2(innerLink) {
2477
2600
  if (!innerLink || innerLink.type === "none")
2478
2601
  return null;
2479
2602
  switch (innerLink.type) {
@@ -2533,11 +2656,15 @@ function getBorderStyleString$1(border) {
2533
2656
  styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color} !important;`);
2534
2657
  return styles.join(" ");
2535
2658
  }
2536
- function Image({ config, devNode, devMode }) {
2659
+ function Image({ config, devNode, devMode, bindings }) {
2537
2660
  var _a, _b;
2538
- const { src, alt, innerLink, mobile } = config;
2661
+ const { src: originalSrc, alt, innerLink, mobile } = config;
2662
+ // In dev mode, if there's no src, use the placeholder
2663
+ const src = devMode && !originalSrc
2664
+ ? "https://placehold.co/300x200?text=select+an+image&font=poppins"
2665
+ : originalSrc;
2539
2666
  // Resolve href and target from innerLink
2540
- const href = buildLinkHref$1(innerLink);
2667
+ const href = buildLinkHref$2(innerLink);
2541
2668
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2542
2669
  const seed = src + (alt || "");
2543
2670
  const instanceId = seed
@@ -2551,43 +2678,37 @@ function Image({ config, devNode, devMode }) {
2551
2678
  const widthAttr = desktopWidth.replace("px", "");
2552
2679
  const heightAttr = (_a = config.height) === null || _a === void 0 ? void 0 : _a.replace("px", "");
2553
2680
  // Determine the table's "initial" width.
2554
- // If it's 300px, the table should be 300px, not 100%.
2555
2681
  const tableWidth = isPercent ? desktopWidth : `${widthAttr}px`;
2556
- // When width is a percentage, Outlook ignores CSS and renders the image at
2557
- // its intrinsic pixel size. Setting a concrete `width` HTML attribute gives
2558
- // Outlook a value to constrain against while modern clients continue to use
2559
- // the CSS `width: 100%` for fluid rendering.
2560
- //
2561
- // If `maxWidth` is a pixel value (e.g. "600px"), we extract the number and
2562
- // use it as the HTML `width` attribute so Outlook enforces that cap.
2563
- // Other clients ignore the attribute and rely on CSS styles instead.
2564
- // If `maxWidth` is not set or is not a pixel value (e.g. "100%"), we fall
2565
- // back to the original behaviour (numeric string for px widths, undefined
2566
- // for % widths).
2567
- const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2568
- ? parseInt(config.maxWidth, 10)
2569
- : undefined;
2570
- const imgWidthAttr = isPercent ? (maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined) : widthAttr;
2571
- // 2. Mobile Overrides only emit CSS properties that are explicitly set,
2572
- // so unspecified properties are left untouched (no forced defaults).
2682
+ // Calculate width attribute for Outlook
2683
+ let imgWidthAttr;
2684
+ if (config.outlookWidth) {
2685
+ // Use explicit outlookWidth if provided
2686
+ imgWidthAttr = parseInt(config.outlookWidth, 10);
2687
+ }
2688
+ else if (isPercent) {
2689
+ // For percentage widths, use maxWidth as fallback for Outlook
2690
+ const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2691
+ ? parseInt(config.maxWidth, 10)
2692
+ : undefined;
2693
+ imgWidthAttr = maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined;
2694
+ }
2695
+ else {
2696
+ // For fixed pixel widths, use that value
2697
+ imgWidthAttr = widthAttr ? parseInt(widthAttr, 10) : undefined;
2698
+ }
2699
+ // 2. Mobile Overrides
2573
2700
  let mobileCss = "";
2574
2701
  if (mobile) {
2575
- // .wrap-${imgClass} rules
2576
- const wrapRules = [
2577
- // Always reset min-width so the px lock from desktop can be overridden
2578
- "min-width: 0 !important;",
2579
- ];
2702
+ const wrapRules = ["min-width: 0 !important;"];
2580
2703
  if (mobile.width !== undefined)
2581
2704
  wrapRules.push(`width: ${mobile.width} !important;`);
2582
2705
  if (mobile.maxWidth !== undefined)
2583
2706
  wrapRules.push(`max-width: ${mobile.maxWidth} !important;`);
2584
- // .td-${imgClass} rules
2585
2707
  const tdRules = [];
2586
2708
  if (mobile.padding !== undefined)
2587
2709
  tdRules.push(`padding: ${mobile.padding} !important;`);
2588
2710
  if (mobile.backgroundColor !== undefined)
2589
2711
  tdRules.push(`background-color: ${mobile.backgroundColor} !important;`);
2590
- // .${imgClass} rules
2591
2712
  const imgRules = [];
2592
2713
  if (mobile.width !== undefined)
2593
2714
  imgRules.push(`width: ${mobile.width} !important;`);
@@ -2610,7 +2731,6 @@ function Image({ config, devNode, devMode }) {
2610
2731
  mobileCss = `
2611
2732
  @media screen and (max-width: 768px) {
2612
2733
  .wrap-${imgClass} {
2613
- /* This breaks the px lock from desktop and makes it fluid */
2614
2734
  ${wrapRules.join("\n ")}
2615
2735
  }
2616
2736
  .td-${imgClass} {
@@ -2636,9 +2756,27 @@ function Image({ config, devNode, devMode }) {
2636
2756
  objectPosition: config.objectPosition,
2637
2757
  };
2638
2758
  const imageElement = (jsxRuntime.jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle, draggable: !devMode }));
2639
- 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
2640
- , style: {
2641
- width: tableWidth, // Fixed px here prevents the 100% "ghost space"
2759
+ // Outlook Classic wrapper - only applied when outlookWidth is explicitly set
2760
+ // OR when we need to constrain a percentage-width image in Outlook
2761
+ const needsOutlookWrapper = config.outlookWidth || (isPercent && imgWidthAttr);
2762
+ const finalImageElement = needsOutlookWrapper ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { dangerouslySetInnerHTML: {
2763
+ __html: `</style>` +
2764
+ `<!--[if mso]>` +
2765
+ `<table cellpadding="0" cellspacing="0" border="0" style="width: ${imgWidthAttr}px;">` +
2766
+ `<tr>` +
2767
+ `<td style="padding: 0; margin: 0;" width="${imgWidthAttr}">` +
2768
+ `<img src="${src}" alt="${alt || ""}" width="${imgWidthAttr}" height="${heightAttr !== "auto" ? heightAttr : ""}" style="display: block; width: 100%; height: auto;" />` +
2769
+ `</td>` +
2770
+ `</tr>` +
2771
+ `</table>` +
2772
+ `<![endif]-->` +
2773
+ `<!--[if !mso]><!-->` +
2774
+ `<style>`,
2775
+ } }), imageElement, jsxRuntime.jsx("style", { dangerouslySetInnerHTML: {
2776
+ __html: `</style>` + `<!--<![endif]-->` + `<style>`,
2777
+ } })] })) : (imageElement);
2778
+ 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", ...rootBindingProps(bindings), style: {
2779
+ width: tableWidth,
2642
2780
  maxWidth: "100%",
2643
2781
  borderCollapse: "collapse",
2644
2782
  margin: "0 auto",
@@ -2647,10 +2785,10 @@ function Image({ config, devNode, devMode }) {
2647
2785
  backgroundColor: config.backgroundColor,
2648
2786
  fontSize: "0",
2649
2787
  lineHeight: "0",
2650
- width: tableWidth, // Lock the cell as well
2788
+ width: tableWidth,
2651
2789
  }, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank"
2652
2790
  ? { rel: "noopener noreferrer" }
2653
- : {}), style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2791
+ : {}), style: { display: "block", width: "100%" }, children: finalImageElement })) : (finalImageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2654
2792
  }
2655
2793
  var Image_default = React.memo(Image, arePropsEqual);
2656
2794
 
@@ -2719,7 +2857,21 @@ function getHrefFromInnerLink(innerLink) {
2719
2857
  return undefined;
2720
2858
  }
2721
2859
  }
2722
- function Row({ children, config, devNode, devMode }) {
2860
+ /**
2861
+ * Resolves the width for a child <td> at the given index based on layoutColumns.
2862
+ *
2863
+ * - "equal" → equal percentage share across all children
2864
+ * - string[] → explicit value at the matching index (px, %, or mixed)
2865
+ * - undefined → undefined, so no width attribute is set (retrocompat)
2866
+ */
2867
+ function resolveChildColumnWidth(layoutColumns, index, numChildren) {
2868
+ if (!layoutColumns)
2869
+ return undefined;
2870
+ if (layoutColumns === "equal")
2871
+ return `${100 / numChildren}%`;
2872
+ return layoutColumns[index];
2873
+ }
2874
+ function Row({ children, config, devNode, devMode, bindings }) {
2723
2875
  var _a, _b, _c, _d, _e, _f;
2724
2876
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2725
2877
  const numChildren = childrenArray.length;
@@ -2730,6 +2882,9 @@ function Row({ children, config, devNode, devMode }) {
2730
2882
  // / mobile-gap-spacer class names so that stacking works via non-@media CSS
2731
2883
  // rules that survive Gmail's stylesheet stripping.
2732
2884
  const isStacking = ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.wrap) === true && numChildren > 1;
2885
+ // Whether layoutColumns is active. When true, table-layout:fixed is applied
2886
+ // to the content table so Outlook Classic honours the declared column widths.
2887
+ const hasLayoutColumns = config.layoutColumns !== undefined;
2733
2888
  // 1. Outer TD: Background, Border Radius, Width, Height.
2734
2889
  const backgroundTdStyle = {
2735
2890
  backgroundColor: config.backgroundColor,
@@ -2770,24 +2925,34 @@ function Row({ children, config, devNode, devMode }) {
2770
2925
  // Content table fills available space, giving Outlook Classic a hard
2771
2926
  // boundary so text children get a constrained box and line wrapping
2772
2927
  // triggers correctly. Use for rows containing text + image layouts.
2928
+ //
2929
+ // layoutColumns (any value) → additionally applies table-layout: fixed
2930
+ // so Outlook Classic honours the per-child width declarations.
2931
+ // Compatible with both fillWidth modes.
2773
2932
  const contentTableStyle = {
2774
- width: config.fillWidth ? "100%" : "auto",
2933
+ // When layoutColumns is active, force 100% so Outlook Classic has a
2934
+ // concrete boundary to resolve percentage column widths against.
2935
+ // table-layout:fixed is meaningless without a fixed reference width.
2936
+ width: hasLayoutColumns || config.fillWidth ? "100%" : "auto",
2775
2937
  height: "100%",
2776
2938
  borderCollapse: "collapse",
2777
2939
  minWidth: "1px",
2778
- ...(!config.fillWidth && { maxWidth: config.width || "100%" }),
2940
+ ...(!config.fillWidth &&
2941
+ !hasLayoutColumns && { maxWidth: config.width || "100%" }),
2942
+ ...(hasLayoutColumns && { tableLayout: "fixed" }),
2779
2943
  };
2780
2944
  // 5. Gap TD.
2781
2945
  const gapTdStyle = {
2782
2946
  width: config.gap || "0",
2783
2947
  lineHeight: "1px",
2784
2948
  fontSize: "1px",
2949
+ background: "transparent",
2785
2950
  };
2786
2951
  const tdAlign = config.justifyContent
2787
2952
  ? justifyMap$1[config.justifyContent]
2788
2953
  : "left";
2789
2954
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2790
- const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2955
+ const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...(!href ? rootBindingProps(bindings) : {}), style: {
2791
2956
  position: "relative",
2792
2957
  width: config.width || "100%",
2793
2958
  height: config.height,
@@ -2796,32 +2961,44 @@ function Row({ children, config, devNode, devMode }) {
2796
2961
  width: "100%",
2797
2962
  height: "100%",
2798
2963
  borderCollapse: "collapse",
2799
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsxRuntime.jsx("table", { "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle, ...(config.height && { height: config.height }), className: "content-table row-content-table", "data-mobile-wrap": ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { align: tdAlign, style: {
2800
- verticalAlign: tdValign,
2801
- textAlign: tdAlign,
2802
- padding: "0",
2803
- margin: "0",
2804
- },
2805
- // Mirror of Container's stack-td pattern: when isStacking,
2806
- // the non-@media .stack-td rule forces display:block +
2807
- // width:100% on each child, which survives Gmail's
2808
- // @media stripping and achieves true mobile stacking.
2809
- className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2810
- index < numChildren - 1 &&
2811
- config.gap && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2812
- display: "none",
2813
- fontSize: "0",
2814
- lineHeight: "0",
2815
- height: config.gap,
2816
- }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2817
- config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle,
2818
- // Mirror of Container's desktop-gap-column pattern:
2819
- // when isStacking, the non-@media .desktop-gap-column
2820
- // rule collapses the between-column gap td so it does
2821
- // not create phantom space while children are stacked.
2822
- className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2964
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsxRuntime.jsx("table", { "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle, ...(config.height && { height: config.height }), className: "content-table row-content-table", "data-mobile-wrap": ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", ...listBindingProps(bindings), children: childrenArray.map((child, index) => {
2965
+ // Resolve the column width for this child based on
2966
+ // layoutColumns. undefined when layoutColumns is not set,
2967
+ // preserving the original behaviour (retrocompat).
2968
+ const columnWidth = resolveChildColumnWidth(config.layoutColumns, index, numChildren);
2969
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { align: tdAlign, ...(columnWidth && {
2970
+ width: columnWidth,
2971
+ }), style: {
2972
+ verticalAlign: tdValign,
2973
+ textAlign: tdAlign,
2974
+ padding: "0",
2975
+ margin: "0",
2976
+ ...(columnWidth && {
2977
+ width: columnWidth,
2978
+ }),
2979
+ },
2980
+ // Mirror of Container's stack-td pattern: when isStacking,
2981
+ // the non-@media .stack-td rule forces display:block +
2982
+ // width:100% on each child, which survives Gmail's
2983
+ // @media stripping and achieves true mobile stacking.
2984
+ className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2985
+ index < numChildren - 1 &&
2986
+ config.gap && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2987
+ display: "none",
2988
+ fontSize: "0",
2989
+ lineHeight: "0",
2990
+ height: config.gap,
2991
+ background: "transparent",
2992
+ }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2993
+ config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle,
2994
+ // Mirror of Container's desktop-gap-column pattern:
2995
+ // when isStacking, the non-@media .desktop-gap-column
2996
+ // rule collapses the between-column gap td so it does
2997
+ // not create phantom space while children are stacked.
2998
+ className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`));
2999
+ }) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2823
3000
  if (href && !devMode) {
2824
- return (jsxRuntime.jsx("a", { href: href, ...({ target }), style: {
3001
+ return (jsxRuntime.jsx("a", { href: href, ...({ target }), ...rootBindingProps(bindings), style: {
2825
3002
  textDecoration: "none",
2826
3003
  color: "inherit",
2827
3004
  display: "block",
@@ -2866,10 +3043,10 @@ function getBorderStyle$1(border) {
2866
3043
  }
2867
3044
  return style;
2868
3045
  }
2869
- const Section = ({ config, children, devNode, }) => {
3046
+ const Section = ({ config, children, devNode, bindings, }) => {
2870
3047
  var _a, _b, _c;
2871
3048
  const { sectionType, padding } = config;
2872
- return (jsxRuntime.jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3049
+ return (jsxRuntime.jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2873
3050
  position: "relative",
2874
3051
  width: "100%",
2875
3052
  backgroundColor: config.backgroundColor,
@@ -2882,58 +3059,84 @@ const Section = ({ config, children, devNode, }) => {
2882
3059
  backgroundPosition: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.position,
2883
3060
  }, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
2884
3061
  padding: padding,
2885
- }, children: children }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { children: [jsxRuntime.jsxs("span", { style: {
2886
- backgroundColor: "black",
2887
- color: "white",
2888
- padding: "4px",
2889
- fontSize: "14px",
2890
- position: "absolute",
2891
- left: 0,
2892
- top: 0,
2893
- }, children: ["Section | ", sectionType] }), children] }) }) }))] }));
3062
+ }, ...listBindingProps(bindings), children: children }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: jsxRuntime.jsxs("span", { style: {
3063
+ backgroundColor: "black",
3064
+ color: "white",
3065
+ padding: "4px",
3066
+ fontSize: "14px",
3067
+ position: "absolute",
3068
+ left: 0,
3069
+ top: 0,
3070
+ }, children: ["Section | ", sectionType] }) }) }) }))] }));
2894
3071
  };
2895
3072
  var Section_default = React.memo(Section, arePropsEqual);
2896
3073
 
2897
- function Spacer({ config, devNode }) {
2898
- const { height, hideOnMobile } = config;
3074
+ function Spacer({ config, devNode, bindings }) {
3075
+ var _a, _b, _c;
3076
+ const { height, hideOnMobile, backgroundColor, backgroundImage } = config;
2899
3077
  // 1. Spacer Table Style
2900
3078
  const spacerTableStyle = {
2901
- // Crucial for compatibility: Ensures no background or border interference
2902
- backgroundColor: "transparent",
2903
3079
  borderCollapse: "collapse",
2904
- border: "0",
2905
3080
  width: "100%",
2906
- // Note the CSS standard dash convention: 'mso-table-lspace'
2907
- // ["mso-table-lspace" as string]: "0pt",
2908
- ["msoTableLspace"]: "0pt",
2909
- // ["mso-table-rspace" as string]: "0pt",
2910
- ["msoTableRspace"]: "0pt",
2911
3081
  };
2912
3082
  // 2. Spacer TD Style: The element that creates the actual vertical space
2913
3083
  const spacerTdStyle = {
2914
3084
  height: height,
2915
- // Critical: Suppress any vertical height created by text/font
3085
+ maxHeight: height,
2916
3086
  fontSize: "0",
2917
3087
  lineHeight: "0",
2918
3088
  padding: "0",
3089
+ margin: "0",
3090
+ border: "none",
3091
+ color: "transparent",
3092
+ // Background applied at TD level for Outlook Classic compatibility
3093
+ ...(backgroundColor && { backgroundColor }),
3094
+ ...(backgroundImage && {
3095
+ backgroundImage: `url(${backgroundImage.src})`,
3096
+ backgroundRepeat: (_a = backgroundImage.repeat) !== null && _a !== void 0 ? _a : "no-repeat",
3097
+ backgroundSize: (_b = backgroundImage.size) !== null && _b !== void 0 ? _b : "cover",
3098
+ backgroundPosition: (_c = backgroundImage.position) !== null && _c !== void 0 ? _c : "center",
3099
+ }),
2919
3100
  };
2920
3101
  // Parse height for the HTML attribute
2921
3102
  const spacerHeightAttribute = parseInt(height, 10) || 1;
2922
- return (
2923
- // Outer table ensures the spacer spans the full width of its container
2924
- jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3103
+ return (jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2925
3104
  // --- Start dev
2926
3105
  position: "relative",
2927
3106
  // --- End dev
2928
3107
  ...spacerTableStyle,
2929
- }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: spacerTdStyle,
2930
- // Explicit height attribute
2931
- height: spacerHeightAttribute, children: "\u00A0" }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
3108
+ }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: spacerTdStyle, height: spacerHeightAttribute, ...(backgroundColor && !backgroundImage
3109
+ ? { bgcolor: backgroundColor }
3110
+ : {}) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2932
3111
  }
2933
3112
  var Spacer_default = React.memo(Spacer, arePropsEqual);
2934
3113
 
2935
- function Text({ config, devMode, children }) {
2936
- const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, } = config;
3114
+ // Helper to build link href based on innerLink type
3115
+ function buildLinkHref$1(innerLink) {
3116
+ if (!innerLink || innerLink.type === "none")
3117
+ return null;
3118
+ switch (innerLink.type) {
3119
+ case "url":
3120
+ return innerLink.url || null;
3121
+ case "email":
3122
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
3123
+ case "phone":
3124
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
3125
+ case "anchor":
3126
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
3127
+ case "page_top":
3128
+ return "#top";
3129
+ case "page_bottom":
3130
+ return "#bottom";
3131
+ default:
3132
+ return null;
3133
+ }
3134
+ }
3135
+ function Text({ config, devMode, children, bindings }) {
3136
+ const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, innerLink, } = config;
3137
+ // Resolve href and target from innerLink
3138
+ const href = buildLinkHref$1(innerLink);
3139
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2937
3140
  // 1. TD Style: Where padding and background are reliably applied.
2938
3141
  // When maxWidth is set, this TD stays at width: 100% so it always fills
2939
3142
  // its parent — the inner maxWidth table (see below) does the actual
@@ -2986,26 +3189,31 @@ function Text({ config, devMode, children }) {
2986
3189
  ? injectLinkStyles(content, contentStyle)
2987
3190
  : "";
2988
3191
  const innerContent = isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content }));
2989
- return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3192
+ const wrappedContent = maxWidth ? (
3193
+ /*
3194
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
3195
+ *
3196
+ * <center> instructs the Word rendering engine to horizontally
3197
+ * centre its child block, equivalent to margin: 0 auto in CSS.
3198
+ *
3199
+ * The inner table carries the `width` HTML attribute set to the
3200
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
3201
+ * cap; it has no concept of max-width so this is the only lever
3202
+ * available. Modern clients receive the CSS max-width on the
3203
+ * same table and behave correctly.
3204
+ *
3205
+ * The outer TD remains at width: 100% so it always fills its
3206
+ * parent cell in every client — only the inner content is capped.
3207
+ */
3208
+ jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: innerContent }) }) }) }) })) : (innerContent);
3209
+ return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2990
3210
  width: "100%",
2991
3211
  borderCollapse: "collapse",
2992
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: maxWidth ? (
2993
- /*
2994
- * maxWidth wrapper — Outlook Classic compatibility pattern:
2995
- *
2996
- * <center> instructs the Word rendering engine to horizontally
2997
- * centre its child block, equivalent to margin: 0 auto in CSS.
2998
- *
2999
- * The inner table carries the `width` HTML attribute set to the
3000
- * maxWidth value. Outlook Classic reads `width` as a hard pixel
3001
- * cap; it has no concept of max-width so this is the only lever
3002
- * available. Modern clients receive the CSS max-width on the
3003
- * same table and behave correctly.
3004
- *
3005
- * The outer TD remains at width: 100% so it always fills its
3006
- * parent cell in every client — only the inner content is capped.
3007
- */
3008
- jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: innerContent }) }) }) }) })) : (innerContent) }) }) }) }));
3212
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank" ? { rel: "noopener noreferrer" } : {}), style: {
3213
+ display: "block",
3214
+ textDecoration: "none",
3215
+ color: "inherit",
3216
+ }, children: wrappedContent })) : (wrappedContent) }) }) }) }));
3009
3217
  }
3010
3218
  var Text_default = React.memo(Text, arePropsEqual);
3011
3219
 
@@ -3131,7 +3339,7 @@ function buildLinkHref(innerLink) {
3131
3339
  return null;
3132
3340
  }
3133
3341
  }
3134
- function Icon({ config, devNode, devMode, children }) {
3342
+ function Icon({ config, devNode, devMode, children, bindings }) {
3135
3343
  const {
3136
3344
  // base64Source,
3137
3345
  width, height, backgroundColor, padding = "0", borderRadius = "0", border, innerLink, justifyContent = "center", } = config;
@@ -3266,7 +3474,7 @@ function Icon({ config, devNode, devMode, children }) {
3266
3474
  <!--<![endif]-->
3267
3475
  `
3268
3476
  : null;
3269
- return (jsxRuntime.jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, style: {
3477
+ return (jsxRuntime.jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, ...rootBindingProps(bindings), style: {
3270
3478
  // --- Start dev
3271
3479
  position: "relative",
3272
3480
  // --- End dev