Skip to content

Commit 148d870

Browse files
Improve support for custom variants in group-*, peer-*, has-*, and not-* variants (#14743)
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
1 parent 5a1c2e7 commit 148d870

13 files changed

+1115
-195
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `not-*` versions of all builtin media query and supports variants ([#14743](https://github.com/tailwindlabs/tailwindcss/pull/14743))
13+
- Improved support for custom variants with `group-*`, `peer-*`, `has-*`, and `not-*` ([#14743](https://github.com/tailwindlabs/tailwindcss/pull/14743))
14+
1015
### Changed
1116

1217
- Don't convert underscores in the first argument to `var()` and `theme()` to spaces ([#14776](https://github.com/tailwindlabs/tailwindcss/pull/14776), [#14781](https://github.com/tailwindlabs/tailwindcss/pull/14781))
@@ -17,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1722
- Don't migrate important modifiers inside conditional statements in Vue and Alpine (e.g. `<div v-if="!border" />`) ([#14774](https://github.com/tailwindlabs/tailwindcss/pull/14774))
1823
- Ensure third-party plugins with `exports` in their `package.json` are resolved correctly ([#14775](https://github.com/tailwindlabs/tailwindcss/pull/14775))
1924
- Ensure underscores in the `url()` function are never unescaped ([#14776](https://github.com/tailwindlabs/tailwindcss/pull/14776))
25+
- Fixed display of complex variants in Intellisense ([#14743](https://github.com/tailwindlabs/tailwindcss/pull/14743))
2026
- _Upgrade (experimental)_: Ensure `@import` statements for relative CSS files are actually migrated to use relative path syntax ([#14769](https://github.com/tailwindlabs/tailwindcss/pull/14769))
2127
- _Upgrade (experimental)_: Only generate Preflight compatibility styles when Preflight is used ([#14773](https://github.com/tailwindlabs/tailwindcss/pull/14773))
2228
- _Upgrade (experimental)_: Don't escape underscores when printing theme values migrated to CSS variables in arbitrary values (e.g. `m-[var(--spacing-1_5)]` instead of `m-[var(--spacing-1\_5)]`) ([#14778](https://github.com/tailwindlabs/tailwindcss/pull/14778))

packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap

+62-1
Original file line numberDiff line numberDiff line change
@@ -3859,7 +3859,68 @@ exports[`getVariants 1`] = `
38593859
"isArbitrary": true,
38603860
"name": "not",
38613861
"selectors": [Function],
3862-
"values": [],
3862+
"values": [
3863+
"not",
3864+
"group",
3865+
"peer",
3866+
"first",
3867+
"last",
3868+
"only",
3869+
"odd",
3870+
"even",
3871+
"first-of-type",
3872+
"last-of-type",
3873+
"only-of-type",
3874+
"visited",
3875+
"target",
3876+
"open",
3877+
"default",
3878+
"checked",
3879+
"indeterminate",
3880+
"placeholder-shown",
3881+
"autofill",
3882+
"optional",
3883+
"required",
3884+
"valid",
3885+
"invalid",
3886+
"in-range",
3887+
"out-of-range",
3888+
"read-only",
3889+
"empty",
3890+
"focus-within",
3891+
"hover",
3892+
"focus",
3893+
"focus-visible",
3894+
"active",
3895+
"enabled",
3896+
"disabled",
3897+
"inert",
3898+
"has",
3899+
"aria",
3900+
"data",
3901+
"nth",
3902+
"nth-last",
3903+
"nth-of-type",
3904+
"nth-last-of-type",
3905+
"supports",
3906+
"motion-safe",
3907+
"motion-reduce",
3908+
"contrast-more",
3909+
"contrast-less",
3910+
"max",
3911+
"sm",
3912+
"min",
3913+
"@max",
3914+
"@",
3915+
"@min",
3916+
"portrait",
3917+
"landscape",
3918+
"ltr",
3919+
"rtl",
3920+
"dark",
3921+
"print",
3922+
"forced-colors",
3923+
],
38633924
},
38643925
{
38653926
"hasDash": true,

packages/tailwindcss/src/ast.ts

+49-4
Original file line numberDiff line numberDiff line change
@@ -87,33 +87,37 @@ export function walk(
8787
parent: AstNode | null
8888
replaceWith(newNode: AstNode | AstNode[]): void
8989
context: Record<string, string>
90+
path: AstNode[]
9091
},
9192
) => void | WalkAction,
92-
parent: AstNode | null = null,
93+
parentPath: AstNode[] = [],
9394
context: Record<string, string> = {},
9495
) {
9596
for (let i = 0; i < ast.length; i++) {
9697
let node = ast[i]
98+
let path = [...parentPath, node]
99+
let parent = parentPath.at(-1) ?? null
97100

98101
// We want context nodes to be transparent in walks. This means that
99102
// whenever we encounter one, we immediately walk through its children and
100103
// furthermore we also don't update the parent.
101104
if (node.kind === 'context') {
102-
walk(node.nodes, visit, parent, { ...context, ...node.context })
105+
walk(node.nodes, visit, parentPath, { ...context, ...node.context })
103106
continue
104107
}
105108

106109
let status =
107110
visit(node, {
108111
parent,
112+
context,
113+
path,
109114
replaceWith(newNode) {
110115
ast.splice(i, 1, ...(Array.isArray(newNode) ? newNode : [newNode]))
111116
// We want to visit the newly replaced node(s), which start at the
112117
// current index (i). By decrementing the index here, the next loop
113118
// will process this position (containing the replaced node) again.
114119
i--
115120
},
116-
context,
117121
}) ?? WalkAction.Continue
118122

119123
// Stop the walk entirely
@@ -123,11 +127,52 @@ export function walk(
123127
if (status === WalkAction.Skip) continue
124128

125129
if (node.kind === 'rule') {
126-
walk(node.nodes, visit, node, context)
130+
walk(node.nodes, visit, path, context)
127131
}
128132
}
129133
}
130134

135+
// This is a depth-first traversal of the AST
136+
export function walkDepth(
137+
ast: AstNode[],
138+
visit: (
139+
node: AstNode,
140+
utils: {
141+
parent: AstNode | null
142+
path: AstNode[]
143+
context: Record<string, string>
144+
replaceWith(newNode: AstNode[]): void
145+
},
146+
) => void,
147+
parentPath: AstNode[] = [],
148+
context: Record<string, string> = {},
149+
) {
150+
for (let i = 0; i < ast.length; i++) {
151+
let node = ast[i]
152+
let path = [...parentPath, node]
153+
let parent = parentPath.at(-1) ?? null
154+
155+
if (node.kind === 'rule') {
156+
walkDepth(node.nodes, visit, path, context)
157+
} else if (node.kind === 'context') {
158+
walkDepth(node.nodes, visit, parentPath, { ...context, ...node.context })
159+
continue
160+
}
161+
162+
visit(node, {
163+
parent,
164+
context,
165+
path,
166+
replaceWith(newNode) {
167+
ast.splice(i, 1, ...newNode)
168+
169+
// Skip over the newly inserted nodes (being depth-first it doesn't make sense to visit them)
170+
i += newNode.length - 1
171+
},
172+
})
173+
}
174+
}
175+
131176
export function toCss(ast: AstNode[]) {
132177
let atRoots: string = ''
133178
let seenAtProperties = new Set<string>()

0 commit comments

Comments
 (0)