email-builder-utils 1.1.52 → 1.1.54

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.
@@ -33,6 +33,8 @@ interface IProps {
33
33
  shape?: string;
34
34
  hideOnDesktop?: boolean;
35
35
  hideOnMobile?: boolean;
36
+ cellWidthUnit?: 'px' | '%';
37
+ widthUnit?: 'px' | '%';
36
38
  }
37
39
  interface IStyle {
38
40
  [key: string]: any;
@@ -1 +1 @@
1
- {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
1
+ {"version":3,"file":"Template.d.ts","sourceRoot":"","sources":["../../src/types/Template.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,IAAI,YAAY;IAChB,KAAK,UAAU;IACf,QAAQ,WAAW;IACnB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;IAC3B,KAAK,UAAU;IACf,KAAK,UAAU;IACf,QAAQ,aAAa;CACtB;AAED,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,WAAW,gBAAgB;CAC5B;AAED,UAAU,MAAM;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC;CACxB;AAED,UAAU,MAAM;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/button.ts"],"names":[],"mappings":"AA6IA,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,UAuFhD"}
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/button.ts"],"names":[],"mappings":"AA2IA,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,UAuFhD"}
@@ -55,15 +55,15 @@ function appendOutlookForButton(buttonData) {
55
55
  const tableMargin = containerAlign === 'center' ? 'margin:0 auto;'
56
56
  : containerAlign === 'right' ? 'margin-left:auto;margin-right:0;'
57
57
  : '';
58
- const nonMsoAnchor = `<!--[if !mso]><!-->
59
- <table border="0" cellpadding="0" cellspacing="0" role="presentation"${tableAlignAttr} style="border-collapse:separate;${tableMargin}">
60
- <tr>
61
- <td bgcolor="${bgColor}" align="center" valign="middle"${tdWidthAttr}${tdHeightAttr} style="background-color:${bgColor};border-radius:${br}px;${borderCss}${tdWidthCss}${tdHeightCss}box-sizing:border-box;mso-padding-alt:0;text-align:center;">
62
- <a href="${safeHref}" target="_blank" rel="noreferrer noopener"
63
- style="${anchorBoxStyles}color:${safeColor};font-family:${safeFF};font-size:${fs}px;font-weight:${fontWeight};text-decoration:none;text-align:center;white-space:nowrap;-webkit-text-size-adjust:none;box-sizing:border-box;">${text}</a>
64
- </td>
65
- </tr>
66
- </table>
58
+ const nonMsoAnchor = `<!--[if !mso]><!-->
59
+ <table border="0" cellpadding="0" cellspacing="0" role="presentation"${tableAlignAttr} style="border-collapse:separate;${tableMargin}">
60
+ <tr>
61
+ <td bgcolor="${bgColor}" align="center" valign="middle"${tdWidthAttr}${tdHeightAttr} style="background-color:${bgColor};border-radius:${br}px;${borderCss}${tdWidthCss}${tdHeightCss}box-sizing:border-box;mso-padding-alt:0;text-align:center;">
62
+ <a href="${safeHref}" target="_blank" rel="noreferrer noopener"
63
+ style="${anchorBoxStyles}color:${safeColor};font-family:${safeFF};font-size:${fs}px;font-weight:${fontWeight};text-decoration:none;text-align:center;white-space:nowrap;-webkit-text-size-adjust:none;box-sizing:border-box;">${text}</a>
64
+ </td>
65
+ </tr>
66
+ </table>
67
67
  <!--<![endif]-->`;
68
68
  // ── MSO: VML bulletproof button (classic Outlook / Word engine) ──
69
69
  // VML arcsize is a percentage of half the shorter side. Clamp to 50% (pill).
@@ -71,18 +71,16 @@ function appendOutlookForButton(buttonData) {
71
71
  const strokeAttrs = bw > 0
72
72
  ? `stroke="true" strokecolor="${bdColor}" strokeweight="${bw}px"`
73
73
  : `stroke="false"`;
74
- const msoButton = `<!--[if mso]>
75
- <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
76
- href="${safeHref}"
77
- style="height:${finalHeight}px;v-text-anchor:middle;width:${vmlWidth}px;"
78
- arcsize="${arcSizePct}%"
79
- ${strokeAttrs}
80
- fillcolor="${bgColor}">
81
- <w:anchorlock/>
82
- <v:textbox inset="0,0,0,0">
83
- <center style="color:${safeColor};font-family:${safeFF};font-size:${fs}px;font-weight:${fontWeight};mso-line-height-rule:exactly;line-height:${finalHeight}px;text-decoration:none;">${text}</center>
84
- </v:textbox>
85
- </v:roundrect>
74
+ const msoButton = `<!--[if mso]>
75
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
76
+ href="${safeHref}"
77
+ style="height:${finalHeight}px;v-text-anchor:middle;width:${vmlWidth}px;"
78
+ arcsize="${arcSizePct}%"
79
+ ${strokeAttrs}
80
+ fillcolor="${bgColor}">
81
+ <w:anchorlock/>
82
+ <center style="color:${safeColor};font-family:${safeFF};font-size:${fs}px;font-weight:${fontWeight};mso-line-height-rule:exactly;line-height:${fs}px;text-decoration:none;">${text}</center>
83
+ </v:roundrect>
86
84
  <![endif]-->`;
87
85
  const innerContent = containerAlign === "center"
88
86
  ? `<center>${msoButton}${nonMsoAnchor}</center>`
@@ -167,14 +165,14 @@ function convertButtonBlock(blockData) {
167
165
  hideOnDesktop: Boolean(props.hideOnDesktop),
168
166
  hideOnMobile: Boolean(props.hideOnMobile),
169
167
  });
170
- return `
171
- <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" class="${visibilityClass}" data-block-type="button" data-block-props="${buttonBlockProps}" style="border-collapse:collapse;table-layout:fixed;">
172
- <tr>
173
- <td align="${computed.containerAlign}"${(0, common_1.buildOutlookBgAttr)(containerBg)}
174
- style="padding:${padding?.top || 0}px ${padding?.right || 0}px ${padding?.bottom || 0}px ${padding?.left || 0}px;background-color:${containerBg || 'transparent'};">
175
- ${innerContent}
176
- </td>
177
- </tr>
178
- </table>
168
+ return `
169
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" class="${visibilityClass}" data-block-type="button" data-block-props="${buttonBlockProps}" style="border-collapse:collapse;table-layout:fixed;">
170
+ <tr>
171
+ <td align="${computed.containerAlign}"${(0, common_1.buildOutlookBgAttr)(containerBg)}
172
+ style="padding:${padding?.top || 0}px ${padding?.right || 0}px ${padding?.bottom || 0}px ${padding?.left || 0}px;background-color:${containerBg || 'transparent'};">
173
+ ${innerContent}
174
+ </td>
175
+ </tr>
176
+ </table>
179
177
  `;
180
178
  }
@@ -21,23 +21,23 @@ function convertDividerBlockToHtml(blockData) {
21
21
  hideOnMobile ? "hide-mobile" : "",
22
22
  hideOnDesktop ? "hide-desktop" : "",
23
23
  ].filter(Boolean).join(" ");
24
- const dividerContent = `
25
- <table
26
- align="${alignAttr}"
27
- width="${dividerWidth}%"
28
- cellpadding="0"
29
- cellspacing="0"
30
- style="margin:${alignMargin};"
31
- >
32
- <tr>
33
- <td
34
- height="${thickness}"
35
- style="font-size:1px; line-height:1px; background:${dividerColor}; width:100%;"
36
- >
37
- &nbsp;
38
- </td>
39
- </tr>
40
- </table>
24
+ const dividerContent = `
25
+ <table
26
+ align="${alignAttr}"
27
+ width="${dividerWidth}%"
28
+ cellpadding="0"
29
+ cellspacing="0"
30
+ style="margin:${alignMargin};"
31
+ >
32
+ <tr>
33
+ <td
34
+ height="${thickness}"
35
+ style="font-size:1px; line-height:1px; background:${dividerColor}; width:100%;"
36
+ >
37
+ &nbsp;
38
+ </td>
39
+ </tr>
40
+ </table>
41
41
  `;
42
42
  return (0, outlookSupport_1.appendOutlookSupport)(dividerContent, contentStyle, visibilityClass);
43
43
  }
@@ -55,18 +55,18 @@ function convertVerticalDividerBlockToHtml(blockData) {
55
55
  perChanges: [],
56
56
  pxChanges: buildStyles_1.allPxAttributes,
57
57
  });
58
- return `
59
- <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
60
- style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}" data-block-type="vdivider">
61
- <tr>
62
- <td style="${outerStyles}; text-align:center; vertical-align:middle;">
63
- <!--[if mso | IE]>
64
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fillcolor="${dividerColor}" style="width:${width}px;height:${height}px;" stroke="f"></v:rect>
65
- <![endif]-->
66
- <!--[if !mso]><!-->
67
- <div style="display:inline-block;width:${width}px;height:${height}px;background:${dividerColor};line-height:0;font-size:0;">&nbsp;</div>
68
- <!--<![endif]-->
69
- </td>
70
- </tr>
58
+ return `
59
+ <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
60
+ style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}" data-block-type="vdivider">
61
+ <tr>
62
+ <td style="${outerStyles}; text-align:center; vertical-align:middle;">
63
+ <!--[if mso | IE]>
64
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fillcolor="${dividerColor}" style="width:${width}px;height:${height}px;" stroke="f"></v:rect>
65
+ <![endif]-->
66
+ <!--[if !mso]><!-->
67
+ <div style="display:inline-block;width:${width}px;height:${height}px;background:${dividerColor};line-height:0;font-size:0;">&nbsp;</div>
68
+ <!--<![endif]-->
69
+ </td>
70
+ </tr>
71
71
  </table>`;
72
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/grid.ts"],"names":[],"mappings":"AAMA,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBA0TtB;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,UAAQ;;;GAwG5B"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/grid.ts"],"names":[],"mappings":"AAMA,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBA2TtB;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,UAAQ;;;GAwG5B"}
@@ -9,7 +9,7 @@ const jsonToHTML_1 = require("../jsonToHTML");
9
9
  async function convertGridBlock(blockData, rootData, cellWidthInPx) {
10
10
  const { style = {}, childrenIds = [], props } = blockData.data;
11
11
  const { columns = 1, cellWidths = [], responsive = true } = props;
12
- const { columnGap = 0, backgroundImage, backgroundColor, ...restStyle } = style;
12
+ const { columnGap = 0, backgroundImage, backgroundColor, cellWidthUnit, ...restStyle } = style;
13
13
  const gridVisibilityClass = (0, common_1.getVisibilityClass)(props);
14
14
  // Detect gradient — check both backgroundImage prop and customCss (gradient may land in
15
15
  // customCss when the block was built via CSS shorthand or custom CSS input).
@@ -64,6 +64,16 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
64
64
  perChanges: [],
65
65
  pxChanges: buildStyles_1.allPxAttributes,
66
66
  });
67
+ // When cellWidthUnit is 'px', convert the stored pixel values to percentage ratios
68
+ // so the rest of the grid export logic (which works entirely in %) is unchanged.
69
+ const effectiveCellWidths = (cellWidthUnit || '%') === 'px'
70
+ ? (() => {
71
+ const total = cellWidths.reduce((s, w) => s + w, 0);
72
+ return total > 0
73
+ ? cellWidths.map((w) => Number(((w / total) * 100).toFixed(2)))
74
+ : cellWidths.map(() => 100 / columns);
75
+ })()
76
+ : cellWidths;
67
77
  const total = childrenIds.length;
68
78
  const visualRows = Math.ceil(total / columns);
69
79
  // OUTLOOK FIX: Use explicit pixel width for Old Outlook (Word engine)
@@ -91,18 +101,18 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
91
101
  : '';
92
102
  const divWrapOpen = divBorderStyle ? `<div style="${divBorderStyle}${divWrapBg}">` : '';
93
103
  const divWrapClose = divBorderStyle ? `</div>` : '';
94
- let html = `
95
- <!--[if mso]>
96
- <table border="0" cellpadding="0" cellspacing="0" width="${msoTableWidth}"${msoBgAttr}
97
- style="border-collapse:collapse;width:${msoTableWidth}px;${msoBgStyle}${innerBgTransparent}"
98
- class="${gridVisibilityClass}">
99
- <![endif]-->
100
- <!--[if !mso]><!-->
101
- <table border="0" cellpadding="0" cellspacing="0" width="100%" align="center"
102
- role="presentation"${nonMsoBgAttr}
103
- style="border-collapse:collapse; table-layout:fixed; ${innerBgTransparent}${tableStyles}; max-width:600px;"
104
- class="${gridVisibilityClass}">
105
- <!--<![endif]-->
104
+ let html = `
105
+ <!--[if mso]>
106
+ <table border="0" cellpadding="0" cellspacing="0" width="${msoTableWidth}"${msoBgAttr}
107
+ style="border-collapse:collapse;width:${msoTableWidth}px;${msoBgStyle}${innerBgTransparent}"
108
+ class="${gridVisibilityClass}">
109
+ <![endif]-->
110
+ <!--[if !mso]><!-->
111
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" align="center"
112
+ role="presentation"${nonMsoBgAttr}
113
+ style="border-collapse:collapse; table-layout:fixed; ${innerBgTransparent}${tableStyles}; max-width:600px;"
114
+ class="${gridVisibilityClass}">
115
+ <!--<![endif]-->
106
116
  `;
107
117
  for (let r = 0; r < visualRows; r++) {
108
118
  html += "<tr>";
@@ -129,7 +139,7 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
129
139
  const cellWidthPercents = [];
130
140
  for (let c = 0; c < columns; c++) {
131
141
  const id = rowIds[c];
132
- let widthPercent = cellWidths[c] ?? safeWidth;
142
+ let widthPercent = effectiveCellWidths[c] ?? safeWidth;
133
143
  if (widthPercent <= 0 || widthPercent > 100) {
134
144
  widthPercent = safeWidth;
135
145
  }
@@ -157,57 +167,47 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
157
167
  const visibilityClass = (0, common_1.getVisibilityClass)(childProps);
158
168
  if (childVisible) {
159
169
  const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, adjustedTableWidth, Boolean(divBorderStyle));
160
- // bgcolor on the cell <td> for Old Outlook (Word engine).
161
- // When the cell has no border-radius and the grid has no border, use the grid's
162
- // backgroundColor as the fallback so the grid background shows through empty cells.
163
- // Skipped for border-radius cells — bgcolor is rectangular and would bleed through
164
- // the div's rounded-corner clip in CSS-capable clients (corner squares).
170
+ // bgcolor on the cell <td> ensures background-color survives Outlook
171
+ // compose paste (Word/Web editors strip CSS but keep bgcolor attribute).
165
172
  const cellBgColor = cellStyle.backgroundColor || '';
166
- const cellHasBorderRadius = Boolean(cellStyle.borderRadius);
167
- const canApplyGridBgFallback = !cellHasBorderRadius && !divBorderStyle;
168
- const effectiveCellBg = cellBgColor || (canApplyGridBgFallback ? msoBgColor : '');
169
- const cellBgAttr = (0, common_1.buildOutlookBgAttr)(effectiveCellBg);
170
- html += `
171
- <td
172
- width="${Math.max(1, Math.round(widthPercent))}%"${cellBgAttr}
173
- class="${[responsive ? "stack-column" : "", visibilityClass].filter(Boolean).join(" ")}"
174
- style="width:${widthPercent}%;vertical-align:${verticalAlign};word-break:break-word;${styles}"
175
- >
176
- ${childHtml}
173
+ const cellBgAttr = (0, common_1.buildOutlookBgAttr)(cellBgColor);
174
+ html += `
175
+ <td
176
+ width="${Math.max(1, Math.round(widthPercent))}%"${cellBgAttr}
177
+ class="${[responsive ? "stack-column" : "", visibilityClass].filter(Boolean).join(" ")}"
178
+ style="width:${widthPercent}%;vertical-align:${verticalAlign};word-break:break-word;${styles}"
179
+ >
180
+ ${childHtml}
177
181
  </td>`;
178
182
  // Spacer td between columns — uses width attribute only (no inline style width) so
179
183
  // Outlook mobile (which strips <style>) treats it proportionally, not as a fixed blocker.
180
184
  // col-gap-spacer class hides it when columns stack via CSS media query.
181
185
  if (columnGap > 0 && c !== lastVisibleCol) {
182
- const gapBgAttr = divBorderStyle ? '' : (0, common_1.buildOutlookBgAttr)(msoBgColor);
183
- html += `<td width="${columnGap}"${gapBgAttr} class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
186
+ html += `<td width="${columnGap}" class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
184
187
  }
185
188
  }
186
189
  }
187
190
  else {
188
- const emptyBgAttr = divBorderStyle ? '' : (0, common_1.buildOutlookBgAttr)(msoBgColor);
189
- html += `
190
- <td width="${Math.max(1, Math.round(widthPercent))}%"
191
- ${emptyBgAttr}
192
- ${responsive ? 'class="stack-column"' : ""}
193
- style="width:${widthPercent}%;vertical-align:top;">
191
+ html += `
192
+ <td width="${Math.max(1, Math.round(widthPercent))}%"
193
+ ${responsive ? 'class="stack-column"' : ""}
194
+ style="width:${widthPercent}%;vertical-align:top;">
194
195
  </td>`;
195
196
  if (columnGap > 0 && c !== lastVisibleCol) {
196
- const gapBgAttr = divBorderStyle ? '' : (0, common_1.buildOutlookBgAttr)(msoBgColor);
197
- html += `<td width="${columnGap}"${gapBgAttr} class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
197
+ html += `<td width="${columnGap}" class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
198
198
  }
199
199
  }
200
200
  }
201
201
  html += "</tr>";
202
202
  }
203
203
  // Close both MSO and non-MSO tables
204
- html += `
205
- <!--[if mso]>
206
- </table>
207
- <![endif]-->
208
- <!--[if !mso]><!-->
209
- </table>
210
- <!--<![endif]-->
204
+ html += `
205
+ <!--[if mso]>
206
+ </table>
207
+ <![endif]-->
208
+ <!--[if !mso]><!-->
209
+ </table>
210
+ <!--<![endif]-->
211
211
  `;
212
212
  // ── Background image: canonical multi-client approach ────────────────────
213
213
  //
@@ -233,35 +233,35 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
233
233
  return `<v:fill type="gradient" color="${c1}" color2="${c2}" angle="${vmlAngle}" />`;
234
234
  })()
235
235
  : `<v:fill type="frame" src="${rawBgImageUrl}" color="${fallbackBgColor}" />`;
236
- html = `
237
- <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
238
- style="border-collapse:collapse;table-layout:fixed;width:100%;max-width:${msoTableWidth}px;">
239
- <tr>
240
- <td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
241
- ${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
242
- style="
243
- width:100%;max-width:${msoTableWidth}px;
244
- background-color:${fallbackBgColor};
245
- ${isGradient ? `background:${effectiveGradient};` : `background-image:url('${rawBgImageUrl}'); background-position:center center; background-size:cover; background-repeat:no-repeat;`}
246
- ">
247
-
248
- <!--[if gte mso 9]>
249
- <v:rect xmlns:v="urn:schemas-microsoft-com:vml"
250
- fill="true" stroke="false"
251
- style="width:${msoTableWidth}px;">
252
- ${vmlFill}
253
- <v:textbox inset="0,0,0,0">
254
- <![endif]-->
255
-
256
- ${html}
257
-
258
- <!--[if gte mso 9]>
259
- </v:textbox>
260
- </v:rect>
261
- <![endif]-->
262
-
263
- </td>
264
- </tr>
236
+ html = `
237
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
238
+ style="border-collapse:collapse;table-layout:fixed;width:100%;max-width:${msoTableWidth}px;">
239
+ <tr>
240
+ <td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
241
+ ${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
242
+ style="
243
+ width:100%;max-width:${msoTableWidth}px;
244
+ background-color:${fallbackBgColor};
245
+ ${isGradient ? `background:${effectiveGradient};` : `background-image:url('${rawBgImageUrl}'); background-position:center center; background-size:cover; background-repeat:no-repeat;`}
246
+ ">
247
+
248
+ <!--[if gte mso 9]>
249
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml"
250
+ fill="true" stroke="false"
251
+ style="width:${msoTableWidth}px;">
252
+ ${vmlFill}
253
+ <v:textbox inset="0,0,0,0">
254
+ <![endif]-->
255
+
256
+ ${html}
257
+
258
+ <!--[if gte mso 9]>
259
+ </v:textbox>
260
+ </v:rect>
261
+ <![endif]-->
262
+
263
+ </td>
264
+ </tr>
265
265
  </table>`;
266
266
  }
267
267
  // Wrap the entire grid (including any bg-image outer table) in a div when the block
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/image.ts"],"names":[],"mappings":"AAIA,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;;;;;GAoB5B;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAiH5E"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/image.ts"],"names":[],"mappings":"AAIA,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;;;;;GAoB5B;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAyH5E"}
@@ -28,24 +28,31 @@ async function convertImageBlock(blockData, cellWidthInPx) {
28
28
  const { style, props } = blockData.data;
29
29
  const { altText, imageUrl, navigateToUrl } = props;
30
30
  const visibilityClass = (0, common_1.getVisibilityClass)(props);
31
- const { width, height, objectFit, borderRadius, borderWidth, borderColor, borderStyle, ...containerStyle } = style;
31
+ const { width, widthUnit, height, objectFit, borderRadius, borderWidth, borderColor, borderStyle, ...containerStyle } = style;
32
32
  // Add border styles to container for fallback clients
33
33
  const containerStyles = (0, buildStyles_1.buildStyles)({
34
34
  ...containerStyle,
35
35
  }, { perChanges: [], pxChanges: buildStyles_1.addPxToAttributes });
36
36
  // OUTLOOK FIX: Ensure cellWidthInPx never exceeds 600px
37
37
  const safeCellWidth = Math.min(cellWidthInPx, 600);
38
- // Parse width percentage (default 100%)
39
- const widthPercent = typeof width === "string" && width.includes("%")
40
- ? parseInt(width.replace("%", ""))
41
- : typeof width === "number"
42
- ? width
43
- : 100;
44
38
  // OUTLOOK FIX: Calculate inner container width based on safe cell width
45
39
  const paddingLeft = style?.padding?.left || 0;
46
40
  const paddingRight = style?.padding?.right || 0;
47
41
  const availableWidth = safeCellWidth - paddingLeft - paddingRight;
48
- const innerContainerWidth = Math.round((widthPercent / 100) * availableWidth);
42
+ // Resolve inner container width px values are capped to the available space;
43
+ // % values are a fraction of available space.
44
+ let innerContainerWidth;
45
+ if ((widthUnit || '%') === 'px') {
46
+ innerContainerWidth = Math.min(Math.round(Number(width) || availableWidth), availableWidth);
47
+ }
48
+ else {
49
+ const widthPercent = typeof width === "string" && width.includes("%")
50
+ ? parseInt(width.replace("%", ""))
51
+ : typeof width === "number"
52
+ ? width
53
+ : 100;
54
+ innerContainerWidth = Math.round((widthPercent / 100) * availableWidth);
55
+ }
49
56
  // Get image dimensions and calculate scaled sizes
50
57
  const { originalWidth, originalHeight, scaledWidth, scaledHeight } = await computeScaledDimensions(imageUrl, innerContainerWidth);
51
58
  // OUTLOOK FIX: For Outlook, we need exact pixel dimensions
@@ -71,11 +78,13 @@ async function convertImageBlock(blockData, cellWidthInPx) {
71
78
  // -ms-interpolation-mode → inline fallback; the <style> block is stripped by
72
79
  // Outlook during forward/reply MIME rewriting
73
80
  const imageElement = `<img src="${imageUrl}" alt="${altText || "Image"}" border="0" width="${finalWidth}" height="${finalHeight}" style="${imageTagStyles}; display:block; width:${finalWidth}px; height:${finalHeight}px; max-width:100%; line-height:0; -ms-interpolation-mode:bicubic;" />`;
74
- const percentWidth = typeof width === "string" && width.endsWith("%")
75
- ? width
76
- : typeof width === "number"
77
- ? `${width}%`
78
- : "100%";
81
+ const percentWidth = (widthUnit || '%') === 'px'
82
+ ? `${innerContainerWidth}px`
83
+ : typeof width === "string" && width.endsWith("%")
84
+ ? width
85
+ : typeof width === "number"
86
+ ? `${width}%`
87
+ : "100%";
79
88
  // Non-MSO wrapper: display:block removes the phantom inline-baseline gap that
80
89
  // display:inline-block creates in Gmail / Apple Mail / Yahoo between images.
81
90
  // margin handles alignment since text-align won't move block elements.