@@ -3,6 +3,7 @@ import { parseCandidate, type Candidate, type Variant } from '../../../../tailwi
33import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
44import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
55import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type'
6+ import * as ValueParser from '../../../../tailwindcss/src/value-parser'
67import { printCandidate } from './candidates'
78
89function memcpy < T extends object , U extends object | null > ( target : T , source : U ) : U {
@@ -146,6 +147,83 @@ export function migrateModernizeArbitraryValues(
146147 continue
147148 }
148149
150+ // Migrate `@media` variants
151+ //
152+ // E.g.: `[@media(scripting:none)]:` -> `noscript:`
153+ if (
154+ // Only top-level, so `in-[@media(scripting:none)]` is not supported
155+ parent === null &&
156+ // [@media (scripting:none)]:flex
157+ // ^^^^^^^^^^^^^^^^^^^^^^
158+ ast . nodes [ 0 ] . nodes [ 0 ] . type === 'tag' &&
159+ ast . nodes [ 0 ] . nodes [ 0 ] . value . startsWith ( '@media' )
160+ ) {
161+ // Replace all whitespace such that `@media (scripting: none)` and
162+ // `@media(scripting:none)` are equivalent.
163+ //
164+ // As arbitrary variants that means that these are equivalent:
165+ // - `[@media_(scripting:_none)]:`
166+ // - `[@media(scripting:none)]:`
167+ let parsed = ValueParser . parse ( ast . nodes [ 0 ] . toString ( ) . trim ( ) . replace ( '@media' , '' ) )
168+
169+ // Drop whitespace
170+ ValueParser . walk ( parsed , ( node , { replaceWith } ) => {
171+ // Drop whitespace nodes
172+ if ( node . kind === 'separator' && ! node . value . trim ( ) ) {
173+ replaceWith ( [ ] )
174+ }
175+
176+ // Trim whitespace
177+ else {
178+ node . value = node . value . trim ( )
179+ }
180+ } )
181+
182+ if (
183+ parsed . length === 1 &&
184+ parsed [ 0 ] . kind === 'function' && // `(` and `)` are considered a function
185+ parsed [ 0 ] . nodes . length === 3 &&
186+ parsed [ 0 ] . nodes [ 0 ] . kind === 'word' &&
187+ parsed [ 0 ] . nodes [ 1 ] . kind === 'separator' &&
188+ parsed [ 0 ] . nodes [ 1 ] . value === ':' &&
189+ parsed [ 0 ] . nodes [ 2 ] . kind === 'word'
190+ ) {
191+ let key = parsed [ 0 ] . nodes [ 0 ] . value
192+ let value = parsed [ 0 ] . nodes [ 2 ] . value
193+ let replacement : string | null = null
194+
195+ if ( key === 'prefers-reduced-motion' && value === 'no-preference' )
196+ replacement = 'motion-safe'
197+ if ( key === 'prefers-reduced-motion' && value === 'reduce' )
198+ replacement = 'motion-reduce'
199+
200+ if ( key === 'prefers-contrast' && value === 'more' ) replacement = 'contrast-more'
201+ if ( key === 'prefers-contrast' && value === 'less' ) replacement = 'contrast-less'
202+
203+ if ( key === 'orientation' && value === 'portrait' ) replacement = 'portrait'
204+ if ( key === 'orientation' && value === 'landscape' ) replacement = 'landscape'
205+
206+ if ( key === 'forced-colors' && value === 'active' ) replacement = 'forced-colors'
207+
208+ if ( key === 'inverted-colors' && value === 'inverted' ) replacement = 'inverted-colors'
209+
210+ if ( key === 'pointer' && value === 'none' ) replacement = 'pointer-none'
211+ if ( key === 'pointer' && value === 'coarse' ) replacement = 'pointer-coarse'
212+ if ( key === 'pointer' && value === 'fine' ) replacement = 'pointer-fine'
213+ if ( key === 'any-pointer' && value === 'none' ) replacement = 'any-pointer-none'
214+ if ( key === 'any-pointer' && value === 'coarse' ) replacement = 'any-pointer-coarse'
215+ if ( key === 'any-pointer' && value === 'fine' ) replacement = 'any-pointer-fine'
216+
217+ if ( key === 'scripting' && value === 'none' ) replacement = 'noscript'
218+
219+ if ( replacement ) {
220+ changed = true
221+ memcpy ( variant , designSystem . parseVariant ( replacement ) )
222+ }
223+ }
224+ continue
225+ }
226+
149227 let prefixedVariant : Variant | null = null
150228
151229 // Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible`
0 commit comments