@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.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);
@@ -1841,6 +1892,7 @@ function Column({ children, config, devNode }) {
1841
1892
  // treat it as content-box height and add padding on top, causing the
1842
1893
  // total to exceed the declared height in preview mode.
1843
1894
  verticalAlign: config.alignItems ? alignMap$2[config.alignItems] : "top",
1895
+ background: "transparent",
1844
1896
  };
1845
1897
  // 4. Gap spacer style (used between children)
1846
1898
  const gapSpacerStyle = {
@@ -1848,6 +1900,7 @@ function Column({ children, config, devNode }) {
1848
1900
  lineHeight: "1px",
1849
1901
  fontSize: "1px",
1850
1902
  width: "100%",
1903
+ background: "transparent",
1851
1904
  };
1852
1905
  // 5. maxWidth constraining table style (modern clients).
1853
1906
  // The `width` HTML attribute on this table is what Outlook Classic
@@ -1862,10 +1915,10 @@ function Column({ children, config, devNode }) {
1862
1915
  borderCollapse: "collapse",
1863
1916
  };
1864
1917
  // Main content rendering
1865
- const renderContent = () => (jsxRuntime.jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap$2[config.alignItems] : "left", children: config.gap && numChildren > 1 ? (jsxRuntime.jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
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: {
1866
1919
  width: "100%",
1867
1920
  borderCollapse: "collapse",
1868
- }, children: jsxRuntime.jsx("tbody", { children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
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: {
1869
1922
  verticalAlign: config.alignItems
1870
1923
  ? alignMap$2[config.alignItems]
1871
1924
  : "top",
@@ -1874,7 +1927,7 @@ function Column({ children, config, devNode }) {
1874
1927
  : "top", align: config.alignItems
1875
1928
  ? alignMap$2[config.alignItems]
1876
1929
  : "left", children: child }) }), index < numChildren - 1 && (jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
1877
- return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
1930
+ return (jsxRuntime.jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
1878
1931
  position: "relative",
1879
1932
  ...outerTableStyle,
1880
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 ? (
@@ -1943,7 +1996,7 @@ function getBorderStyle$4(border) {
1943
1996
  }
1944
1997
  return style;
1945
1998
  }
1946
- function Container({ children, config, devMode, devNode }) {
1999
+ function Container({ children, config, bindings, devMode, devNode, }) {
1947
2000
  var _a, _b, _c;
1948
2001
  const { widthType, childrenConstraints } = config;
1949
2002
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
@@ -1961,7 +2014,7 @@ function Container({ children, config, devMode, devNode }) {
1961
2014
  return 0;
1962
2015
  })();
1963
2016
  const getChildWidths = (() => {
1964
- const { widthDistributionType } = childrenConstraints;
2017
+ const { widthDistributionType } = childrenConstraints !== null && childrenConstraints !== void 0 ? childrenConstraints : {};
1965
2018
  const totalGapSpace = gapWidthPx * (numChildren > 1 ? numChildren - 1 : 0);
1966
2019
  const remainingContentSpace = containerWidthPx - totalGapSpace;
1967
2020
  switch (widthDistributionType) {
@@ -2035,6 +2088,7 @@ function Container({ children, config, devMode, devNode }) {
2035
2088
  width: config.gap || "0",
2036
2089
  lineHeight: "1px",
2037
2090
  fontSize: "1px",
2091
+ background: "transparent",
2038
2092
  };
2039
2093
  const justifyAlign = config.justifyContent
2040
2094
  ? justifyMap$2[config.justifyContent]
@@ -2055,6 +2109,7 @@ function Container({ children, config, devMode, devNode }) {
2055
2109
  fontSize: "0",
2056
2110
  lineHeight: "0",
2057
2111
  height: config.gap,
2112
+ background: "transparent",
2058
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}`));
2059
2114
  }
2060
2115
  return (jsxRuntime.jsx("td", { className: isStacking ? "stack-td" : undefined, width: getChildWidths[index], style: childTdStyle, children: child }, `child-${index}`));
@@ -2062,7 +2117,7 @@ function Container({ children, config, devMode, devNode }) {
2062
2117
  return (jsxRuntime.jsx("table", { "aria-label": `Container | Table Outer`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: {
2063
2118
  position: "relative",
2064
2119
  ...outerTableStyle,
2065
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { align: justifyAlign, children: [jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedWrapper } }), jsxRuntime.jsxs("table", { className: [
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: [
2066
2121
  widthType === "fixed" ? "container-fixed-width" : undefined,
2067
2122
  devMode ? "main-wrapper relative" : undefined,
2068
2123
  ]
@@ -2071,22 +2126,22 @@ function Container({ children, config, devMode, devNode }) {
2071
2126
  width: "100%",
2072
2127
  maxWidth: widthType === "fixed" ? config.width || "600px" : undefined,
2073
2128
  borderCollapse: "collapse",
2074
- }, width: containerWidthAttr, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: backgroundTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Border Wrapper`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: borderTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: innerTdStyle, children: jsxRuntime.jsx("table", { "aria-label": `Container | Content Table`, cellPadding: 0, cellSpacing: 0, role: "presentation", border: 0, style: contentTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: rowElements }) }) }) }) }) }) }) }) }) }), !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }), jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: msoFixedFooter } })] }) }) }) }));
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 } })] }) }) }) }));
2075
2130
  }
2076
2131
  var Container_default = React.memo(Container, arePropsEqual);
2077
2132
 
2078
- function Divider({ config, devNode }) {
2133
+ function Divider({ config, devNode, bindings }) {
2079
2134
  const { height = "1px", color = "#cccccc", width = "100%", margin = "20px 0", align = "center", hideOnMobile, } = config;
2080
2135
  const heightPx = parseInt(height, 10) || 1;
2081
2136
  // Parse margin into paddingTop / paddingBottom for the outer TD.
2082
2137
  // Outlook ignores shorthand "20px 0" on TDs — must be explicit.
2083
- const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw] = margin.trim().split(/\s+/);
2138
+ const [marginTopRaw = "0", marginRightRaw = "0", marginBottomRaw, marginLeftRaw,] = margin.trim().split(/\s+/);
2084
2139
  const marginTop = marginTopRaw;
2085
2140
  const marginBottom = marginBottomRaw !== null && marginBottomRaw !== void 0 ? marginBottomRaw : marginTopRaw; // "20px 0" → top=20px, bottom=20px
2086
2141
  // Outlook requires align on the outer TD to correctly position
2087
2142
  // a fixed-width inner table (e.g. width="300px").
2088
2143
  const alignAttr = align === "left" ? "left" : align === "right" ? "right" : "center";
2089
- 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: {
2090
2145
  position: "relative", // dev overlay anchor
2091
2146
  width: "100%",
2092
2147
  borderCollapse: "collapse",
@@ -2405,8 +2460,32 @@ function injectLinkStyles(html, fallback) {
2405
2460
  return result;
2406
2461
  }
2407
2462
 
2408
- function Heading({ config, devMode, children }) {
2409
- const { text, level = "h1", padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, wordBreak, whiteSpace, } = config;
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";
2410
2489
  // Determine the content to render
2411
2490
  const content = text !== null && text !== void 0 ? text : children;
2412
2491
  const isString = typeof content === "string";
@@ -2443,12 +2522,17 @@ function Heading({ config, devMode, children }) {
2443
2522
  : "";
2444
2523
  // Dynamically create the Heading element
2445
2524
  const HeadingTag = level;
2525
+ const headingElement = isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content }));
2446
2526
  return (
2447
2527
  // Wrap the heading content in a table for padding/width/background management.
2448
- jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2528
+ jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2449
2529
  width: "100%",
2450
2530
  borderCollapse: "collapse",
2451
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
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) }) }) }) }));
2452
2536
  }
2453
2537
  var Heading_default = React.memo(Heading, arePropsEqual);
2454
2538
 
@@ -2473,7 +2557,7 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2473
2557
  }
2474
2558
 
2475
2559
  // Helper to build link href based on innerLink type
2476
- function buildLinkHref$1(innerLink) {
2560
+ function buildLinkHref$2(innerLink) {
2477
2561
  if (!innerLink || innerLink.type === "none")
2478
2562
  return null;
2479
2563
  switch (innerLink.type) {
@@ -2533,11 +2617,15 @@ function getBorderStyleString$1(border) {
2533
2617
  styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color} !important;`);
2534
2618
  return styles.join(" ");
2535
2619
  }
2536
- function Image({ config, devNode, devMode }) {
2620
+ function Image({ config, devNode, devMode, bindings }) {
2537
2621
  var _a, _b;
2538
- 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;
2539
2627
  // Resolve href and target from innerLink
2540
- const href = buildLinkHref$1(innerLink);
2628
+ const href = buildLinkHref$2(innerLink);
2541
2629
  const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_blank";
2542
2630
  const seed = src + (alt || "");
2543
2631
  const instanceId = seed
@@ -2551,43 +2639,37 @@ function Image({ config, devNode, devMode }) {
2551
2639
  const widthAttr = desktopWidth.replace("px", "");
2552
2640
  const heightAttr = (_a = config.height) === null || _a === void 0 ? void 0 : _a.replace("px", "");
2553
2641
  // Determine the table's "initial" width.
2554
- // If it's 300px, the table should be 300px, not 100%.
2555
2642
  const tableWidth = isPercent ? desktopWidth : `${widthAttr}px`;
2556
- // When width is a percentage, Outlook ignores CSS and renders the image at
2557
- // its intrinsic pixel size. Setting a concrete `width` HTML attribute gives
2558
- // Outlook a value to constrain against while modern clients continue to use
2559
- // the CSS `width: 100%` for fluid rendering.
2560
- //
2561
- // If `maxWidth` is a pixel value (e.g. "600px"), we extract the number and
2562
- // use it as the HTML `width` attribute so Outlook enforces that cap.
2563
- // Other clients ignore the attribute and rely on CSS styles instead.
2564
- // If `maxWidth` is not set or is not a pixel value (e.g. "100%"), we fall
2565
- // back to the original behaviour (numeric string for px widths, undefined
2566
- // for % widths).
2567
- const maxWidthPx = ((_b = config.maxWidth) === null || _b === void 0 ? void 0 : _b.endsWith("px"))
2568
- ? parseInt(config.maxWidth, 10)
2569
- : undefined;
2570
- const imgWidthAttr = isPercent ? (maxWidthPx !== null && maxWidthPx !== void 0 ? maxWidthPx : undefined) : widthAttr;
2571
- // 2. Mobile Overrides only emit CSS properties that are explicitly set,
2572
- // so unspecified properties are left untouched (no forced defaults).
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
2573
2661
  let mobileCss = "";
2574
2662
  if (mobile) {
2575
- // .wrap-${imgClass} rules
2576
- const wrapRules = [
2577
- // Always reset min-width so the px lock from desktop can be overridden
2578
- "min-width: 0 !important;",
2579
- ];
2663
+ const wrapRules = ["min-width: 0 !important;"];
2580
2664
  if (mobile.width !== undefined)
2581
2665
  wrapRules.push(`width: ${mobile.width} !important;`);
2582
2666
  if (mobile.maxWidth !== undefined)
2583
2667
  wrapRules.push(`max-width: ${mobile.maxWidth} !important;`);
2584
- // .td-${imgClass} rules
2585
2668
  const tdRules = [];
2586
2669
  if (mobile.padding !== undefined)
2587
2670
  tdRules.push(`padding: ${mobile.padding} !important;`);
2588
2671
  if (mobile.backgroundColor !== undefined)
2589
2672
  tdRules.push(`background-color: ${mobile.backgroundColor} !important;`);
2590
- // .${imgClass} rules
2591
2673
  const imgRules = [];
2592
2674
  if (mobile.width !== undefined)
2593
2675
  imgRules.push(`width: ${mobile.width} !important;`);
@@ -2610,7 +2692,6 @@ function Image({ config, devNode, devMode }) {
2610
2692
  mobileCss = `
2611
2693
  @media screen and (max-width: 768px) {
2612
2694
  .wrap-${imgClass} {
2613
- /* This breaks the px lock from desktop and makes it fluid */
2614
2695
  ${wrapRules.join("\n ")}
2615
2696
  }
2616
2697
  .td-${imgClass} {
@@ -2636,9 +2717,27 @@ function Image({ config, devNode, devMode }) {
2636
2717
  objectPosition: config.objectPosition,
2637
2718
  };
2638
2719
  const imageElement = (jsxRuntime.jsx("img", { src: src, alt: alt, width: imgWidthAttr, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle, draggable: !devMode }));
2639
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [mobile && jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: mobileCss } }), jsxRuntime.jsxs("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: `wrap-${imgClass}`, align: "center" // Ensures a 300px image stays centered in its parent
2640
- , style: {
2641
- width: tableWidth, // Fixed px here prevents the 100% "ghost space"
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,
2642
2741
  maxWidth: "100%",
2643
2742
  borderCollapse: "collapse",
2644
2743
  margin: "0 auto",
@@ -2647,10 +2746,10 @@ function Image({ config, devNode, devMode }) {
2647
2746
  backgroundColor: config.backgroundColor,
2648
2747
  fontSize: "0",
2649
2748
  lineHeight: "0",
2650
- width: tableWidth, // Lock the cell as well
2749
+ width: tableWidth,
2651
2750
  }, children: href && !devMode ? (jsxRuntime.jsx("a", { href: href, target: target, ...(target === "_blank"
2652
2751
  ? { rel: "noopener noreferrer" }
2653
- : {}), style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2752
+ : {}), style: { display: "block", width: "100%" }, children: finalImageElement })) : (finalImageElement) }) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })] }));
2654
2753
  }
2655
2754
  var Image_default = React.memo(Image, arePropsEqual);
2656
2755
 
@@ -2719,7 +2818,21 @@ function getHrefFromInnerLink(innerLink) {
2719
2818
  return undefined;
2720
2819
  }
2721
2820
  }
2722
- function Row({ children, config, devNode, devMode }) {
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 }) {
2723
2836
  var _a, _b, _c, _d, _e, _f;
2724
2837
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2725
2838
  const numChildren = childrenArray.length;
@@ -2730,6 +2843,9 @@ function Row({ children, config, devNode, devMode }) {
2730
2843
  // / mobile-gap-spacer class names so that stacking works via non-@media CSS
2731
2844
  // rules that survive Gmail's stylesheet stripping.
2732
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;
2733
2849
  // 1. Outer TD: Background, Border Radius, Width, Height.
2734
2850
  const backgroundTdStyle = {
2735
2851
  backgroundColor: config.backgroundColor,
@@ -2770,24 +2886,34 @@ function Row({ children, config, devNode, devMode }) {
2770
2886
  // Content table fills available space, giving Outlook Classic a hard
2771
2887
  // boundary so text children get a constrained box and line wrapping
2772
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.
2773
2893
  const contentTableStyle = {
2774
- 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",
2775
2898
  height: "100%",
2776
2899
  borderCollapse: "collapse",
2777
2900
  minWidth: "1px",
2778
- ...(!config.fillWidth && { maxWidth: config.width || "100%" }),
2901
+ ...(!config.fillWidth &&
2902
+ !hasLayoutColumns && { maxWidth: config.width || "100%" }),
2903
+ ...(hasLayoutColumns && { tableLayout: "fixed" }),
2779
2904
  };
2780
2905
  // 5. Gap TD.
2781
2906
  const gapTdStyle = {
2782
2907
  width: config.gap || "0",
2783
2908
  lineHeight: "1px",
2784
2909
  fontSize: "1px",
2910
+ background: "transparent",
2785
2911
  };
2786
2912
  const tdAlign = config.justifyContent
2787
2913
  ? justifyMap$1[config.justifyContent]
2788
2914
  : "left";
2789
2915
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2790
- const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2916
+ const content = (jsxRuntime.jsxs("table", { "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...(!href ? rootBindingProps(bindings) : {}), style: {
2791
2917
  position: "relative",
2792
2918
  width: config.width || "100%",
2793
2919
  height: config.height,
@@ -2796,32 +2922,44 @@ function Row({ children, config, devNode, devMode }) {
2796
2922
  width: "100%",
2797
2923
  height: "100%",
2798
2924
  borderCollapse: "collapse",
2799
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsxRuntime.jsx("table", { "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle, ...(config.height && { height: config.height }), className: "content-table row-content-table", "data-mobile-wrap": ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("td", { align: tdAlign, style: {
2800
- verticalAlign: tdValign,
2801
- textAlign: tdAlign,
2802
- padding: "0",
2803
- margin: "0",
2804
- },
2805
- // Mirror of Container's stack-td pattern: when isStacking,
2806
- // the non-@media .stack-td rule forces display:block +
2807
- // width:100% on each child, which survives Gmail's
2808
- // @media stripping and achieves true mobile stacking.
2809
- className: `child-cell${isStacking ? " stack-td" : ""}`, children: [child, isStacking &&
2810
- index < numChildren - 1 &&
2811
- config.gap && (jsxRuntime.jsx("div", { className: "mobile-gap-spacer", style: {
2812
- display: "none",
2813
- fontSize: "0",
2814
- lineHeight: "0",
2815
- height: config.gap,
2816
- }, children: "\u00A0" }))] }), index < numChildren - 1 &&
2817
- config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle,
2818
- // Mirror of Container's desktop-gap-column pattern:
2819
- // when isStacking, the non-@media .desktop-gap-column
2820
- // rule collapses the between-column gap td so it does
2821
- // not create phantom space while children are stacked.
2822
- className: `row-gap-td${isStacking ? " desktop-gap-column" : ""}`, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) }) }) }) }) }) }) }) }) }) }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
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 }) }) }))] }));
2823
2961
  if (href && !devMode) {
2824
- return (jsxRuntime.jsx("a", { href: href, ...({ target }), style: {
2962
+ return (jsxRuntime.jsx("a", { href: href, ...({ target }), ...rootBindingProps(bindings), style: {
2825
2963
  textDecoration: "none",
2826
2964
  color: "inherit",
2827
2965
  display: "block",
@@ -2866,10 +3004,10 @@ function getBorderStyle$1(border) {
2866
3004
  }
2867
3005
  return style;
2868
3006
  }
2869
- const Section = ({ config, children, devNode, }) => {
3007
+ const Section = ({ config, children, devNode, bindings, }) => {
2870
3008
  var _a, _b, _c;
2871
3009
  const { sectionType, padding } = config;
2872
- 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: {
2873
3011
  position: "relative",
2874
3012
  width: "100%",
2875
3013
  backgroundColor: config.backgroundColor,
@@ -2882,58 +3020,84 @@ const Section = ({ config, children, devNode, }) => {
2882
3020
  backgroundPosition: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.position,
2883
3021
  }, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: {
2884
3022
  padding: padding,
2885
- }, children: children }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsxs("td", { children: [jsxRuntime.jsxs("span", { style: {
2886
- backgroundColor: "black",
2887
- color: "white",
2888
- padding: "4px",
2889
- fontSize: "14px",
2890
- position: "absolute",
2891
- left: 0,
2892
- top: 0,
2893
- }, children: ["Section | ", sectionType] }), children] }) }) }))] }));
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] }) }) }) }))] }));
2894
3032
  };
2895
3033
  var Section_default = React.memo(Section, arePropsEqual);
2896
3034
 
2897
- function Spacer({ config, devNode }) {
2898
- const { height, hideOnMobile } = config;
3035
+ function Spacer({ config, devNode, bindings }) {
3036
+ var _a, _b, _c;
3037
+ const { height, hideOnMobile, backgroundColor, backgroundImage } = config;
2899
3038
  // 1. Spacer Table Style
2900
3039
  const spacerTableStyle = {
2901
- // Crucial for compatibility: Ensures no background or border interference
2902
- backgroundColor: "transparent",
2903
3040
  borderCollapse: "collapse",
2904
- border: "0",
2905
3041
  width: "100%",
2906
- // Note the CSS standard dash convention: 'mso-table-lspace'
2907
- // ["mso-table-lspace" as string]: "0pt",
2908
- ["msoTableLspace"]: "0pt",
2909
- // ["mso-table-rspace" as string]: "0pt",
2910
- ["msoTableRspace"]: "0pt",
2911
3042
  };
2912
3043
  // 2. Spacer TD Style: The element that creates the actual vertical space
2913
3044
  const spacerTdStyle = {
2914
3045
  height: height,
2915
- // Critical: Suppress any vertical height created by text/font
3046
+ maxHeight: height,
2916
3047
  fontSize: "0",
2917
3048
  lineHeight: "0",
2918
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
+ }),
2919
3061
  };
2920
3062
  // Parse height for the HTML attribute
2921
3063
  const spacerHeightAttribute = parseInt(height, 10) || 1;
2922
- return (
2923
- // Outer table ensures the spacer spans the full width of its container
2924
- jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
3064
+ return (jsxRuntime.jsxs("table", { "aria-label": "Vertical Spacer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, ...rootBindingProps(bindings), style: {
2925
3065
  // --- Start dev
2926
3066
  position: "relative",
2927
3067
  // --- End dev
2928
3068
  ...spacerTableStyle,
2929
- }, ...{ height: spacerHeightAttribute }, className: hideOnMobile ? "hide-on-mobile" : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: spacerTdStyle,
2930
- // Explicit height attribute
2931
- height: spacerHeightAttribute, children: "\u00A0" }) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
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 }) }) }))] }));
2932
3072
  }
2933
3073
  var Spacer_default = React.memo(Spacer, arePropsEqual);
2934
3074
 
2935
- function Text({ config, devMode, children }) {
2936
- const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth, } = config;
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";
2937
3101
  // 1. TD Style: Where padding and background are reliably applied.
2938
3102
  // When maxWidth is set, this TD stays at width: 100% so it always fills
2939
3103
  // its parent — the inner maxWidth table (see below) does the actual
@@ -2986,26 +3150,31 @@ function Text({ config, devMode, children }) {
2986
3150
  ? injectLinkStyles(content, contentStyle)
2987
3151
  : "";
2988
3152
  const innerContent = isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: processedHtml } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content }));
2989
- return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
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: {
2990
3171
  width: "100%",
2991
3172
  borderCollapse: "collapse",
2992
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: maxWidth ? (
2993
- /*
2994
- * maxWidth wrapper — Outlook Classic compatibility pattern:
2995
- *
2996
- * <center> instructs the Word rendering engine to horizontally
2997
- * centre its child block, equivalent to margin: 0 auto in CSS.
2998
- *
2999
- * The inner table carries the `width` HTML attribute set to the
3000
- * maxWidth value. Outlook Classic reads `width` as a hard pixel
3001
- * cap; it has no concept of max-width so this is the only lever
3002
- * available. Modern clients receive the CSS max-width on the
3003
- * same table and behave correctly.
3004
- *
3005
- * The outer TD remains at width: 100% so it always fills its
3006
- * parent cell in every client — only the inner content is capped.
3007
- */
3008
- jsxRuntime.jsx("center", { children: jsxRuntime.jsx("table", { "aria-label": "Text Max Width Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: maxWidth, style: maxWidthTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: innerContent }) }) }) }) })) : (innerContent) }) }) }) }));
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) }) }) }) }));
3009
3178
  }
3010
3179
  var Text_default = React.memo(Text, arePropsEqual);
3011
3180
 
@@ -3131,7 +3300,7 @@ function buildLinkHref(innerLink) {
3131
3300
  return null;
3132
3301
  }
3133
3302
  }
3134
- function Icon({ config, devNode, devMode, children }) {
3303
+ function Icon({ config, devNode, devMode, children, bindings }) {
3135
3304
  const {
3136
3305
  // base64Source,
3137
3306
  width, height, backgroundColor, padding = "0", borderRadius = "0", border, innerLink, justifyContent = "center", } = config;
@@ -3266,7 +3435,7 @@ function Icon({ config, devNode, devMode, children }) {
3266
3435
  <!--<![endif]-->
3267
3436
  `
3268
3437
  : null;
3269
- 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: {
3270
3439
  // --- Start dev
3271
3440
  position: "relative",
3272
3441
  // --- End dev