@pagenflow/email 1.4.7 → 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.esm.js CHANGED
@@ -1531,6 +1531,57 @@ function arePropsEqual(prevProps, nextProps) {
1531
1531
  return isEqual(prevProps, nextProps);
1532
1532
  }
1533
1533
 
1534
+ /**
1535
+ * bindingAttribute.ts
1536
+ * -------------------
1537
+ * Serialises DataBindings into discrete HTML attributes:
1538
+ *
1539
+ * data-bind-if → visible condition → stamped on the root element
1540
+ * data-bind-list → repeater (dataList + itemAlias) → stamped on the
1541
+ * direct parent of the children loop, NOT the root
1542
+ * data-bind → propertyMap → stamped on the root element
1543
+ *
1544
+ * Keeping the three concerns in separate attributes lets the post-processor
1545
+ * handle them independently without ambiguity.
1546
+ */
1547
+ // ─── Root element props (visible + propertyMap) ───────────────────────────────
1548
+ /**
1549
+ * Props to spread onto the component's root element.
1550
+ * Carries `data-bind-if` and/or `data-bind` (propertyMap).
1551
+ */
1552
+ function rootBindingProps(bindings) {
1553
+ if (!bindings)
1554
+ return {};
1555
+ const props = {};
1556
+ if (bindings.visible) {
1557
+ props['data-bind-if'] = bindings.visible;
1558
+ }
1559
+ if (bindings.propertyMap && Object.keys(bindings.propertyMap).length) {
1560
+ props['data-bind'] = JSON.stringify({ propertyMap: bindings.propertyMap });
1561
+ }
1562
+ return props;
1563
+ }
1564
+ // ─── List wrapper props (dataList + itemAlias) ────────────────────────────────
1565
+ /**
1566
+ * Props to spread onto the *direct parent* of the children loop.
1567
+ * Carries `data-bind-list` only when a repeater is defined.
1568
+ *
1569
+ * Usage inside a component:
1570
+ * <tbody {...listBindingProps(bindings)}>
1571
+ * {children}
1572
+ * </tbody>
1573
+ */
1574
+ function listBindingProps(bindings) {
1575
+ if (!(bindings === null || bindings === void 0 ? void 0 : bindings.dataList))
1576
+ return {};
1577
+ return {
1578
+ 'data-bind-list': JSON.stringify({
1579
+ dataList: bindings.dataList,
1580
+ ...(bindings.itemAlias && { itemAlias: bindings.itemAlias }),
1581
+ }),
1582
+ };
1583
+ }
1584
+
1534
1585
  // Map alignment to HTML 'align' attribute
1535
1586
  const justifyMap$3 = {
1536
1587
  start: "left",
@@ -1538,7 +1589,7 @@ const justifyMap$3 = {
1538
1589
  end: "right",
1539
1590
  };
1540
1591
  // Helper to build link href based on innerLink type (mirrors Icon component)
1541
- function buildLinkHref$2(innerLink) {
1592
+ function buildLinkHref$4(innerLink) {
1542
1593
  if (!innerLink || innerLink.type === "none")
1543
1594
  return null;
1544
1595
  switch (innerLink.type) {
@@ -1592,10 +1643,10 @@ function getBorderStyleString$2(border) {
1592
1643
  }
1593
1644
  return styles.join(" ");
1594
1645
  }
1595
- function Button({ config, devMode }) {
1646
+ function Button({ config, devMode, bindings }) {
1596
1647
  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;
1597
1648
  // Resolve href from innerLink
1598
- const href = buildLinkHref$2(innerLink);
1649
+ const href = buildLinkHref$4(innerLink);
1599
1650
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
1600
1651
  // Sanitize fontFamily early so safeFontFamily is available for all paths below.
1601
1652
  const safeFontFamily = fontFamily
@@ -1679,7 +1730,7 @@ function Button({ config, devMode }) {
1679
1730
  .join(" ");
1680
1731
  return (
1681
1732
  // Wrapper table for alignment - maintains proper positioning for hover indicators
1682
- jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1733
+ jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1683
1734
  width: "100%",
1684
1735
  borderCollapse: "collapse",
1685
1736
  boxSizing: "border-box",
@@ -1787,7 +1838,7 @@ function getBorderStyle$5(border) {
1787
1838
  }
1788
1839
  return style;
1789
1840
  }
1790
- function Column({ children, config, devNode }) {
1841
+ function Column({ children, config, devNode, bindings }) {
1791
1842
  var _a, _b, _c;
1792
1843
  // Process children array for gap support
1793
1844
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1839,6 +1890,7 @@ function Column({ children, config, devNode }) {
1839
1890
  // treat it as content-box height and add padding on top, causing the
1840
1891
  // total to exceed the declared height in preview mode.
1841
1892
  verticalAlign: config.alignItems ? alignMap$2[config.alignItems] : "top",
1893
+ background: "transparent",
1842
1894
  };
1843
1895
  // 4. Gap spacer style (used between children)
1844
1896
  const gapSpacerStyle = {
@@ -1846,6 +1898,7 @@ function Column({ children, config, devNode }) {
1846
1898
  lineHeight: "1px",
1847
1899
  fontSize: "1px",
1848
1900
  width: "100%",
1901
+ background: "transparent",
1849
1902
  };
1850
1903
  // 5. maxWidth constraining table style (modern clients).
1851
1904
  // The `width` HTML attribute on this table is what Outlook Classic
@@ -1860,10 +1913,10 @@ function Column({ children, config, devNode }) {
1860
1913
  borderCollapse: "collapse",
1861
1914
  };
1862
1915
  // Main content rendering
1863
- const renderContent = () => (jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap$2[config.alignItems] : "left", children: config.gap && numChildren > 1 ? (jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1916
+ const renderContent = () => (jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsx("tbody", { children: jsx("tr", { children: 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 ? (jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1864
1917
  width: "100%",
1865
1918
  borderCollapse: "collapse",
1866
- }, children: jsx("tbody", { children: childrenArray.map((child, index) => (jsxs(Fragment, { children: [jsx("tr", { children: jsx("td", { style: {
1919
+ }, children: jsx("tbody", { ...listBindingProps(bindings), children: childrenArray.map((child, index) => (jsxs(Fragment, { children: [jsx("tr", { children: jsx("td", { style: {
1867
1920
  verticalAlign: config.alignItems
1868
1921
  ? alignMap$2[config.alignItems]
1869
1922
  : "top",
@@ -1872,7 +1925,7 @@ function Column({ children, config, devNode }) {
1872
1925
  : "top", align: config.alignItems
1873
1926
  ? alignMap$2[config.alignItems]
1874
1927
  : "left", children: child }) }), index < numChildren - 1 && (jsx("tr", { children: jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
1875
- return (jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1928
+ return (jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1876
1929
  position: "relative",
1877
1930
  ...outerTableStyle,
1878
1931
  }, ...(config.height && { height: config.height }), children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: outerTdStyle, ...(config.width && { width: config.width }), ...(config.height && { height: config.height }), children: config.maxWidth ? (
@@ -1941,7 +1994,7 @@ function getBorderStyle$4(border) {
1941
1994
  }
1942
1995
  return style;
1943
1996
  }
1944
- function Container({ children, config, devMode, devNode }) {
1997
+ function Container({ children, config, bindings, devMode, devNode, }) {
1945
1998
  var _a, _b, _c;
1946
1999
  const { widthType, childrenConstraints } = config;
1947
2000
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1959,7 +2012,7 @@ function Container({ children, config, devMode, devNode }) {
1959
2012
  return 0;
1960
2013
  })();
1961
2014
  const getChildWidths = (() => {
1962
- const { widthDistributionType } = childrenConstraints;
2015
+ const { widthDistributionType } = childrenConstraints !== null && childrenConstraints !== void 0 ? childrenConstraints : {};
1963
2016
  const totalGapSpace = gapWidthPx * (numChildren > 1 ? numChildren - 1 : 0);
1964
2017
  const remainingContentSpace = containerWidthPx - totalGapSpace;
1965
2018
  switch (widthDistributionType) {
@@ -2033,6 +2086,7 @@ function Container({ children, config, devMode, devNode }) {
2033
2086
  width: config.gap || "0",
2034
2087
  lineHeight: "1px",
2035
2088
  fontSize: "1px",
2089
+ background: "transparent",
2036
2090
  };
2037
2091
  const justifyAlign = config.justifyContent
2038
2092
  ? justifyMap$2[config.justifyContent]
@@ -2053,6 +2107,7 @@ function Container({ children, config, devMode, devNode }) {
2053
2107
  fontSize: "0",
2054
2108
  lineHeight: "0",
2055
2109
  height: config.gap,
2110
+ background: "transparent",
2056
2111
  }, children: "\u00A0" }))] }, `child-${index}`), jsx("td", { className: isStacking ? "desktop-gap-column" : undefined, width: config.gap, style: gapTdStyle, children: "\u00A0" }, `gap-${index}`)] }, `ctn:${index}`));
2057
2112
  }
2058
2113
  return (jsx("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: child }, `child-${index}`));
@@ -2060,7 +2115,7 @@ function Container({ children, config, devMode, devNode }) {
2060
2115
  return (jsx("table", { "aria-label": `Container | Table Outer`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: {
2061
2116
  position: "relative",
2062
2117
  ...outerTableStyle,
2063
- }, children: jsx("tbody", { children: jsx("tr", { children: jsxs("td", { align: justifyAlign, children: [jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedWrapper } }), jsxs("table", { className: [
2118
+ }, ...rootBindingProps(bindings), children: jsx("tbody", { children: jsx("tr", { children: jsxs("td", { align: justifyAlign, children: [jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedWrapper } }), jsxs("table", { className: [
2064
2119
  widthType === "fixed" ? "container-fixed-width" : undefined,
2065
2120
  devMode ? "main-wrapper relative" : undefined,
2066
2121
  ]
@@ -2069,22 +2124,22 @@ function Container({ children, config, devMode, devNode }) {
2069
2124
  width: "100%",
2070
2125
  maxWidth: widthType === "fixed" ? config.width || "600px" : undefined,
2071
2126
  borderCollapse: "collapse",
2072
- }, width: containerWidthAttr, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: backgroundTdStyle, children: jsx("table", { "aria-label": `Container | Border Wrapper`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: borderTableStyle, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: innerTdStyle, children: jsx("table", { "aria-label": `Container | Content Table`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: contentTableStyle, children: jsx("tbody", { children: jsx("tr", { children: rowElements }) }) }) }) }) }) }) }) }) }), !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }), jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedFooter } })] }) }) }) }));
2127
+ }, width: containerWidthAttr, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: backgroundTdStyle, children: jsx("table", { "aria-label": `Container | Border Wrapper`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: borderTableStyle, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: innerTdStyle, children: jsx("table", { "aria-label": `Container | Content Table`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: contentTableStyle, children: jsx("tbody", { children: jsx("tr", { ...listBindingProps(bindings), children: rowElements }) }) }) }) }) }) }) }) }) }), !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }), jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedFooter } })] }) }) }) }));
2073
2128
  }
2074
2129
  var Container_default = memo(Container, arePropsEqual);
2075
2130
 
2076
- function Divider({ config, devNode }) {
2131
+ function Divider({ config, devNode, bindings }) {
2077
2132
  const { height = "1px", color = "#cccccc", width = "100%", margin = "20px 0", align = "center", hideOnMobile, } = config;
2078
2133
  const heightPx = parseInt(height, 10) || 1;
2079
2134
  // Parse margin into paddingTop / paddingBottom for the outer TD.
2080
2135
  // Outlook ignores shorthand "20px 0" on TDs — must be explicit.
2081
- const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw] = margin.trim().split(/\s+/);
2136
+ const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw,] = margin.trim().split(/\s+/);
2082
2137
  const marginTop = marginTopRaw;
2083
2138
  const marginBottom = marginBottomRaw !== null && marginBottomRaw !== void 0 ? marginBottomRaw : marginTopRaw; // "20px 0" → top=20px, bottom=20px
2084
2139
  // Outlook requires align on the outer TD to correctly position
2085
2140
  // a fixed-width inner table (e.g. width="300px").
2086
2141
  const alignAttr = align === "left" ? "left" : align === "right" ? "right" : "center";
2087
- return (jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2142
+ return (jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2088
2143
  position: "relative", // dev overlay anchor
2089
2144
  width: "100%",
2090
2145
  borderCollapse: "collapse",
@@ -2403,8 +2458,32 @@ function injectLinkStyles(html, fallback) {
2403
2458
  return result;
2404
2459
  }
2405
2460
 
2406
- function Heading({ config, devMode, children }) {
2407
- const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
2461
+ // Helper to build link href based on innerLink type
2462
+ function buildLinkHref$3(innerLink) {
2463
+ if (!innerLink || innerLink.type === "none")
2464
+ return null;
2465
+ switch (innerLink.type) {
2466
+ case "url":
2467
+ return innerLink.url || null;
2468
+ case "email":
2469
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
2470
+ case "phone":
2471
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
2472
+ case "anchor":
2473
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
2474
+ case "page_top":
2475
+ return "#top";
2476
+ case "page_bottom":
2477
+ return "#bottom";
2478
+ default:
2479
+ return null;
2480
+ }
2481
+ }
2482
+ function Heading({ config, devMode, children, bindings }) {
2483
+ const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, innerLink, } = config;
2484
+ // Resolve href and target from innerLink
2485
+ const href = buildLinkHref$3(innerLink);
2486
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2408
2487
  // Determine the content to render
2409
2488
  const content = text !== null && text !== void 0 ? text : children;
2410
2489
  const isString = typeof content === "string";
@@ -2441,12 +2520,17 @@ function Heading({ config, devMode, children }) {
2441
2520
  : "";
2442
2521
  // Dynamically create the Heading element
2443
2522
  const HeadingTag = level;
2523
+ const headingElement = isString ? (jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsx(HeadingTag, { style: headingStyle, children: content }));
2444
2524
  return (
2445
2525
  // Wrap the heading content in a table for padding/width/background management.
2446
- jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2526
+ jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2447
2527
  width: "100%",
2448
2528
  borderCollapse: "collapse",
2449
- }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
2529
+ }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: textAlign, children: href && !devMode ? (jsx("a", { href: href, target: target, ...(target === "_blank" ? { rel: "noopener noreferrer" } : {}), style: {
2530
+ display: "block",
2531
+ textDecoration: "none",
2532
+ color: "inherit",
2533
+ }, children: headingElement })) : (headingElement) }) }) }) }));
2450
2534
  }
2451
2535
  var Heading_default = memo(Heading, arePropsEqual);
2452
2536
 
@@ -2471,7 +2555,7 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2471
2555
  }
2472
2556
 
2473
2557
  // Helper to build link href based on innerLink type
2474
- function buildLinkHref$1(innerLink) {
2558
+ function buildLinkHref$2(innerLink) {
2475
2559
  if (!innerLink || innerLink.type === "none")
2476
2560
  return null;
2477
2561
  switch (innerLink.type) {
@@ -2531,11 +2615,15 @@ function getBorderStyleString$1(border) {
2531
2615
  styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color} !important;`);
2532
2616
  return styles.join(" ");
2533
2617
  }
2534
- function Image({ config, devNode, devMode }) {
2618
+ function Image({ config, devNode, devMode, bindings }) {
2535
2619
  var _a, _b;
2536
- const { src, alt, innerLink, mobile } = config;
2620
+ const { src: originalSrc, alt, innerLink, mobile } = config;
2621
+ // In dev mode, if there's no src, use the placeholder
2622
+ const src = devMode && !originalSrc
2623
+ ? "https://placehold.co/300x200?text=select+an+image&font=poppins"
2624
+ : originalSrc;
2537
2625
  // Resolve href and target from innerLink
2538
- const href = buildLinkHref$1(innerLink);
2626
+ const href = buildLinkHref$2(innerLink);
2539
2627
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2540
2628
  const seed = src + (alt || "");
2541
2629
  const instanceId = seed
@@ -2549,43 +2637,37 @@ function Image({ config, devNode, devMode }) {
2549
2637
  const widthAttr = desktopWidth.replace("px", "");
2550
2638
  const heightAttr = (_a = config.height) === null || _a === void 0 ? void 0 : _a.replace("px", "");
2551
2639
  // Determine the table's "initial" width.
2552
- // If it's 300px, the table should be 300px, not 100%.
2553
2640
  const tableWidth = isPercent ? desktopWidth : `${widthAttr}px`;
2554
- // When width is a percentage, Outlook ignores CSS and renders the image at
2555
- // its intrinsic pixel size. Setting a concrete `width` HTML attribute gives
2556
- // Outlook a value to constrain against while modern clients continue to use
2557
- // the CSS `width: 100%` for fluid rendering.
2558
- //
2559
- // If `maxWidth` is a pixel value (e.g. "600px"), we extract the number and
2560
- // use it as the HTML `width` attribute so Outlook enforces that cap.
2561
- // Other clients ignore the attribute and rely on CSS styles instead.
2562
- // If `maxWidth` is not set or is not a pixel value (e.g. "100%"), we fall
2563
- // back to the original behaviour (numeric string for px widths, undefined
2564
- // for % widths).
2565
- const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2566
- ? parseInt(config.maxWidth, 10)
2567
- : undefined;
2568
- const imgWidthAttr = isPercent ? (maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined) : widthAttr;
2569
- // 2. Mobile Overrides only emit CSS properties that are explicitly set,
2570
- // so unspecified properties are left untouched (no forced defaults).
2641
+ // Calculate width attribute for Outlook
2642
+ let imgWidthAttr;
2643
+ if (config.outlookWidth) {
2644
+ // Use explicit outlookWidth if provided
2645
+ imgWidthAttr = parseInt(config.outlookWidth, 10);
2646
+ }
2647
+ else if (isPercent) {
2648
+ // For percentage widths, use maxWidth as fallback for Outlook
2649
+ const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2650
+ ? parseInt(config.maxWidth, 10)
2651
+ : undefined;
2652
+ imgWidthAttr = maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined;
2653
+ }
2654
+ else {
2655
+ // For fixed pixel widths, use that value
2656
+ imgWidthAttr = widthAttr ? parseInt(widthAttr, 10) : undefined;
2657
+ }
2658
+ // 2. Mobile Overrides
2571
2659
  let mobileCss = "";
2572
2660
  if (mobile) {
2573
- // .wrap-${imgClass} rules
2574
- const wrapRules = [
2575
- // Always reset min-width so the px lock from desktop can be overridden
2576
- "min-width: 0 !important;",
2577
- ];
2661
+ const wrapRules = ["min-width: 0 !important;"];
2578
2662
  if (mobile.width !== undefined)
2579
2663
  wrapRules.push(`width: ${mobile.width} !important;`);
2580
2664
  if (mobile.maxWidth !== undefined)
2581
2665
  wrapRules.push(`max-width: ${mobile.maxWidth} !important;`);
2582
- // .td-${imgClass} rules
2583
2666
  const tdRules = [];
2584
2667
  if (mobile.padding !== undefined)
2585
2668
  tdRules.push(`padding: ${mobile.padding} !important;`);
2586
2669
  if (mobile.backgroundColor !== undefined)
2587
2670
  tdRules.push(`background-color: ${mobile.backgroundColor} !important;`);
2588
- // .${imgClass} rules
2589
2671
  const imgRules = [];
2590
2672
  if (mobile.width !== undefined)
2591
2673
  imgRules.push(`width: ${mobile.width} !important;`);
@@ -2608,7 +2690,6 @@ function Image({ config, devNode, devMode }) {
2608
2690
  mobileCss = `
2609
2691
  @media screen and (max-width: 768px) {
2610
2692
  .wrap-${imgClass} {
2611
- /* This breaks the px lock from desktop and makes it fluid */
2612
2693
  ${wrapRules.join("\n ")}
2613
2694
  }
2614
2695
  .td-${imgClass} {
@@ -2634,9 +2715,27 @@ function Image({ config, devNode, devMode }) {
2634
2715
  objectPosition: config.objectPosition,
2635
2716
  };
2636
2717
  const imageElement = (jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle, draggable: !devMode }));
2637
- return (jsxs(Fragment$1, { children: [mobile && jsx("style", { dangerouslySetInnerHTML: { __html: mobileCss } }), jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: `wrap-${imgClass}`, align: "center" // Ensures a 300px image stays centered in its parent
2638
- , style: {
2639
- width: tableWidth, // Fixed px here prevents the 100% "ghost space"
2718
+ // Outlook Classic wrapper - only applied when outlookWidth is explicitly set
2719
+ // OR when we need to constrain a percentage-width image in Outlook
2720
+ const needsOutlookWrapper = config.outlookWidth || (isPercent && imgWidthAttr);
2721
+ const finalImageElement = needsOutlookWrapper ? (jsxs(Fragment$1, { children: [jsx("style", { dangerouslySetInnerHTML: {
2722
+ __html: `</style>` +
2723
+ `<!--[if mso]>` +
2724
+ `<table cellpadding="0" cellspacing="0" border="0" style="width: ${imgWidthAttr}px;">` +
2725
+ `<tr>` +
2726
+ `<td style="padding: 0; margin: 0;" width="${imgWidthAttr}">` +
2727
+ `<img src="${src}" alt="${alt || ""}" width="${imgWidthAttr}" height="${heightAttr !== "auto" ? heightAttr : ""}" style="display: block; width: 100%; height: auto;" />` +
2728
+ `</td>` +
2729
+ `</tr>` +
2730
+ `</table>` +
2731
+ `<![endif]-->` +
2732
+ `<!--[if !mso]><!-->` +
2733
+ `<style>`,
2734
+ } }), imageElement, jsx("style", { dangerouslySetInnerHTML: {
2735
+ __html: `</style>` + `<!--<![endif]-->` + `<style>`,
2736
+ } })] })) : (imageElement);
2737
+ return (jsxs(Fragment$1, { children: [mobile && jsx("style", { dangerouslySetInnerHTML: { __html: mobileCss } }), jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: `wrap-${imgClass}`, align: "center", ...rootBindingProps(bindings), style: {
2738
+ width: tableWidth,
2640
2739
  maxWidth: "100%",
2641
2740
  borderCollapse: "collapse",
2642
2741
  margin: "0 auto",
@@ -2645,10 +2744,10 @@ function Image({ config, devNode, devMode }) {
2645
2744
  backgroundColor: config.backgroundColor,
2646
2745
  fontSize: "0",
2647
2746
  lineHeight: "0",
2648
- width: tableWidth, // Lock the cell as well
2747
+ width: tableWidth,
2649
2748
  }, children: href && !devMode ? (jsx("a", { href: href, target: target, ...(target === "_blank"
2650
2749
  ? { rel: "noopener noreferrer" }
2651
- : {}), style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] })] }));
2750
+ : {}), style: { display: "block", width: "100%" }, children: finalImageElement })) : (finalImageElement) }) }) }), devMode && !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] })] }));
2652
2751
  }
2653
2752
  var Image_default = memo(Image, arePropsEqual);
2654
2753
 
@@ -2717,7 +2816,21 @@ function getHrefFromInnerLink(innerLink) {
2717
2816
  return undefined;
2718
2817
  }
2719
2818
  }
2720
- function Row({ children, config, devNode, devMode }) {
2819
+ /**
2820
+ * Resolves the width for a child <td> at the given index based on layoutColumns.
2821
+ *
2822
+ * - "equal" → equal percentage share across all children
2823
+ * - string[] → explicit value at the matching index (px, %, or mixed)
2824
+ * - undefined → undefined, so no width attribute is set (retrocompat)
2825
+ */
2826
+ function resolveChildColumnWidth(layoutColumns, index, numChildren) {
2827
+ if (!layoutColumns)
2828
+ return undefined;
2829
+ if (layoutColumns === "equal")
2830
+ return `${100 / numChildren}%`;
2831
+ return layoutColumns[index];
2832
+ }
2833
+ function Row({ children, config, devNode, devMode, bindings }) {
2721
2834
  var _a, _b, _c, _d, _e, _f;
2722
2835
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2723
2836
  const numChildren = childrenArray.length;
@@ -2728,6 +2841,9 @@ function Row({ children, config, devNode, devMode }) {
2728
2841
  // / mobile-gap-spacer class names so that stacking works via non-@media CSS
2729
2842
  // rules that survive Gmail's stylesheet stripping.
2730
2843
  const isStacking = ((_b = config.mobile) === null || _b === void 0 ? void 0 : _b.wrap) === true && numChildren > 1;
2844
+ // Whether layoutColumns is active. When true, table-layout:fixed is applied
2845
+ // to the content table so Outlook Classic honours the declared column widths.
2846
+ const hasLayoutColumns = config.layoutColumns !== undefined;
2731
2847
  // 1. Outer TD: Background, Border Radius, Width, Height.
2732
2848
  const backgroundTdStyle = {
2733
2849
  backgroundColor: config.backgroundColor,
@@ -2768,24 +2884,34 @@ function Row({ children, config, devNode, devMode }) {
2768
2884
  // Content table fills available space, giving Outlook Classic a hard
2769
2885
  // boundary so text children get a constrained box and line wrapping
2770
2886
  // triggers correctly. Use for rows containing text + image layouts.
2887
+ //
2888
+ // layoutColumns (any value) → additionally applies table-layout: fixed
2889
+ // so Outlook Classic honours the per-child width declarations.
2890
+ // Compatible with both fillWidth modes.
2771
2891
  const contentTableStyle = {
2772
- width: config.fillWidth ? "100%" : "auto",
2892
+ // When layoutColumns is active, force 100% so Outlook Classic has a
2893
+ // concrete boundary to resolve percentage column widths against.
2894
+ // table-layout:fixed is meaningless without a fixed reference width.
2895
+ width: hasLayoutColumns || config.fillWidth ? "100%" : "auto",
2773
2896
  height: "100%",
2774
2897
  borderCollapse: "collapse",
2775
2898
  minWidth: "1px",
2776
- ...(!config.fillWidth && { maxWidth: config.width || "100%" }),
2899
+ ...(!config.fillWidth &&
2900
+ !hasLayoutColumns && { maxWidth: config.width || "100%" }),
2901
+ ...(hasLayoutColumns && { tableLayout: "fixed" }),
2777
2902
  };
2778
2903
  // 5. Gap TD.
2779
2904
  const gapTdStyle = {
2780
2905
  width: config.gap || "0",
2781
2906
  lineHeight: "1px",
2782
2907
  fontSize: "1px",
2908
+ background: "transparent",
2783
2909
  };
2784
2910
  const tdAlign = config.justifyContent
2785
2911
  ? justifyMap$1[config.justifyContent]
2786
2912
  : "left";
2787
2913
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2788
- const content = (jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2914
+ const content = (jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...(!href ? rootBindingProps(bindings) : {}), style: {
2789
2915
  position: "relative",
2790
2916
  width: config.width || "100%",
2791
2917
  height: config.height,
@@ -2794,32 +2920,44 @@ function Row({ children, config, devNode, devMode }) {
2794
2920
  width: "100%",
2795
2921
  height: "100%",
2796
2922
  borderCollapse: "collapse",
2797
- }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: 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: jsx("tbody", { children: jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxs(Fragment, { children: [jsxs("td", { align: tdAlign, style: {
2798
- verticalAlign: tdValign,
2799
- textAlign: tdAlign,
2800
- padding: "0",
2801
- margin: "0",
2802
- },
2803
- // Mirror of Container's stack-td pattern: when isStacking,
2804
- // the non-@media .stack-td rule forces display:block +
2805
- // width:100% on each child, which survives Gmail's
2806
- // @media stripping and achieves true mobile stacking.
2807
- className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2808
- index < numChildren - 1 &&
2809
- config.gap && (jsx("div", { className: "mobile-gap-spacer", style: {
2810
- display: "none",
2811
- fontSize: "0",
2812
- lineHeight: "0",
2813
- height: config.gap,
2814
- }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2815
- config.gap && (jsx("td", { width: config.gap, style: gapTdStyle,
2816
- // Mirror of Container's desktop-gap-column pattern:
2817
- // when isStacking, the non-@media .desktop-gap-column
2818
- // rule collapses the between-column gap td so it does
2819
- // not create phantom space while children are stacked.
2820
- className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
2923
+ }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: 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: jsx("tbody", { children: jsx("tr", { className: "content-tr", ...listBindingProps(bindings), children: childrenArray.map((child, index) => {
2924
+ // Resolve the column width for this child based on
2925
+ // layoutColumns. undefined when layoutColumns is not set,
2926
+ // preserving the original behaviour (retrocompat).
2927
+ const columnWidth = resolveChildColumnWidth(config.layoutColumns, index, numChildren);
2928
+ return (jsxs(Fragment, { children: [jsxs("td", { align: tdAlign, ...(columnWidth && {
2929
+ width: columnWidth,
2930
+ }), style: {
2931
+ verticalAlign: tdValign,
2932
+ textAlign: tdAlign,
2933
+ padding: "0",
2934
+ margin: "0",
2935
+ ...(columnWidth && {
2936
+ width: columnWidth,
2937
+ }),
2938
+ },
2939
+ // Mirror of Container's stack-td pattern: when isStacking,
2940
+ // the non-@media .stack-td rule forces display:block +
2941
+ // width:100% on each child, which survives Gmail's
2942
+ // @media stripping and achieves true mobile stacking.
2943
+ className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2944
+ index < numChildren - 1 &&
2945
+ config.gap && (jsx("div", { className: "mobile-gap-spacer", style: {
2946
+ display: "none",
2947
+ fontSize: "0",
2948
+ lineHeight: "0",
2949
+ height: config.gap,
2950
+ background: "transparent",
2951
+ }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2952
+ config.gap && (jsx("td", { width: config.gap, style: gapTdStyle,
2953
+ // Mirror of Container's desktop-gap-column pattern:
2954
+ // when isStacking, the non-@media .desktop-gap-column
2955
+ // rule collapses the between-column gap td so it does
2956
+ // not create phantom space while children are stacked.
2957
+ className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`));
2958
+ }) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
2821
2959
  if (href && !devMode) {
2822
- return (jsx("a", { href: href, ...({ target }), style: {
2960
+ return (jsx("a", { href: href, ...({ target }), ...rootBindingProps(bindings), style: {
2823
2961
  textDecoration: "none",
2824
2962
  color: "inherit",
2825
2963
  display: "block",
@@ -2864,10 +3002,10 @@ function getBorderStyle$1(border) {
2864
3002
  }
2865
3003
  return style;
2866
3004
  }
2867
- const Section = ({ config, children, devNode, }) => {
3005
+ const Section = ({ config, children, devNode, bindings, }) => {
2868
3006
  var _a, _b, _c;
2869
3007
  const { sectionType, padding } = config;
2870
- return (jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3008
+ return (jsxs("table", { "aria-label": `Section |Table | ${sectionType}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2871
3009
  position: "relative",
2872
3010
  width: "100%",
2873
3011
  backgroundColor: config.backgroundColor,
@@ -2880,58 +3018,84 @@ const Section = ({ config, children, devNode, }) => {
2880
3018
  backgroundPosition: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.position,
2881
3019
  }, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: {
2882
3020
  padding: padding,
2883
- }, children: children }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsxs("td", { children: [jsxs("span", { style: {
2884
- backgroundColor: "black",
2885
- color: "white",
2886
- padding: "4px",
2887
- fontSize: "14px",
2888
- position: "absolute",
2889
- left: 0,
2890
- top: 0,
2891
- }, children: ["Section | ", sectionType] }), children] }) }) }))] }));
3021
+ }, ...listBindingProps(bindings), children: children }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: jsxs("span", { style: {
3022
+ backgroundColor: "black",
3023
+ color: "white",
3024
+ padding: "4px",
3025
+ fontSize: "14px",
3026
+ position: "absolute",
3027
+ left: 0,
3028
+ top: 0,
3029
+ }, children: ["Section | ", sectionType] }) }) }) }))] }));
2892
3030
  };
2893
3031
  var Section_default = memo(Section, arePropsEqual);
2894
3032
 
2895
- function Spacer({ config, devNode }) {
2896
- const { height, hideOnMobile } = config;
3033
+ function Spacer({ config, devNode, bindings }) {
3034
+ var _a, _b, _c;
3035
+ const { height, hideOnMobile, backgroundColor, backgroundImage } = config;
2897
3036
  // 1. Spacer Table Style
2898
3037
  const spacerTableStyle = {
2899
- // Crucial for compatibility: Ensures no background or border interference
2900
- backgroundColor: "transparent",
2901
3038
  borderCollapse: "collapse",
2902
- border: "0",
2903
3039
  width: "100%",
2904
- // Note the CSS standard dash convention: 'mso-table-lspace'
2905
- // ["mso-table-lspace" as string]: "0pt",
2906
- ["msoTableLspace"]: "0pt",
2907
- // ["mso-table-rspace" as string]: "0pt",
2908
- ["msoTableRspace"]: "0pt",
2909
3040
  };
2910
3041
  // 2. Spacer TD Style: The element that creates the actual vertical space
2911
3042
  const spacerTdStyle = {
2912
3043
  height: height,
2913
- // Critical: Suppress any vertical height created by text/font
3044
+ maxHeight: height,
2914
3045
  fontSize: "0",
2915
3046
  lineHeight: "0",
2916
3047
  padding: "0",
3048
+ margin: "0",
3049
+ border: "none",
3050
+ color: "transparent",
3051
+ // Background applied at TD level for Outlook Classic compatibility
3052
+ ...(backgroundColor && { backgroundColor }),
3053
+ ...(backgroundImage && {
3054
+ backgroundImage: `url(${backgroundImage.src})`,
3055
+ backgroundRepeat: (_a = backgroundImage.repeat) !== null && _a !== void 0 ? _a : "no-repeat",
3056
+ backgroundSize: (_b = backgroundImage.size) !== null && _b !== void 0 ? _b : "cover",
3057
+ backgroundPosition: (_c = backgroundImage.position) !== null && _c !== void 0 ? _c : "center",
3058
+ }),
2917
3059
  };
2918
3060
  // Parse height for the HTML attribute
2919
3061
  const spacerHeightAttribute = parseInt(height, 10) || 1;
2920
- return (
2921
- // Outer table ensures the spacer spans the full width of its container
2922
- jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3062
+ return (jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2923
3063
  // --- Start dev
2924
3064
  position: "relative",
2925
3065
  // --- End dev
2926
3066
  ...spacerTableStyle,
2927
- }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: spacerTdStyle,
2928
- // Explicit height attribute
2929
- height: spacerHeightAttribute, children: "\u00A0" }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
3067
+ }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: spacerTdStyle, height: spacerHeightAttribute, ...(backgroundColor && !backgroundImage
3068
+ ? { bgcolor: backgroundColor }
3069
+ : {}) }) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
2930
3070
  }
2931
3071
  var Spacer_default = memo(Spacer, arePropsEqual);
2932
3072
 
2933
- function Text({ config, devMode, children }) {
2934
- const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, } = config;
3073
+ // Helper to build link href based on innerLink type
3074
+ function buildLinkHref$1(innerLink) {
3075
+ if (!innerLink || innerLink.type === "none")
3076
+ return null;
3077
+ switch (innerLink.type) {
3078
+ case "url":
3079
+ return innerLink.url || null;
3080
+ case "email":
3081
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
3082
+ case "phone":
3083
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
3084
+ case "anchor":
3085
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
3086
+ case "page_top":
3087
+ return "#top";
3088
+ case "page_bottom":
3089
+ return "#bottom";
3090
+ default:
3091
+ return null;
3092
+ }
3093
+ }
3094
+ function Text({ config, devMode, children, bindings }) {
3095
+ const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, innerLink, } = config;
3096
+ // Resolve href and target from innerLink
3097
+ const href = buildLinkHref$1(innerLink);
3098
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2935
3099
  // 1. TD Style: Where padding and background are reliably applied.
2936
3100
  // When maxWidth is set, this TD stays at width: 100% so it always fills
2937
3101
  // its parent — the inner maxWidth table (see below) does the actual
@@ -2984,26 +3148,31 @@ function Text({ config, devMode, children }) {
2984
3148
  ? injectLinkStyles(content, contentStyle)
2985
3149
  : "";
2986
3150
  const innerContent = isString ? (jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsx("div", { style: contentStyle, children: content }));
2987
- return (jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3151
+ const wrappedContent = maxWidth ? (
3152
+ /*
3153
+ * maxWidth wrapper — Outlook Classic compatibility pattern:
3154
+ *
3155
+ * <center> instructs the Word rendering engine to horizontally
3156
+ * centre its child block, equivalent to margin: 0 auto in CSS.
3157
+ *
3158
+ * The inner table carries the `width` HTML attribute set to the
3159
+ * maxWidth value. Outlook Classic reads `width` as a hard pixel
3160
+ * cap; it has no concept of max-width so this is the only lever
3161
+ * available. Modern clients receive the CSS max-width on the
3162
+ * same table and behave correctly.
3163
+ *
3164
+ * The outer TD remains at width: 100% so it always fills its
3165
+ * parent cell in every client — only the inner content is capped.
3166
+ */
3167
+ jsx("center", { children: jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { children: innerContent }) }) }) }) })) : (innerContent);
3168
+ return (jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2988
3169
  width: "100%",
2989
3170
  borderCollapse: "collapse",
2990
- }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: textAlign, children: maxWidth ? (
2991
- /*
2992
- * maxWidth wrapper — Outlook Classic compatibility pattern:
2993
- *
2994
- * <center> instructs the Word rendering engine to horizontally
2995
- * centre its child block, equivalent to margin: 0 auto in CSS.
2996
- *
2997
- * The inner table carries the `width` HTML attribute set to the
2998
- * maxWidth value. Outlook Classic reads `width` as a hard pixel
2999
- * cap; it has no concept of max-width so this is the only lever
3000
- * available. Modern clients receive the CSS max-width on the
3001
- * same table and behave correctly.
3002
- *
3003
- * The outer TD remains at width: 100% so it always fills its
3004
- * parent cell in every client — only the inner content is capped.
3005
- */
3006
- jsx("center", { children: jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { children: innerContent }) }) }) }) })) : (innerContent) }) }) }) }));
3171
+ }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: textAlign, children: href && !devMode ? (jsx("a", { href: href, target: target, ...(target === "_blank" ? { rel: "noopener noreferrer" } : {}), style: {
3172
+ display: "block",
3173
+ textDecoration: "none",
3174
+ color: "inherit",
3175
+ }, children: wrappedContent })) : (wrappedContent) }) }) }) }));
3007
3176
  }
3008
3177
  var Text_default = memo(Text, arePropsEqual);
3009
3178
 
@@ -3129,7 +3298,7 @@ function buildLinkHref(innerLink) {
3129
3298
  return null;
3130
3299
  }
3131
3300
  }
3132
- function Icon({ config, devNode, devMode, children }) {
3301
+ function Icon({ config, devNode, devMode, children, bindings }) {
3133
3302
  const {
3134
3303
  // base64Source,
3135
3304
  width, height, backgroundColor, padding = "0", borderRadius = "0", border, innerLink, justifyContent = "center", } = config;
@@ -3264,7 +3433,7 @@ function Icon({ config, devNode, devMode, children }) {
3264
3433
  <!--<![endif]-->
3265
3434
  `
3266
3435
  : null;
3267
- return (jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, style: {
3436
+ return (jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, ...rootBindingProps(bindings), style: {
3268
3437
  // --- Start dev
3269
3438
  position: "relative",
3270
3439
  // --- End dev