@pagenflow/email 1.3.3 → 1.3.5

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.
@@ -15,15 +15,22 @@ export interface HeadingConfig {
15
15
  fontSize?: string;
16
16
  /** Font weight (e.g., 'normal', 'bold', or '700'). */
17
17
  fontWeight?: string;
18
- /** Font style (e.g., 'italic'). */ fontStyle?: string;
18
+ /** Font style (e.g., 'italic'). */
19
+ fontStyle?: string;
19
20
  /** Line height (e.g., '1.3' or '30px'). */
20
21
  lineHeight?: string;
21
- /** Letter spacing (e.g., '0.5px', '1px'). */ letterSpacing?: string;
22
- /** Text transform (e.g., 'uppercase', 'lowercase', 'capitalize'). */ textTransform?: string;
23
- /** Text decoration (e.g., 'underline', 'line-through'). */ textDecoration?: string;
24
- /** Text direction (e.g., 'ltr', 'rtl'). */ direction?: string;
25
- /** Vertical alignment (e.g., 'sub', 'super'). Applied to content wrapper in Text, applied to TD here for alignment. */ verticalAlign?: string;
26
- /** Background color of the heading block. */ backgroundColor?: string;
22
+ /** Letter spacing (e.g., '0.5px', '1px'). */
23
+ letterSpacing?: string;
24
+ /** Text transform (e.g., 'uppercase', 'lowercase', 'capitalize'). */
25
+ textTransform?: string;
26
+ /** Text decoration (e.g., 'underline', 'line-through'). */
27
+ textDecoration?: string;
28
+ /** Text direction (e.g., 'ltr', 'rtl'). */
29
+ direction?: string;
30
+ /** Vertical alignment (e.g., 'sub', 'super'). Applied to content wrapper in Text, applied to TD here for alignment. */
31
+ verticalAlign?: string;
32
+ /** Background color of the heading block. */
33
+ backgroundColor?: string;
27
34
  }
28
35
  export type HeadingProps = {
29
36
  config: HeadingConfig;
@@ -20,6 +20,7 @@ export interface RowConfig {
20
20
  mobile?: {
21
21
  justifyContent?: JustifyContent;
22
22
  alignItems?: AlignItems;
23
+ wrap?: boolean;
23
24
  };
24
25
  }
25
26
  export type RowProps = {
package/dist/index.cjs.js CHANGED
@@ -1526,7 +1526,7 @@ function arePropsEqual(prevProps, nextProps) {
1526
1526
  }
1527
1527
 
1528
1528
  // Map alignment to HTML 'align' attribute
1529
- const justifyMap$2 = {
1529
+ const justifyMap$3 = {
1530
1530
  start: "left",
1531
1531
  center: "center",
1532
1532
  end: "right",
@@ -1549,7 +1549,7 @@ function Button({ config, devMode }) {
1549
1549
  const vmlFillColor = backgroundColor.startsWith("#")
1550
1550
  ? backgroundColor
1551
1551
  : `#${backgroundColor}`;
1552
- const align = justifyMap$2[justifyContent];
1552
+ const align = justifyMap$3[justifyContent];
1553
1553
  // Build VML font styles
1554
1554
  const vmlFontWeight = fontWeight || "bold";
1555
1555
  const vmlFontStyle = fontStyle === "italic" ? "font-style:italic;" : "";
@@ -1712,7 +1712,7 @@ const alignMap$1 = {
1712
1712
  center: "middle",
1713
1713
  end: "bottom",
1714
1714
  };
1715
- const justifyMap$1 = {
1715
+ const justifyMap$2 = {
1716
1716
  start: "left",
1717
1717
  center: "center",
1718
1718
  end: "right",
@@ -1816,7 +1816,7 @@ function Container({ children, config, devMode, devNode }) {
1816
1816
  fontSize: "1px",
1817
1817
  };
1818
1818
  const justifyAlign = config.justifyContent
1819
- ? justifyMap$1[config.justifyContent]
1819
+ ? justifyMap$2[config.justifyContent]
1820
1820
  : "center";
1821
1821
  const containerWidthAttr = widthType === "fixed" ? containerWidthPx : undefined;
1822
1822
  const isStacking = config.shouldWrap && numChildren > 1;
@@ -1926,14 +1926,14 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
1926
1926
  }
1927
1927
  `;
1928
1928
  const globalStyles = `
1929
- @media screen and (max-width: 600px) {
1929
+ @media screen and (max-width: 768px) {
1930
1930
  .container-fixed-width {
1931
1931
  width: 100% !important;
1932
1932
  max-width: 100% !important;
1933
1933
  }
1934
1934
  }
1935
1935
 
1936
- @media screen and (max-width: 600px) {
1936
+ @media screen and (max-width: 768px) {
1937
1937
  .stack-td {
1938
1938
  width: 100% !important;
1939
1939
  display: block !important;
@@ -1957,8 +1957,8 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
1957
1957
  }
1958
1958
  }
1959
1959
 
1960
- @media only screen and (max-width: 600px) {
1961
- /* 1. Handling Mobile Alignment (Justify) */
1960
+ @media only screen and (max-width: 768px) {
1961
+ /* 1. Handling Mobile Alignment (Justify) - Works for both wrapped and non-wrapped */
1962
1962
  /* We target the inner table alignment */
1963
1963
  .responsive-row[data-mobile-justify="center"] .content-table {
1964
1964
  margin: 0 auto !important;
@@ -1973,17 +1973,104 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
1973
1973
  float: right !important;
1974
1974
  }
1975
1975
 
1976
+ /* Mobile justify for wrapped children - we need to target the outer wrapper td */
1977
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="center"] td[align] {
1978
+ text-align: center !important;
1979
+ }
1980
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="start"] td[align] {
1981
+ text-align: left !important;
1982
+ }
1983
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="end"] td[align] {
1984
+ text-align: right !important;
1985
+ }
1986
+
1987
+ /* Also apply to child content tables for better support */
1988
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="center"] .child-cell table {
1989
+ margin-left: auto !important;
1990
+ margin-right: auto !important;
1991
+ }
1992
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="start"] .child-cell table {
1993
+ margin-left: 0 !important;
1994
+ margin-right: auto !important;
1995
+ }
1996
+ .responsive-row[data-mobile-wrap="true"][data-mobile-justify="end"] .child-cell table {
1997
+ margin-left: auto !important;
1998
+ margin-right: 0 !important;
1999
+ }
2000
+
1976
2001
  /* 2. Handling Mobile Vertical Alignment (Align Items) */
1977
- /* We target the child cells if they are still side-by-side */
1978
- .responsive-row[data-mobile-align="center"] .child-cell {
2002
+ /* For non-wrapped rows - controls vertical alignment when cells are side-by-side */
2003
+ .responsive-row[data-mobile-align="center"]:not([data-mobile-wrap="true"]) .child-cell {
2004
+ vertical-align: middle !important;
2005
+ }
2006
+ .responsive-row[data-mobile-align="start"]:not([data-mobile-wrap="true"]) .child-cell {
2007
+ vertical-align: top !important;
2008
+ }
2009
+ .responsive-row[data-mobile-align="end"]:not([data-mobile-wrap="true"]) .child-cell {
2010
+ vertical-align: bottom !important;
2011
+ }
2012
+
2013
+ /* For wrapped rows - alignItems controls vertical alignment of content within each child cell */
2014
+ .responsive-row[data-mobile-wrap="true"][data-mobile-align="center"] .child-cell {
1979
2015
  vertical-align: middle !important;
1980
2016
  }
1981
- .responsive-row[data-mobile-align="start"] .child-cell {
2017
+ .responsive-row[data-mobile-wrap="true"][data-mobile-align="start"] .child-cell {
1982
2018
  vertical-align: top !important;
1983
2019
  }
1984
- .responsive-row[data-mobile-align="end"] .child-cell {
2020
+ .responsive-row[data-mobile-wrap="true"][data-mobile-align="end"] .child-cell {
1985
2021
  vertical-align: bottom !important;
1986
2022
  }
2023
+
2024
+ /* 3. Handling Mobile Wrap - Pure CSS Solution */
2025
+ /* Force table to act like block container */
2026
+ .responsive-row[data-mobile-wrap="true"] .content-table {
2027
+ width: 100% !important;
2028
+ max-width: 100% !important;
2029
+ }
2030
+
2031
+ /* Force table row to stack cells */
2032
+ .responsive-row[data-mobile-wrap="true"] .content-tr {
2033
+ display: block !important;
2034
+ }
2035
+
2036
+ /* Force each child cell to be full width block */
2037
+ .responsive-row[data-mobile-wrap="true"] .child-cell {
2038
+ display: block !important;
2039
+ width: 100% !important;
2040
+ box-sizing: border-box !important;
2041
+ }
2042
+
2043
+ /* Hide horizontal gap cells and create vertical spacing with padding */
2044
+ .responsive-row[data-mobile-wrap="true"] .row-gap-td {
2045
+ display: none !important;
2046
+ width: 0 !important;
2047
+ height: 0 !important;
2048
+ }
2049
+
2050
+ /* Add vertical spacing between stacked cells using margin */
2051
+ .responsive-row[data-mobile-wrap="true"] .child-cell:not(:last-child) {
2052
+ margin-bottom: 20px !important;
2053
+ }
2054
+
2055
+ /* Dynamic gap support - common values */
2056
+ .responsive-row[data-mobile-wrap="true"][data-gap="10px"] .child-cell:not(:last-child) {
2057
+ margin-bottom: 10px !important;
2058
+ }
2059
+ .responsive-row[data-mobile-wrap="true"][data-gap="15px"] .child-cell:not(:last-child) {
2060
+ margin-bottom: 15px !important;
2061
+ }
2062
+ .responsive-row[data-mobile-wrap="true"][data-gap="20px"] .child-cell:not(:last-child) {
2063
+ margin-bottom: 20px !important;
2064
+ }
2065
+ .responsive-row[data-mobile-wrap="true"][data-gap="24px"] .child-cell:not(:last-child) {
2066
+ margin-bottom: 24px !important;
2067
+ }
2068
+ .responsive-row[data-mobile-wrap="true"][data-gap="30px"] .child-cell:not(:last-child) {
2069
+ margin-bottom: 30px !important;
2070
+ }
2071
+ .responsive-row[data-mobile-wrap="true"][data-gap="40px"] .child-cell:not(:last-child) {
2072
+ margin-bottom: 40px !important;
2073
+ }
1987
2074
  }
1988
2075
 
1989
2076
  /* ================================================= */
@@ -2063,8 +2150,10 @@ function Head({ children, backgroundColor = "#ffffff", title = "Email Preview",
2063
2150
  }
2064
2151
 
2065
2152
  function Heading({ config, devMode, children }) {
2066
- var _a;
2067
2153
  const { text, level = "h1", padding, color, textAlign, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, } = config;
2154
+ // Determine the content to render
2155
+ const content = text !== null && text !== void 0 ? text : children;
2156
+ const isString = typeof content === "string";
2068
2157
  // 1. TD Style: Where padding, background, width, and verticalAlign are applied.
2069
2158
  const tdStyle = {
2070
2159
  padding: padding,
@@ -2091,7 +2180,6 @@ function Heading({ config, devMode, children }) {
2091
2180
  fontFamily: "Arial, Helvetica, sans-serif",
2092
2181
  // Outlook specific fixes (using string indexing)
2093
2182
  ["msoLineHeightRule"]: "exactly",
2094
- // ["mso-line-height-rule" as string]: "exactly",
2095
2183
  };
2096
2184
  // Dynamically create the Heading element
2097
2185
  const HeadingTag = level;
@@ -2100,7 +2188,7 @@ function Heading({ config, devMode, children }) {
2100
2188
  jsxRuntime.jsx("table", { "aria-label": "Heading Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2101
2189
  width: "100%",
2102
2190
  borderCollapse: "collapse",
2103
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: jsxRuntime.jsx(HeadingTag, { style: headingStyle, dangerouslySetInnerHTML: { __html: (_a = text !== null && text !== void 0 ? text : children) !== null && _a !== void 0 ? _a : "" } }) }) }) }) }));
2191
+ }, 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: content } })) : (jsxRuntime.jsx(HeadingTag, { style: headingStyle, children: content })) }) }) }) }));
2104
2192
  }
2105
2193
  var Heading_default = React.memo(Heading, arePropsEqual);
2106
2194
 
@@ -2181,7 +2269,7 @@ function Image({ config, devNode, devMode }) {
2181
2269
  }
2182
2270
  var Image_default = React.memo(Image, arePropsEqual);
2183
2271
 
2184
- const justifyMap = {
2272
+ const justifyMap$1 = {
2185
2273
  start: "left",
2186
2274
  center: "center",
2187
2275
  end: "right",
@@ -2213,7 +2301,7 @@ function getBorderStyle$1(border) {
2213
2301
  return style;
2214
2302
  }
2215
2303
  function Row({ children, config, devNode }) {
2216
- var _a, _b, _c, _d, _e;
2304
+ var _a, _b, _c, _d, _e, _f;
2217
2305
  const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
2218
2306
  const numChildren = childrenArray.length;
2219
2307
  // 1. Outer TD for Background and Border Radius (no border here)
@@ -2246,7 +2334,7 @@ function Row({ children, config, devNode }) {
2246
2334
  fontSize: "1px",
2247
2335
  };
2248
2336
  const tdAlign = config.justifyContent
2249
- ? justifyMap[config.justifyContent]
2337
+ ? justifyMap$1[config.justifyContent]
2250
2338
  : "left";
2251
2339
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2252
2340
  return (jsxRuntime.jsxs("table", Object.assign({ "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
@@ -2254,7 +2342,7 @@ function Row({ children, config, devNode }) {
2254
2342
  width: config.width || "100%",
2255
2343
  height: config.height,
2256
2344
  borderCollapse: "collapse",
2257
- } }, (config.height && { height: config.height }), { "data-mobile-justify": (_d = config.mobile) === null || _d === void 0 ? void 0 : _d.justifyContent, "data-mobile-align": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.alignItems, className: "responsive-row", children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", Object.assign({ style: backgroundTdStyle }, (config.height && { height: config.height }), { children: jsxRuntime.jsx("table", { "aria-label": "Row Border Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: borderTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: paddingTdStyle, children: jsxRuntime.jsx("table", { "aria-label": "Row Justification Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2345
+ } }, (config.height && { height: config.height }), { "data-mobile-justify": (_d = config.mobile) === null || _d === void 0 ? void 0 : _d.justifyContent, "data-mobile-align": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.alignItems, "data-mobile-wrap": ((_f = config.mobile) === null || _f === void 0 ? void 0 : _f.wrap) ? "true" : undefined, "data-gap": config.gap, className: "responsive-row", children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", Object.assign({ style: backgroundTdStyle }, (config.height && { height: config.height }), { children: jsxRuntime.jsx("table", { "aria-label": "Row Border Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: borderTableStyle, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: paddingTdStyle, children: jsxRuntime.jsx("table", { "aria-label": "Row Justification Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2258
2346
  width: "100%",
2259
2347
  height: "100%",
2260
2348
  borderCollapse: "collapse",
@@ -2264,7 +2352,7 @@ function Row({ children, config, devNode }) {
2264
2352
  padding: "0",
2265
2353
  margin: "0",
2266
2354
  }, className: "child-cell", children: child }), index < numChildren - 1 &&
2267
- config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle, children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) })) }) }) }) }) }) }) }) }) })) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })));
2355
+ config.gap && (jsxRuntime.jsx("td", { width: config.gap, style: gapTdStyle, className: "row-gap-td", children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) })) }) }) }) }) }) }) }) }) })) }) }), devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] })));
2268
2356
  }
2269
2357
  var Row_default = React.memo(Row, arePropsEqual);
2270
2358
 
@@ -2345,7 +2433,6 @@ function Spacer({ config, devNode }) {
2345
2433
  var Spacer_default = React.memo(Spacer, arePropsEqual);
2346
2434
 
2347
2435
  function Text({ config, devMode, children }) {
2348
- var _a;
2349
2436
  const { text, padding, color, textAlign, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, } = config;
2350
2437
  // 1. TD Style: Where padding and background are reliably applied.
2351
2438
  const tdStyle = {
@@ -2354,9 +2441,7 @@ function Text({ config, devMode, children }) {
2354
2441
  width: "100%",
2355
2442
  verticalAlign: "top",
2356
2443
  };
2357
- // 2. Content Style: Applied directly to a wrapper element (like a <div> or <p>)
2358
- // or inherited by the children. For max compatibility, we apply core styles
2359
- // directly to the TD or a wrapper <p> (if children is just a string).
2444
+ // 2. Content Style: Applied directly to a wrapper element
2360
2445
  const contentStyle = {
2361
2446
  color: color,
2362
2447
  textAlign: textAlign,
@@ -2371,19 +2456,188 @@ function Text({ config, devMode, children }) {
2371
2456
  verticalAlign: verticalAlign,
2372
2457
  opacity: opacity,
2373
2458
  whiteSpace: whiteSpace,
2374
- margin: "0", // Crucial: Remove default margin from <p> tags
2459
+ margin: "0",
2375
2460
  padding: "0",
2376
- fontFamily: "Arial, Helvetica, sans-serif", // Use a widely supported font stack
2461
+ fontFamily: "Arial, Helvetica, sans-serif",
2377
2462
  };
2378
- return (
2379
- // Wrap the text content in a table for padding/background/width management.
2380
- jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2463
+ // Determine content to render
2464
+ const content = text !== null && text !== void 0 ? text : children;
2465
+ const isString = typeof content === "string";
2466
+ return (jsxRuntime.jsx("table", { "aria-label": "Text Block Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2381
2467
  width: "100%",
2382
2468
  borderCollapse: "collapse",
2383
- }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: (_a = text !== null && text !== void 0 ? text : children) !== null && _a !== void 0 ? _a : "" } }) }) }) }) }));
2469
+ }, children: jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { style: tdStyle, align: textAlign, children: isString ? (jsxRuntime.jsx("div", { style: contentStyle, dangerouslySetInnerHTML: { __html: content } })) : (jsxRuntime.jsx("div", { style: contentStyle, children: content })) }) }) }) }));
2384
2470
  }
2385
2471
  var Text_default = React.memo(Text, arePropsEqual);
2386
2472
 
2473
+ // Map alignment to HTML 'align' attribute
2474
+ const justifyMap = {
2475
+ start: "left",
2476
+ center: "center",
2477
+ end: "right",
2478
+ };
2479
+ // Helper to build Iconify API URL
2480
+ function buildIconifyUrl(config) {
2481
+ const { iconIdentifier, height = 24, color = "000000", rotate = 0, rotateOrientation = "cw", } = config;
2482
+ if (!iconIdentifier)
2483
+ return null;
2484
+ // Remove # from color if present
2485
+ const cleanColor = color.replace("#", "");
2486
+ // Build URL from template
2487
+ const template = process.env.ICONIFY_API_IMAGE_URI ||
2488
+ "https://iconify.pagenflow.com/api/image/{{height}}/{{color}}/{{rotate}}-{{rotate-orientation}}/{{icon-full-name}}.png";
2489
+ return template
2490
+ .replace("{{height}}", String(Number(height) * Number(2)))
2491
+ .replace("{{color}}", cleanColor)
2492
+ .replace("{{rotate}}", String(rotate))
2493
+ .replace("{{rotate-orientation}}", rotateOrientation)
2494
+ .replace("{{icon-full-name}}", iconIdentifier);
2495
+ }
2496
+ // Helper to build link href based on innerLink type
2497
+ function buildLinkHref(innerLink) {
2498
+ if (!innerLink || innerLink.type === "none")
2499
+ return null;
2500
+ switch (innerLink.type) {
2501
+ case "url":
2502
+ return innerLink.url || null;
2503
+ case "email":
2504
+ return innerLink.email ? `mailto:${innerLink.email}` : null;
2505
+ case "phone":
2506
+ return innerLink.phone ? `tel:${innerLink.phone}` : null;
2507
+ case "anchor":
2508
+ return innerLink.anchor ? `#${innerLink.anchor}` : null;
2509
+ case "page_top":
2510
+ return "#top";
2511
+ case "page_bottom":
2512
+ return "#bottom";
2513
+ default:
2514
+ return null;
2515
+ }
2516
+ }
2517
+ function Icon({ config, devNode, devMode, children }) {
2518
+ const {
2519
+ // base64Source,
2520
+ width, height, backgroundColor, padding = "0", borderRadius = "0", innerLink, justifyContent = "center", } = config;
2521
+ // Determine icon source
2522
+ const iconSrc = buildIconifyUrl(config);
2523
+ const href = buildLinkHref(innerLink);
2524
+ const target = (innerLink === null || innerLink === void 0 ? void 0 : innerLink.target) || "_self";
2525
+ const align = justifyMap[justifyContent];
2526
+ // Convert width/height to string with px if number
2527
+ const widthStr = typeof width === "number" ? `${width}px` : width;
2528
+ const heightStr = typeof height === "number" ? `${height}px` : height;
2529
+ // Parse numeric values for HTML attributes
2530
+ const widthNum = typeof width === "number"
2531
+ ? width
2532
+ : typeof width === "string" && width.endsWith("px")
2533
+ ? parseInt(width, 10)
2534
+ : undefined;
2535
+ const heightNum = typeof height === "number"
2536
+ ? height
2537
+ : typeof height === "string" && height.endsWith("px")
2538
+ ? parseInt(height, 10)
2539
+ : undefined;
2540
+ // 1. Image Style: Critical for compatibility
2541
+ const imgStyle = {
2542
+ display: "block", // Prevents extra vertical space
2543
+ border: 0, // No default border
2544
+ width: widthStr || "auto",
2545
+ height: heightStr || "auto",
2546
+ };
2547
+ // 2. Link Style: No underline or color changes
2548
+ const linkStyle = {
2549
+ display: "block",
2550
+ textDecoration: "none",
2551
+ border: 0,
2552
+ outline: "none",
2553
+ };
2554
+ // 3. TD Style: Padding and background
2555
+ const tdStyle = {
2556
+ padding: padding,
2557
+ backgroundColor: backgroundColor,
2558
+ fontSize: "0", // CRITICAL: Collapses extra space
2559
+ lineHeight: "0", // CRITICAL: Collapses extra space7
2560
+ borderRadius: borderRadius,
2561
+ overflow: "hidden",
2562
+ };
2563
+ // --- VML Calculation for Outlook Compatibility ---
2564
+ const numericPadding = parseInt(padding.split(" ")[0] || "0", 10);
2565
+ const vmlWidth = (widthNum || 24) + numericPadding * 2;
2566
+ const vmlHeight = (heightNum || 24) + numericPadding * 2;
2567
+ // VML colors must use full hex format
2568
+ const vmlFillColor = (backgroundColor === null || backgroundColor === void 0 ? void 0 : backgroundColor.startsWith("#"))
2569
+ ? backgroundColor
2570
+ : backgroundColor
2571
+ ? `#${backgroundColor}`
2572
+ : "#ffffff";
2573
+ // Calculate arcsize percentage for VML
2574
+ const numericBorderRadius = parseInt(borderRadius || "0", 10);
2575
+ const arcsize = numericBorderRadius > 0
2576
+ ? Math.min((numericBorderRadius / Math.min(vmlWidth, vmlHeight)) * 100, 100)
2577
+ : 0;
2578
+ // Build VML code for Outlook
2579
+ const vmlIcon = backgroundColor && numericBorderRadius > 0
2580
+ ? `
2581
+ <!--[if mso]>
2582
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" ${href && !devMode ? `href="${href}"` : ""} style="height:${vmlHeight}px;width:${vmlWidth}px;v-text-anchor:middle;" arcsize="${arcsize}%" stroke="false" fillcolor="${vmlFillColor}">
2583
+ <w:anchorlock/>
2584
+ <v:textbox inset="0,0,0,0" style="text-align: center;">
2585
+ <center style="padding:${padding};">
2586
+ <img src="${iconSrc || ""}" alt="" width="${widthNum || 24}" height="${heightNum || 24}" border="0" style="display:block;border:0;" />
2587
+ </center>
2588
+ </v:textbox>
2589
+ </v:roundrect>
2590
+ <![endif]-->
2591
+ `
2592
+ : "";
2593
+ // If no icon source, return empty
2594
+ if (!iconSrc && !devMode) {
2595
+ return null;
2596
+ }
2597
+ // Icon image element
2598
+ const iconElement = devMode && !!children ? (children) : iconSrc ? (jsxRuntime.jsx("img", { draggable: false, src: iconSrc, alt: "" // Icons are decorative, empty alt is appropriate
2599
+ , style: imgStyle, width: widthNum, height: heightNum, border: 0 })) : (jsxRuntime.jsx(jsxRuntime.Fragment, {}));
2600
+ // Wrap in link if href exists and not in dev mode
2601
+ const content = href && !devMode ? (jsxRuntime.jsx("a", Object.assign({ href: href, target: target, style: linkStyle }, (target === "_blank" ? { rel: "noopener noreferrer" } : {}), { children: iconElement }))) : (iconElement);
2602
+ // Build the HTML content with VML support (only when NOT in dev mode)
2603
+ const useVML = !devMode && backgroundColor && numericBorderRadius > 0;
2604
+ const htmlContent = useVML
2605
+ ? `
2606
+ ${vmlIcon}
2607
+ <!--[if !mso]><!-->
2608
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; width: 100%;">
2609
+ <tbody>
2610
+ <tr>
2611
+ <td style="background-color: ${backgroundColor}; border-radius: ${borderRadius}; padding: ${padding}; font-size: 0; line-height: 0; overflow: hidden;">
2612
+ ${href
2613
+ ? `<a href="${href}" target="${target}" style="display:block;text-decoration:none;border:0;outline:none;" ${target === "_blank" ? 'rel="noopener noreferrer"' : ""}>
2614
+ <img draggable="false" src="${iconSrc}" alt="" width="${widthNum || 24}" height="${heightNum || 24}" border="0" style="display:block;border:0;width:${widthStr || "auto"};height:${heightStr || "auto"};" />
2615
+ </a>`
2616
+ : `<img draggable="false" src="${iconSrc}" alt="" width="${widthNum || 24}" height="${heightNum || 24}" border="0" style="display:block;border:0;width:${widthStr || "auto"};height:${heightStr || "auto"};" />`}
2617
+ </td>
2618
+ </tr>
2619
+ </tbody>
2620
+ </table>
2621
+ <!--<![endif]-->
2622
+ `
2623
+ : null;
2624
+ return (jsxRuntime.jsxs("table", { "aria-label": "Icon", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, align: align, style: {
2625
+ // --- Start dev
2626
+ position: "relative",
2627
+ // --- End dev
2628
+ width: widthStr || "auto",
2629
+ borderCollapse: "collapse",
2630
+ // base
2631
+ boxSizing: "border-box",
2632
+ border: 0,
2633
+ margin: 0,
2634
+ padding: 0,
2635
+ }, onClick: devMode ? (e) => e.preventDefault() : undefined, children: [jsxRuntime.jsx("tbody", { children: jsxRuntime.jsx("tr", { children: useVML ? (jsxRuntime.jsx("td", { dangerouslySetInnerHTML: {
2636
+ __html: htmlContent !== null && htmlContent !== void 0 ? htmlContent : "",
2637
+ } })) : (jsxRuntime.jsx("td", { style: tdStyle, align: align, children: content })) }) }), devMode && !!devNode && (jsxRuntime.jsx("tfoot", { children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { children: devNode }) }) }))] }));
2638
+ }
2639
+ var Icon_default = React.memo(Icon, arePropsEqual);
2640
+
2387
2641
  exports.Body = Body;
2388
2642
  exports.Button = Button_default;
2389
2643
  exports.Column = Column_default;
@@ -2392,6 +2646,7 @@ exports.Divider = Divider_default;
2392
2646
  exports.Head = Head;
2393
2647
  exports.Heading = Heading_default;
2394
2648
  exports.Html = Html;
2649
+ exports.Icon = Icon_default;
2395
2650
  exports.Image = Image_default;
2396
2651
  exports.Row = Row_default;
2397
2652
  exports.Section = Section_default;