@pagenflow/email 1.4.6 → 1.4.8

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",
@@ -1789,7 +1840,7 @@ function getBorderStyle$5(border) {
1789
1840
  }
1790
1841
  return style;
1791
1842
  }
1792
- function Column({ children, config, devNode }) {
1843
+ function Column({ children, config, devNode, bindings }) {
1793
1844
  var _a, _b, _c;
1794
1845
  // Process children array for gap support
1795
1846
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1803,6 +1854,9 @@ function Column({ children, config, devNode }) {
1803
1854
  };
1804
1855
  // 2. Outer TD style: Background and Border Radius (no border here).
1805
1856
  // height is set so the TD occupies the full declared height.
1857
+ // When maxWidth is set, the outer TD stays at its normal width so it
1858
+ // always fills its parent — the inner maxWidth table (see below) does
1859
+ // the actual capping.
1806
1860
  const outerTdStyle = {
1807
1861
  width: config.width,
1808
1862
  height: config.height,
@@ -1838,6 +1892,7 @@ function Column({ children, config, devNode }) {
1838
1892
  // treat it as content-box height and add padding on top, causing the
1839
1893
  // total to exceed the declared height in preview mode.
1840
1894
  verticalAlign: config.alignItems ? alignMap$2[config.alignItems] : "top",
1895
+ background: "transparent",
1841
1896
  };
1842
1897
  // 4. Gap spacer style (used between children)
1843
1898
  const gapSpacerStyle = {
@@ -1845,12 +1900,25 @@ function Column({ children, config, devNode }) {
1845
1900
  lineHeight: "1px",
1846
1901
  fontSize: "1px",
1847
1902
  width: "100%",
1903
+ background: "transparent",
1904
+ };
1905
+ // 5. maxWidth constraining table style (modern clients).
1906
+ // The `width` HTML attribute on this table is what Outlook Classic
1907
+ // (Word engine) reads — it has no concept of max-width, but it does
1908
+ // honour the `width` attribute as a hard column cap.
1909
+ // The CSS max-width here handles modern web/email clients correctly.
1910
+ // <center> around it ensures the constrained block stays horizontally
1911
+ // centred in both the Word engine and standards-based renderers.
1912
+ const maxWidthTableStyle = {
1913
+ width: "100%",
1914
+ maxWidth: config.maxWidth,
1915
+ borderCollapse: "collapse",
1848
1916
  };
1849
1917
  // Main content rendering
1850
- 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: {
1918
+ 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: {
1851
1919
  width: "100%",
1852
1920
  borderCollapse: "collapse",
1853
- }, children: jsxRuntime.jsx("tbody", { children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
1921
+ }, children: jsxRuntime.jsx("tbody", { ...listBindingProps(bindings), children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
1854
1922
  verticalAlign: config.alignItems
1855
1923
  ? alignMap$2[config.alignItems]
1856
1924
  : "top",
@@ -1859,10 +1927,27 @@ function Column({ children, config, devNode }) {
1859
1927
  : "top", align: config.alignItems
1860
1928
  ? alignMap$2[config.alignItems]
1861
1929
  : "left", children: child }) }), index < numChildren - 1 && (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
1862
- return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1930
+ return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1863
1931
  position: "relative",
1864
1932
  ...outerTableStyle,
1865
- }, ...(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: renderContent() }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
1933
+ }, ...(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 ? (
1934
+ /*
1935
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
1936
+ *
1937
+ * <center> instructs the Word rendering engine to horizontally
1938
+ * centre its child block, equivalent to margin: 0 auto in CSS.
1939
+ *
1940
+ * The inner table carries the `width` HTML attribute set to the
1941
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
1942
+ * cap; it has no concept of max-width so this is the only lever
1943
+ * available. Modern clients receive the CSS max-width on the
1944
+ * same table and behave correctly.
1945
+ *
1946
+ * The outer column remains at its normal width so it always
1947
+ * fills its parent cell in every client — only the inner
1948
+ * content is capped.
1949
+ */
1950
+ jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Column Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: config.maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: renderContent() }) }) }) }) })) : (renderContent()) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
1866
1951
  }
1867
1952
  var Column_default = React.memo(Column, arePropsEqual);
1868
1953
 
@@ -1911,7 +1996,7 @@ function getBorderStyle$4(border) {
1911
1996
  }
1912
1997
  return style;
1913
1998
  }
1914
- function Container({ children, config, devMode, devNode }) {
1999
+ function Container({ children, config, bindings, devMode, devNode, }) {
1915
2000
  var _a, _b, _c;
1916
2001
  const { widthType, childrenConstraints } = config;
1917
2002
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1929,7 +2014,7 @@ function Container({ children, config, devMode, devNode }) {
1929
2014
  return 0;
1930
2015
  })();
1931
2016
  const getChildWidths = (() => {
1932
- const { widthDistributionType } = childrenConstraints;
2017
+ const { widthDistributionType } = childrenConstraints !== null && childrenConstraints !== void 0 ? childrenConstraints : {};
1933
2018
  const totalGapSpace = gapWidthPx * (numChildren > 1 ? numChildren - 1 : 0);
1934
2019
  const remainingContentSpace = containerWidthPx - totalGapSpace;
1935
2020
  switch (widthDistributionType) {
@@ -2003,6 +2088,7 @@ function Container({ children, config, devMode, devNode }) {
2003
2088
  width: config.gap || "0",
2004
2089
  lineHeight: "1px",
2005
2090
  fontSize: "1px",
2091
+ background: "transparent",
2006
2092
  };
2007
2093
  const justifyAlign = config.justifyContent
2008
2094
  ? justifyMap$2[config.justifyContent]
@@ -2023,6 +2109,7 @@ function Container({ children, config, devMode, devNode }) {
2023
2109
  fontSize: "0",
2024
2110
  lineHeight: "0",
2025
2111
  height: config.gap,
2112
+ background: "transparent",
2026
2113
  }, 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
2114
  }
2028
2115
  return (jsxRuntime.jsx("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: child }, `child-${index}`));
@@ -2030,7 +2117,7 @@ function Container({ children, config, devMode, devNode }) {
2030
2117
  return (jsxRuntime.jsx("table", { "aria-label": `Container | Table Outer`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: {
2031
2118
  position: "relative",
2032
2119
  ...outerTableStyle,
2033
- }, 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: [
2120
+ }, ...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: [
2034
2121
  widthType === "fixed" ? "container-fixed-width" : undefined,
2035
2122
  devMode ? "main-wrapper relative" : undefined,
2036
2123
  ]
@@ -2039,22 +2126,22 @@ function Container({ children, config, devMode, devNode }) {
2039
2126
  width: "100%",
2040
2127
  maxWidth: widthType === "fixed" ? config.width || "600px" : undefined,
2041
2128
  borderCollapse: "collapse",
2042
- }, 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 } })] }) }) }) }));
2129
+ }, 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 } })] }) }) }) }));
2043
2130
  }
2044
2131
  var Container_default = React.memo(Container, arePropsEqual);
2045
2132
 
2046
- function Divider({ config, devNode }) {
2133
+ function Divider({ config, devNode, bindings }) {
2047
2134
  const { height = "1px", color = "#cccccc", width = "100%", margin = "20px 0", align = "center", hideOnMobile, } = config;
2048
2135
  const heightPx = parseInt(height, 10) || 1;
2049
2136
  // Parse margin into paddingTop / paddingBottom for the outer TD.
2050
2137
  // Outlook ignores shorthand "20px 0" on TDs — must be explicit.
2051
- const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw] = margin.trim().split(/\s+/);
2138
+ const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw,] = margin.trim().split(/\s+/);
2052
2139
  const marginTop = marginTopRaw;
2053
2140
  const marginBottom = marginBottomRaw !== null && marginBottomRaw !== void 0 ? marginBottomRaw : marginTopRaw; // "20px 0" → top=20px, bottom=20px
2054
2141
  // Outlook requires align on the outer TD to correctly position
2055
2142
  // a fixed-width inner table (e.g. width="300px").
2056
2143
  const alignAttr = align === "left" ? "left" : align === "right" ? "right" : "center";
2057
- return (jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2144
+ return (jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2058
2145
  position: "relative", // dev overlay anchor
2059
2146
  width: "100%",
2060
2147
  borderCollapse: "collapse",
@@ -2194,36 +2281,7 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2194
2281
  mso-line-height-rule: exactly;
2195
2282
  }
2196
2283
  }
2197
- @media only screen and (max-width: 768px) {
2198
- .row-content-table[data-mobile-justify="center"] { margin: 0 auto !important; float: none !important; }
2199
- .row-content-table[data-mobile-justify="start"] { margin: 0 !important; float: left !important; }
2200
- .row-content-table[data-mobile-justify="end"] { margin: 0 0 0 auto !important; float: right !important; }
2201
- .row-content-table[data-mobile-align="center"] .child-cell { vertical-align: middle !important; }
2202
- .row-content-table[data-mobile-align="start"] .child-cell { vertical-align: top !important; }
2203
- .row-content-table[data-mobile-align="end"] .child-cell { vertical-align: bottom !important; }
2204
- .row-content-table[data-mobile-wrap="true"] { width: 100% !important; max-width: 100% !important; }
2205
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr { display: block !important; }
2206
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell {
2207
- display: block !important;
2208
- width: 100% !important;
2209
- box-sizing: border-box !important;
2210
- }
2211
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .row-gap-td {
2212
- display: none !important;
2213
- width: 0 !important;
2214
- height: 0 !important;
2215
- }
2216
- .row-content-table[data-mobile-wrap="true"] > tbody > .content-tr > .child-cell:not(:last-child) {
2217
- margin-bottom: 20px !important;
2218
- }
2219
- ${["10px", "15px", "20px", "24px", "30px", "40px", ...rowGaps]
2220
- .filter((gap, index, self) => self.indexOf(gap) === index)
2221
- .map((gap) => `
2222
- .row-content-table[data-mobile-wrap="true"][data-gap="${gap}"] > tbody > .content-tr > .child-cell:not(:last-child) {
2223
- margin-bottom: ${gap} !important;
2224
- }`)
2225
- .join("\n")}
2226
- }
2284
+
2227
2285
  a { color: inherit; text-decoration: none; }
2228
2286
  ol, ul { margin: 0px; padding: 0px; list-style: none; }
2229
2287
  li {
@@ -2375,7 +2433,9 @@ function resolveAnchorStyles(anchor, fallback) {
2375
2433
  else if (fallback === null || fallback === void 0 ? void 0 : fallback[prop]) {
2376
2434
  resolved[prop] = fallback[prop];
2377
2435
  }
2378
- // if neither leave it out entirely, don't force inherit
2436
+ else if (prop === "text-decoration") {
2437
+ resolved[prop] = "none";
2438
+ }
2379
2439
  }
2380
2440
  return Object.entries(resolved)
2381
2441
  .map(([k, v]) => `${k}:${v}`)
@@ -2400,8 +2460,32 @@ function injectLinkStyles(html, fallback) {
2400
2460
  return result;
2401
2461
  }
2402
2462
 
2403
- function Heading({ config, devMode, children }) {
2404
- const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2463
+ // Helper to build link href based on innerLink type
2464
+ function buildLinkHref$3(innerLink) {
2465
+ if (!innerLink || innerLink.type === "none")
2466
+ return null;
2467
+ switch (innerLink.type) {
2468
+ case "url":
2469
+ return innerLink.url || null;
2470
+ case "email":
2471
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
2472
+ case "phone":
2473
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
2474
+ case "anchor":
2475
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
2476
+ case "page_top":
2477
+ return "#top";
2478
+ case "page_bottom":
2479
+ return "#bottom";
2480
+ default:
2481
+ return null;
2482
+ }
2483
+ }
2484
+ function Heading({ config, devMode, children, bindings }) {
2485
+ const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, innerLink, } = config;
2486
+ // Resolve href and target from innerLink
2487
+ const href = buildLinkHref$3(innerLink);
2488
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2405
2489
  // Determine the content to render
2406
2490
  const content = text !== null && text !== void 0 ? text : children;
2407
2491
  const isString = typeof content === "string";
@@ -2438,12 +2522,17 @@ function Heading({ config, devMode, children }) {
2438
2522
  : "";
2439
2523
  // Dynamically create the Heading element
2440
2524
  const HeadingTag = level;
2525
+ const headingElement = isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content }));
2441
2526
  return (
2442
2527
  // Wrap the heading content in a table for padding/width/background management.
2443
- jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2528
+ jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2444
2529
  width: "100%",
2445
2530
  borderCollapse: "collapse",
2446
- }, 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 })) }) }) }) }));
2531
+ }, 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: {
2532
+ display: "block",
2533
+ textDecoration: "none",
2534
+ color: "inherit",
2535
+ }, children: headingElement })) : (headingElement) }) }) }) }));
2447
2536
  }
2448
2537
  var Heading_default = React.memo(Heading, arePropsEqual);
2449
2538
 
@@ -2468,7 +2557,7 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2468
2557
  }
2469
2558
 
2470
2559
  // Helper to build link href based on innerLink type
2471
- function buildLinkHref$1(innerLink) {
2560
+ function buildLinkHref$2(innerLink) {
2472
2561
  if (!innerLink || innerLink.type === "none")
2473
2562
  return null;
2474
2563
  switch (innerLink.type) {
@@ -2528,11 +2617,15 @@ function getBorderStyleString$1(border) {
2528
2617
  styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color} !important;`);
2529
2618
  return styles.join(" ");
2530
2619
  }
2531
- function Image({ config, devNode, devMode }) {
2620
+ function Image({ config, devNode, devMode, bindings }) {
2532
2621
  var _a, _b;
2533
- const { src, alt, innerLink, mobile } = config;
2622
+ const { src: originalSrc, alt, innerLink, mobile } = config;
2623
+ // In dev mode, if there's no src, use the placeholder
2624
+ const src = devMode && !originalSrc
2625
+ ? "https://placehold.co/300x200?text=select+an+image&font=poppins"
2626
+ : originalSrc;
2534
2627
  // Resolve href and target from innerLink
2535
- const href = buildLinkHref$1(innerLink);
2628
+ const href = buildLinkHref$2(innerLink);
2536
2629
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2537
2630
  const seed = src + (alt || "");
2538
2631
  const instanceId = seed
@@ -2546,43 +2639,37 @@ function Image({ config, devNode, devMode }) {
2546
2639
  const widthAttr = desktopWidth.replace("px", "");
2547
2640
  const heightAttr = (_a = config.height) === null || _a === void 0 ? void 0 : _a.replace("px", "");
2548
2641
  // Determine the table's "initial" width.
2549
- // If it's 300px, the table should be 300px, not 100%.
2550
2642
  const tableWidth = isPercent ? desktopWidth : `${widthAttr}px`;
2551
- // When width is a percentage, Outlook ignores CSS and renders the image at
2552
- // its intrinsic pixel size. Setting a concrete `width` HTML attribute gives
2553
- // Outlook a value to constrain against while modern clients continue to use
2554
- // the CSS `width: 100%` for fluid rendering.
2555
- //
2556
- // If `maxWidth` is a pixel value (e.g. "600px"), we extract the number and
2557
- // use it as the HTML `width` attribute so Outlook enforces that cap.
2558
- // Other clients ignore the attribute and rely on CSS styles instead.
2559
- // If `maxWidth` is not set or is not a pixel value (e.g. "100%"), we fall
2560
- // back to the original behaviour (numeric string for px widths, undefined
2561
- // for % widths).
2562
- const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2563
- ? parseInt(config.maxWidth, 10)
2564
- : undefined;
2565
- const imgWidthAttr = isPercent ? (maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined) : widthAttr;
2566
- // 2. Mobile Overrides only emit CSS properties that are explicitly set,
2567
- // so unspecified properties are left untouched (no forced defaults).
2643
+ // Calculate width attribute for Outlook
2644
+ let imgWidthAttr;
2645
+ if (config.outlookWidth) {
2646
+ // Use explicit outlookWidth if provided
2647
+ imgWidthAttr = parseInt(config.outlookWidth, 10);
2648
+ }
2649
+ else if (isPercent) {
2650
+ // For percentage widths, use maxWidth as fallback for Outlook
2651
+ const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2652
+ ? parseInt(config.maxWidth, 10)
2653
+ : undefined;
2654
+ imgWidthAttr = maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined;
2655
+ }
2656
+ else {
2657
+ // For fixed pixel widths, use that value
2658
+ imgWidthAttr = widthAttr ? parseInt(widthAttr, 10) : undefined;
2659
+ }
2660
+ // 2. Mobile Overrides
2568
2661
  let mobileCss = "";
2569
2662
  if (mobile) {
2570
- // .wrap-${imgClass} rules
2571
- const wrapRules = [
2572
- // Always reset min-width so the px lock from desktop can be overridden
2573
- "min-width: 0 !important;",
2574
- ];
2663
+ const wrapRules = ["min-width: 0 !important;"];
2575
2664
  if (mobile.width !== undefined)
2576
2665
  wrapRules.push(`width: ${mobile.width} !important;`);
2577
2666
  if (mobile.maxWidth !== undefined)
2578
2667
  wrapRules.push(`max-width: ${mobile.maxWidth} !important;`);
2579
- // .td-${imgClass} rules
2580
2668
  const tdRules = [];
2581
2669
  if (mobile.padding !== undefined)
2582
2670
  tdRules.push(`padding: ${mobile.padding} !important;`);
2583
2671
  if (mobile.backgroundColor !== undefined)
2584
2672
  tdRules.push(`background-color: ${mobile.backgroundColor} !important;`);
2585
- // .${imgClass} rules
2586
2673
  const imgRules = [];
2587
2674
  if (mobile.width !== undefined)
2588
2675
  imgRules.push(`width: ${mobile.width} !important;`);
@@ -2605,7 +2692,6 @@ function Image({ config, devNode, devMode }) {
2605
2692
  mobileCss = `
2606
2693
  @media screen and (max-width: 768px) {
2607
2694
  .wrap-${imgClass} {
2608
- /* This breaks the px lock from desktop and makes it fluid */
2609
2695
  ${wrapRules.join("\n ")}
2610
2696
  }
2611
2697
  .td-${imgClass} {
@@ -2631,9 +2717,27 @@ function Image({ config, devNode, devMode }) {
2631
2717
  objectPosition: config.objectPosition,
2632
2718
  };
2633
2719
  const imageElement = (jsxRuntime.jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle, draggable: !devMode }));
2634
- 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
2635
- , style: {
2636
- width: tableWidth, // Fixed px here prevents the 100% "ghost space"
2720
+ // Outlook Classic wrapper - only applied when outlookWidth is explicitly set
2721
+ // OR when we need to constrain a percentage-width image in Outlook
2722
+ const needsOutlookWrapper = config.outlookWidth || (isPercent && imgWidthAttr);
2723
+ const finalImageElement = needsOutlookWrapper ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { dangerouslySetInnerHTML: {
2724
+ __html: `</style>` +
2725
+ `<!--[if mso]>` +
2726
+ `<table cellpadding="0" cellspacing="0" border="0" style="width: ${imgWidthAttr}px;">` +
2727
+ `<tr>` +
2728
+ `<td style="padding: 0; margin: 0;" width="${imgWidthAttr}">` +
2729
+ `<img src="${src}" alt="${alt || ""}" width="${imgWidthAttr}" height="${heightAttr !== "auto" ? heightAttr : ""}" style="display: block; width: 100%; height: auto;" />` +
2730
+ `</td>` +
2731
+ `</tr>` +
2732
+ `</table>` +
2733
+ `<![endif]-->` +
2734
+ `<!--[if !mso]><!-->` +
2735
+ `<style>`,
2736
+ } }), imageElement, jsxRuntime.jsx("style", { dangerouslySetInnerHTML: {
2737
+ __html: `</style>` + `<!--<![endif]-->` + `<style>`,
2738
+ } })] })) : (imageElement);
2739
+ 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: {
2740
+ width: tableWidth,
2637
2741
  maxWidth: "100%",
2638
2742
  borderCollapse: "collapse",
2639
2743
  margin: "0 auto",
@@ -2642,10 +2746,10 @@ function Image({ config, devNode, devMode }) {
2642
2746
  backgroundColor: config.backgroundColor,
2643
2747
  fontSize: "0",
2644
2748
  lineHeight: "0",
2645
- width: tableWidth, // Lock the cell as well
2749
+ width: tableWidth,
2646
2750
  }, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank"
2647
2751
  ? { rel: "noopener noreferrer" }
2648
- : {}), style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2752
+ : {}), style: { display: "block", width: "100%" }, children: finalImageElement })) : (finalImageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2649
2753
  }
2650
2754
  var Image_default = React.memo(Image, arePropsEqual);
2651
2755
 
@@ -2714,12 +2818,34 @@ function getHrefFromInnerLink(innerLink) {
2714
2818
  return undefined;
2715
2819
  }
2716
2820
  }
2717
- function Row({ children, config, devNode, devMode }) {
2718
- var _a, _b, _c, _d, _e, _f, _g;
2821
+ /**
2822
+ * Resolves the width for a child <td> at the given index based on layoutColumns.
2823
+ *
2824
+ * - "equal" → equal percentage share across all children
2825
+ * - string[] → explicit value at the matching index (px, %, or mixed)
2826
+ * - undefined → undefined, so no width attribute is set (retrocompat)
2827
+ */
2828
+ function resolveChildColumnWidth(layoutColumns, index, numChildren) {
2829
+ if (!layoutColumns)
2830
+ return undefined;
2831
+ if (layoutColumns === "equal")
2832
+ return `${100 / numChildren}%`;
2833
+ return layoutColumns[index];
2834
+ }
2835
+ function Row({ children, config, devNode, devMode, bindings }) {
2836
+ var _a, _b, _c, _d, _e, _f;
2719
2837
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2720
2838
  const numChildren = childrenArray.length;
2721
2839
  const href = getHrefFromInnerLink(config.innerLink);
2722
2840
  const target = ((_a = config.innerLink) === null || _a === void 0 ? void 0 : _a.target) || "_blank";
2841
+ // Whether children should stack on mobile.
2842
+ // Mirrors Container's isStacking pattern: drives stack-td / desktop-gap-column
2843
+ // / mobile-gap-spacer class names so that stacking works via non-@media CSS
2844
+ // rules that survive Gmail's stylesheet stripping.
2845
+ const isStacking = ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.wrap) === true && numChildren > 1;
2846
+ // Whether layoutColumns is active. When true, table-layout:fixed is applied
2847
+ // to the content table so Outlook Classic honours the declared column widths.
2848
+ const hasLayoutColumns = config.layoutColumns !== undefined;
2723
2849
  // 1. Outer TD: Background, Border Radius, Width, Height.
2724
2850
  const backgroundTdStyle = {
2725
2851
  backgroundColor: config.backgroundColor,
@@ -2729,9 +2855,9 @@ function Row({ children, config, devNode, devMode }) {
2729
2855
  backgroundImage: config.backgroundImage
2730
2856
  ? `url(${config.backgroundImage.src})`
2731
2857
  : undefined,
2732
- backgroundRepeat: (_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat,
2733
- backgroundSize: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size,
2734
- backgroundPosition: (_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position,
2858
+ backgroundRepeat: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.repeat,
2859
+ backgroundSize: (_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.size,
2860
+ backgroundPosition: (_e = config.backgroundImage) === null || _e === void 0 ? void 0 : _e.position,
2735
2861
  ...(config.borderRadius && { overflow: "hidden" }),
2736
2862
  };
2737
2863
  // 2. Inner Table: Border and Border Radius.
@@ -2760,24 +2886,34 @@ function Row({ children, config, devNode, devMode }) {
2760
2886
  // Content table fills available space, giving Outlook Classic a hard
2761
2887
  // boundary so text children get a constrained box and line wrapping
2762
2888
  // triggers correctly. Use for rows containing text + image layouts.
2889
+ //
2890
+ // layoutColumns (any value) → additionally applies table-layout: fixed
2891
+ // so Outlook Classic honours the per-child width declarations.
2892
+ // Compatible with both fillWidth modes.
2763
2893
  const contentTableStyle = {
2764
- width: config.fillWidth ? "100%" : "auto",
2894
+ // When layoutColumns is active, force 100% so Outlook Classic has a
2895
+ // concrete boundary to resolve percentage column widths against.
2896
+ // table-layout:fixed is meaningless without a fixed reference width.
2897
+ width: hasLayoutColumns || config.fillWidth ? "100%" : "auto",
2765
2898
  height: "100%",
2766
2899
  borderCollapse: "collapse",
2767
2900
  minWidth: "1px",
2768
- ...(!config.fillWidth && { maxWidth: config.width || "100%" }),
2901
+ ...(!config.fillWidth &&
2902
+ !hasLayoutColumns && { maxWidth: config.width || "100%" }),
2903
+ ...(hasLayoutColumns && { tableLayout: "fixed" }),
2769
2904
  };
2770
2905
  // 5. Gap TD.
2771
2906
  const gapTdStyle = {
2772
2907
  width: config.gap || "0",
2773
2908
  lineHeight: "1px",
2774
2909
  fontSize: "1px",
2910
+ background: "transparent",
2775
2911
  };
2776
2912
  const tdAlign = config.justifyContent
2777
2913
  ? justifyMap$1[config.justifyContent]
2778
2914
  : "left";
2779
2915
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2780
- const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2916
+ const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...(!href ? rootBindingProps(bindings) : {}), style: {
2781
2917
  position: "relative",
2782
2918
  width: config.width || "100%",
2783
2919
  height: config.height,
@@ -2786,15 +2922,44 @@ function Row({ children, config, devNode, devMode }) {
2786
2922
  width: "100%",
2787
2923
  height: "100%",
2788
2924
  borderCollapse: "collapse",
2789
- }, 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-justify": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.justifyContent, "data-mobile-align": (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.alignItems, "data-mobile-wrap": ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("td", { valign: tdValign, style: {
2790
- verticalAlign: tdValign,
2791
- textAlign: "left",
2792
- padding: "0",
2793
- margin: "0",
2794
- }, className: "child-cell", children: child }), index < numChildren - 1 &&
2795
- 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 }) }) }))] }));
2925
+ }, 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) => {
2926
+ // Resolve the column width for this child based on
2927
+ // layoutColumns. undefined when layoutColumns is not set,
2928
+ // preserving the original behaviour (retrocompat).
2929
+ const columnWidth = resolveChildColumnWidth(config.layoutColumns, index, numChildren);
2930
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { align: tdAlign, ...(columnWidth && {
2931
+ width: columnWidth,
2932
+ }), style: {
2933
+ verticalAlign: tdValign,
2934
+ textAlign: tdAlign,
2935
+ padding: "0",
2936
+ margin: "0",
2937
+ ...(columnWidth && {
2938
+ width: columnWidth,
2939
+ }),
2940
+ },
2941
+ // Mirror of Container's stack-td pattern: when isStacking,
2942
+ // the non-@media .stack-td rule forces display:block +
2943
+ // width:100% on each child, which survives Gmail's
2944
+ // @media stripping and achieves true mobile stacking.
2945
+ className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2946
+ index < numChildren - 1 &&
2947
+ config.gap && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2948
+ display: "none",
2949
+ fontSize: "0",
2950
+ lineHeight: "0",
2951
+ height: config.gap,
2952
+ background: "transparent",
2953
+ }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2954
+ config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle,
2955
+ // Mirror of Container's desktop-gap-column pattern:
2956
+ // when isStacking, the non-@media .desktop-gap-column
2957
+ // rule collapses the between-column gap td so it does
2958
+ // not create phantom space while children are stacked.
2959
+ className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`));
2960
+ }) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2796
2961
  if (href && !devMode) {
2797
- return (jsxRuntime.jsx("a", { href: href, ...({ target }), style: {
2962
+ return (jsxRuntime.jsx("a", { href: href, ...({ target }), ...rootBindingProps(bindings), style: {
2798
2963
  textDecoration: "none",
2799
2964
  color: "inherit",
2800
2965
  display: "block",
@@ -2839,10 +3004,10 @@ function getBorderStyle$1(border) {
2839
3004
  }
2840
3005
  return style;
2841
3006
  }
2842
- const Section = ({ config, children, devNode, }) => {
3007
+ const Section = ({ config, children, devNode, bindings, }) => {
2843
3008
  var _a, _b, _c;
2844
3009
  const { sectionType, padding } = config;
2845
- return (jsxRuntime.jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3010
+ return (jsxRuntime.jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2846
3011
  position: "relative",
2847
3012
  width: "100%",
2848
3013
  backgroundColor: config.backgroundColor,
@@ -2855,66 +3020,98 @@ const Section = ({ config, children, devNode, }) => {
2855
3020
  backgroundPosition: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.position,
2856
3021
  }, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
2857
3022
  padding: padding,
2858
- }, children: children }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { children: [jsxRuntime.jsxs("span", { style: {
2859
- backgroundColor: "black",
2860
- color: "white",
2861
- padding: "4px",
2862
- fontSize: "14px",
2863
- position: "absolute",
2864
- left: 0,
2865
- top: 0,
2866
- }, children: ["Section | ", sectionType] }), children] }) }) }))] }));
3023
+ }, ...listBindingProps(bindings), children: children }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: jsxRuntime.jsxs("span", { style: {
3024
+ backgroundColor: "black",
3025
+ color: "white",
3026
+ padding: "4px",
3027
+ fontSize: "14px",
3028
+ position: "absolute",
3029
+ left: 0,
3030
+ top: 0,
3031
+ }, children: ["Section | ", sectionType] }) }) }) }))] }));
2867
3032
  };
2868
3033
  var Section_default = React.memo(Section, arePropsEqual);
2869
3034
 
2870
- function Spacer({ config, devNode }) {
2871
- const { height, hideOnMobile } = config;
3035
+ function Spacer({ config, devNode, bindings }) {
3036
+ var _a, _b, _c;
3037
+ const { height, hideOnMobile, backgroundColor, backgroundImage } = config;
2872
3038
  // 1. Spacer Table Style
2873
3039
  const spacerTableStyle = {
2874
- // Crucial for compatibility: Ensures no background or border interference
2875
- backgroundColor: "transparent",
2876
3040
  borderCollapse: "collapse",
2877
- border: "0",
2878
3041
  width: "100%",
2879
- // Note the CSS standard dash convention: 'mso-table-lspace'
2880
- // ["mso-table-lspace" as string]: "0pt",
2881
- ["msoTableLspace"]: "0pt",
2882
- // ["mso-table-rspace" as string]: "0pt",
2883
- ["msoTableRspace"]: "0pt",
2884
3042
  };
2885
3043
  // 2. Spacer TD Style: The element that creates the actual vertical space
2886
3044
  const spacerTdStyle = {
2887
3045
  height: height,
2888
- // Critical: Suppress any vertical height created by text/font
3046
+ maxHeight: height,
2889
3047
  fontSize: "0",
2890
3048
  lineHeight: "0",
2891
3049
  padding: "0",
3050
+ margin: "0",
3051
+ border: "none",
3052
+ color: "transparent",
3053
+ // Background applied at TD level for Outlook Classic compatibility
3054
+ ...(backgroundColor && { backgroundColor }),
3055
+ ...(backgroundImage && {
3056
+ backgroundImage: `url(${backgroundImage.src})`,
3057
+ backgroundRepeat: (_a = backgroundImage.repeat) !== null && _a !== void 0 ? _a : "no-repeat",
3058
+ backgroundSize: (_b = backgroundImage.size) !== null && _b !== void 0 ? _b : "cover",
3059
+ backgroundPosition: (_c = backgroundImage.position) !== null && _c !== void 0 ? _c : "center",
3060
+ }),
2892
3061
  };
2893
3062
  // Parse height for the HTML attribute
2894
3063
  const spacerHeightAttribute = parseInt(height, 10) || 1;
2895
- return (
2896
- // Outer table ensures the spacer spans the full width of its container
2897
- jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3064
+ return (jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2898
3065
  // --- Start dev
2899
3066
  position: "relative",
2900
3067
  // --- End dev
2901
3068
  ...spacerTableStyle,
2902
- }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: spacerTdStyle,
2903
- // Explicit height attribute
2904
- height: spacerHeightAttribute, children: "\u00A0" }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
3069
+ }, ...{ 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
3070
+ ? { bgcolor: backgroundColor }
3071
+ : {}) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2905
3072
  }
2906
3073
  var Spacer_default = React.memo(Spacer, arePropsEqual);
2907
3074
 
2908
- function Text({ config, devMode, children }) {
2909
- const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, } = config;
3075
+ // Helper to build link href based on innerLink type
3076
+ function buildLinkHref$1(innerLink) {
3077
+ if (!innerLink || innerLink.type === "none")
3078
+ return null;
3079
+ switch (innerLink.type) {
3080
+ case "url":
3081
+ return innerLink.url || null;
3082
+ case "email":
3083
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
3084
+ case "phone":
3085
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
3086
+ case "anchor":
3087
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
3088
+ case "page_top":
3089
+ return "#top";
3090
+ case "page_bottom":
3091
+ return "#bottom";
3092
+ default:
3093
+ return null;
3094
+ }
3095
+ }
3096
+ function Text({ config, devMode, children, bindings }) {
3097
+ const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, innerLink, } = config;
3098
+ // Resolve href and target from innerLink
3099
+ const href = buildLinkHref$1(innerLink);
3100
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2910
3101
  // 1. TD Style: Where padding and background are reliably applied.
3102
+ // When maxWidth is set, this TD stays at width: 100% so it always fills
3103
+ // its parent — the inner maxWidth table (see below) does the actual
3104
+ // capping, keeping the outer layout intact in all clients.
2911
3105
  const tdStyle = {
2912
3106
  padding: padding,
2913
3107
  backgroundColor: backgroundColor,
2914
3108
  width: "100%",
2915
3109
  verticalAlign: "top",
2916
3110
  };
2917
- // 2. Content Style: Applied directly to a wrapper element
3111
+ // 2. Content Style: Applied directly to the inner div wrapper.
3112
+ // maxWidth is intentionally omitted here — putting it on a div has no
3113
+ // effect in Outlook Classic (Word engine ignores CSS on div elements).
3114
+ // The constraint is enforced at the table level instead (see below).
2918
3115
  const contentStyle = {
2919
3116
  color: color,
2920
3117
  textAlign: textAlign,
@@ -2933,7 +3130,18 @@ function Text({ config, devMode, children }) {
2933
3130
  wordBreak: wordBreak,
2934
3131
  margin: "0",
2935
3132
  padding: "0",
2936
- maxWidth,
3133
+ };
3134
+ // 3. maxWidth constraining table style (modern clients).
3135
+ // The `width` HTML attribute on this table is what Outlook Classic
3136
+ // (Word engine) reads — it has no concept of max-width, but it does
3137
+ // honour the `width` attribute as a hard column cap.
3138
+ // The CSS max-width here handles modern web/email clients correctly.
3139
+ // <center> around it ensures the constrained block stays horizontally
3140
+ // centred in both the Word engine and standards-based renderers.
3141
+ const maxWidthTableStyle = {
3142
+ width: "100%",
3143
+ maxWidth: maxWidth,
3144
+ borderCollapse: "collapse",
2937
3145
  };
2938
3146
  // Determine content to render
2939
3147
  const content = text !== null && text !== void 0 ? text : children;
@@ -2941,10 +3149,32 @@ function Text({ config, devMode, children }) {
2941
3149
  const processedHtml = isString
2942
3150
  ? injectLinkStyles(content, contentStyle)
2943
3151
  : "";
2944
- return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3152
+ const innerContent = isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content }));
3153
+ const wrappedContent = maxWidth ? (
3154
+ /*
3155
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
3156
+ *
3157
+ * <center> instructs the Word rendering engine to horizontally
3158
+ * centre its child block, equivalent to margin: 0 auto in CSS.
3159
+ *
3160
+ * The inner table carries the `width` HTML attribute set to the
3161
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
3162
+ * cap; it has no concept of max-width so this is the only lever
3163
+ * available. Modern clients receive the CSS max-width on the
3164
+ * same table and behave correctly.
3165
+ *
3166
+ * The outer TD remains at width: 100% so it always fills its
3167
+ * parent cell in every client — only the inner content is capped.
3168
+ */
3169
+ 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);
3170
+ return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2945
3171
  width: "100%",
2946
3172
  borderCollapse: "collapse",
2947
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content })) }) }) }) }));
3173
+ }, 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: {
3174
+ display: "block",
3175
+ textDecoration: "none",
3176
+ color: "inherit",
3177
+ }, children: wrappedContent })) : (wrappedContent) }) }) }) }));
2948
3178
  }
2949
3179
  var Text_default = React.memo(Text, arePropsEqual);
2950
3180
 
@@ -3070,7 +3300,7 @@ function buildLinkHref(innerLink) {
3070
3300
  return null;
3071
3301
  }
3072
3302
  }
3073
- function Icon({ config, devNode, devMode, children }) {
3303
+ function Icon({ config, devNode, devMode, children, bindings }) {
3074
3304
  const {
3075
3305
  // base64Source,
3076
3306
  width, height, backgroundColor, padding = "0", borderRadius = "0", border, innerLink, justifyContent = "center", } = config;
@@ -3205,7 +3435,7 @@ function Icon({ config, devNode, devMode, children }) {
3205
3435
  <!--<![endif]-->
3206
3436
  `
3207
3437
  : null;
3208
- return (jsxRuntime.jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, style: {
3438
+ return (jsxRuntime.jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, ...rootBindingProps(bindings), style: {
3209
3439
  // --- Start dev
3210
3440
  position: "relative",
3211
3441
  // --- End dev