@pagenflow/email 1.4.0 → 1.4.2

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
@@ -8,20 +8,14 @@ function Body({ children, config = {} }) {
8
8
  const globalFontSize = config.fontSize || "16px";
9
9
  const globalBackgroundColor = config.backgroundColor || "#ffffff";
10
10
  const globalLineHeight = config.lineHeight || "1.4";
11
+ const globalFontFamily = config.fontFamily || "Arial, Helvetica, sans-serif";
11
12
  // Background image properties
12
13
  const bgImage = ((_a = config.backgroundImage) === null || _a === void 0 ? void 0 : _a.src) || "";
13
14
  const bgRepeat = ((_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat) || "no-repeat";
14
15
  const bgSize = ((_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size) || "cover";
15
16
  const bgPosition = ((_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position) || "center";
16
17
  // 1. Style for the <body> tag inline
17
- const bodyStyle = Object.assign({ backgroundColor: globalBackgroundColor, color: globalColor, fontSize: globalFontSize, lineHeight: globalLineHeight, padding: "0", margin: "0", WebkitTextSizeAdjust: "100%", overflowX: "hidden",
18
- // ✅ FIX 1: Use string indexing for MSO property
19
- // ['ms-text-size-adjust' as string]: '100%',
20
- ["msTextSizeAdjust"]: "100%",
21
- // ["mso-line-height-rule" as string]: "exactly",
22
- ["msoLineHeightRule"]: "exactly",
23
- // Base font for body
24
- fontFamily: "Arial, Helvetica, sans-serif" }, (bgImage && {
18
+ const bodyStyle = Object.assign({ backgroundColor: globalBackgroundColor, color: globalColor, fontSize: globalFontSize, lineHeight: globalLineHeight, padding: "0", margin: "0", WebkitTextSizeAdjust: "100%", overflowX: "hidden", ["msTextSizeAdjust"]: "100%", ["msoLineHeightRule"]: "exactly", fontFamily: globalFontFamily }, (bgImage && {
25
19
  backgroundImage: `url(${bgImage})`,
26
20
  backgroundRepeat: bgRepeat,
27
21
  backgroundSize: bgSize,
@@ -30,14 +24,10 @@ function Body({ children, config = {} }) {
30
24
  // 2. Style for the top-level <table> wrapper
31
25
  const outerTableStyle = {
32
26
  width: "100%",
33
- // ✅ FIX 1 (on table): Use string indexing for MSO property
34
27
  ["msoLineHeightRule"]: "exactly",
35
- // ['mso-line-height-rule' as string]: 'exactly',
36
28
  borderCollapse: "collapse",
37
29
  };
38
- return (
39
- // The <body> tag with inline styles
40
- jsxs("body", { style: bodyStyle, children: [jsx("center", { style: Object.assign({ width: "100%", background: globalBackgroundColor }, (bgImage && {
30
+ return (jsxs("body", { style: bodyStyle, children: [jsx("center", { style: Object.assign({ width: "100%", background: globalBackgroundColor }, (bgImage && {
41
31
  backgroundImage: `url(${bgImage})`,
42
32
  backgroundRepeat: bgRepeat,
43
33
  backgroundSize: bgSize,
@@ -1679,9 +1669,12 @@ function Button({ config, devMode }) {
1679
1669
  else {
1680
1670
  vmlAlignStyle = `text-align:${textAlign};`;
1681
1671
  }
1672
+ // Border radius is intentionally omitted (arcsize="0%") for Outlook Classic.
1673
+ // Outlook Classic does not reliably support rounded corners and the result
1674
+ // is inconsistent, so we render sharp corners there instead.
1682
1675
  vmlButton = `
1683
1676
  <!--[if mso]>
1684
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:${vmlHeight}px;width:${vmlWidth}px;" arcsize="${Math.min((parseInt(borderRadius) / vmlHeight) * 100, 50)}%" strokecolor="${vmlStrokeColor}" ${hasVmlStroke ? `strokeweight="${vmlStrokeWeight}px"` : 'stroke="f"'} fillcolor="${vmlFillColor}">
1677
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${href}" style="height:${vmlHeight}px;width:${vmlWidth}px;" arcsize="0%" strokecolor="${vmlStrokeColor}" ${hasVmlStroke ? `strokeweight="${vmlStrokeWeight}px"` : 'stroke="f"'} fillcolor="${vmlFillColor}">
1685
1678
  <w:anchorlock/>
1686
1679
  <v:textbox inset="${horizontalPaddingValue}px,${numericPadding}px,${horizontalPaddingValue}px,${numericPadding}px">
1687
1680
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse;">
@@ -1714,6 +1707,9 @@ function Button({ config, devMode }) {
1714
1707
  const directionProp = direction ? `direction: ${direction};` : "";
1715
1708
  const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
1716
1709
  const wordBreakProp = wordBreak !== "break-word" ? `word-break: ${wordBreak};` : "";
1710
+ // Border radius is intentionally omitted from the Outlook Classic table cell.
1711
+ // Outlook Classic ignores border-radius on table cells anyway, and including it
1712
+ // can cause unexpected rendering artifacts, so we explicitly leave it out.
1717
1713
  simpleOutlookButton = `
1718
1714
  <!--[if mso]>
1719
1715
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse: collapse;">
@@ -1721,7 +1717,7 @@ function Button({ config, devMode }) {
1721
1717
  <td align="${align}" style="padding: 0;">
1722
1718
  <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="${width || "auto"}" style="border-collapse: collapse;">
1723
1719
  <tr>
1724
- <td bgcolor="${backgroundColor}" align="${textAlign}" style="padding: ${padding}; text-align: ${textAlign}; border-radius: ${borderRadius}; ${borderStyleString}">
1720
+ <td bgcolor="${backgroundColor}" align="${textAlign}" style="padding: ${padding}; text-align: ${textAlign}; ${borderStyleString}">
1725
1721
  <a href="${href}" target="_blank" rel="noopener noreferrer" style="color: ${color}; ${textDecorationStyle} display: block; font-family: ${safeFontFamily}; font-size: ${fontSize}; font-weight: ${fontWeight}; ${fontStyleProp} line-height: ${lineHeight}; ${letterSpacingProp} ${textTransformProp} text-align: ${textAlign}; ${whiteSpaceProp} ${directionProp} ${opacityProp} ${wordBreakProp} mso-line-height-rule: exactly;">
1726
1722
  ${typeof children === "string" ? children : ""}
1727
1723
  </a>
@@ -2350,28 +2346,6 @@ function Html({ children, backgroundColor = "#ffffff", }) {
2350
2346
  );
2351
2347
  }
2352
2348
 
2353
- /**
2354
- * Content rendered by Outlook Classic only.
2355
- * Outputs: <!--[if mso]> ... <![endif]-->
2356
- */
2357
- function MsoOnly({ html }) {
2358
- return (jsx("td", { dangerouslySetInnerHTML: {
2359
- __html: `<!--[if mso]>${html}<![endif]-->`,
2360
- } }));
2361
- }
2362
- /**
2363
- * Content hidden from Outlook Classic, visible in all other clients.
2364
- * Outputs: <!--[if !mso]><!--> ... <!--<![endif]-->
2365
- */
2366
- function NonMso({ html }) {
2367
- return (jsx("td", { dangerouslySetInnerHTML: {
2368
- __html: `<!--[if !mso]><!-->${html}<!--<![endif]-->`,
2369
- } }));
2370
- }
2371
-
2372
- // ---------------------------------------------------------------------------
2373
- // Helpers
2374
- // ---------------------------------------------------------------------------
2375
2349
  function getBorderStyle$3(border) {
2376
2350
  if (!border)
2377
2351
  return {};
@@ -2379,15 +2353,6 @@ function getBorderStyle$3(border) {
2379
2353
  if (border.width && border.style && border.color) {
2380
2354
  style.border = `${border.width} ${border.style} ${border.color}`;
2381
2355
  }
2382
- else {
2383
- const hasIndividual = border.top || border.right || border.bottom || border.left;
2384
- if (hasIndividual) {
2385
- style.borderTop = "none";
2386
- style.borderRight = "none";
2387
- style.borderBottom = "none";
2388
- style.borderLeft = "none";
2389
- }
2390
- }
2391
2356
  if (border.top)
2392
2357
  style.borderTop = `${border.top.width} ${border.top.style} ${border.top.color}`;
2393
2358
  if (border.right)
@@ -2402,143 +2367,87 @@ function getBorderStyleString$1(border) {
2402
2367
  if (!border)
2403
2368
  return "";
2404
2369
  const styles = [];
2370
+ // Standard shorthand
2405
2371
  if (border.width && border.style && border.color) {
2406
- styles.push(`border:${border.width} ${border.style} ${border.color};`);
2372
+ styles.push(`border:${border.width} ${border.style} ${border.color} !important;`);
2407
2373
  }
2408
2374
  else {
2409
- const hasIndividual = border.top || border.right || border.bottom || border.left;
2410
- if (hasIndividual) {
2411
- styles.push("border-top:none;", "border-right:none;", "border-bottom:none;", "border-left:none;");
2412
- }
2375
+ // If desktop had a border and mobile wants "none", we must explicitly kill it
2376
+ styles.push(`border: none !important;`);
2413
2377
  }
2378
+ // Individual sides
2414
2379
  if (border.top)
2415
- styles.push(`border-top:${border.top.width} ${border.top.style} ${border.top.color};`);
2380
+ styles.push(`border-top:${border.top.width} ${border.top.style} ${border.top.color} !important;`);
2416
2381
  if (border.right)
2417
- styles.push(`border-right:${border.right.width} ${border.right.style} ${border.right.color};`);
2382
+ styles.push(`border-right:${border.right.width} ${border.right.style} ${border.right.color} !important;`);
2418
2383
  if (border.bottom)
2419
- styles.push(`border-bottom:${border.bottom.width} ${border.bottom.style} ${border.bottom.color};`);
2384
+ styles.push(`border-bottom:${border.bottom.width} ${border.bottom.style} ${border.bottom.color} !important;`);
2420
2385
  if (border.left)
2421
- styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color};`);
2386
+ styles.push(`border-left:${border.left.width} ${border.left.style} ${border.left.color} !important;`);
2422
2387
  return styles.join(" ");
2423
2388
  }
2424
- // ---------------------------------------------------------------------------
2425
- // Merged styles helper — applies mobile overrides on top of desktop values
2426
- // ---------------------------------------------------------------------------
2427
- function mergeConfig(config, overrides) {
2428
- var _a, _b, _c, _d, _e, _f, _g, _h;
2429
- return {
2430
- width: (_a = overrides === null || overrides === void 0 ? void 0 : overrides.width) !== null && _a !== void 0 ? _a : config.width,
2431
- height: (_b = overrides === null || overrides === void 0 ? void 0 : overrides.height) !== null && _b !== void 0 ? _b : config.height,
2432
- maxWidth: (_c = overrides === null || overrides === void 0 ? void 0 : overrides.maxWidth) !== null && _c !== void 0 ? _c : config.maxWidth,
2433
- maxHeight: (_d = overrides === null || overrides === void 0 ? void 0 : overrides.maxHeight) !== null && _d !== void 0 ? _d : config.maxHeight,
2434
- backgroundColor: (_e = overrides === null || overrides === void 0 ? void 0 : overrides.backgroundColor) !== null && _e !== void 0 ? _e : config.backgroundColor,
2435
- padding: (_f = overrides === null || overrides === void 0 ? void 0 : overrides.padding) !== null && _f !== void 0 ? _f : config.padding,
2436
- borderRadius: (_g = overrides === null || overrides === void 0 ? void 0 : overrides.borderRadius) !== null && _g !== void 0 ? _g : config.borderRadius,
2437
- border: (_h = overrides === null || overrides === void 0 ? void 0 : overrides.border) !== null && _h !== void 0 ? _h : config.border,
2438
- };
2439
- }
2440
- // ---------------------------------------------------------------------------
2441
- // Desktop table — JSX (same as original)
2442
- // ---------------------------------------------------------------------------
2443
- function renderDesktopTable({ config, className, devNode, devMode, }) {
2444
- const { src, alt, href, target } = config;
2445
- const { width, height, maxWidth, maxHeight, backgroundColor, padding, borderRadius, border, } = mergeConfig(config);
2446
- const borderStyle = getBorderStyle$3(border);
2447
- const imgStyle = Object.assign({ display: "block", objectFit: "cover", width: width || "100%", height: height || "auto", maxWidth: maxWidth || "100%", maxHeight: maxHeight, border: "0", borderRadius: borderRadius }, borderStyle);
2448
- const linkStyle = {
2449
- display: "block",
2450
- textDecoration: "none",
2451
- border: "0",
2452
- outline: "none",
2453
- };
2454
- const tdStyle = {
2455
- padding: padding,
2456
- backgroundColor: backgroundColor,
2457
- fontSize: "0",
2458
- lineHeight: "0",
2459
- };
2460
- const widthNum = (width === null || width === void 0 ? void 0 : width.endsWith("px")) ? parseInt(width, 10) : undefined;
2461
- const maxWidthNum = (maxWidth === null || maxWidth === void 0 ? void 0 : maxWidth.endsWith("px"))
2462
- ? parseInt(maxWidth, 10)
2463
- : undefined;
2464
- const heightNum = (height === null || height === void 0 ? void 0 : height.endsWith("px")) ? parseInt(height, 10) : undefined;
2465
- const imageElement = (jsx("img", { draggable: false, src: src, alt: alt, style: imgStyle, width: widthNum && maxWidthNum
2466
- ? Math.min(widthNum, maxWidthNum)
2467
- : widthNum || maxWidthNum, height: heightNum, border: 0 }));
2468
- const content = href && !devMode ? (jsx("a", Object.assign({ href: href, target: target, style: linkStyle }, (target === "_blank" ? { rel: "noopener noreferrer" } : {}), { children: imageElement }))) : (imageElement);
2469
- return (jsxs("table", { "aria-label": `Image Wrapper for: ${alt}`, role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, className: className, style: {
2470
- position: "relative",
2471
- width: width || "100%",
2472
- borderCollapse: "collapse",
2473
- }, onClick: devMode ? (e) => e.preventDefault() : undefined, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { style: tdStyle, align: "center", children: content }) }) }), devMode && !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] }));
2474
- }
2475
- // ---------------------------------------------------------------------------
2476
- // Mobile table — HTML string (injected via NonMso, same pattern as Icon VML)
2477
- // ---------------------------------------------------------------------------
2478
- function buildMobileTableHTML({ config, overrides, className, }) {
2479
- const { src, alt, href, target } = config;
2480
- const { width, height, maxWidth, maxHeight, backgroundColor, padding, borderRadius, border, } = mergeConfig(config, overrides);
2481
- const borderStyleStr = getBorderStyleString$1(border);
2482
- const widthNum = (width === null || width === void 0 ? void 0 : width.endsWith("px")) ? parseInt(width, 10) : undefined;
2483
- const maxWidthNum = (maxWidth === null || maxWidth === void 0 ? void 0 : maxWidth.endsWith("px"))
2484
- ? parseInt(maxWidth, 10)
2485
- : undefined;
2486
- const heightNum = (height === null || height === void 0 ? void 0 : height.endsWith("px")) ? parseInt(height, 10) : undefined;
2487
- const resolvedWidth = widthNum && maxWidthNum
2488
- ? Math.min(widthNum, maxWidthNum)
2489
- : widthNum || maxWidthNum;
2490
- const imgTag = `<img
2491
- draggable="false"
2492
- src="${src}"
2493
- alt="${alt}"
2494
- ${resolvedWidth ? `width="${resolvedWidth}"` : ""}
2495
- ${heightNum ? `height="${heightNum}"` : ""}
2496
- border="0"
2497
- style="display:block;object-fit:cover;width:${width || "100%"};height:${height || "auto"};max-width:${maxWidth || "100%"};${maxHeight ? `max-height:${maxHeight};` : ""}border:0;${borderRadius ? `border-radius:${borderRadius};` : ""}${borderStyleStr}"
2498
- />`;
2499
- const content = href
2500
- ? `<a href="${href}" target="${target || "_self"}" style="display:block;text-decoration:none;border:0;outline:none;"${target === "_blank" ? ' rel="noopener noreferrer"' : ""}>${imgTag}</a>`
2501
- : imgTag;
2502
- return `
2503
- <table
2504
- aria-label="Image Wrapper for: ${alt}"
2505
- role="presentation"
2506
- cellpadding="0"
2507
- cellspacing="0"
2508
- border="0"
2509
- class="${className}"
2510
- style="position:relative;width:${width || "100%"};border-collapse:collapse;"
2511
- >
2512
- <tbody>
2513
- <tr>
2514
- <td
2515
- align="center"
2516
- style="padding:${padding || ""};background-color:${backgroundColor || ""};font-size:0;line-height:0;"
2517
- >
2518
- ${content}
2519
- </td>
2520
- </tr>
2521
- </tbody>
2522
- </table>
2523
- `;
2524
- }
2525
- // ---------------------------------------------------------------------------
2526
- // Component
2527
- // ---------------------------------------------------------------------------
2528
2389
  function Image({ config, devNode, devMode }) {
2529
- const { mobile } = config;
2530
- const hasMobileOverrides = !!mobile && !mobile.hidden;
2531
- const isHiddenOnMobile = !!(mobile === null || mobile === void 0 ? void 0 : mobile.hidden);
2532
- return (jsxs(Fragment$1, { children: [renderDesktopTable({
2533
- config,
2534
- className: hasMobileOverrides || isHiddenOnMobile ? "hide-on-mobile" : undefined,
2535
- devNode,
2536
- devMode,
2537
- }), hasMobileOverrides && !devMode && (jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: { width: "100%", borderCollapse: "collapse" }, children: jsx("tbody", { children: jsx("tr", { children: jsx(NonMso, { html: buildMobileTableHTML({
2538
- config,
2539
- overrides: mobile,
2540
- className: "hide-on-desktop",
2541
- }) }) }) }) }))] }));
2390
+ var _a;
2391
+ const { src, alt, href, target, mobile } = config;
2392
+ const seed = src + (alt || "");
2393
+ const instanceId = seed
2394
+ .split("")
2395
+ .reduce((acc, char) => acc + char.charCodeAt(0), 0)
2396
+ .toString(36);
2397
+ const imgClass = `img-${instanceId}`;
2398
+ // 1. Desktop Dimensional Logic
2399
+ const desktopWidth = config.width || "100%";
2400
+ const isPercent = desktopWidth.includes("%");
2401
+ const widthAttr = desktopWidth.replace("px", "");
2402
+ const heightAttr = (_a = config.height) === null || _a === void 0 ? void 0 : _a.replace("px", "");
2403
+ // Determine the table's "initial" width.
2404
+ // If it's 300px, the table should be 300px, not 100%.
2405
+ const tableWidth = isPercent ? desktopWidth : `${widthAttr}px`;
2406
+ // 2. Mobile Overrides (Every property used)
2407
+ let mobileCss = "";
2408
+ if (mobile) {
2409
+ mobileCss = `
2410
+ @media screen and (max-width: 768px) {
2411
+ .wrap-${imgClass} {
2412
+ /* This breaks the px lock from desktop and makes it fluid */
2413
+ width: ${mobile.width || "100%"} !important;
2414
+ max-width: ${mobile.maxWidth || "100%"} !important;
2415
+ min-width: 0 !important;
2416
+ }
2417
+ .td-${imgClass} {
2418
+ padding: ${mobile.padding || "0"} !important;
2419
+ background-color: ${mobile.backgroundColor || "transparent"} !important;
2420
+ width: 100% !important;
2421
+ }
2422
+ .${imgClass} {
2423
+ width: ${mobile.width || "100%"} !important;
2424
+ height: ${mobile.height || "auto"} !important;
2425
+ max-width: ${mobile.maxWidth || "100%"} !important;
2426
+ max-height: ${mobile.maxHeight || "none"} !important;
2427
+ border-radius: ${mobile.borderRadius || "0"} !important;
2428
+ display: ${mobile.hidden ? "none" : "block"} !important;
2429
+ object-fit: ${mobile.objectFit || "fill"} !important;
2430
+ object-position: ${mobile.objectPosition || "center"} !important;
2431
+ ${getBorderStyleString$1(mobile.border)}
2432
+ }
2433
+ }
2434
+ `;
2435
+ }
2436
+ const imgStyle = Object.assign(Object.assign({ display: "block", width: isPercent ? "100%" : desktopWidth, height: config.height || "auto", maxWidth: config.maxWidth || "100%", maxHeight: config.maxHeight || "none", borderRadius: config.borderRadius || "0" }, getBorderStyle$3(config.border)), { outline: "none", textDecoration: "none", objectFit: config.objectFit, objectPosition: config.objectPosition });
2437
+ const imageElement = (jsx("img", { src: src, alt: alt, width: !isPercent ? widthAttr : undefined, height: heightAttr !== "auto" ? heightAttr : undefined, className: imgClass, style: imgStyle }));
2438
+ 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
2439
+ , style: {
2440
+ width: tableWidth, // Fixed px here prevents the 100% "ghost space"
2441
+ maxWidth: "100%",
2442
+ borderCollapse: "collapse",
2443
+ margin: "0 auto",
2444
+ }, children: [jsx("tbody", { children: jsx("tr", { children: jsx("td", { className: `td-${imgClass}`, align: "center", style: {
2445
+ padding: config.padding,
2446
+ backgroundColor: config.backgroundColor,
2447
+ fontSize: "0",
2448
+ lineHeight: "0",
2449
+ width: tableWidth, // Lock the cell as well
2450
+ }, children: href && !devMode ? (jsx("a", { href: href, target: target, style: { display: "block", width: "100%" }, children: imageElement })) : (imageElement) }) }) }), devMode && !!devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] })] }));
2542
2451
  }
2543
2452
  var Image_default = memo(Image, arePropsEqual);
2544
2453
 
@@ -2613,35 +2522,31 @@ function Row({ children, config, devNode, devMode }) {
2613
2522
  const numChildren = childrenArray.length;
2614
2523
  const href = getHrefFromInnerLink(config.innerLink);
2615
2524
  const target = (_a = config.innerLink) === null || _a === void 0 ? void 0 : _a.target;
2616
- // 1. Outer TD for Background and Border Radius (no border here).
2617
- // height declared here is the *total* outer height.
2618
- const backgroundTdStyle = Object.assign({ backgroundColor: config.backgroundColor, borderRadius: config.borderRadius, width: config.width || "100%", height: config.height,
2619
- // Background Image styles
2620
- backgroundImage: config.backgroundImage
2525
+ // 1. Outer TD: Background, Border Radius, Width, Height.
2526
+ const backgroundTdStyle = Object.assign({ backgroundColor: config.backgroundColor, borderRadius: config.borderRadius, width: config.width || "100%", height: config.height, backgroundImage: config.backgroundImage
2621
2527
  ? `url(${config.backgroundImage.src})`
2622
2528
  : undefined, backgroundRepeat: (_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat, backgroundSize: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size, backgroundPosition: (_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position }, (config.borderRadius && { overflow: "hidden" }));
2623
- // 2. Inner Table for Border and Border Radius.
2624
- // height: 100% so it stretches to fill the outer TD.
2529
+ // 2. Inner Table: Border and Border Radius.
2625
2530
  const borderTableStyle = Object.assign({ width: "100%", height: "100%", borderCollapse: "separate", borderSpacing: 0, borderRadius: config.borderRadius }, getBorderStyle$2(config.border));
2626
- // 3. TD for Padding only — no height.
2627
- // The outer TD owns the total height; setting height here would cause
2628
- // browsers/email clients to treat it as content-box height and add
2629
- // padding on top, making the row taller than the declared height.
2531
+ // 3. Padding TD.
2630
2532
  const paddingTdStyle = {
2631
2533
  padding: config.padding,
2632
2534
  width: "100%",
2633
- // height intentionally omitted — padding must be inner, not additive
2634
2535
  verticalAlign: "top",
2635
2536
  };
2636
- // 4. Content Table - horizontal layout
2637
- const contentTableStyle = {
2638
- width: "auto",
2639
- height: "100%",
2640
- borderCollapse: "collapse",
2641
- minWidth: "1px",
2642
- maxWidth: config.width || "100%",
2643
- };
2644
- // 5. Gap styles for horizontal spacing between children
2537
+ // 4. Content Table.
2538
+ //
2539
+ // fillWidth: false/undefined (default) → width: "auto"
2540
+ // Original behavior. Children shrink-wrap to their natural sizes.
2541
+ // Use for icon rows, button rows, social link rows.
2542
+ // Centering works via the Justification Wrapper TD (align + width="100%").
2543
+ //
2544
+ // fillWidth: true → width: "100%"
2545
+ // Content table fills available space, giving Outlook Classic a hard
2546
+ // boundary so text children get a constrained box and line wrapping
2547
+ // triggers correctly. Use for rows containing text + image layouts.
2548
+ const contentTableStyle = Object.assign({ width: config.fillWidth ? "100%" : "auto", height: "100%", borderCollapse: "collapse", minWidth: "1px" }, (!config.fillWidth && { maxWidth: config.width || "100%" }));
2549
+ // 5. Gap TD.
2645
2550
  const gapTdStyle = {
2646
2551
  width: config.gap || "0",
2647
2552
  lineHeight: "1px",
@@ -2651,7 +2556,6 @@ function Row({ children, config, devNode, devMode }) {
2651
2556
  ? justifyMap$1[config.justifyContent]
2652
2557
  : "left";
2653
2558
  const tdValign = config.alignItems ? alignMap[config.alignItems] : "top";
2654
- // Content to render - wrapped in anchor if innerLink is defined
2655
2559
  const content = (jsxs("table", Object.assign({ "aria-label": "Row Outer", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
2656
2560
  position: "relative",
2657
2561
  width: config.width || "100%",
@@ -2661,14 +2565,13 @@ function Row({ children, config, devNode, devMode }) {
2661
2565
  width: "100%",
2662
2566
  height: "100%",
2663
2567
  borderCollapse: "collapse",
2664
- }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { align: tdAlign, children: jsx("table", Object.assign({ "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle }, (config.height && { height: config.height }), { className: "content-table row-content-table", "data-mobile-justify": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.justifyContent, "data-mobile-align": (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.alignItems, "data-mobile-wrap": ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsx("tbody", { children: jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxs(Fragment, { children: [jsx("td", { valign: tdValign, style: {
2568
+ }, children: jsx("tbody", { children: jsx("tr", { children: jsx("td", { align: tdAlign, width: "100%", style: { width: "100%" }, children: jsx("table", Object.assign({ "aria-label": "Row Content", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: contentTableStyle }, (config.height && { height: config.height }), { className: "content-table row-content-table", "data-mobile-justify": (_e = config.mobile) === null || _e === void 0 ? void 0 : _e.justifyContent, "data-mobile-align": (_f = config.mobile) === null || _f === void 0 ? void 0 : _f.alignItems, "data-mobile-wrap": ((_g = config.mobile) === null || _g === void 0 ? void 0 : _g.wrap) ? "true" : undefined, "data-gap": config.gap, children: jsx("tbody", { children: jsx("tr", { className: "content-tr", children: childrenArray.map((child, index) => (jsxs(Fragment, { children: [jsx("td", { valign: tdValign, style: {
2665
2569
  verticalAlign: tdValign,
2666
2570
  textAlign: "left",
2667
2571
  padding: "0",
2668
2572
  margin: "0",
2669
2573
  }, className: "child-cell", children: child }), index < numChildren - 1 &&
2670
2574
  config.gap && (jsx("td", { width: config.gap, style: gapTdStyle, className: "row-gap-td", children: "\u00A0" }, `row-gap-${index}`))] }, `row-child-${index}`))) }) }) })) }) }) }) }) }) }) }) }) })) }) }), devNode && (jsx("tfoot", { children: jsx("tr", { children: jsx("td", { children: devNode }) }) }))] })));
2671
- // Wrap in anchor tag if innerLink is defined and NOT in dev mode
2672
2575
  if (href && !devMode) {
2673
2576
  return (jsx("a", Object.assign({ href: href }, (target && { target }), { style: {
2674
2577
  textDecoration: "none",
@@ -2770,7 +2673,7 @@ function Spacer({ config, devNode }) {
2770
2673
  var Spacer_default = memo(Spacer, arePropsEqual);
2771
2674
 
2772
2675
  function Text({ config, devMode, children }) {
2773
- const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", } = config;
2676
+ const { text, padding, color, textAlign, fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textTransform, textDecoration, direction, verticalAlign, backgroundColor, opacity, whiteSpace, wordBreak = "break-all", maxWidth } = config;
2774
2677
  // 1. TD Style: Where padding and background are reliably applied.
2775
2678
  const tdStyle = {
2776
2679
  padding: padding,
@@ -2797,6 +2700,7 @@ function Text({ config, devMode, children }) {
2797
2700
  wordBreak: wordBreak,
2798
2701
  margin: "0",
2799
2702
  padding: "0",
2703
+ maxWidth
2800
2704
  };
2801
2705
  // Determine content to render
2802
2706
  const content = text !== null && text !== void 0 ? text : children;
@@ -3076,5 +2980,24 @@ function Icon({ config, devNode, devMode, children }) {
3076
2980
  }
3077
2981
  var Icon_default = memo(Icon, arePropsEqual);
3078
2982
 
2983
+ /**
2984
+ * Content rendered by Outlook Classic only.
2985
+ * Outputs: <!--[if mso]> ... <![endif]-->
2986
+ */
2987
+ function MsoOnly({ html }) {
2988
+ return (jsx("td", { dangerouslySetInnerHTML: {
2989
+ __html: `<!--[if mso]>${html}<![endif]-->`,
2990
+ } }));
2991
+ }
2992
+ /**
2993
+ * Content hidden from Outlook Classic, visible in all other clients.
2994
+ * Outputs: <!--[if !mso]><!--> ... <!--<![endif]-->
2995
+ */
2996
+ function NonMso({ html }) {
2997
+ return (jsx("td", { dangerouslySetInnerHTML: {
2998
+ __html: `<!--[if !mso]><!-->${html}<!--<![endif]-->`,
2999
+ } }));
3000
+ }
3001
+
3079
3002
  export { Body, Button_default as Button, Column_default as Column, Container_default as Container, Divider_default as Divider, Font, Head, Heading_default as Heading, Html, Icon_default as Icon, Image_default as Image, MsoOnly, NonMso, Row_default as Row, Section_default as Section, Spacer_default as Spacer, Text_default as Text };
3080
3003
  //# sourceMappingURL=index.esm.js.map