diff --git a/.changeset/ten-pens-refuse.md b/.changeset/ten-pens-refuse.md new file mode 100644 index 000000000..b7515cf15 --- /dev/null +++ b/.changeset/ten-pens-refuse.md @@ -0,0 +1,6 @@ +--- +'@compiled/babel-plugin': minor +'@compiled/css': minor +--- + +Added support for the @starting-style at-rule. diff --git a/package.json b/package.json index 9f8253a04..fc37365e3 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/utils": "^6.21.0", "babel-loader": "^9.1.2", + "csstype": "^3.1.3", "eslint": "^8.57.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json-files": "^2.2.0", diff --git a/packages/babel-plugin/src/css-map/__tests__/at-rules-and-selectors.test.ts b/packages/babel-plugin/src/css-map/__tests__/at-rules-and-selectors.test.ts index f9d816e97..b9ac90fa5 100644 --- a/packages/babel-plugin/src/css-map/__tests__/at-rules-and-selectors.test.ts +++ b/packages/babel-plugin/src/css-map/__tests__/at-rules-and-selectors.test.ts @@ -277,6 +277,35 @@ describe('css map advanced functionality (at rules, selectors object)', () => { } }); + // TODO: add a unit test for the `@starting-style` at-rule when it is NOT nested. This is currently not working as + // Compiled only supports processing at-rules that have two "halves", e.g. `@media screen` + // When nested, the at-rule is not processed like an at-rule - it is processed like a CSS selector. + it('should parse the @starting-style at-rule when nested', () => { + const actual = transform(` + import { cssMap } from '@compiled/react'; + + const styles = cssMap({ + success: { + color: 'red', + '@media (prefers-reduced-motion: no-preference)': { + '@starting-style': { + color: 'blue' + }, + }, + }, + }); + + ${EXAMPLE_USAGE} + `); + + expect(actual).toIncludeMultiple([ + '._syaz5scu{color:red}', + '@media (prefers-reduced-motion:no-preference){@starting-style{._ff1013q2{color:blue}}}', + + 'const styles={success:"_syaz5scu _ff1013q2"}', + ]); + }); + it('should error if more than one selectors key passed', () => { expect(() => { transform(` diff --git a/packages/babel-plugin/src/utils/css-map.ts b/packages/babel-plugin/src/utils/css-map.ts index 984bcbb0a..1335c420e 100644 --- a/packages/babel-plugin/src/utils/css-map.ts +++ b/packages/babel-plugin/src/utils/css-map.ts @@ -20,7 +20,9 @@ const atRules: Record = { '@namespace': true, '@page': true, '@property': true, + '@scope': true, '@scroll-timeline': true, + '@starting-style': true, '@supports': true, '@viewport': true, }; diff --git a/packages/css/src/plugins/__tests__/atomicify-rules.test.ts b/packages/css/src/plugins/__tests__/atomicify-rules.test.ts index 193d554fd..4d18f284d 100644 --- a/packages/css/src/plugins/__tests__/atomicify-rules.test.ts +++ b/packages/css/src/plugins/__tests__/atomicify-rules.test.ts @@ -384,10 +384,14 @@ describe('atomicify rules', () => { @supports selector(h2 > p) { color: pink; } + + @starting-style { + color: green; + } `; expect(actual).toMatchInlineSnapshot( - `"@container (width > 300px){._eq985scu h2{color:red}}@when font-tech(color-COLRv1) and font-tech(variations){@font-face{font-family:test;src:url(test.woff2)}}@else font-tech(color-SVG){@font-face{font-family:test;src:url(test2.woff2)}}@else{@font-face{font-family:test;src:url(test3.woff2)}}@-moz-document url-prefix(){._qral13q2{color:blue}}@layer state{._8tgm6x50{background-color:brown}}@media (min-width: 30rem){._hi7c1ule{display:block}._1l5zgktf{font-size:20px}}@supports selector(h2 > p){._1ll732ev{color:pink}}"` + `"@container (width > 300px){._eq985scu h2{color:red}}@when font-tech(color-COLRv1) and font-tech(variations){@font-face{font-family:test;src:url(test.woff2)}}@else font-tech(color-SVG){@font-face{font-family:test;src:url(test2.woff2)}}@else{@font-face{font-family:test;src:url(test3.woff2)}}@-moz-document url-prefix(){._qral13q2{color:blue}}@layer state{._8tgm6x50{background-color:brown}}@media (min-width: 30rem){._hi7c1ule{display:block}._1l5zgktf{font-size:20px}}@supports selector(h2 > p){._1ll732ev{color:pink}}@starting-style{._p77hbf54{color:green}}"` ); }); diff --git a/packages/css/src/plugins/atomicify-rules.ts b/packages/css/src/plugins/atomicify-rules.ts index 0afa7a365..9179d401b 100644 --- a/packages/css/src/plugins/atomicify-rules.ts +++ b/packages/css/src/plugins/atomicify-rules.ts @@ -188,6 +188,7 @@ const canAtomicifyAtRule = (node: AtRule): boolean => { 'else', 'layer', 'media', + 'starting-style', 'supports', 'when', ]; diff --git a/packages/css/src/transform.ts b/packages/css/src/transform.ts index 61c28722a..7d809f8d7 100644 --- a/packages/css/src/transform.ts +++ b/packages/css/src/transform.ts @@ -42,7 +42,17 @@ export const transformCss = ( discardEmptyRules(), parentOrphanedPseudos(), nested({ - bubble: ['container', '-moz-document', 'layer', 'else', 'when'], + bubble: [ + 'container', + '-moz-document', + 'layer', + 'else', + 'when', + // postcss-nested bubbles `starting-style` by default in versions from 6.0.2 onwards: + // https://github.com/postcss/postcss-nested?tab=readme-ov-file#bubble + // When we upgrade to a version that includes this change, we can remove this from the list. + 'starting-style', + ], unwrap: ['color-profile', 'counter-style', 'font-palette-values', 'page', 'property'], }), ...normalizeCSS(opts), diff --git a/yarn.lock b/yarn.lock index 53527f1c8..d888ccaca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7600,10 +7600,10 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2, csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.0.2, csstype@^3.1.2, csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== csv-generate@^3.4.3: version "3.4.3"