react-email 6.5.0 → 6.6.1

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.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 21bac49: Fix `Markdown` corrupting double quotes in `markdownCustomStyles`. Inline style values containing a `"` (e.g. `fontFamily: '"Times New Roman", serif'`) were escaped to the apostrophe entity `'` instead of `"`, silently rewriting the quoted value. Double quotes are now escaped as `"`.
8
+ - Updated dependencies [60a5b09]
9
+ - @react-email/render@2.0.9
10
+
11
+ ## 6.6.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 16ff94c: add a useTitleTag in Preview component so users can disable it if they want to
16
+
3
17
  ## 6.5.0
4
18
 
5
19
  ### Minor Changes
@@ -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.5.0",
6526
+ version: "6.6.1",
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",
@@ -6562,7 +6562,7 @@ var package_default = {
6562
6562
  dependencies: {
6563
6563
  "@babel/parser": "catalog:",
6564
6564
  "@babel/traverse": "catalog:",
6565
- "@react-email/render": "workspace:>=2.0.8",
6565
+ "@react-email/render": "workspace:>=2.0.9",
6566
6566
  "chokidar": "^4.0.3",
6567
6567
  "commander": "catalog:",
6568
6568
  "conf": "^15.0.2",
package/dist/index.cjs CHANGED
@@ -17730,7 +17730,7 @@ function camelToKebabCase(str) {
17730
17730
  return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
17731
17731
  }
17732
17732
  function escapeQuotes(value) {
17733
- if (typeof value === "string" && value.includes("\"")) return value.replace(/"/g, "'");
17733
+ if (typeof value === "string" && value.includes("\"")) return value.replace(/"/g, """);
17734
17734
  return value;
17735
17735
  }
17736
17736
  function parseCssInJsToInlineCss(cssProperties) {
@@ -17879,9 +17879,9 @@ Markdown.displayName = "Markdown";
17879
17879
  //#endregion
17880
17880
  //#region src/components/preview/preview.tsx
17881
17881
  const PREVIEW_MAX_LENGTH = 200;
17882
- const Preview = react.forwardRef(({ children = "", ...props }, ref) => {
17882
+ const Preview = react.forwardRef(({ children = "", useTitleTag = true, ...props }, ref) => {
17883
17883
  const text = (Array.isArray(children) ? children.join("") : children).substring(0, PREVIEW_MAX_LENGTH);
17884
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: text }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17884
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [useTitleTag ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: text }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17885
17885
  style: {
17886
17886
  display: "none",
17887
17887
  overflow: "hidden",
package/dist/index.d.cts CHANGED
@@ -5058,9 +5058,17 @@ declare const Markdown: React$2.ForwardRefExoticComponent<Readonly<{
5058
5058
  //#endregion
5059
5059
  //#region src/components/preview/preview.d.ts
5060
5060
  type PreviewProps = Readonly<React$2.ComponentPropsWithoutRef<'div'> & {
5061
+ /**
5062
+ * @default true
5063
+ */
5064
+ useTitleTag?: boolean;
5061
5065
  children: string | string[];
5062
5066
  }>;
5063
5067
  declare const Preview: React$2.ForwardRefExoticComponent<Readonly<Omit<React$2.DetailedHTMLProps<React$2.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
5068
+ /**
5069
+ * @default true
5070
+ */
5071
+ useTitleTag?: boolean;
5064
5072
  children: string | string[];
5065
5073
  }> & React$2.RefAttributes<HTMLDivElement>>;
5066
5074
  declare const renderWhiteSpace: (text: string) => _$react_jsx_runtime0.JSX.Element | null;
package/dist/index.d.mts CHANGED
@@ -5058,9 +5058,17 @@ declare const Markdown: React$2.ForwardRefExoticComponent<Readonly<{
5058
5058
  //#endregion
5059
5059
  //#region src/components/preview/preview.d.ts
5060
5060
  type PreviewProps = Readonly<React$2.ComponentPropsWithoutRef<'div'> & {
5061
+ /**
5062
+ * @default true
5063
+ */
5064
+ useTitleTag?: boolean;
5061
5065
  children: string | string[];
5062
5066
  }>;
5063
5067
  declare const Preview: React$2.ForwardRefExoticComponent<Readonly<Omit<React$2.DetailedHTMLProps<React$2.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
5068
+ /**
5069
+ * @default true
5070
+ */
5071
+ useTitleTag?: boolean;
5064
5072
  children: string | string[];
5065
5073
  }> & React$2.RefAttributes<HTMLDivElement>>;
5066
5074
  declare const renderWhiteSpace: (text: string) => _$react_jsx_runtime0.JSX.Element | null;
package/dist/index.mjs CHANGED
@@ -17709,7 +17709,7 @@ function camelToKebabCase(str) {
17709
17709
  return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
17710
17710
  }
17711
17711
  function escapeQuotes(value) {
17712
- if (typeof value === "string" && value.includes("\"")) return value.replace(/"/g, "&#x27;");
17712
+ if (typeof value === "string" && value.includes("\"")) return value.replace(/"/g, "&quot;");
17713
17713
  return value;
17714
17714
  }
17715
17715
  function parseCssInJsToInlineCss(cssProperties) {
@@ -17858,9 +17858,9 @@ Markdown.displayName = "Markdown";
17858
17858
  //#endregion
17859
17859
  //#region src/components/preview/preview.tsx
17860
17860
  const PREVIEW_MAX_LENGTH = 200;
17861
- const Preview = React$1.forwardRef(({ children = "", ...props }, ref) => {
17861
+ const Preview = React$1.forwardRef(({ children = "", useTitleTag = true, ...props }, ref) => {
17862
17862
  const text = (Array.isArray(children) ? children.join("") : children).substring(0, PREVIEW_MAX_LENGTH);
17863
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("title", { children: text }), /* @__PURE__ */ jsxs("div", {
17863
+ return /* @__PURE__ */ jsxs(Fragment, { children: [useTitleTag ? /* @__PURE__ */ jsx("title", { children: text }) : null, /* @__PURE__ */ jsxs("div", {
17864
17864
  style: {
17865
17865
  display: "none",
17866
17866
  overflow: "hidden",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.5.0",
3
+ "version": "6.6.1",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@babel/parser": "7.27.0",
39
39
  "@babel/traverse": "7.27.0",
40
- "@react-email/render": ">=2.0.8",
40
+ "@react-email/render": ">=2.0.9",
41
41
  "chokidar": "^4.0.3",
42
42
  "commander": "^13.0.0",
43
43
  "conf": "^15.0.2",
@@ -74,7 +74,7 @@
74
74
  "tsx": "4.21.0",
75
75
  "typescript": "5.9.3",
76
76
  "yalc": "1.0.0-pre.53",
77
- "@react-email/render": "2.0.8"
77
+ "@react-email/render": "2.0.9"
78
78
  },
79
79
  "scripts": {
80
80
  "build": "tsdown",
@@ -116,7 +116,7 @@ console.log(\`Hello, $\{name}!\`);
116
116
  </Markdown>,
117
117
  );
118
118
  expect(actualOutput).toMatchInlineSnapshot(`
119
- "<!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><strong style="font:700 23px / 32px &#x27;Roobert PRO&#x27;, system-ui, sans-serif;background:url(&#x27;path/to/image&#x27;)">This is sample bold text in markdown</strong> and <em style="font-style:italic">this is italic text</em></p>
119
+ "<!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><strong style="font:700 23px / 32px &quot;Roobert PRO&quot;, system-ui, sans-serif;background:url(&quot;path/to/image&quot;)">This is sample bold text in markdown</strong> and <em style="font-style:italic">this is italic text</em></p>
120
120
  </div><!--/$-->"
121
121
  `);
122
122
  });
@@ -0,0 +1,42 @@
1
+ import { parseCssInJsToInlineCss } from './parse-css-in-js-to-inline-css.js';
2
+
3
+ describe('parseCssInJsToInlineCss', () => {
4
+ it('returns an empty string for undefined styles', () => {
5
+ expect(parseCssInJsToInlineCss(undefined)).toBe('');
6
+ });
7
+
8
+ it('converts camelCase properties to kebab-case', () => {
9
+ expect(parseCssInJsToInlineCss({ fontFamily: 'serif' })).toBe(
10
+ 'font-family:serif',
11
+ );
12
+ });
13
+
14
+ it('appends px to numeric values of numerical properties', () => {
15
+ expect(parseCssInJsToInlineCss({ fontSize: 16 })).toBe('font-size:16px');
16
+ });
17
+
18
+ it('joins multiple declarations with a semicolon', () => {
19
+ expect(parseCssInJsToInlineCss({ color: 'red', fontSize: 16 })).toBe(
20
+ 'color:red;font-size:16px',
21
+ );
22
+ });
23
+
24
+ it('escapes double quotes so they do not break the style attribute', () => {
25
+ const result = parseCssInJsToInlineCss({
26
+ fontFamily: '"Times New Roman", serif',
27
+ });
28
+
29
+ expect(result).toBe('font-family:&quot;Times New Roman&quot;, serif');
30
+ expect(result).not.toContain('"');
31
+ });
32
+
33
+ it('does not corrupt double-quoted values into apostrophes', () => {
34
+ const result = parseCssInJsToInlineCss({
35
+ fontFamily: '"Times New Roman", serif',
36
+ });
37
+
38
+ // `&#x27;` is the apostrophe entity and would silently change the
39
+ // quoted font name; a double quote must be escaped as `&quot;`.
40
+ expect(result).not.toContain('&#x27;');
41
+ });
42
+ });
@@ -4,7 +4,7 @@ function camelToKebabCase(str: string): string {
4
4
 
5
5
  function escapeQuotes(value: unknown) {
6
6
  if (typeof value === 'string' && value.includes('"')) {
7
- return value.replace(/"/g, '&#x27;');
7
+ return value.replace(/"/g, '&quot;');
8
8
  }
9
9
  return value;
10
10
  }
@@ -2,6 +2,10 @@ import * as React from 'react';
2
2
 
3
3
  export type PreviewProps = Readonly<
4
4
  React.ComponentPropsWithoutRef<'div'> & {
5
+ /**
6
+ * @default true
7
+ */
8
+ useTitleTag?: boolean;
5
9
  children: string | string[];
6
10
  }
7
11
  >;
@@ -9,14 +13,14 @@ export type PreviewProps = Readonly<
9
13
  const PREVIEW_MAX_LENGTH = 200;
10
14
 
11
15
  export const Preview = React.forwardRef<HTMLDivElement, PreviewProps>(
12
- ({ children = '', ...props }, ref) => {
16
+ ({ children = '', useTitleTag = true, ...props }, ref) => {
13
17
  const text = (
14
18
  Array.isArray(children) ? children.join('') : children
15
19
  ).substring(0, PREVIEW_MAX_LENGTH);
16
20
 
17
21
  return (
18
22
  <>
19
- <title>{text}</title>
23
+ {useTitleTag ? <title>{text}</title> : null}
20
24
  <div
21
25
  style={{
22
26
  display: 'none',