react-email 6.11.1 → 6.11.3

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # react-email
2
2
 
3
+ ## 6.11.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [f356e81]
8
+ - @react-email/render@2.1.1
9
+
10
+ ## 6.11.2
11
+
12
+ ### Patch Changes
13
+
14
+ - 7190dca: Leave the direction marks U+200E and U+200F out of the `<Preview>` padding, so spam filters such as rspamd no longer score it as hidden text. The padding sequence is now `\xa0‌​‍`; if you post-process rendered HTML by matching the old sequence, update it.
15
+ - 63f159a: `<Tailwind>` now inlines the default color for `shadow`, `shadow-md`, `ring-2` and similar classes, so these shadows show in email clients.
16
+
3
17
  ## 6.11.1
4
18
 
5
19
  ### Patch Changes
@@ -6533,7 +6533,7 @@ const getTracingRootDir = async (usersProjectLocation) => {
6533
6533
  //#region package.json
6534
6534
  var package_default = {
6535
6535
  name: "react-email",
6536
- version: "6.11.1",
6536
+ version: "6.11.3",
6537
6537
  description: "A live preview of your emails right in your browser.",
6538
6538
  bin: { "email": "./dist/cli/index.mjs" },
6539
6539
  type: "module",
@@ -6573,7 +6573,7 @@ var package_default = {
6573
6573
  dependencies: {
6574
6574
  "@babel/parser": "catalog:",
6575
6575
  "@babel/traverse": "catalog:",
6576
- "@react-email/render": "workspace:>=2.1.0",
6576
+ "@react-email/render": "workspace:>=2.1.1",
6577
6577
  "chokidar": "^5.0.0",
6578
6578
  "commander": "catalog:",
6579
6579
  "conf": "^15.0.2",
@@ -24,7 +24,7 @@ const Preview = react.forwardRef(({ children = "", useTitleTag = true, ...props
24
24
  });
25
25
  Preview.displayName = "Preview";
26
26
  require_element_marker.markAsElement(Preview);
27
- const whiteSpaceCodes = "\xA0‌​‍‎‏";
27
+ const whiteSpaceCodes = "\xA0‌​‍";
28
28
  const renderWhiteSpace = (text) => {
29
29
  if (text.length >= PREVIEW_MAX_LENGTH) return null;
30
30
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: whiteSpaceCodes.repeat(PREVIEW_MAX_LENGTH - text.length) });
@@ -22,7 +22,7 @@ const Preview = React$1.forwardRef(({ children = "", useTitleTag = true, ...prop
22
22
  });
23
23
  Preview.displayName = "Preview";
24
24
  markAsElement(Preview);
25
- const whiteSpaceCodes = "\xA0‌​‍‎‏";
25
+ const whiteSpaceCodes = "\xA0‌​‍";
26
26
  const renderWhiteSpace = (text) => {
27
27
  if (text.length >= PREVIEW_MAX_LENGTH) return null;
28
28
  return /* @__PURE__ */ jsx("div", { children: whiteSpaceCodes.repeat(PREVIEW_MAX_LENGTH - text.length) });
@@ -1,6 +1,6 @@
1
- const require_resolve_all_css_variables = require("./utils/css/resolve-all-css-variables.cjs");
2
1
  const require_resolve_calc_expressions = require("./utils/css/resolve-calc-expressions.cjs");
3
2
  const require_sanitize_declarations = require("./utils/css/sanitize-declarations.cjs");
3
+ const require_resolve_all_css_variables = require("./utils/css/resolve-all-css-variables.cjs");
4
4
  //#region src/components/tailwind/sanitize-stylesheet.ts
5
5
  function sanitizeStyleSheet(styleSheet) {
6
6
  require_resolve_all_css_variables.resolveAllCssVariables(styleSheet);
@@ -1,6 +1,6 @@
1
- import { resolveAllCssVariables } from "./utils/css/resolve-all-css-variables.mjs";
2
1
  import { resolveCalcExpressions } from "./utils/css/resolve-calc-expressions.mjs";
3
2
  import { sanitizeDeclarations } from "./utils/css/sanitize-declarations.mjs";
3
+ import { resolveAllCssVariables } from "./utils/css/resolve-all-css-variables.mjs";
4
4
  //#region src/components/tailwind/sanitize-stylesheet.ts
5
5
  function sanitizeStyleSheet(styleSheet) {
6
6
  resolveAllCssVariables(styleSheet);
@@ -1,9 +1,24 @@
1
1
  require("../../../../_virtual/_rolldown/runtime.cjs");
2
2
  const require_get_react_property = require("../compatibility/get-react-property.cjs");
3
+ const require_resolve_calc_expressions = require("./resolve-calc-expressions.cjs");
4
+ const require_sanitize_declarations = require("./sanitize-declarations.cjs");
3
5
  const require_strip_empty_tailwind_vars = require("./strip-empty-tailwind-vars.cjs");
4
6
  const require_unwrap_value = require("./unwrap-value.cjs");
5
7
  let css_tree = require("css-tree");
6
8
  //#region src/components/tailwind/utils/css/make-inline-styles-for.ts
9
+ function hasVarFunction(node) {
10
+ let found = false;
11
+ (0, css_tree.walk)(node, {
12
+ visit: "Function",
13
+ enter(func) {
14
+ if (func.name === "var") {
15
+ found = true;
16
+ return this.break;
17
+ }
18
+ }
19
+ });
20
+ return found;
21
+ }
7
22
  function makeInlineStylesFor(inlinableRules, customProperties) {
8
23
  const styles = {};
9
24
  const localVariableDeclarations = /* @__PURE__ */ new Map();
@@ -13,39 +28,48 @@ function makeInlineStylesFor(inlinableRules, customProperties) {
13
28
  if (declaration.property.startsWith("--")) localVariableDeclarations.set(declaration.property, declaration);
14
29
  }
15
30
  });
16
- for (const rule of inlinableRules) {
17
- (0, css_tree.walk)(rule, {
31
+ const resolveValue = (value, resolving = /* @__PURE__ */ new Set()) => {
32
+ const resolved = (0, css_tree.parse)((0, css_tree.generate)(value), { context: "value" });
33
+ (0, css_tree.walk)(resolved, {
18
34
  visit: "Function",
19
- enter(func, funcParentListItem) {
20
- if (func.name === "var") {
21
- let variableName;
22
- (0, css_tree.walk)(func, {
23
- visit: "Identifier",
24
- enter(identifier) {
25
- variableName = identifier.name;
26
- return this.break;
27
- }
28
- });
29
- if (variableName) {
30
- const definition = localVariableDeclarations.get(variableName);
31
- if (definition) funcParentListItem.data = require_unwrap_value.unwrapValue(definition.value);
32
- else {
33
- const customProperty = customProperties.get(variableName);
34
- if (customProperty?.initialValue) funcParentListItem.data = require_unwrap_value.unwrapValue(customProperty.initialValue.value);
35
- }
36
- }
35
+ leave(func, item) {
36
+ if (func.name !== "var") return;
37
+ const children = func.children.toArray();
38
+ const name = children[0] ? (0, css_tree.generate)(children[0]).trim() : "";
39
+ if (resolving.has(name)) return;
40
+ const definition = localVariableDeclarations.get(name);
41
+ const initialValue = customProperties.get(name)?.initialValue;
42
+ const comma = children.findIndex((child) => child.type === "Operator" && child.value === ",");
43
+ const fallback = comma === -1 ? void 0 : children.slice(comma + 1).map((child) => (0, css_tree.generate)(child)).join("");
44
+ const replacement = definition?.value ?? initialValue?.value;
45
+ if (replacement) item.data = require_unwrap_value.unwrapValue(resolveValue(replacement, new Set([...resolving, name])));
46
+ else if (fallback) {
47
+ const declaration = {
48
+ type: "Declaration",
49
+ property: "value",
50
+ important: false,
51
+ value: resolveValue((0, css_tree.parse)(fallback, { context: "value" }), resolving)
52
+ };
53
+ require_sanitize_declarations.sanitizeDeclarations(declaration);
54
+ item.data = require_unwrap_value.unwrapValue(declaration.value);
37
55
  }
38
56
  }
39
57
  });
40
- (0, css_tree.walk)(rule, {
41
- visit: "Declaration",
42
- enter(declaration) {
43
- if (declaration.property.startsWith("--")) return;
44
- require_strip_empty_tailwind_vars.stripEmptyTailwindVars(declaration.value);
45
- styles[require_get_react_property.getReactProperty(declaration.property)] = (0, css_tree.generate)(declaration.value).trim() + (declaration.important ? "!important" : "");
58
+ return resolved;
59
+ };
60
+ for (const rule of inlinableRules) (0, css_tree.walk)(rule, {
61
+ visit: "Declaration",
62
+ enter(declaration) {
63
+ if (declaration.property.startsWith("--")) return;
64
+ let value = declaration.value;
65
+ if (hasVarFunction(value)) {
66
+ value = resolveValue(value);
67
+ require_resolve_calc_expressions.resolveCalcExpressions(value);
68
+ require_strip_empty_tailwind_vars.stripEmptyTailwindVars(value);
46
69
  }
47
- });
48
- }
70
+ styles[require_get_react_property.getReactProperty(declaration.property)] = (0, css_tree.generate)(value).trim() + (declaration.important ? "!important" : "");
71
+ }
72
+ });
49
73
  return styles;
50
74
  }
51
75
  //#endregion
@@ -1,8 +1,23 @@
1
1
  import { getReactProperty } from "../compatibility/get-react-property.mjs";
2
+ import { resolveCalcExpressions } from "./resolve-calc-expressions.mjs";
3
+ import { sanitizeDeclarations } from "./sanitize-declarations.mjs";
2
4
  import { stripEmptyTailwindVars } from "./strip-empty-tailwind-vars.mjs";
3
5
  import { unwrapValue } from "./unwrap-value.mjs";
4
- import { generate, walk } from "css-tree/dist/csstree.esm";
6
+ import { generate, parse, walk } from "css-tree/dist/csstree.esm";
5
7
  //#region src/components/tailwind/utils/css/make-inline-styles-for.ts
8
+ function hasVarFunction(node) {
9
+ let found = false;
10
+ walk(node, {
11
+ visit: "Function",
12
+ enter(func) {
13
+ if (func.name === "var") {
14
+ found = true;
15
+ return this.break;
16
+ }
17
+ }
18
+ });
19
+ return found;
20
+ }
6
21
  function makeInlineStylesFor(inlinableRules, customProperties) {
7
22
  const styles = {};
8
23
  const localVariableDeclarations = /* @__PURE__ */ new Map();
@@ -12,39 +27,48 @@ function makeInlineStylesFor(inlinableRules, customProperties) {
12
27
  if (declaration.property.startsWith("--")) localVariableDeclarations.set(declaration.property, declaration);
13
28
  }
14
29
  });
15
- for (const rule of inlinableRules) {
16
- walk(rule, {
30
+ const resolveValue = (value, resolving = /* @__PURE__ */ new Set()) => {
31
+ const resolved = parse(generate(value), { context: "value" });
32
+ walk(resolved, {
17
33
  visit: "Function",
18
- enter(func, funcParentListItem) {
19
- if (func.name === "var") {
20
- let variableName;
21
- walk(func, {
22
- visit: "Identifier",
23
- enter(identifier) {
24
- variableName = identifier.name;
25
- return this.break;
26
- }
27
- });
28
- if (variableName) {
29
- const definition = localVariableDeclarations.get(variableName);
30
- if (definition) funcParentListItem.data = unwrapValue(definition.value);
31
- else {
32
- const customProperty = customProperties.get(variableName);
33
- if (customProperty?.initialValue) funcParentListItem.data = unwrapValue(customProperty.initialValue.value);
34
- }
35
- }
34
+ leave(func, item) {
35
+ if (func.name !== "var") return;
36
+ const children = func.children.toArray();
37
+ const name = children[0] ? generate(children[0]).trim() : "";
38
+ if (resolving.has(name)) return;
39
+ const definition = localVariableDeclarations.get(name);
40
+ const initialValue = customProperties.get(name)?.initialValue;
41
+ const comma = children.findIndex((child) => child.type === "Operator" && child.value === ",");
42
+ const fallback = comma === -1 ? void 0 : children.slice(comma + 1).map((child) => generate(child)).join("");
43
+ const replacement = definition?.value ?? initialValue?.value;
44
+ if (replacement) item.data = unwrapValue(resolveValue(replacement, new Set([...resolving, name])));
45
+ else if (fallback) {
46
+ const declaration = {
47
+ type: "Declaration",
48
+ property: "value",
49
+ important: false,
50
+ value: resolveValue(parse(fallback, { context: "value" }), resolving)
51
+ };
52
+ sanitizeDeclarations(declaration);
53
+ item.data = unwrapValue(declaration.value);
36
54
  }
37
55
  }
38
56
  });
39
- walk(rule, {
40
- visit: "Declaration",
41
- enter(declaration) {
42
- if (declaration.property.startsWith("--")) return;
43
- stripEmptyTailwindVars(declaration.value);
44
- styles[getReactProperty(declaration.property)] = generate(declaration.value).trim() + (declaration.important ? "!important" : "");
57
+ return resolved;
58
+ };
59
+ for (const rule of inlinableRules) walk(rule, {
60
+ visit: "Declaration",
61
+ enter(declaration) {
62
+ if (declaration.property.startsWith("--")) return;
63
+ let value = declaration.value;
64
+ if (hasVarFunction(value)) {
65
+ value = resolveValue(value);
66
+ resolveCalcExpressions(value);
67
+ stripEmptyTailwindVars(value);
45
68
  }
46
- });
47
- }
69
+ styles[getReactProperty(declaration.property)] = generate(value).trim() + (declaration.important ? "!important" : "");
70
+ }
71
+ });
48
72
  return styles;
49
73
  }
50
74
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.11.1",
3
+ "version": "6.11.3",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@babel/parser": "7.29.2",
40
40
  "@babel/traverse": "7.29.0",
41
- "@react-email/render": ">=2.1.0",
41
+ "@react-email/render": ">=2.1.1",
42
42
  "chokidar": "^5.0.0",
43
43
  "commander": "^13.0.0",
44
44
  "conf": "^15.0.2",
@@ -67,7 +67,7 @@
67
67
  "@types/mime-types": "2.1.4",
68
68
  "@types/prompts": "2.4.9",
69
69
  "@types/shelljs": "0.10.0",
70
- "next": "16.3.3",
70
+ "next": "16.4.1",
71
71
  "react": "19.2.4",
72
72
  "react-dom": "19.2.4",
73
73
  "shelljs": "0.10.0",
@@ -75,7 +75,7 @@
75
75
  "tsx": "4.21.0",
76
76
  "typescript": "5.9.3",
77
77
  "yalc": "1.0.0-pre.53",
78
- "@react-email/render": "2.1.0"
78
+ "@react-email/render": "2.1.1"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsdown",
@@ -5,7 +5,7 @@ describe('<Preview> component', () => {
5
5
  it('renders correctly', async () => {
6
6
  const actualOutput = await render(<Preview>Email preview text</Preview>);
7
7
  expect(actualOutput).toMatchInlineSnapshot(
8
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
8
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍</div></div><!--/$-->"`,
9
9
  );
10
10
  });
11
11
 
@@ -14,10 +14,19 @@ describe('<Preview> component', () => {
14
14
  <Preview>Email preview text</Preview>,
15
15
  );
16
16
  expect(actualOutputArray).toMatchInlineSnapshot(
17
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
17
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍ ‌​‍</div></div><!--/$-->"`,
18
18
  );
19
19
  });
20
20
 
21
+ it('pads only with characters spam filters ignore as preview padding', async () => {
22
+ const actualOutput = await render(<Preview>Short text</Preview>);
23
+ const padding = actualOutput.match(/<div>([^<]*)<\/div>/)?.[1];
24
+
25
+ // rspamd's HIDDEN_TEXT rule counts any other hidden character, so padding
26
+ // made of anything else gets scored as hidden text
27
+ expect(padding).toMatch(/^[\s\p{Z}​-‍]+$/u);
28
+ });
29
+
21
30
  it('renders correctly with really long text', async () => {
22
31
  const longText = 'really long'.repeat(100);
23
32
  const actualOutputLong = await render(<Preview>{longText}</Preview>);
@@ -37,7 +46,7 @@ describe('renderWhiteSpace', () => {
37
46
 
38
47
  it('renders white space characters when text length is less than PREVIEW_MAX_LENGTH', () => {
39
48
  const text = 'Short text';
40
- const whiteSpaceCharacters = '\xa0\u200C\u200B\u200D\u200E\u200F\uFEFF';
49
+ const whiteSpaceCharacters = '\xa0\u200C\u200B\u200D\uFEFF';
41
50
 
42
51
  const html = renderWhiteSpace(text);
43
52
  expect(html).not.toBeNull();
@@ -46,7 +46,9 @@ export const Preview = React.forwardRef<HTMLDivElement, PreviewProps>(
46
46
  Preview.displayName = 'Preview';
47
47
  markAsElement(Preview);
48
48
 
49
- const whiteSpaceCodes = '\xa0\u200C\u200B\u200D\u200E\u200F\uFEFF';
49
+ // Spam filters such as rspamd ignore these as preview padding, but count the
50
+ // direction marks U+200E and U+200F as hidden text, so those are left out.
51
+ const whiteSpaceCodes = '\xa0\u200C\u200B\u200D\uFEFF';
50
52
  export const renderWhiteSpace = (text: string) => {
51
53
  if (text.length >= PREVIEW_MAX_LENGTH) {
52
54
  return null;
@@ -89,6 +89,64 @@ describe('Tailwind component', () => {
89
89
  `);
90
90
  });
91
91
 
92
+ it('resolves variables nested inside the definitions of other variables', async () => {
93
+ const html = await render(
94
+ <Tailwind>
95
+ <div className="shadow-md ring-2">default shadow color</div>
96
+ </Tailwind>,
97
+ );
98
+
99
+ expect(html).not.toContain('var(');
100
+ expect(html).toContain('0 4px 6px -1px rgb(0,0,0,0.1)');
101
+ expect(html).toContain('currentcolor');
102
+ });
103
+
104
+ it("uses each element's default shadow and ring colors independently", async () => {
105
+ const html = await render(
106
+ <Tailwind>
107
+ <div className="shadow-md ring-2">card</div>
108
+ <div className="shadow-red-500 ring-blue-500">button</div>
109
+ </Tailwind>,
110
+ );
111
+
112
+ expect(html).not.toContain('var(');
113
+ expect(html).toContain('0 4px 6px -1px rgb(0,0,0,0.1)');
114
+ expect(html).toContain('currentcolor');
115
+ });
116
+
117
+ it('preserves local colors without changing a sibling with the same utilities', async () => {
118
+ const html = await render(
119
+ <Tailwind>
120
+ <div className="shadow-md ring-2 shadow-[#ff0000] ring-[#0000ff]">
121
+ colored
122
+ </div>
123
+ <div className="shadow-md ring-2">default</div>
124
+ </Tailwind>,
125
+ );
126
+ const document = new DOMParser().parseFromString(html, 'text/html');
127
+ const [colored, defaultElement] = document.querySelectorAll('div');
128
+
129
+ expect(html).not.toContain('var(');
130
+ expect(colored?.style.boxShadow).toContain('rgb(255,0,0');
131
+ expect(colored?.style.boxShadow).toContain('rgb(0,0,255');
132
+ expect(defaultElement?.style.boxShadow).toContain('rgb(0,0,0,0.1)');
133
+ expect(defaultElement?.style.boxShadow).toContain('currentcolor');
134
+ expect(defaultElement?.style.boxShadow).not.toContain('rgb(255,0,0');
135
+ });
136
+
137
+ it('does not use hover shadow colors for the inline default', async () => {
138
+ const html = await render(
139
+ <Tailwind>
140
+ <Head />
141
+ <div className="shadow-md hover:shadow-red-500">card</div>
142
+ </Tailwind>,
143
+ );
144
+
145
+ expect(html).not.toContain('var(');
146
+ expect(html).toContain('0 4px 6px -1px rgb(0,0,0,0.1)');
147
+ expect(html).toContain('hover_shadow-red-500');
148
+ });
149
+
92
150
  it('works with class manipulation done on components', async () => {
93
151
  const MyComponnt = (props: {
94
152
  className?: string;
@@ -1,9 +1,32 @@
1
- import { type CssNode, type Declaration, generate, walk } from 'css-tree';
1
+ import {
2
+ type CssNode,
3
+ type Declaration,
4
+ generate,
5
+ parse,
6
+ type Value,
7
+ walk,
8
+ } from 'css-tree';
2
9
  import { getReactProperty } from '../compatibility/get-react-property.js';
3
10
  import type { CustomProperties } from './get-custom-properties.js';
11
+ import { resolveCalcExpressions } from './resolve-calc-expressions.js';
12
+ import { sanitizeDeclarations } from './sanitize-declarations.js';
4
13
  import { stripEmptyTailwindVars } from './strip-empty-tailwind-vars.js';
5
14
  import { unwrapValue } from './unwrap-value.js';
6
15
 
16
+ function hasVarFunction(node: CssNode) {
17
+ let found = false;
18
+ walk(node, {
19
+ visit: 'Function',
20
+ enter(func) {
21
+ if (func.name === 'var') {
22
+ found = true;
23
+ return this.break;
24
+ }
25
+ },
26
+ });
27
+ return found;
28
+ }
29
+
7
30
  export function makeInlineStylesFor(
8
31
  inlinableRules: CssNode[],
9
32
  customProperties: CustomProperties,
@@ -22,49 +45,70 @@ export function makeInlineStylesFor(
22
45
  });
23
46
  }
24
47
 
25
- for (const rule of inlinableRules) {
26
- walk(rule, {
48
+ const resolveValue = (
49
+ value: CssNode,
50
+ resolving = new Set<string>(),
51
+ ): CssNode => {
52
+ const resolved = parse(generate(value), { context: 'value' }) as Value;
53
+ walk(resolved, {
27
54
  visit: 'Function',
28
- enter(func, funcParentListItem) {
29
- if (func.name === 'var') {
30
- let variableName: string | undefined;
31
- walk(func, {
32
- visit: 'Identifier',
33
- enter(identifier) {
34
- variableName = identifier.name;
35
- return this.break;
36
- },
37
- });
38
- if (variableName) {
39
- const definition = localVariableDeclarations.get(variableName);
40
- if (definition) {
41
- funcParentListItem.data = unwrapValue(definition.value);
42
- } else {
43
- // For most variables tailwindcss defines, they also define a custom
44
- // property for them with an initial value that we can inline here
45
- const customProperty = customProperties.get(variableName);
46
- if (customProperty?.initialValue) {
47
- funcParentListItem.data = unwrapValue(
48
- customProperty.initialValue.value,
49
- );
50
- }
51
- }
52
- }
55
+ leave(func, item) {
56
+ if (func.name !== 'var') return;
57
+ const children = func.children.toArray();
58
+ const name = children[0] ? generate(children[0]).trim() : '';
59
+ if (resolving.has(name)) return;
60
+
61
+ const definition = localVariableDeclarations.get(name);
62
+ const initialValue = customProperties.get(name)?.initialValue;
63
+ const comma = children.findIndex(
64
+ (child) => child.type === 'Operator' && child.value === ',',
65
+ );
66
+ const fallback =
67
+ comma === -1
68
+ ? undefined
69
+ : children
70
+ .slice(comma + 1)
71
+ .map((child) => generate(child))
72
+ .join('');
73
+ const replacement = definition?.value ?? initialValue?.value;
74
+ if (replacement) {
75
+ item.data = unwrapValue(
76
+ resolveValue(replacement, new Set([...resolving, name])) as Value,
77
+ );
78
+ } else if (fallback) {
79
+ const declaration: Declaration = {
80
+ type: 'Declaration',
81
+ property: 'value',
82
+ important: false,
83
+ value: resolveValue(
84
+ parse(fallback, { context: 'value' }),
85
+ resolving,
86
+ ) as Value,
87
+ };
88
+ sanitizeDeclarations(declaration);
89
+ item.data = unwrapValue(declaration.value);
53
90
  }
54
91
  },
55
92
  });
93
+ return resolved;
94
+ };
56
95
 
96
+ for (const rule of inlinableRules) {
57
97
  walk(rule, {
58
98
  visit: 'Declaration',
59
99
  enter(declaration) {
60
100
  if (declaration.property.startsWith('--')) {
61
101
  return;
62
102
  }
63
- stripEmptyTailwindVars(declaration.value);
103
+ let value: CssNode = declaration.value;
104
+ if (hasVarFunction(value)) {
105
+ value = resolveValue(value);
106
+ resolveCalcExpressions(value);
107
+ stripEmptyTailwindVars(value);
108
+ }
64
109
 
65
110
  styles[getReactProperty(declaration.property)] =
66
- generate(declaration.value).trim() +
67
- (declaration.important ? '!important' : '');
111
+ generate(value).trim() + (declaration.important ? '!important' : '');
68
112
  },
69
113
  });
70
114
  }