@pagenflow/email 1.4.5 → 1.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagenflow/email",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "Free Email Compatible Components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -1,57 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export default function Body({ children, config = {} }) {
3
- var _a, _b, _c, _d;
4
- // Extract config values with fallbacks
5
- const globalColor = config.color || "#000000";
6
- const globalFontSize = config.fontSize || "16px";
7
- const globalBackgroundColor = config.backgroundColor || "#ffffff";
8
- const globalLineHeight = config.lineHeight || "1.4";
9
- const globalFontFamily = config.fontFamily || "Arial, Helvetica, sans-serif";
10
- // Background image properties
11
- const bgImage = ((_a = config.backgroundImage) === null || _a === void 0 ? void 0 : _a.src) || "";
12
- const bgRepeat = ((_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat) || "no-repeat";
13
- const bgSize = ((_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size) || "cover";
14
- const bgPosition = ((_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position) || "center";
15
- // 1. Style for the <body> tag inline
16
- const bodyStyle = {
17
- backgroundColor: globalBackgroundColor,
18
- color: globalColor,
19
- fontSize: globalFontSize,
20
- lineHeight: globalLineHeight,
21
- padding: "0",
22
- margin: "0",
23
- WebkitTextSizeAdjust: "100%",
24
- overflowX: "hidden",
25
- ["msTextSizeAdjust"]: "100%",
26
- ["msoLineHeightRule"]: "exactly",
27
- fontFamily: globalFontFamily,
28
- // Background image support (if provided)
29
- ...(bgImage && {
30
- backgroundImage: `url(${bgImage})`,
31
- backgroundRepeat: bgRepeat,
32
- backgroundSize: bgSize,
33
- backgroundPosition: bgPosition,
34
- }),
35
- };
36
- // 2. Style for the top-level <table> wrapper
37
- const outerTableStyle = {
38
- width: "100%",
39
- ["msoLineHeightRule"]: "exactly",
40
- borderCollapse: "collapse",
41
- };
42
- return (_jsxs("body", { style: bodyStyle, children: [_jsx("center", { style: {
43
- width: "100%",
44
- background: globalBackgroundColor,
45
- ...(bgImage && {
46
- backgroundImage: `url(${bgImage})`,
47
- backgroundRepeat: bgRepeat,
48
- backgroundSize: bgSize,
49
- backgroundPosition: bgPosition,
50
- }),
51
- }, children: _jsx("table", { role: "presentation", border: 0, cellPadding: 0, cellSpacing: 0, align: "center", width: "100%", style: outerTableStyle, children: _jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { align: "center", style: { padding: "0", margin: "0" }, children: children }) }) }) }) }), _jsx("div", { style: {
52
- display: "none",
53
- whiteSpace: "nowrap",
54
- font: "15px courier",
55
- lineHeight: "0",
56
- }, children: "\u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0 \u00A0" })] }));
57
- }
@@ -1,57 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- export default function BodyDev({ children, config = {} }) {
3
- var _a, _b, _c, _d;
4
- // Extract config values with fallbacks
5
- const globalColor = config.color || "#000000";
6
- const globalFontSize = config.fontSize || "16px";
7
- const globalBackgroundColor = config.backgroundColor || "#ffffff";
8
- const globalLineHeight = config.lineHeight || "1.4";
9
- // Background image properties
10
- const bgImage = ((_a = config.backgroundImage) === null || _a === void 0 ? void 0 : _a.src) || "";
11
- const bgRepeat = ((_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.repeat) || "no-repeat";
12
- const bgSize = ((_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.size) || "cover";
13
- const bgPosition = ((_d = config.backgroundImage) === null || _d === void 0 ? void 0 : _d.position) || "center";
14
- // Main container style (simulates body behavior in dev mode)
15
- const bodyDevStyle = {
16
- backgroundColor: globalBackgroundColor,
17
- color: globalColor,
18
- fontSize: globalFontSize,
19
- lineHeight: globalLineHeight,
20
- padding: "0",
21
- margin: "0",
22
- fontFamily: "Arial, Helvetica, sans-serif",
23
- overflowX: "hidden",
24
- // Background image support (if provided)
25
- ...(bgImage && {
26
- backgroundImage: `url(${bgImage})`,
27
- backgroundRepeat: bgRepeat,
28
- backgroundSize: bgSize,
29
- backgroundPosition: bgPosition,
30
- }),
31
- };
32
- // // Center wrapper style
33
- // const centerStyle: CSSProperties = {
34
- // width: '100%',
35
- // minHeight: '100vh',
36
- // display: 'flex',
37
- // justifyContent: 'center',
38
- // alignItems: 'flex-start',
39
- // background: globalBackgroundColor,
40
- // ...(bgImage && {
41
- // backgroundImage: `url(${bgImage})`,
42
- // backgroundRepeat: bgRepeat,
43
- // backgroundSize: bgSize,
44
- // backgroundPosition: bgPosition,
45
- // }),
46
- // };
47
- // // Table wrapper style
48
- // const tableWrapperStyle: CSSProperties = {
49
- // width: '100%',
50
- // maxWidth: '100%',
51
- // };
52
- return (_jsx("div", { className: "builder-canvas body-dev", style: {
53
- ...bodyDevStyle,
54
- containerName: "builder-canvas",
55
- containerType: "inline-size",
56
- }, children: children }));
57
- }
@@ -1,327 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { memo } from "react";
3
- import { arePropsEqual } from "../utils/memoUtils";
4
- // Map alignment to HTML 'align' attribute
5
- const justifyMap = {
6
- start: "left",
7
- center: "center",
8
- end: "right",
9
- };
10
- function getBorderStyle(border) {
11
- if (!border)
12
- return {};
13
- const style = {};
14
- // If a full border is specified, apply it
15
- if (border.width && border.style && border.color) {
16
- style.border = `${border.width} ${border.style} ${border.color}`;
17
- }
18
- else {
19
- // If only individual borders are specified, explicitly set others to 'none'
20
- // to prevent Outlook Classic from showing black borders
21
- const hasIndividualBorders = border.top || border.right || border.bottom || border.left;
22
- if (hasIndividualBorders) {
23
- // Default all borders to none
24
- style.borderTop = "none";
25
- style.borderRight = "none";
26
- style.borderBottom = "none";
27
- style.borderLeft = "none";
28
- }
29
- }
30
- // Override with specific borders if provided
31
- if (border.top) {
32
- style.borderTop = `${border.top.width} ${border.top.style} ${border.top.color}`;
33
- }
34
- if (border.right) {
35
- style.borderRight = `${border.right.width} ${border.right.style} ${border.right.color}`;
36
- }
37
- if (border.bottom) {
38
- style.borderBottom = `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}`;
39
- }
40
- if (border.left) {
41
- style.borderLeft = `${border.left.width} ${border.left.style} ${border.left.color}`;
42
- }
43
- return style;
44
- }
45
- function getBorderStyleString(border) {
46
- if (!border)
47
- return "";
48
- const styles = [];
49
- // If a full border is specified, apply it
50
- if (border.width && border.style && border.color) {
51
- styles.push(`border: ${border.width} ${border.style} ${border.color};`);
52
- }
53
- else {
54
- // If only individual borders are specified
55
- const hasIndividualBorders = border.top || border.right || border.bottom || border.left;
56
- if (hasIndividualBorders) {
57
- // Default all borders to none
58
- styles.push("border-top: none;");
59
- styles.push("border-right: none;");
60
- styles.push("border-bottom: none;");
61
- styles.push("border-left: none;");
62
- }
63
- }
64
- // Override with specific borders if provided
65
- if (border.top) {
66
- styles.push(`border-top: ${border.top.width} ${border.top.style} ${border.top.color};`);
67
- }
68
- if (border.right) {
69
- styles.push(`border-right: ${border.right.width} ${border.right.style} ${border.right.color};`);
70
- }
71
- if (border.bottom) {
72
- styles.push(`border-bottom: ${border.bottom.width} ${border.bottom.style} ${border.bottom.color};`);
73
- }
74
- if (border.left) {
75
- styles.push(`border-left: ${border.left.width} ${border.left.style} ${border.left.color};`);
76
- }
77
- return styles.join(" ");
78
- }
79
- function Button({ config, devMode }) {
80
- const { href, children, backgroundColor = "#007bff", // Default blue
81
- color = "#ffffff", padding = "12px 24px", borderRadius = "3px", border, width, maxWidth, justifyContent = "center", textAlign = "center", fontSize = "16px", fontWeight = "500", fontStyle, fontFamily = "Arial, sans-serif", lineHeight = "1.2", letterSpacing, textTransform, textDecoration = "none", direction, verticalAlign, opacity, whiteSpace = "normal", wordBreak = "break-word", } = config;
82
- // Sanitize fontFamily early so safeFontFamily is available for all paths below.
83
- const safeFontFamily = fontFamily
84
- ? fontFamily.replace(/['"]/g, "")
85
- : fontFamily;
86
- // 1. Link (A) Tag Styles (Fallback for Webmail/Mobile)
87
- const linkStyle = {
88
- color: color,
89
- textDecoration: textDecoration,
90
- display: "block",
91
- padding: padding,
92
- wordBreak: wordBreak,
93
- fontFamily: fontFamily,
94
- fontSize: fontSize,
95
- fontWeight: fontWeight,
96
- fontStyle: fontStyle,
97
- lineHeight: lineHeight,
98
- letterSpacing: letterSpacing,
99
- textTransform: textTransform,
100
- textAlign: textAlign,
101
- direction: direction,
102
- verticalAlign: verticalAlign,
103
- opacity: opacity,
104
- whiteSpace: whiteSpace,
105
- };
106
- // 2. Outer TD Style for Background and Border Radius (no border)
107
- const backgroundTdStyle = {
108
- backgroundColor: backgroundColor,
109
- borderRadius: borderRadius,
110
- width: width || "auto",
111
- ...(maxWidth && { maxWidth: maxWidth }),
112
- // Overflow hidden to clip background to border-radius
113
- ...(borderRadius && { overflow: "hidden" }),
114
- };
115
- // 3. Border styles
116
- const borderStyle = getBorderStyle(border);
117
- const borderStyleString = getBorderStyleString(border);
118
- // --- Determine Button Approach Based on Width ---
119
- // Check if width is percentage-based or not defined
120
- const isPercentageWidth = !width || width.includes("%");
121
- const useSimpleOutlookApproach = isPercentageWidth;
122
- const align = justifyMap[justifyContent];
123
- // --- VML Calculation and Code for Outlook Compatibility (Fixed Width Only) ---
124
- let vmlButton = "";
125
- if (!useSimpleOutlookApproach) {
126
- // Parse maxWidth if provided (always in px)
127
- const maxWidthPx = maxWidth ? parseInt(maxWidth, 10) : null;
128
- // VML needs fixed pixel height. We estimate it based on padding and potential wrapping.
129
- const numericPadding = parseInt(padding.split(" ")[0] || "12", 10);
130
- const numericFontSize = parseInt(fontSize, 10);
131
- const numericLineHeight = lineHeight.includes("px")
132
- ? parseInt(lineHeight, 10)
133
- : numericFontSize * parseFloat(lineHeight);
134
- // Trust user's explicit pixel width - no calculation needed
135
- const vmlWidth = parseInt(width, 10);
136
- // Calculate VML height - trust user's padding and let text wrap naturally
137
- // VML v:textbox will handle text wrapping automatically
138
- const textContent = typeof children === "string" ? children : "";
139
- // Estimate number of lines based on text length and button width
140
- const horizontalPadding = padding.split(" ")[1]
141
- ? parseInt(padding.split(" ")[1], 10) * 2
142
- : numericPadding * 2;
143
- const availableTextWidth = vmlWidth - horizontalPadding;
144
- const charWidthMultiplier = fontWeight && parseInt(fontWeight) >= 500 ? 0.7 : 0.6;
145
- const avgCharWidth = numericFontSize * charWidthMultiplier;
146
- const charsPerLine = Math.max(Math.floor(availableTextWidth / avgCharWidth), 1);
147
- const numberOfLines = Math.max(Math.ceil(textContent.length / charsPerLine), 1);
148
- // Calculate height: vertical padding + (lines * line height) + extra buffer for VML
149
- const textHeight = numberOfLines * numericLineHeight;
150
- // Add extra 4px buffer to prevent bottom cropping in VML
151
- const vmlHeight = Math.max(numericPadding * 2 + textHeight + 4, 40);
152
- // VML colors must use the full hex format (e.g., #000000)
153
- const vmlFillColor = backgroundColor.startsWith("#")
154
- ? backgroundColor
155
- : `#${backgroundColor}`;
156
- // VML stroke color for border
157
- const vmlStrokeColor = (border === null || border === void 0 ? void 0 : border.color) || vmlFillColor;
158
- const vmlStrokeWeight = (border === null || border === void 0 ? void 0 : border.width) ? parseInt(border.width, 10) : 0;
159
- const hasVmlStroke = vmlStrokeWeight > 0;
160
- // Build VML font styles - consistent with other rendering paths
161
- const vmlFontWeight = fontWeight || "500";
162
- const vmlFontStyle = fontStyle === "italic" ? "font-style:italic;" : "";
163
- const vmlLetterSpacing = letterSpacing
164
- ? `letter-spacing:${letterSpacing};`
165
- : "";
166
- const vmlTextTransform = textTransform
167
- ? `text-transform:${textTransform};`
168
- : "";
169
- const vmlTextDecoration = textDecoration && textDecoration !== "none"
170
- ? `text-decoration:${textDecoration};`
171
- : "";
172
- const vmlWhiteSpace = whiteSpace !== "normal" ? `white-space:${whiteSpace};` : "";
173
- const vmlDirection = direction ? `direction:${direction};` : "";
174
- const vmlOpacity = opacity !== undefined ? `opacity:${opacity};` : "";
175
- // VML code uses MSO conditional comments to render only in Outlook
176
- // Use table with explicit MSO height for vertical centering
177
- const horizontalPaddingValue = padding.split(" ")[1]
178
- ? parseInt(padding.split(" ")[1], 10)
179
- : numericPadding;
180
- // For VML, we need to use a table inside to properly apply padding and centering
181
- let vmlAlignAttr = "";
182
- let vmlAlignStyle = "";
183
- if (textAlign === "center") {
184
- vmlAlignAttr = 'align="center"';
185
- }
186
- else {
187
- vmlAlignStyle = `text-align:${textAlign};`;
188
- }
189
- // Border radius is intentionally omitted (arcsize="0%") for Outlook Classic.
190
- // Outlook Classic does not reliably support rounded corners and the result
191
- // is inconsistent, so we render sharp corners there instead.
192
- vmlButton = `
193
- <!--[if mso]>
194
- <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}">
195
- <w:anchorlock/>
196
- <v:textbox inset="${horizontalPaddingValue}px,${numericPadding}px,${horizontalPaddingValue}px,${numericPadding}px">
197
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse:collapse;">
198
- <tr>
199
- <td ${vmlAlignAttr} valign="middle" style="${vmlAlignStyle}color:${color};font-family:${safeFontFamily};font-size:${fontSize};font-weight:${vmlFontWeight};${vmlFontStyle}${vmlLetterSpacing}${vmlTextTransform}${vmlTextDecoration}${vmlWhiteSpace}${vmlDirection}${vmlOpacity}line-height:${lineHeight};mso-line-height-rule:exactly;">
200
- ${typeof children === "string" ? children : ""}
201
- </td>
202
- </tr>
203
- </table>
204
- </v:textbox>
205
- </v:roundrect>
206
- <![endif]-->
207
- `;
208
- }
209
- // --- Simple Outlook Approach for Percentage Widths ---
210
- let simpleOutlookButton = "";
211
- if (useSimpleOutlookApproach) {
212
- // Build consistent inline styles for text properties
213
- const textDecorationStyle = textDecoration && textDecoration !== "none"
214
- ? `text-decoration: ${textDecoration};`
215
- : "";
216
- const fontStyleProp = fontStyle ? `font-style: ${fontStyle};` : "";
217
- const letterSpacingProp = letterSpacing
218
- ? `letter-spacing: ${letterSpacing};`
219
- : "";
220
- const textTransformProp = textTransform
221
- ? `text-transform: ${textTransform};`
222
- : "";
223
- const whiteSpaceProp = whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "";
224
- const directionProp = direction ? `direction: ${direction};` : "";
225
- const opacityProp = opacity !== undefined ? `opacity: ${opacity};` : "";
226
- const wordBreakProp = wordBreak !== "break-word" ? `word-break: ${wordBreak};` : "";
227
- // Border radius is intentionally omitted from the Outlook Classic table cell.
228
- // Outlook Classic ignores border-radius on table cells anyway, and including it
229
- // can cause unexpected rendering artifacts, so we explicitly leave it out.
230
- simpleOutlookButton = `
231
- <!--[if mso]>
232
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="border-collapse: collapse;">
233
- <tr>
234
- <td align="${align}" style="padding: 0;">
235
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="${width || "auto"}" style="border-collapse: collapse;">
236
- <tr>
237
- <td bgcolor="${backgroundColor}" align="${textAlign}" style="padding: ${padding}; text-align: ${textAlign}; ${borderStyleString}">
238
- <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;">
239
- ${typeof children === "string" ? children : ""}
240
- </a>
241
- </td>
242
- </tr>
243
- </table>
244
- </td>
245
- </tr>
246
- </table>
247
- <![endif]-->
248
- `;
249
- }
250
- // Build shared inline style fragments for the non-MSO path.
251
- // fontFamily uses the sanitized value so embedded quotes never break the
252
- // style attribute string (which is always wrapped in double quotes).
253
- const sharedTextStyles = [
254
- `color: ${color};`,
255
- safeFontFamily ? `font-family: ${safeFontFamily};` : "",
256
- fontSize ? `font-size: ${fontSize};` : "",
257
- fontWeight ? `font-weight: ${fontWeight};` : "",
258
- fontStyle ? `font-style: ${fontStyle};` : "",
259
- lineHeight ? `line-height: ${lineHeight};` : "",
260
- letterSpacing ? `letter-spacing: ${letterSpacing};` : "",
261
- textTransform ? `text-transform: ${textTransform};` : "",
262
- textDecoration && textDecoration !== "none"
263
- ? `text-decoration: ${textDecoration};`
264
- : "",
265
- direction ? `direction: ${direction};` : "",
266
- opacity !== undefined ? `opacity: ${opacity};` : "",
267
- whiteSpace !== "normal" ? `white-space: ${whiteSpace};` : "",
268
- ]
269
- .filter(Boolean)
270
- .join(" ");
271
- return (
272
- // Wrapper table for alignment - maintains proper positioning for hover indicators
273
- _jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
274
- width: "100%",
275
- borderCollapse: "collapse",
276
- boxSizing: "border-box",
277
- border: 0,
278
- margin: 0,
279
- padding: 0,
280
- }, children: _jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { align: align, style: {
281
- padding: 0,
282
- }, children: _jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
283
- // --- Start dev
284
- position: "relative",
285
- // --- End dev
286
- width: width || "auto",
287
- ...(maxWidth && { maxWidth: maxWidth }),
288
- borderCollapse: "collapse",
289
- // base
290
- boxSizing: "border-box",
291
- border: 0,
292
- margin: 0,
293
- padding: 0,
294
- }, onClick: devMode ? (e) => e.preventDefault() : undefined, children: _jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { dangerouslySetInnerHTML: {
295
- __html: `
296
- ${devMode ? "" : useSimpleOutlookApproach ? simpleOutlookButton : vmlButton}
297
- <!--[if !mso]><!-->
298
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse; width: 100%;">
299
- <tbody>
300
- <tr>
301
- <td style="background-color: ${backgroundTdStyle.backgroundColor}; border-radius: ${backgroundTdStyle.borderRadius}; width: ${backgroundTdStyle.width}; ${maxWidth ? `max-width: ${maxWidth};` : ""} ${borderRadius ? "overflow: hidden;" : ""}">
302
- <table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse: separate; border-spacing: 0; border-radius: ${borderRadius}; width: 100%; ${borderStyleString}">
303
- <tbody>
304
- <tr>
305
- <td style="padding: 0;">
306
- ${devMode
307
- ? `<span style="${sharedTextStyles} display: block; text-align: ${textAlign}; word-break: ${wordBreak}; padding: ${padding};">
308
- ${typeof children === "string" ? children : ""}
309
- </span>`
310
- : `<a href="${href}" target="_blank" rel="noopener noreferrer" style="${sharedTextStyles} ${textDecoration && textDecoration !== "none" ? "" : "text-decoration: none;"} display: block; word-break: ${wordBreak}; text-align: ${textAlign}; padding: ${padding};">
311
- <span style="${sharedTextStyles}">
312
- ${typeof children === "string" ? children : ""}
313
- </span>
314
- </a>`}
315
- </td>
316
- </tr>
317
- </tbody>
318
- </table>
319
- </td>
320
- </tr>
321
- </tbody>
322
- </table>
323
- <!--<![endif]-->
324
- `,
325
- } }) }) }) }) }) }) }) }));
326
- }
327
- export default memo(Button, arePropsEqual);
@@ -1,127 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment, memo } from "react";
3
- import { arePropsEqual } from "../utils/memoUtils";
4
- // Helper for vertical alignment
5
- const vAlignMap = {
6
- start: "top",
7
- center: "middle",
8
- end: "bottom",
9
- };
10
- // Helper for horizontal alignment
11
- const alignMap = {
12
- start: "left",
13
- center: "center",
14
- end: "right",
15
- };
16
- // Helper to convert border config to CSS border shorthand
17
- function getBorderStyle(border) {
18
- if (!border)
19
- return {};
20
- const style = {};
21
- // If a full border is specified, apply it
22
- if (border.width && border.style && border.color) {
23
- style.border = `${border.width} ${border.style} ${border.color}`;
24
- }
25
- else {
26
- // If only individual borders are specified, explicitly set others to 'none'
27
- // to prevent Outlook Classic from showing black borders
28
- const hasIndividualBorders = border.top || border.right || border.bottom || border.left;
29
- if (hasIndividualBorders) {
30
- // Default all borders to none
31
- style.borderTop = "none";
32
- style.borderRight = "none";
33
- style.borderBottom = "none";
34
- style.borderLeft = "none";
35
- }
36
- }
37
- // Override with specific borders if provided
38
- if (border.top) {
39
- style.borderTop = `${border.top.width} ${border.top.style} ${border.top.color}`;
40
- }
41
- if (border.right) {
42
- style.borderRight = `${border.right.width} ${border.right.style} ${border.right.color}`;
43
- }
44
- if (border.bottom) {
45
- style.borderBottom = `${border.bottom.width} ${border.bottom.style} ${border.bottom.color}`;
46
- }
47
- if (border.left) {
48
- style.borderLeft = `${border.left.width} ${border.left.style} ${border.left.color}`;
49
- }
50
- return style;
51
- }
52
- function Column({ children, config, devNode }) {
53
- var _a, _b, _c;
54
- // Process children array for gap support
55
- const childrenArray = (Array.isArray(children) ? children : [children]).filter((child) => child != null);
56
- const numChildren = childrenArray.length;
57
- // 1. Outer table style: Takes up the full width/height of its parent TD.
58
- // height here drives the *total* outer height of the column.
59
- const outerTableStyle = {
60
- width: "100%",
61
- height: config.height,
62
- borderCollapse: "collapse",
63
- };
64
- // 2. Outer TD style: Background and Border Radius (no border here).
65
- // height is set so the TD occupies the full declared height.
66
- const outerTdStyle = {
67
- width: config.width,
68
- height: config.height,
69
- backgroundColor: config.backgroundColor,
70
- borderRadius: config.borderRadius,
71
- // Background Image styles
72
- backgroundImage: config.backgroundImage
73
- ? `url(${config.backgroundImage.src})`
74
- : undefined,
75
- backgroundRepeat: (_a = config.backgroundImage) === null || _a === void 0 ? void 0 : _a.repeat,
76
- backgroundSize: (_b = config.backgroundImage) === null || _b === void 0 ? void 0 : _b.size,
77
- backgroundPosition: (_c = config.backgroundImage) === null || _c === void 0 ? void 0 : _c.position,
78
- // Overflow hidden to clip background to border-radius
79
- ...(config.borderRadius && { overflow: "hidden" }),
80
- };
81
- // 2b. Inner table style: Border and Border Radius.
82
- // height: 100% so it stretches to fill the outer TD's declared height.
83
- const innerTableStyle = {
84
- width: "100%",
85
- height: "100%", // fill the outer TD rather than re-declaring the pixel value
86
- borderCollapse: "separate",
87
- borderSpacing: 0,
88
- borderRadius: config.borderRadius,
89
- ...getBorderStyle(config.border),
90
- };
91
- // 3. Inner TD style: Padding and Vertical Alignment only.
92
- // *** No height here. ***
93
- // The outer TD/table owns the height; padding is purely inner spacing,
94
- // so the total rendered height = declared height (padding is inside).
95
- const innerTdStyle = {
96
- padding: config.padding,
97
- // height intentionally omitted — setting it here would make browsers
98
- // treat it as content-box height and add padding on top, causing the
99
- // total to exceed the declared height in preview mode.
100
- verticalAlign: config.alignItems ? alignMap[config.alignItems] : "top",
101
- };
102
- // 4. Gap spacer style (used between children)
103
- const gapSpacerStyle = {
104
- height: config.gap || "0",
105
- lineHeight: "1px",
106
- fontSize: "1px",
107
- width: "100%",
108
- };
109
- // Main content rendering
110
- const renderContent = () => (_jsx("table", { "aria-label": "Column Padding", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: innerTableStyle, children: _jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { style: innerTdStyle, valign: config.justifyContent ? vAlignMap[config.justifyContent] : "top", align: config.alignItems ? alignMap[config.alignItems] : "left", children: config.gap && numChildren > 1 ? (_jsx("table", { "aria-label": "Column Gap Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
111
- width: "100%",
112
- borderCollapse: "collapse",
113
- }, children: _jsx("tbody", { children: childrenArray.map((child, index) => (_jsxs(Fragment, { children: [_jsx("tr", { children: _jsx("td", { style: {
114
- verticalAlign: config.alignItems
115
- ? alignMap[config.alignItems]
116
- : "top",
117
- }, valign: config.justifyContent
118
- ? vAlignMap[config.justifyContent]
119
- : "top", align: config.alignItems
120
- ? alignMap[config.alignItems]
121
- : "left", children: child }) }), index < numChildren - 1 && (_jsx("tr", { children: _jsx("td", { style: gapSpacerStyle, children: "\u00A0" }) }))] }, `col-child-${index}`))) }) })) : (children) }) }) }) }));
122
- return (_jsxs("table", { "aria-label": "Column Wrapper", role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, style: {
123
- position: "relative",
124
- ...outerTableStyle,
125
- }, ...(config.height && { height: config.height }), children: [_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { style: outerTdStyle, ...(config.width && { width: config.width }), ...(config.height && { height: config.height }), children: renderContent() }) }) }), devNode && (_jsx("tfoot", { children: _jsx("tr", { children: _jsx("td", { children: devNode }) }) }))] }));
126
- }
127
- export default memo(Column, arePropsEqual);