diff --git a/packages/babel-plugin/__tests__/stylex-transform-create-test.js b/packages/babel-plugin/__tests__/stylex-transform-create-test.js index 9cb9f0b2..21556442 100644 --- a/packages/babel-plugin/__tests__/stylex-transform-create-test.js +++ b/packages/babel-plugin/__tests__/stylex-transform-create-test.js @@ -697,6 +697,139 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".x1en94km::-webkit-slider-thumb, .x1en94km::-moz-range-thumb, .x1en94km::-ms-thumb{width:16px}", 9000);" `); }); + + test('transforms pseudo class within a pseudo element', () => { + expect( + transform(` + import stylex from 'stylex'; + export const styles = stylex.create({ + foo: { + '::before': { + color: { + default: 'red', + ':hover': 'blue', + } + }, + }, + }); + `), + ).toMatchInlineSnapshot(` + "import _inject from "@stylexjs/stylex/lib/stylex-inject"; + var _inject2 = _inject; + import stylex from 'stylex'; + _inject2(".x16oeupf::before{color:red}", 8000); + _inject2(".xeb2lg0::before:hover{color:blue}", 8130); + export const styles = { + foo: { + "::before_color": "x16oeupf xeb2lg0", + $$css: true + } + };" + `); + }); + + test('transforms legacy pseudo class within a pseudo element', () => { + expect( + transform(` + import stylex from 'stylex'; + export const styles = stylex.create({ + foo: { + '::before': { + color: 'red', + ':hover': { + color: 'blue', + }, + }, + }, + }); + `), + ).toMatchInlineSnapshot(` + "import _inject from "@stylexjs/stylex/lib/stylex-inject"; + var _inject2 = _inject; + import stylex from 'stylex'; + _inject2(".x16oeupf::before{color:red}", 8000); + _inject2(".xeb2lg0::before:hover{color:blue}", 8130); + export const styles = { + foo: { + "::before_color": "x16oeupf", + "::before_:hover_color": "xeb2lg0", + $$css: true + } + };" + `); + }); + + test('transforms pseudo elements within legeacy pseudo class', () => { + expect( + transform(` + import stylex from 'stylex'; + export const styles = stylex.create({ + foo: { + '::before': { + color: 'red', + }, + ':hover': { + '::before': { + color: 'blue', + }, + }, + }, + }); + `), + ).toMatchInlineSnapshot(` + "import _inject from "@stylexjs/stylex/lib/stylex-inject"; + var _inject2 = _inject; + import stylex from 'stylex'; + _inject2(".x16oeupf::before{color:red}", 8000); + _inject2(".xeb2lg0:hover::before{color:blue}", 8130); + export const styles = { + foo: { + "::before_color": "x16oeupf", + ":hover_::before_color": "xeb2lg0", + $$css: true + } + };" + `); + }); + + test('transforms pseudo elements sandwiched within pseudo classes', () => { + expect( + transform(` + import stylex from 'stylex'; + export const styles = stylex.create({ + foo: { + '::before': { + color: 'red', + }, + ':hover': { + '::before': { + color: { + default: 'blue', + ':hover': 'green', + ':active': 'purple', + }, + }, + }, + }, + }); + `), + ).toMatchInlineSnapshot(` + "import _inject from "@stylexjs/stylex/lib/stylex-inject"; + var _inject2 = _inject; + import stylex from 'stylex'; + _inject2(".x16oeupf::before{color:red}", 8000); + _inject2(".xeb2lg0:hover::before{color:blue}", 8130); + _inject2(".x18ezmze:hover::before:hover{color:green}", 8260); + _inject2(".xnj3kot:hover::before:active{color:purple}", 8300); + export const styles = { + foo: { + "::before_color": "x16oeupf", + ":hover_::before_color": "xeb2lg0 x18ezmze xnj3kot", + $$css: true + } + };" + `); + }); }); describe('queries', () => { diff --git a/packages/shared/__tests__/stylex-create-test.js b/packages/shared/__tests__/stylex-create-test.js index e7d14abb..c7302857 100644 --- a/packages/shared/__tests__/stylex-create-test.js +++ b/packages/shared/__tests__/stylex-create-test.js @@ -516,6 +516,185 @@ describe('stylex-create-test', () => { `); }); + test('transforms nested pseudo-classes within pseudo elements', () => { + expect( + styleXCreate({ + default: { + '::before': { + color: { + default: 'red', + ':hover': 'blue', + }, + }, + }, + }), + ).toMatchInlineSnapshot(` + [ + { + "default": { + "$$css": true, + "::before_color": "x16oeupf xeb2lg0", + }, + }, + { + "x16oeupf": { + "ltr": ".x16oeupf::before{color:red}", + "priority": 8000, + "rtl": null, + }, + "xeb2lg0": { + "ltr": ".xeb2lg0::before:hover{color:blue}", + "priority": 8130, + "rtl": null, + }, + }, + { + "default": { + "x16oeupf": [ + "::before", + "default", + "color", + ], + "xeb2lg0": [ + "::before", + ":hover", + "color", + ], + }, + }, + ] + `); + }); + + test('transforms nested legacy pseudo-classes within pseudo elements', () => { + expect( + styleXCreate({ + default: { + '::before': { + color: 'red', + ':hover': { + color: 'blue', + }, + }, + }, + }), + ).toMatchInlineSnapshot(` + [ + { + "default": { + "$$css": true, + "::before_:hover_color": "xeb2lg0", + "::before_color": "x16oeupf", + }, + }, + { + "x16oeupf": { + "ltr": ".x16oeupf::before{color:red}", + "priority": 8000, + "rtl": null, + }, + "xeb2lg0": { + "ltr": ".xeb2lg0::before:hover{color:blue}", + "priority": 8130, + "rtl": null, + }, + }, + { + "default": { + "x16oeupf": [ + "::before", + "color", + ], + "xeb2lg0": [ + "::before", + ":hover", + "color", + ], + }, + }, + ] + `); + }); + + test('transforms nested pseudo-element within legacy pseudo class', () => { + expect( + styleXCreate({ + default: { + '::before': { + color: 'blue', + }, + ':hover': { + '::before': { + color: { + default: 'red', + ':hover': 'green', + ':active': 'yellow', + }, + }, + }, + }, + }), + ).toMatchInlineSnapshot(` + [ + { + "default": { + "$$css": true, + "::before_color": "xvg9oe5", + ":hover_::before_color": "x1qdmp1q x18ezmze x14o3fp0", + }, + }, + { + "x14o3fp0": { + "ltr": ".x14o3fp0:hover::before:active{color:yellow}", + "priority": 8300, + "rtl": null, + }, + "x18ezmze": { + "ltr": ".x18ezmze:hover::before:hover{color:green}", + "priority": 8260, + "rtl": null, + }, + "x1qdmp1q": { + "ltr": ".x1qdmp1q:hover::before{color:red}", + "priority": 8130, + "rtl": null, + }, + "xvg9oe5": { + "ltr": ".xvg9oe5::before{color:blue}", + "priority": 8000, + "rtl": null, + }, + }, + { + "default": { + "x14o3fp0": [ + ":hover", + "::before", + ":active", + "color", + ], + "x18ezmze": [ + ":hover", + "::before", + ":hover", + "color", + ], + "x1qdmp1q": [ + ":hover", + "::before", + "default", + "color", + ], + "xvg9oe5": [ + "::before", + "color", + ], + }, + }, + ] + `); + }); + // This API will not launch as an array, but internally we can continue to use arrays test('transforms array values as fallbacks', () => { expect( diff --git a/packages/shared/src/preprocess-rules/PreRule.js b/packages/shared/src/preprocess-rules/PreRule.js index 5876d0ee..3c42d8fe 100644 --- a/packages/shared/src/preprocess-rules/PreRule.js +++ b/packages/shared/src/preprocess-rules/PreRule.js @@ -77,6 +77,38 @@ const stringComparator = (a: string, b: string): number => { return a.localeCompare(b); }; +const sortPseudos = ( + pseudos: $ReadOnlyArray, +): $ReadOnlyArray => { + if (pseudos.length < 2) { + return pseudos; + } + + return pseudos + .reduce( + (acc, pseudo) => { + if (pseudo.startsWith('::')) { + return [...acc, pseudo]; + } + + const lastElement = acc[acc.length - 1]; + const allButLast = acc.slice(0, acc.length - 1); + if (Array.isArray(lastElement)) { + return [...allButLast, [...lastElement, pseudo]]; + } else { + return [...allButLast, lastElement, [pseudo]].filter(Boolean); + } + }, + [] as $ReadOnlyArray>, + ) + .flatMap((pseudo) => { + if (Array.isArray(pseudo)) { + return arraySort(pseudo, stringComparator); + } + return [pseudo]; + }); +}; + export class PreRule implements IPreRule { +property: string; +value: string | number | $ReadOnlyArray; @@ -94,7 +126,9 @@ export class PreRule implements IPreRule { get pseudos(): $ReadOnlyArray { const unsortedPseudos = this.keyPath.filter((key) => key.startsWith(':')); - return arraySort(unsortedPseudos, stringComparator); + + return sortPseudos(unsortedPseudos); + // return arraySort(unsortedPseudos, stringComparator); } get atRules(): $ReadOnlyArray {