Migrate supports theme keys (#18817)
This PR is a follow up of #18815 and #18816, but this time let's migrate the `supports` theme keys. Let's imagine you have the following Tailwind CSS v3 configuration: ```ts export default { content: ['./src/**/*.html'], theme: { extend: { supports: { // Automatically handled by bare values (using CSS variable as the value) foo: 'foo: var(--foo)', // parentheses are optional bar: '(bar: var(--bar))', // Not automatically handled because names differ baz: 'qux: var(--foo)', // ^^^ ^^^ ← different names // Custom grid: 'display: grid', }, }, }, } ``` Then we would generate the following Tailwind CSS v4 CSS: ```css @custom-variant supports-baz { @supports (qux: var(--foo)) { @slot; } } @custom-variant supports-grid { @supports (display: grid) { @slot; } } ``` Notice how we didn't generate a custom variant for `data-foo` or `data-bar` because those are automatically handled by bare values. I also went with the longer form of `@custom-variant`, we could use the single selector approach, but that felt less clear to me. ```css @custom-variant supports-baz (@supports (qux: var(--foo))); @custom-variant supports-grid (@supports (display: grid)); ``` --------- Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
This commit is contained in:
parent
82034ec327
commit
e578238da5
3 changed files with 127 additions and 7 deletions
|
|
@ -17,8 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Discard matched variants with unknown named values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799))
|
||||
- Discard matched variants with non-string values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799))
|
||||
- Show suggestions for known `matchVariant` values ([#18798](https://github.com/tailwindlabs/tailwindcss/pull/18798))
|
||||
- Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815))
|
||||
- Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816))
|
||||
- Upgrade: Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815))
|
||||
- Upgrade: Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816))
|
||||
- Upgrade: Migrate `supports` theme keys to `@custom-variant` ([#18817](https://github.com/tailwindlabs/tailwindcss/pull/18817))
|
||||
|
||||
## [4.1.12] - 2025-08-13
|
||||
|
||||
|
|
|
|||
|
|
@ -1122,6 +1122,95 @@ test(
|
|||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'migrate supports theme keys to custom variants',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
export default {
|
||||
content: {
|
||||
relative: true,
|
||||
files: ['./src/**/*.html'],
|
||||
},
|
||||
theme: {
|
||||
extend: {
|
||||
supports: {
|
||||
// Automatically handled by bare values (using CSS variable as the value)
|
||||
foo: 'foo: var(--foo)', // parentheses are optional
|
||||
bar: '(bar: var(--bar))',
|
||||
|
||||
// Not automatically handled by bare values because names differ
|
||||
foo: 'bar: var(--foo)', // parentheses are optional
|
||||
bar: '(qux: var(--bar))',
|
||||
|
||||
// Custom
|
||||
grid: 'display: grid',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs, expect }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
@custom-variant supports-foo {
|
||||
@supports (bar: var(--foo)) {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
@custom-variant supports-bar {
|
||||
@supports ((qux: var(--bar))) {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
@custom-variant supports-grid {
|
||||
@supports (display: grid) {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentcolor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentcolor);
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
describe('border compatibility', () => {
|
||||
test(
|
||||
'migrate border compatibility',
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
isValidOpacityValue,
|
||||
isValidSpacingMultiplier,
|
||||
} from '../../../../tailwindcss/src/utils/infer-data-type'
|
||||
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
|
||||
import { findStaticPlugins, type StaticPluginOptions } from '../../utils/extract-static-plugins'
|
||||
import { highlight, info, relative } from '../../utils/renderer'
|
||||
|
||||
|
|
@ -164,6 +165,35 @@ async function migrateTheme(
|
|||
}
|
||||
delete resolvedConfig.theme.data
|
||||
}
|
||||
|
||||
if ('supports' in resolvedConfig.theme) {
|
||||
for (let [key, value] of Object.entries(resolvedConfig.theme.supports ?? {})) {
|
||||
// Will be handled by bare values if the value of the declaration is a
|
||||
// CSS variable.
|
||||
let parsed = ValueParser.parse(`${value}`)
|
||||
|
||||
// Unwrap the parens, e.g.: `(foo: var(--bar))` → `foo: var(--bar)`
|
||||
if (parsed.length === 1 && parsed[0].kind === 'function' && parsed[0].value === '') {
|
||||
parsed = parsed[0].nodes
|
||||
}
|
||||
|
||||
// Verify structure: `foo: var(--bar)`
|
||||
// ^^^ ← must match the `key`
|
||||
if (
|
||||
parsed.length === 3 &&
|
||||
parsed[0].kind === 'word' &&
|
||||
parsed[0].value === key &&
|
||||
parsed[2].kind === 'function' &&
|
||||
parsed[2].value === 'var'
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create custom variant
|
||||
variants.set(`supports-${key}`, `{@supports(${value}){@slot;}}`)
|
||||
}
|
||||
delete resolvedConfig.theme.supports
|
||||
}
|
||||
}
|
||||
|
||||
// Convert theme values to CSS custom properties
|
||||
|
|
@ -242,7 +272,11 @@ async function migrateTheme(
|
|||
if (previousRoot !== root) css += '\n'
|
||||
previousRoot = root
|
||||
|
||||
css += `@custom-variant ${name} (${selector});\n`
|
||||
if (selector.startsWith('{')) {
|
||||
css += `@custom-variant ${name} ${selector}\n`
|
||||
} else {
|
||||
css += `@custom-variant ${name} (${selector});\n`
|
||||
}
|
||||
}
|
||||
css += '}\n'
|
||||
}
|
||||
|
|
@ -407,15 +441,11 @@ const ALLOWED_THEME_KEYS = [
|
|||
// Used by @tailwindcss/container-queries
|
||||
'containers',
|
||||
]
|
||||
const BLOCKED_THEME_KEYS = ['supports']
|
||||
function onlyAllowedThemeValues(theme: ThemeConfig): boolean {
|
||||
for (let key of Object.keys(theme)) {
|
||||
if (!ALLOWED_THEME_KEYS.includes(key)) {
|
||||
return false
|
||||
}
|
||||
if (BLOCKED_THEME_KEYS.includes(key)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue