react-email 6.6.8 → 6.6.9

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,11 @@
1
1
  # react-email
2
2
 
3
+ ## 6.6.9
4
+
5
+ ### Patch Changes
6
+
7
+ - bc2f7e3: Fix `group` utilities including bad rules that don't work
8
+
3
9
  ## 6.6.8
4
10
 
5
11
  ### 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.6.8",
6536
+ version: "6.6.9",
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",
package/dist/index.cjs CHANGED
@@ -37710,8 +37710,10 @@ function extractRulesPerClass(root, classes) {
37710
37710
  walk(root, {
37711
37711
  visit: "Rule",
37712
37712
  enter(rule) {
37713
+ const firstSelector = rule.prelude.type === "SelectorList" ? rule.prelude.children.first : null;
37714
+ if (firstSelector?.type === "Selector" && firstSelector.children.first?.type === "NestingSelector") return;
37713
37715
  const selectorClasses = [];
37714
- walk(rule, {
37716
+ walk(rule.prelude, {
37715
37717
  visit: "ClassSelector",
37716
37718
  enter(classSelector) {
37717
37719
  selectorClasses.push(decode$1(classSelector.name));
package/dist/index.mjs CHANGED
@@ -37689,8 +37689,10 @@ function extractRulesPerClass(root, classes) {
37689
37689
  walk(root, {
37690
37690
  visit: "Rule",
37691
37691
  enter(rule) {
37692
+ const firstSelector = rule.prelude.type === "SelectorList" ? rule.prelude.children.first : null;
37693
+ if (firstSelector?.type === "Selector" && firstSelector.children.first?.type === "NestingSelector") return;
37692
37694
  const selectorClasses = [];
37693
- walk(rule, {
37695
+ walk(rule.prelude, {
37694
37696
  visit: "ClassSelector",
37695
37697
  enter(classSelector) {
37696
37698
  selectorClasses.push(decode$1(classSelector.name));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.6.8",
3
+ "version": "6.6.9",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"
@@ -440,6 +440,40 @@ describe('Tailwind component', () => {
440
440
  );
441
441
  });
442
442
 
443
+ // See https://github.com/resend/react-email/pull/3594
444
+ it('does not leak a bare nested group/peer rule into the <head> <style>', async () => {
445
+ const html = await render(
446
+ <Html>
447
+ <Tailwind>
448
+ <Head />
449
+ <div className="group">
450
+ <a className="group-hover:underline" href="https://react.email">
451
+ link
452
+ </a>
453
+ </div>
454
+ </Tailwind>
455
+ </Html>,
456
+ ).then(pretty);
457
+
458
+ expect(html).toMatchInlineSnapshot(`
459
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
460
+ <html dir="ltr" lang="en">
461
+ <head>
462
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
463
+ <meta name="x-apple-disable-message-reformatting" />
464
+ <style>
465
+ .group-hover_underline{@media (hover:hover){&:is(:where(.group):hover *){text-decoration-line:underline!important}}}
466
+ </style></head
467
+ ><!--$--><!--html--><!--head-->
468
+ <div class="group">
469
+ <a class="group-hover_underline" href="https://react.email">link</a>
470
+ </div>
471
+ <!--/$-->
472
+ </html>
473
+ "
474
+ `);
475
+ });
476
+
443
477
  it('recognizes custom responsive screen', async () => {
444
478
  const actualOutput = await render(
445
479
  <Html>
@@ -206,4 +206,27 @@ describe('extractRulesPerClass()', async () => {
206
206
  }
207
207
  `);
208
208
  });
209
+
210
+ it('does not emit a bare nested rule for group/peer marker classes', async () => {
211
+ const tailwind = await setupTailwind({});
212
+ const classes = ['group', 'group-hover:underline'];
213
+ tailwind.addUtilities(classes);
214
+
215
+ const stylesheet = tailwind.getStyleSheet();
216
+ const { inlinable, nonInlinable } = extractRulesPerClass(
217
+ stylesheet,
218
+ classes,
219
+ );
220
+
221
+ // Only the real utility is keyed; the `group` marker must not produce a
222
+ // duplicate, parentless `&:is(...)` rule.
223
+ expect(Object.keys(convertToComparable(inlinable))).toEqual([]);
224
+ expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(`
225
+ {
226
+ "group-hover:underline": [
227
+ ".group-hover\\:underline{&:is(:where(.group):hover *){@media (hover:hover){text-decoration-line:underline}}}",
228
+ ],
229
+ }
230
+ `);
231
+ });
209
232
  });
@@ -26,8 +26,24 @@ export function extractRulesPerClass(root: CssNode, classes: string[]) {
26
26
  walk(root, {
27
27
  visit: 'Rule',
28
28
  enter(rule) {
29
+ // A nested rule (e.g. group/peer's `&:is(:where(.group):hover *)`) belongs
30
+ // to its parent utility; processing it standalone emits a bare, parentless
31
+ // `&` rule into the <style> block, so skip it here.
32
+ const firstSelector =
33
+ rule.prelude.type === 'SelectorList'
34
+ ? rule.prelude.children.first
35
+ : null;
36
+ if (
37
+ firstSelector?.type === 'Selector' &&
38
+ firstSelector.children.first?.type === 'NestingSelector'
39
+ ) {
40
+ return;
41
+ }
42
+
43
+ // Only the prelude names the class that owns the rule; classes referenced
44
+ // inside the block (e.g. `.group` in `:where(.group)`) must not key it.
29
45
  const selectorClasses: string[] = [];
30
- walk(rule, {
46
+ walk(rule.prelude, {
31
47
  visit: 'ClassSelector',
32
48
  enter(classSelector) {
33
49
  selectorClasses.push(string.decode(classSelector.name));