react-email 6.6.6 → 6.6.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/CHANGELOG.md +10 -0
- package/dist/cli/index.mjs +1 -1
- package/dist/index.cjs +20 -12
- package/dist/index.mjs +20 -12
- package/package.json +1 -1
- package/src/components/markdown/markdown.spec.tsx +12 -0
- package/src/components/markdown/markdown.tsx +3 -3
- package/src/components/tailwind/inline-styles.spec.ts +49 -0
- package/src/components/tailwind/inline-styles.ts +1 -1
- package/src/components/tailwind/tailwind.spec.tsx +79 -1
- package/src/components/tailwind/tailwind.tsx +1 -1
- package/src/components/tailwind/utils/css/extract-rules-per-class.spec.ts +71 -12
- package/src/components/tailwind/utils/css/extract-rules-per-class.ts +21 -5
- package/src/components/tailwind/utils/css/sanitize-declarations.spec.ts +41 -0
- package/src/components/tailwind/utils/css/sanitize-declarations.ts +5 -5
- package/src/components/tailwind/utils/react/is-component.ts +6 -0
- package/src/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# react-email
|
|
2
2
|
|
|
3
|
+
## 6.6.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6a5ff2a: Escape double quotes in `Markdown` link `href`/`title` and image `title` attributes, matching the escaping already applied to image `src`/`alt`. A markdown title like `'The "Complete" Guide'` no longer breaks out of the attribute in the rendered HTML.
|
|
8
|
+
- 4cf4c72: Fix two-value logical shorthands whose values aren't all numeric (e.g. `margin-inline: 1rem auto`, `padding-inline: 10px calc(1rem + 2px)`) producing invalid duplicated longhands. They are now split into the correct per-side declarations.
|
|
9
|
+
- fa77d55: Merge declarations when the same class is defined by multiple Tailwind rules (e.g. a preset and a child config override).
|
|
10
|
+
- fa52a04: Convert Tailwind's `rgba(r g b / a)` syntax to `rgb(r,g,b,a)` syntax for better email client support.
|
|
11
|
+
- fc8318c: Fix Tailwind classes not being inlined into styles for `<Section>`, `<Column>` and `<Row>`.
|
|
12
|
+
|
|
3
13
|
## 6.6.6
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/cli/index.mjs
CHANGED
|
@@ -6523,7 +6523,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
|
|
|
6523
6523
|
//#region package.json
|
|
6524
6524
|
var package_default = {
|
|
6525
6525
|
name: "react-email",
|
|
6526
|
-
version: "6.6.
|
|
6526
|
+
version: "6.6.7",
|
|
6527
6527
|
description: "A live preview of your emails right in your browser.",
|
|
6528
6528
|
bin: { "email": "./dist/cli/index.mjs" },
|
|
6529
6529
|
type: "module",
|
package/dist/index.cjs
CHANGED
|
@@ -17821,11 +17821,11 @@ const Markdown = react.forwardRef(({ children, markdownContainerStyles, markdown
|
|
|
17821
17821
|
return `<hr${parseCssInJsToInlineCss(finalStyles.hr) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.hr)}"` : ""} />\n`;
|
|
17822
17822
|
};
|
|
17823
17823
|
renderer.image = ({ href, text, title }) => {
|
|
17824
|
-
return `<img src="${href.replaceAll("\"", """)}" alt="${text.replaceAll("\"", """)}"${title ? ` title="${title}"` : ""}${parseCssInJsToInlineCss(finalStyles.image) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.image)}"` : ""}>`;
|
|
17824
|
+
return `<img src="${href.replaceAll("\"", """)}" alt="${text.replaceAll("\"", """)}"${title ? ` title="${title.replaceAll("\"", """)}"` : ""}${parseCssInJsToInlineCss(finalStyles.image) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.image)}"` : ""}>`;
|
|
17825
17825
|
};
|
|
17826
17826
|
renderer.link = ({ href, title, tokens }) => {
|
|
17827
17827
|
const text = renderer.parser.parseInline(tokens);
|
|
17828
|
-
return `<a href="${href}" target="_blank"${title ? ` title="${title}"` : ""}${parseCssInJsToInlineCss(finalStyles.link) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.link)}"` : ""}>${text}</a>`;
|
|
17828
|
+
return `<a href="${href.replaceAll("\"", """)}" target="_blank"${title ? ` title="${title.replaceAll("\"", """)}"` : ""}${parseCssInJsToInlineCss(finalStyles.link) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.link)}"` : ""}>${text}</a>`;
|
|
17829
17829
|
};
|
|
17830
17830
|
renderer.listitem = ({ tokens, loose }) => {
|
|
17831
17831
|
const hasNestedList = tokens.some((token) => token.type === "list");
|
|
@@ -37702,6 +37702,11 @@ function extractRulesPerClass(root, classes) {
|
|
|
37702
37702
|
const classSet = new Set(classes);
|
|
37703
37703
|
const inlinableRules = /* @__PURE__ */ new Map();
|
|
37704
37704
|
const nonInlinableRules = /* @__PURE__ */ new Map();
|
|
37705
|
+
const appendRule = (map, className, rule) => {
|
|
37706
|
+
const existing = map.get(className);
|
|
37707
|
+
if (existing) existing.push(rule);
|
|
37708
|
+
else map.set(className, [rule]);
|
|
37709
|
+
};
|
|
37705
37710
|
walk(root, {
|
|
37706
37711
|
visit: "Rule",
|
|
37707
37712
|
enter(rule) {
|
|
@@ -37713,13 +37718,13 @@ function extractRulesPerClass(root, classes) {
|
|
|
37713
37718
|
}
|
|
37714
37719
|
});
|
|
37715
37720
|
if (isRuleInlinable(rule)) {
|
|
37716
|
-
for (const className of selectorClasses) if (classSet.has(className)) inlinableRules
|
|
37721
|
+
for (const className of selectorClasses) if (classSet.has(className)) appendRule(inlinableRules, className, rule);
|
|
37717
37722
|
} else {
|
|
37718
37723
|
const { inlinablePart, nonInlinablePart } = splitMixedRule(rule);
|
|
37719
37724
|
for (const className of selectorClasses) {
|
|
37720
37725
|
if (!classSet.has(className)) continue;
|
|
37721
|
-
if (inlinablePart) inlinableRules
|
|
37722
|
-
if (nonInlinablePart) nonInlinableRules
|
|
37726
|
+
if (inlinablePart) appendRule(inlinableRules, className, inlinablePart);
|
|
37727
|
+
if (nonInlinablePart) appendRule(nonInlinableRules, className, nonInlinablePart);
|
|
37723
37728
|
}
|
|
37724
37729
|
}
|
|
37725
37730
|
}
|
|
@@ -37880,7 +37885,7 @@ function makeInlineStylesFor(inlinableRules, customProperties) {
|
|
|
37880
37885
|
function inlineStyles(styleSheet, classes) {
|
|
37881
37886
|
const { inlinable: inlinableRules } = extractRulesPerClass(styleSheet, classes);
|
|
37882
37887
|
const customProperties = getCustomProperties(styleSheet);
|
|
37883
|
-
return makeInlineStylesFor(Array.from(inlinableRules.values()), customProperties);
|
|
37888
|
+
return makeInlineStylesFor(Array.from(inlinableRules.values()).flat(), customProperties);
|
|
37884
37889
|
}
|
|
37885
37890
|
//#endregion
|
|
37886
37891
|
//#region src/components/tailwind/utils/css/resolve-all-css-variables.ts
|
|
@@ -38152,7 +38157,7 @@ function oklchToRgb(oklch) {
|
|
|
38152
38157
|
}
|
|
38153
38158
|
function separteShorthandDeclaration(shorthandToReplace, [start, end]) {
|
|
38154
38159
|
shorthandToReplace.property = start;
|
|
38155
|
-
const values = shorthandToReplace.value.type === "Value" ? shorthandToReplace.value.children.toArray().filter((child) => child.type
|
|
38160
|
+
const values = shorthandToReplace.value.type === "Value" ? shorthandToReplace.value.children.toArray().filter((child) => child.type !== "Operator" && child.type !== "WhiteSpace") : [shorthandToReplace.value];
|
|
38156
38161
|
let endValue = shorthandToReplace.value;
|
|
38157
38162
|
if (values.length === 2) {
|
|
38158
38163
|
endValue = {
|
|
@@ -38240,7 +38245,7 @@ function sanitizeDeclarations(nodeContainingDeclarations) {
|
|
|
38240
38245
|
});
|
|
38241
38246
|
funcParentListItem.data = rgbNode(rgb.r, rgb.g, rgb.b, a);
|
|
38242
38247
|
}
|
|
38243
|
-
if (func.name === "rgb") {
|
|
38248
|
+
if (func.name === "rgb" || func.name === "rgba") {
|
|
38244
38249
|
let r;
|
|
38245
38250
|
let g;
|
|
38246
38251
|
let b;
|
|
@@ -38676,12 +38681,15 @@ const componentsToTreatAsElements = [
|
|
|
38676
38681
|
Button,
|
|
38677
38682
|
CodeBlock,
|
|
38678
38683
|
CodeInline,
|
|
38684
|
+
Column,
|
|
38679
38685
|
Container,
|
|
38680
38686
|
Heading,
|
|
38681
38687
|
Hr,
|
|
38682
38688
|
Img,
|
|
38683
38689
|
Link,
|
|
38684
38690
|
Preview,
|
|
38691
|
+
Row,
|
|
38692
|
+
Section,
|
|
38685
38693
|
Text
|
|
38686
38694
|
];
|
|
38687
38695
|
const isComponent = (element) => {
|
|
@@ -38723,10 +38731,10 @@ function cloneElementWithInlinedStyles(element, inlinableRules, nonInlinableRule
|
|
|
38723
38731
|
const residualClasses = [];
|
|
38724
38732
|
const rules = [];
|
|
38725
38733
|
for (const className of classes) {
|
|
38726
|
-
const
|
|
38727
|
-
if (
|
|
38734
|
+
const classRules = inlinableRules.get(className);
|
|
38735
|
+
if (classRules) rules.push(...classRules);
|
|
38728
38736
|
if (nonInlinableRules.has(className)) residualClasses.push(className);
|
|
38729
|
-
else if (!
|
|
38737
|
+
else if (!classRules) residualClasses.push(className);
|
|
38730
38738
|
}
|
|
38731
38739
|
propsToOverwrite.style = {
|
|
38732
38740
|
...makeInlineStylesFor(rules, customProperties),
|
|
@@ -40663,7 +40671,7 @@ function Tailwind({ children, config, theme, utility }) {
|
|
|
40663
40671
|
const customProperties = getCustomProperties(styleSheet);
|
|
40664
40672
|
const nonInlineStyles = {
|
|
40665
40673
|
type: "StyleSheet",
|
|
40666
|
-
children: new List().fromArray(Array.from(nonInlinableRules.values()))
|
|
40674
|
+
children: new List().fromArray(Array.from(nonInlinableRules.values()).flat())
|
|
40667
40675
|
};
|
|
40668
40676
|
sanitizeNonInlinableRules(nonInlineStyles);
|
|
40669
40677
|
downlevelForEmailClients(nonInlineStyles);
|
package/dist/index.mjs
CHANGED
|
@@ -17800,11 +17800,11 @@ const Markdown = React$1.forwardRef(({ children, markdownContainerStyles, markdo
|
|
|
17800
17800
|
return `<hr${parseCssInJsToInlineCss(finalStyles.hr) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.hr)}"` : ""} />\n`;
|
|
17801
17801
|
};
|
|
17802
17802
|
renderer.image = ({ href, text, title }) => {
|
|
17803
|
-
return `<img src="${href.replaceAll("\"", """)}" alt="${text.replaceAll("\"", """)}"${title ? ` title="${title}"` : ""}${parseCssInJsToInlineCss(finalStyles.image) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.image)}"` : ""}>`;
|
|
17803
|
+
return `<img src="${href.replaceAll("\"", """)}" alt="${text.replaceAll("\"", """)}"${title ? ` title="${title.replaceAll("\"", """)}"` : ""}${parseCssInJsToInlineCss(finalStyles.image) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.image)}"` : ""}>`;
|
|
17804
17804
|
};
|
|
17805
17805
|
renderer.link = ({ href, title, tokens }) => {
|
|
17806
17806
|
const text = renderer.parser.parseInline(tokens);
|
|
17807
|
-
return `<a href="${href}" target="_blank"${title ? ` title="${title}"` : ""}${parseCssInJsToInlineCss(finalStyles.link) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.link)}"` : ""}>${text}</a>`;
|
|
17807
|
+
return `<a href="${href.replaceAll("\"", """)}" target="_blank"${title ? ` title="${title.replaceAll("\"", """)}"` : ""}${parseCssInJsToInlineCss(finalStyles.link) !== "" ? ` style="${parseCssInJsToInlineCss(finalStyles.link)}"` : ""}>${text}</a>`;
|
|
17808
17808
|
};
|
|
17809
17809
|
renderer.listitem = ({ tokens, loose }) => {
|
|
17810
17810
|
const hasNestedList = tokens.some((token) => token.type === "list");
|
|
@@ -37681,6 +37681,11 @@ function extractRulesPerClass(root, classes) {
|
|
|
37681
37681
|
const classSet = new Set(classes);
|
|
37682
37682
|
const inlinableRules = /* @__PURE__ */ new Map();
|
|
37683
37683
|
const nonInlinableRules = /* @__PURE__ */ new Map();
|
|
37684
|
+
const appendRule = (map, className, rule) => {
|
|
37685
|
+
const existing = map.get(className);
|
|
37686
|
+
if (existing) existing.push(rule);
|
|
37687
|
+
else map.set(className, [rule]);
|
|
37688
|
+
};
|
|
37684
37689
|
walk(root, {
|
|
37685
37690
|
visit: "Rule",
|
|
37686
37691
|
enter(rule) {
|
|
@@ -37692,13 +37697,13 @@ function extractRulesPerClass(root, classes) {
|
|
|
37692
37697
|
}
|
|
37693
37698
|
});
|
|
37694
37699
|
if (isRuleInlinable(rule)) {
|
|
37695
|
-
for (const className of selectorClasses) if (classSet.has(className)) inlinableRules
|
|
37700
|
+
for (const className of selectorClasses) if (classSet.has(className)) appendRule(inlinableRules, className, rule);
|
|
37696
37701
|
} else {
|
|
37697
37702
|
const { inlinablePart, nonInlinablePart } = splitMixedRule(rule);
|
|
37698
37703
|
for (const className of selectorClasses) {
|
|
37699
37704
|
if (!classSet.has(className)) continue;
|
|
37700
|
-
if (inlinablePart) inlinableRules
|
|
37701
|
-
if (nonInlinablePart) nonInlinableRules
|
|
37705
|
+
if (inlinablePart) appendRule(inlinableRules, className, inlinablePart);
|
|
37706
|
+
if (nonInlinablePart) appendRule(nonInlinableRules, className, nonInlinablePart);
|
|
37702
37707
|
}
|
|
37703
37708
|
}
|
|
37704
37709
|
}
|
|
@@ -37859,7 +37864,7 @@ function makeInlineStylesFor(inlinableRules, customProperties) {
|
|
|
37859
37864
|
function inlineStyles(styleSheet, classes) {
|
|
37860
37865
|
const { inlinable: inlinableRules } = extractRulesPerClass(styleSheet, classes);
|
|
37861
37866
|
const customProperties = getCustomProperties(styleSheet);
|
|
37862
|
-
return makeInlineStylesFor(Array.from(inlinableRules.values()), customProperties);
|
|
37867
|
+
return makeInlineStylesFor(Array.from(inlinableRules.values()).flat(), customProperties);
|
|
37863
37868
|
}
|
|
37864
37869
|
//#endregion
|
|
37865
37870
|
//#region src/components/tailwind/utils/css/resolve-all-css-variables.ts
|
|
@@ -38131,7 +38136,7 @@ function oklchToRgb(oklch) {
|
|
|
38131
38136
|
}
|
|
38132
38137
|
function separteShorthandDeclaration(shorthandToReplace, [start, end]) {
|
|
38133
38138
|
shorthandToReplace.property = start;
|
|
38134
|
-
const values = shorthandToReplace.value.type === "Value" ? shorthandToReplace.value.children.toArray().filter((child) => child.type
|
|
38139
|
+
const values = shorthandToReplace.value.type === "Value" ? shorthandToReplace.value.children.toArray().filter((child) => child.type !== "Operator" && child.type !== "WhiteSpace") : [shorthandToReplace.value];
|
|
38135
38140
|
let endValue = shorthandToReplace.value;
|
|
38136
38141
|
if (values.length === 2) {
|
|
38137
38142
|
endValue = {
|
|
@@ -38219,7 +38224,7 @@ function sanitizeDeclarations(nodeContainingDeclarations) {
|
|
|
38219
38224
|
});
|
|
38220
38225
|
funcParentListItem.data = rgbNode(rgb.r, rgb.g, rgb.b, a);
|
|
38221
38226
|
}
|
|
38222
|
-
if (func.name === "rgb") {
|
|
38227
|
+
if (func.name === "rgb" || func.name === "rgba") {
|
|
38223
38228
|
let r;
|
|
38224
38229
|
let g;
|
|
38225
38230
|
let b;
|
|
@@ -38655,12 +38660,15 @@ const componentsToTreatAsElements = [
|
|
|
38655
38660
|
Button,
|
|
38656
38661
|
CodeBlock,
|
|
38657
38662
|
CodeInline,
|
|
38663
|
+
Column,
|
|
38658
38664
|
Container,
|
|
38659
38665
|
Heading,
|
|
38660
38666
|
Hr,
|
|
38661
38667
|
Img,
|
|
38662
38668
|
Link,
|
|
38663
38669
|
Preview,
|
|
38670
|
+
Row,
|
|
38671
|
+
Section,
|
|
38664
38672
|
Text
|
|
38665
38673
|
];
|
|
38666
38674
|
const isComponent = (element) => {
|
|
@@ -38702,10 +38710,10 @@ function cloneElementWithInlinedStyles(element, inlinableRules, nonInlinableRule
|
|
|
38702
38710
|
const residualClasses = [];
|
|
38703
38711
|
const rules = [];
|
|
38704
38712
|
for (const className of classes) {
|
|
38705
|
-
const
|
|
38706
|
-
if (
|
|
38713
|
+
const classRules = inlinableRules.get(className);
|
|
38714
|
+
if (classRules) rules.push(...classRules);
|
|
38707
38715
|
if (nonInlinableRules.has(className)) residualClasses.push(className);
|
|
38708
|
-
else if (!
|
|
38716
|
+
else if (!classRules) residualClasses.push(className);
|
|
38709
38717
|
}
|
|
38710
38718
|
propsToOverwrite.style = {
|
|
38711
38719
|
...makeInlineStylesFor(rules, customProperties),
|
|
@@ -40642,7 +40650,7 @@ function Tailwind({ children, config, theme, utility }) {
|
|
|
40642
40650
|
const customProperties = getCustomProperties(styleSheet);
|
|
40643
40651
|
const nonInlineStyles = {
|
|
40644
40652
|
type: "StyleSheet",
|
|
40645
|
-
children: new List().fromArray(Array.from(nonInlinableRules.values()))
|
|
40653
|
+
children: new List().fromArray(Array.from(nonInlinableRules.values()).flat())
|
|
40646
40654
|
};
|
|
40647
40655
|
sanitizeNonInlinableRules(nonInlineStyles);
|
|
40648
40656
|
downlevelForEmailClients(nonInlineStyles);
|
package/package.json
CHANGED
|
@@ -131,6 +131,18 @@ console.log(\`Hello, $\{name}!\`);
|
|
|
131
131
|
`);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
it('escapes double quotes in link/image href and title attributes', async () => {
|
|
135
|
+
const actualOutput = await render(
|
|
136
|
+
<Markdown>
|
|
137
|
+
{`[guide](https://example.com/?q="a" 'The "Complete" Guide') and `}
|
|
138
|
+
</Markdown>,
|
|
139
|
+
);
|
|
140
|
+
expect(actualOutput).toMatchInlineSnapshot(`
|
|
141
|
+
"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div data-id="react-email-markdown"><p><a href="https://example.com/?q="a"" target="_blank" title="The "Complete" Guide" style="color:#007bff;text-decoration:underline;background-color:transparent">guide</a> and <img src="https://cdn.example.com/a.png" alt="logo" title="Acme "logo""></p>
|
|
142
|
+
</div><!--/$-->"
|
|
143
|
+
`);
|
|
144
|
+
});
|
|
145
|
+
|
|
134
146
|
it('renders lists in the correct format for browsers', async () => {
|
|
135
147
|
const actualOutput = await render(
|
|
136
148
|
<Markdown>
|
|
@@ -98,7 +98,7 @@ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(
|
|
|
98
98
|
|
|
99
99
|
renderer.image = ({ href, text, title }) => {
|
|
100
100
|
return `<img src="${href.replaceAll('"', '"')}" alt="${text.replaceAll('"', '"')}"${
|
|
101
|
-
title ? ` title="${title}"` : ''
|
|
101
|
+
title ? ` title="${title.replaceAll('"', '"')}"` : ''
|
|
102
102
|
}${
|
|
103
103
|
parseCssInJsToInlineCss(finalStyles.image) !== ''
|
|
104
104
|
? ` style="${parseCssInJsToInlineCss(finalStyles.image)}"`
|
|
@@ -109,8 +109,8 @@ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(
|
|
|
109
109
|
renderer.link = ({ href, title, tokens }) => {
|
|
110
110
|
const text = renderer.parser.parseInline(tokens);
|
|
111
111
|
|
|
112
|
-
return `<a href="${href}" target="_blank"${
|
|
113
|
-
title ? ` title="${title}"` : ''
|
|
112
|
+
return `<a href="${href.replaceAll('"', '"')}" target="_blank"${
|
|
113
|
+
title ? ` title="${title.replaceAll('"', '"')}"` : ''
|
|
114
114
|
}${
|
|
115
115
|
parseCssInJsToInlineCss(finalStyles.link) !== ''
|
|
116
116
|
? ` style="${parseCssInJsToInlineCss(finalStyles.link)}"`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { parse, type StyleSheet } from 'css-tree';
|
|
2
|
+
import { inlineStyles } from './inline-styles.js';
|
|
3
|
+
|
|
4
|
+
describe('inlineStyles()', () => {
|
|
5
|
+
it('inlines a single rule for a class', () => {
|
|
6
|
+
const styleSheet = parse(`
|
|
7
|
+
.box { border-radius: 8px; background-color: white; padding: 16px; }
|
|
8
|
+
`) as StyleSheet;
|
|
9
|
+
|
|
10
|
+
expect(inlineStyles(styleSheet, ['box'])).toMatchInlineSnapshot(`
|
|
11
|
+
{
|
|
12
|
+
"backgroundColor": "white",
|
|
13
|
+
"borderRadius": "8px",
|
|
14
|
+
"padding": "16px",
|
|
15
|
+
}
|
|
16
|
+
`);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('merges declarations when the same class is defined across multiple rules', () => {
|
|
20
|
+
// Regression for #3628: a Tailwind preset and a child config both defining `.box`.
|
|
21
|
+
const styleSheet = parse(`
|
|
22
|
+
.box { border-radius: 8px; background-color: white; padding: 16px; }
|
|
23
|
+
.box { background-color: red; }
|
|
24
|
+
`) as StyleSheet;
|
|
25
|
+
|
|
26
|
+
expect(inlineStyles(styleSheet, ['box'])).toMatchInlineSnapshot(`
|
|
27
|
+
{
|
|
28
|
+
"backgroundColor": "red",
|
|
29
|
+
"borderRadius": "8px",
|
|
30
|
+
"padding": "16px",
|
|
31
|
+
}
|
|
32
|
+
`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("keeps a class's own cascade order when its rules are split by another class", () => {
|
|
36
|
+
const styleSheet = parse(`
|
|
37
|
+
.box { color: red; }
|
|
38
|
+
.other { font-weight: bold; }
|
|
39
|
+
.box { color: blue; }
|
|
40
|
+
`) as StyleSheet;
|
|
41
|
+
|
|
42
|
+
expect(inlineStyles(styleSheet, ['box', 'other'])).toMatchInlineSnapshot(`
|
|
43
|
+
{
|
|
44
|
+
"color": "blue",
|
|
45
|
+
"fontWeight": "bold",
|
|
46
|
+
}
|
|
47
|
+
`);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -4,11 +4,14 @@ import React from 'react';
|
|
|
4
4
|
import plugin from 'tailwindcss/plugin';
|
|
5
5
|
import { Body } from '../body/index.js';
|
|
6
6
|
import { Button } from '../button/index.js';
|
|
7
|
+
import { Column } from '../column/index.js';
|
|
7
8
|
import { Head } from '../head/index.js';
|
|
8
9
|
import { Heading } from '../heading/index.js';
|
|
9
10
|
import { Hr } from '../hr/index.js';
|
|
10
11
|
import { Html } from '../html/index.js';
|
|
11
12
|
import { Link } from '../link/index.js';
|
|
13
|
+
import { Row } from '../row/index.js';
|
|
14
|
+
import { Section } from '../section/index.js';
|
|
12
15
|
import type { TailwindConfig } from './tailwind.js';
|
|
13
16
|
import { Tailwind } from './tailwind.js';
|
|
14
17
|
|
|
@@ -261,6 +264,45 @@ describe('Tailwind component', () => {
|
|
|
261
264
|
);
|
|
262
265
|
});
|
|
263
266
|
|
|
267
|
+
it('routes Tailwind padding on <Section> to the inner <td>', async () => {
|
|
268
|
+
const html = await render(
|
|
269
|
+
<Tailwind>
|
|
270
|
+
<Section className="bg-white p-4">x</Section>
|
|
271
|
+
</Tailwind>,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(html).toContain('<td style="padding:1rem">');
|
|
275
|
+
expect(html).not.toMatch(/<table[^>]*style="[^"]*padding:1rem/);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('inlines Tailwind classes on <Column> onto its <td>', async () => {
|
|
279
|
+
const html = await render(
|
|
280
|
+
<Tailwind>
|
|
281
|
+
<Row>
|
|
282
|
+
<Column className="bg-white p-4">x</Column>
|
|
283
|
+
</Row>
|
|
284
|
+
</Tailwind>,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(html).toMatch(
|
|
288
|
+
/<td[^>]*data-id="__react-email-column"[^>]*style="[^"]*padding:1rem/,
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('inlines Tailwind classes on <Row> onto its <table>', async () => {
|
|
293
|
+
const html = await render(
|
|
294
|
+
<Tailwind>
|
|
295
|
+
<Row className="bg-white p-4">
|
|
296
|
+
<Column>x</Column>
|
|
297
|
+
</Row>
|
|
298
|
+
</Tailwind>,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(html).toMatch(
|
|
302
|
+
/<table[^>]*role="presentation"[^>]*style="[^"]*padding:1rem/,
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
264
306
|
it('works with components that use React.forwardRef', async () => {
|
|
265
307
|
const Wrapper = (props: { children: React.ReactNode }) => {
|
|
266
308
|
return <Tailwind>{props.children}</Tailwind>;
|
|
@@ -742,6 +784,42 @@ describe('Tailwind component', () => {
|
|
|
742
784
|
});
|
|
743
785
|
});
|
|
744
786
|
|
|
787
|
+
describe('with duplicate classes across presets and plugins', () => {
|
|
788
|
+
it('merges declarations from a preset and a child override for the same class', async () => {
|
|
789
|
+
const base: TailwindConfig = {
|
|
790
|
+
plugins: [
|
|
791
|
+
plugin(({ addComponents }) => {
|
|
792
|
+
addComponents({ '.box': { '@apply rounded-lg bg-white p-4': {} } });
|
|
793
|
+
}),
|
|
794
|
+
],
|
|
795
|
+
};
|
|
796
|
+
const config: TailwindConfig = {
|
|
797
|
+
presets: [base],
|
|
798
|
+
plugins: [
|
|
799
|
+
plugin(({ addComponents }) => {
|
|
800
|
+
addComponents({ '.box': { '@apply bg-red-500': {} } });
|
|
801
|
+
}),
|
|
802
|
+
],
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const actualOutput = await render(
|
|
806
|
+
<Tailwind config={config}>
|
|
807
|
+
<div className="box">hi</div>
|
|
808
|
+
</Tailwind>,
|
|
809
|
+
).then(pretty);
|
|
810
|
+
|
|
811
|
+
expect(actualOutput).toMatchInlineSnapshot(`
|
|
812
|
+
"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
813
|
+
<!--$-->
|
|
814
|
+
<div style="border-radius:0.5rem;background-color:rgb(251,44,54);padding:1rem">
|
|
815
|
+
hi
|
|
816
|
+
</div>
|
|
817
|
+
<!--/$-->
|
|
818
|
+
"
|
|
819
|
+
`);
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
745
823
|
describe('with custom theme config', () => {
|
|
746
824
|
it('supports custom colors', async () => {
|
|
747
825
|
const config: TailwindConfig = {
|
|
@@ -1102,7 +1180,7 @@ describe('Tailwind component', () => {
|
|
|
1102
1180
|
"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1103
1181
|
<!--$-->
|
|
1104
1182
|
<div
|
|
1105
|
-
style="box-shadow:0 4px 6px
|
|
1183
|
+
style="box-shadow:0 4px 6px rgb(0,0,0,0.1);border-radius:8px;padding:16px"></div>
|
|
1106
1184
|
<!--/$-->
|
|
1107
1185
|
"
|
|
1108
1186
|
`);
|
|
@@ -124,7 +124,7 @@ export function Tailwind({ children, config, theme, utility }: TailwindProps) {
|
|
|
124
124
|
const nonInlineStyles: StyleSheet = {
|
|
125
125
|
type: 'StyleSheet',
|
|
126
126
|
children: new List<CssNode>().fromArray(
|
|
127
|
-
Array.from(nonInlinableRules.values()),
|
|
127
|
+
Array.from(nonInlinableRules.values()).flat(),
|
|
128
128
|
),
|
|
129
129
|
};
|
|
130
130
|
sanitizeNonInlinableRules(nonInlineStyles);
|
|
@@ -3,8 +3,14 @@ import { setupTailwind } from '../tailwindcss/setup-tailwind.js';
|
|
|
3
3
|
import { extractRulesPerClass } from './extract-rules-per-class.js';
|
|
4
4
|
|
|
5
5
|
describe('extractRulesPerClass()', async () => {
|
|
6
|
-
function convertToComparable(
|
|
7
|
-
|
|
6
|
+
function convertToComparable(
|
|
7
|
+
map: Map<string, Rule[]>,
|
|
8
|
+
): Record<string, string[]> {
|
|
9
|
+
return Object.fromEntries(
|
|
10
|
+
map
|
|
11
|
+
.entries()
|
|
12
|
+
.map(([k, rules]) => [k, rules.map((rule) => generate(rule))]),
|
|
13
|
+
);
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
it('works with just inlinable utilities', async () => {
|
|
@@ -21,13 +27,50 @@ describe('extractRulesPerClass()', async () => {
|
|
|
21
27
|
|
|
22
28
|
expect(convertToComparable(inlinable)).toMatchInlineSnapshot(`
|
|
23
29
|
{
|
|
24
|
-
"bg-red-500":
|
|
25
|
-
|
|
30
|
+
"bg-red-500": [
|
|
31
|
+
".bg-red-500{background-color:var(--color-red-500)}",
|
|
32
|
+
],
|
|
33
|
+
"text-center": [
|
|
34
|
+
".text-center{text-align:center}",
|
|
35
|
+
],
|
|
26
36
|
}
|
|
27
37
|
`);
|
|
28
38
|
expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot('{}');
|
|
29
39
|
});
|
|
30
40
|
|
|
41
|
+
it('keeps every rule when a class is defined more than once', async () => {
|
|
42
|
+
const tailwind = await setupTailwind({
|
|
43
|
+
config: {
|
|
44
|
+
plugins: [
|
|
45
|
+
{
|
|
46
|
+
handler: (api) => {
|
|
47
|
+
api.addComponents({
|
|
48
|
+
'.box': { '@apply rounded-lg bg-white p-4': {} },
|
|
49
|
+
});
|
|
50
|
+
api.addComponents({
|
|
51
|
+
'.box': { '@apply bg-red-500': {} },
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const classes = ['box'];
|
|
59
|
+
tailwind.addUtilities(classes);
|
|
60
|
+
|
|
61
|
+
const stylesheet = tailwind.getStyleSheet();
|
|
62
|
+
const { inlinable } = extractRulesPerClass(stylesheet, classes);
|
|
63
|
+
|
|
64
|
+
expect(convertToComparable(inlinable)).toMatchInlineSnapshot(`
|
|
65
|
+
{
|
|
66
|
+
"box": [
|
|
67
|
+
".box{border-radius:var(--radius-lg);background-color:var(--color-white);padding:calc(var(--spacing)*4)}",
|
|
68
|
+
".box{background-color:var(--color-red-500)}",
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
`);
|
|
72
|
+
});
|
|
73
|
+
|
|
31
74
|
it('handles non-inlinable utilities', async () => {
|
|
32
75
|
const tailwind = await setupTailwind({});
|
|
33
76
|
const classes = ['lg:w-1/2'];
|
|
@@ -43,7 +86,9 @@ describe('extractRulesPerClass()', async () => {
|
|
|
43
86
|
expect(convertToComparable(inlinable)).toMatchInlineSnapshot('{}');
|
|
44
87
|
expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(`
|
|
45
88
|
{
|
|
46
|
-
"lg:w-1/2":
|
|
89
|
+
"lg:w-1/2": [
|
|
90
|
+
".lg\\:w-1\\/2{@media (width>=64rem){width:calc(1/2*100%)}}",
|
|
91
|
+
],
|
|
47
92
|
}
|
|
48
93
|
`);
|
|
49
94
|
});
|
|
@@ -66,14 +111,22 @@ describe('extractRulesPerClass()', async () => {
|
|
|
66
111
|
);
|
|
67
112
|
expect(convertToComparable(inlinable)).toMatchInlineSnapshot(`
|
|
68
113
|
{
|
|
69
|
-
"bg-red-500":
|
|
70
|
-
|
|
71
|
-
|
|
114
|
+
"bg-red-500": [
|
|
115
|
+
".bg-red-500{background-color:var(--color-red-500)}",
|
|
116
|
+
],
|
|
117
|
+
"text-center": [
|
|
118
|
+
".text-center{text-align:center}",
|
|
119
|
+
],
|
|
120
|
+
"w-full": [
|
|
121
|
+
".w-full{width:100%}",
|
|
122
|
+
],
|
|
72
123
|
}
|
|
73
124
|
`);
|
|
74
125
|
expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(`
|
|
75
126
|
{
|
|
76
|
-
"lg:w-1/2":
|
|
127
|
+
"lg:w-1/2": [
|
|
128
|
+
".lg\\:w-1\\/2{@media (width>=64rem){width:calc(1/2*100%)}}",
|
|
129
|
+
],
|
|
77
130
|
}
|
|
78
131
|
`);
|
|
79
132
|
});
|
|
@@ -105,12 +158,16 @@ describe('extractRulesPerClass()', async () => {
|
|
|
105
158
|
|
|
106
159
|
expect(convertToComparable(inlinable)).toMatchInlineSnapshot(`
|
|
107
160
|
{
|
|
108
|
-
"text-body":
|
|
161
|
+
"text-body": [
|
|
162
|
+
".text-body{color:green}",
|
|
163
|
+
],
|
|
109
164
|
}
|
|
110
165
|
`);
|
|
111
166
|
expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(`
|
|
112
167
|
{
|
|
113
|
-
"text-body":
|
|
168
|
+
"text-body": [
|
|
169
|
+
".text-body{@media (width>=40rem){color:darkgreen}}",
|
|
170
|
+
],
|
|
114
171
|
}
|
|
115
172
|
`);
|
|
116
173
|
});
|
|
@@ -143,7 +200,9 @@ describe('extractRulesPerClass()', async () => {
|
|
|
143
200
|
expect(convertToComparable(inlinable)).toMatchInlineSnapshot('{}');
|
|
144
201
|
expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(`
|
|
145
202
|
{
|
|
146
|
-
"btn":
|
|
203
|
+
"btn": [
|
|
204
|
+
".btn{&:hover{color:red}}",
|
|
205
|
+
],
|
|
147
206
|
}
|
|
148
207
|
`);
|
|
149
208
|
});
|
|
@@ -5,8 +5,24 @@ import { splitMixedRule } from './split-mixed-rule.js';
|
|
|
5
5
|
export function extractRulesPerClass(root: CssNode, classes: string[]) {
|
|
6
6
|
const classSet = new Set(classes);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// A class can be defined by multiple rules (e.g. a preset and a child config
|
|
9
|
+
// override), so keep them all to merge instead of the last one clobbering.
|
|
10
|
+
const inlinableRules = new Map<string, Rule[]>();
|
|
11
|
+
const nonInlinableRules = new Map<string, Rule[]>();
|
|
12
|
+
|
|
13
|
+
const appendRule = (
|
|
14
|
+
map: Map<string, Rule[]>,
|
|
15
|
+
className: string,
|
|
16
|
+
rule: Rule,
|
|
17
|
+
) => {
|
|
18
|
+
const existing = map.get(className);
|
|
19
|
+
if (existing) {
|
|
20
|
+
existing.push(rule);
|
|
21
|
+
} else {
|
|
22
|
+
map.set(className, [rule]);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
10
26
|
walk(root, {
|
|
11
27
|
visit: 'Rule',
|
|
12
28
|
enter(rule) {
|
|
@@ -20,7 +36,7 @@ export function extractRulesPerClass(root: CssNode, classes: string[]) {
|
|
|
20
36
|
if (isRuleInlinable(rule)) {
|
|
21
37
|
for (const className of selectorClasses) {
|
|
22
38
|
if (classSet.has(className)) {
|
|
23
|
-
inlinableRules
|
|
39
|
+
appendRule(inlinableRules, className, rule);
|
|
24
40
|
}
|
|
25
41
|
}
|
|
26
42
|
} else {
|
|
@@ -28,10 +44,10 @@ export function extractRulesPerClass(root: CssNode, classes: string[]) {
|
|
|
28
44
|
for (const className of selectorClasses) {
|
|
29
45
|
if (!classSet.has(className)) continue;
|
|
30
46
|
if (inlinablePart) {
|
|
31
|
-
inlinableRules
|
|
47
|
+
appendRule(inlinableRules, className, inlinablePart);
|
|
32
48
|
}
|
|
33
49
|
if (nonInlinablePart) {
|
|
34
|
-
nonInlinableRules
|
|
50
|
+
appendRule(nonInlinableRules, className, nonInlinablePart);
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
}
|
|
@@ -129,6 +129,26 @@ describe('sanitizeDeclarations', () => {
|
|
|
129
129
|
);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
it('separates two-value logical shorthands with auto, var, or calc', () => {
|
|
133
|
+
let root = parse('.x { margin-inline: 1rem auto; }');
|
|
134
|
+
sanitizeDeclarations(root);
|
|
135
|
+
expect(generate(root)).toMatchInlineSnapshot(
|
|
136
|
+
`".x{margin-right:auto;margin-left:1rem}"`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
root = parse('.x { margin-inline: 0 auto; }');
|
|
140
|
+
sanitizeDeclarations(root);
|
|
141
|
+
expect(generate(root)).toMatchInlineSnapshot(
|
|
142
|
+
`".x{margin-right:auto;margin-left:0}"`,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
root = parse('.x { padding-inline: 10px calc(1rem + 2px); }');
|
|
146
|
+
sanitizeDeclarations(root);
|
|
147
|
+
expect(generate(root)).toMatchInlineSnapshot(
|
|
148
|
+
`".x{padding-right:calc(1rem + 2px);padding-left:10px}"`,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
132
152
|
test('oklch to rgb conversion', () => {
|
|
133
153
|
let stylesheet = parse('div { color: oklch(90.5% 0.2 180); }');
|
|
134
154
|
sanitizeDeclarations(stylesheet);
|
|
@@ -230,6 +250,27 @@ describe('sanitizeDeclarations', () => {
|
|
|
230
250
|
generate(stylesheet),
|
|
231
251
|
'treatment for already supported rgb syntax',
|
|
232
252
|
).toMatchInlineSnapshot(`"div{color:rgb(255,0,128)}"`);
|
|
253
|
+
|
|
254
|
+
stylesheet = parse('div { color: rgba(255 0 128 / 0.5); }');
|
|
255
|
+
sanitizeDeclarations(stylesheet);
|
|
256
|
+
expect(
|
|
257
|
+
generate(stylesheet),
|
|
258
|
+
'rgba() space syntax with alpha',
|
|
259
|
+
).toMatchInlineSnapshot(`"div{color:rgb(255,0,128,0.5)}"`);
|
|
260
|
+
|
|
261
|
+
stylesheet = parse('div { color: rgba(100% 0% 50% / 100%); }');
|
|
262
|
+
sanitizeDeclarations(stylesheet);
|
|
263
|
+
expect(
|
|
264
|
+
generate(stylesheet),
|
|
265
|
+
'rgba() percentage syntax with full alpha',
|
|
266
|
+
).toMatchInlineSnapshot(`"div{color:rgb(255,0,128)}"`);
|
|
267
|
+
|
|
268
|
+
stylesheet = parse('div { color: rgba(255, 0, 128, 0.5); }');
|
|
269
|
+
sanitizeDeclarations(stylesheet);
|
|
270
|
+
expect(
|
|
271
|
+
generate(stylesheet),
|
|
272
|
+
'legacy comma rgba() is left untouched',
|
|
273
|
+
).toMatchInlineSnapshot(`"div{color:rgb(255,0,128,0.5)}"`);
|
|
233
274
|
});
|
|
234
275
|
|
|
235
276
|
test('hex to rgb conversion', () => {
|
|
@@ -133,11 +133,11 @@ function separteShorthandDeclaration(
|
|
|
133
133
|
shorthandToReplace.value.type === 'Value'
|
|
134
134
|
? shorthandToReplace.value.children
|
|
135
135
|
.toArray()
|
|
136
|
+
// Count every real value component, not just numeric ones, so a
|
|
137
|
+
// two-value shorthand with `auto`/`var()`/`calc()` (e.g.
|
|
138
|
+
// `margin-inline: 1rem auto`) still splits into two longhands.
|
|
136
139
|
.filter(
|
|
137
|
-
(child) =>
|
|
138
|
-
child.type === 'Dimension' ||
|
|
139
|
-
child.type === 'Number' ||
|
|
140
|
-
child.type === 'Percentage',
|
|
140
|
+
(child) => child.type !== 'Operator' && child.type !== 'WhiteSpace',
|
|
141
141
|
)
|
|
142
142
|
: [shorthandToReplace.value];
|
|
143
143
|
let endValue = shorthandToReplace.value;
|
|
@@ -252,7 +252,7 @@ export function sanitizeDeclarations(nodeContainingDeclarations: CssNode) {
|
|
|
252
252
|
funcParentListItem.data = rgbNode(rgb.r, rgb.g, rgb.b, a);
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
if (func.name === 'rgb') {
|
|
255
|
+
if (func.name === 'rgb' || func.name === 'rgba') {
|
|
256
256
|
let r: number | undefined;
|
|
257
257
|
let g: number | undefined;
|
|
258
258
|
let b: number | undefined;
|
|
@@ -2,12 +2,15 @@ import { Body } from '../../../body/index.js';
|
|
|
2
2
|
import { Button } from '../../../button/index.js';
|
|
3
3
|
import { CodeBlock } from '../../../code-block/index.js';
|
|
4
4
|
import { CodeInline } from '../../../code-inline/index.js';
|
|
5
|
+
import { Column } from '../../../column/index.js';
|
|
5
6
|
import { Container } from '../../../container/index.js';
|
|
6
7
|
import { Heading } from '../../../heading/index.js';
|
|
7
8
|
import { Hr } from '../../../hr/index.js';
|
|
8
9
|
import { Img } from '../../../img/index.js';
|
|
9
10
|
import { Link } from '../../../link/index.js';
|
|
10
11
|
import { Preview } from '../../../preview/index.js';
|
|
12
|
+
import { Row } from '../../../row/index.js';
|
|
13
|
+
import { Section } from '../../../section/index.js';
|
|
11
14
|
import { Text } from '../../../text/index.js';
|
|
12
15
|
|
|
13
16
|
const componentsToTreatAsElements: React.ReactElement['type'][] = [
|
|
@@ -15,12 +18,15 @@ const componentsToTreatAsElements: React.ReactElement['type'][] = [
|
|
|
15
18
|
Button,
|
|
16
19
|
CodeBlock,
|
|
17
20
|
CodeInline,
|
|
21
|
+
Column,
|
|
18
22
|
Container,
|
|
19
23
|
Heading,
|
|
20
24
|
Hr,
|
|
21
25
|
Img,
|
|
22
26
|
Link,
|
|
23
27
|
Preview,
|
|
28
|
+
Row,
|
|
29
|
+
Section,
|
|
24
30
|
Text,
|
|
25
31
|
];
|
|
26
32
|
|
|
@@ -8,8 +8,8 @@ import { isComponent } from '../react/is-component.js';
|
|
|
8
8
|
|
|
9
9
|
export function cloneElementWithInlinedStyles(
|
|
10
10
|
element: React.ReactElement<EmailElementProps>,
|
|
11
|
-
inlinableRules: Map<string, Rule>,
|
|
12
|
-
nonInlinableRules: Map<string, Rule>,
|
|
11
|
+
inlinableRules: Map<string, Rule[]>,
|
|
12
|
+
nonInlinableRules: Map<string, Rule[]>,
|
|
13
13
|
customProperties: CustomProperties,
|
|
14
14
|
) {
|
|
15
15
|
const propsToOverwrite: Partial<EmailElementProps> = {};
|
|
@@ -21,13 +21,13 @@ export function cloneElementWithInlinedStyles(
|
|
|
21
21
|
|
|
22
22
|
const rules: Rule[] = [];
|
|
23
23
|
for (const className of classes) {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
rules.push(
|
|
24
|
+
const classRules = inlinableRules.get(className);
|
|
25
|
+
if (classRules) {
|
|
26
|
+
rules.push(...classRules);
|
|
27
27
|
}
|
|
28
28
|
if (nonInlinableRules.has(className)) {
|
|
29
29
|
residualClasses.push(className);
|
|
30
|
-
} else if (!
|
|
30
|
+
} else if (!classRules) {
|
|
31
31
|
residualClasses.push(className);
|
|
32
32
|
}
|
|
33
33
|
}
|