Skip to content

Commit a6d4309

Browse files
committed
replace arbitrary @media variants
This will replace variants such as `[@media(pointer:fine)]:flex` to `pointer-fine:flex`
1 parent 6085b47 commit a6d4309

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ test.each([
8080
// Attribute selector wrapped in `&:is(…)`
8181
['[&:is([data-visible])]:flex', 'data-visible:flex'],
8282

83+
// Media queries
84+
['[@media(pointer:fine)]:flex', 'pointer-fine:flex'],
85+
['[@media_(pointer_:_fine)]:flex', 'pointer-fine:flex'],
86+
8387
// Compound arbitrary variants
8488
['has-[[data-visible]]:flex', 'has-data-visible:flex'],
8589
['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'],

packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { parseCandidate, type Candidate, type Variant } from '../../../../tailwi
33
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
44
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
55
import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type'
6+
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
67
import { printCandidate } from './candidates'
78

89
function 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

Comments
 (0)