Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Detect classes in new files when using `@tailwindcss/postcss` ([#14829](https://github.com/tailwindlabs/tailwindcss/pull/14829))
- Ensure utilities with variants are sorted deterministically ([#14835](https://github.com/tailwindlabs/tailwindcss/pull/14835))

## [4.0.0-alpha.31] - 2024-10-29

Expand Down
12 changes: 6 additions & 6 deletions packages/tailwindcss/src/compat/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1109,16 +1109,16 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s
'print:flex',
]),
).toMatchInlineSnapshot(`
".aria-polite\\:underline {
&[aria-live="polite"] {
text-decoration-line: underline;
}
}
.aria-hidden\\:flex {
".aria-hidden\\:flex {
&[aria-hidden="true"] {
display: flex;
}
}
.aria-polite\\:underline {
&[aria-live="polite"] {
text-decoration-line: underline;
}
}
.data-checked\\:underline {
&[data-ui~="checked"] {
text-decoration-line: underline;
Expand Down
174 changes: 117 additions & 57 deletions packages/tailwindcss/src/variants.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dedent from 'dedent'
import { expect, test } from 'vitest'
import { compileCss, run } from './test-utils/run'
import { Compounds, compoundsForSelectors } from './variants'
Expand Down Expand Up @@ -1623,26 +1624,26 @@ test('supports', async () => {
}
}

@supports (display: grid) {
.supports-\\[display\\:grid\\]\\:flex {
display: flex;
@supports (display: grid) and font-format(opentype) {
.supports-\\[\\(display\\:grid\\)_and_font-format\\(opentype\\)\\]\\:grid {
display: grid;
}
}

@supports selector(A > B) {
.supports-\\[selector\\(A_\\>_B\\)\\]\\:flex {
@supports (--test: var(--tw)) {
.supports-\\[--test\\]\\:flex {
display: flex;
}
}

@supports font-format(opentype) {
.supports-\\[font-format\\(opentype\\)\\]\\:grid {
display: grid;
@supports (display: grid) {
.supports-\\[display\\:grid\\]\\:flex {
display: flex;
}
}

@supports (display: grid) and font-format(opentype) {
.supports-\\[\\(display\\:grid\\)_and_font-format\\(opentype\\)\\]\\:grid {
@supports font-format(opentype) {
.supports-\\[font-format\\(opentype\\)\\]\\:grid {
display: grid;
}
}
Expand All @@ -1653,14 +1654,14 @@ test('supports', async () => {
}
}

@supports var(--test) {
.supports-\\[var\\(--test\\)\\]\\:flex {
@supports selector(A > B) {
.supports-\\[selector\\(A_\\>_B\\)\\]\\:flex {
display: flex;
}
}

@supports (--test: var(--tw)) {
.supports-\\[--test\\]\\:flex {
@supports var(--test) {
.supports-\\[var\\(--test\\)\\]\\:flex {
display: flex;
}
}"
Expand Down Expand Up @@ -2399,56 +2400,56 @@ test('aria', async () => {
'peer-aria-[modal]:flex',
'peer-aria-checked:flex',
'peer-aria-[valuenow=1]:flex',
'peer-aria-[modal]/parent-name:flex',
'peer-aria-checked/parent-name:flex',
'peer-aria-[valuenow=1]/parent-name:flex',
'peer-aria-[modal]/sibling-name:flex',
'peer-aria-checked/sibling-name:flex',
'peer-aria-[valuenow=1]/sibling-name:flex',
]),
).toMatchInlineSnapshot(`
".group-aria-\\[modal\\]\\:flex:is(:where(.group)[aria-modal] *) {
".group-aria-checked\\:flex:is(:where(.group)[aria-checked="true"] *) {
display: flex;
}

.group-aria-checked\\:flex:is(:where(.group)[aria-checked="true"] *) {
.group-aria-checked\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-checked="true"] *) {
display: flex;
}

.group-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.group)[aria-valuenow="1"] *) {
.group-aria-\\[modal\\]\\:flex:is(:where(.group)[aria-modal] *) {
display: flex;
}

.group-aria-\\[modal\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-modal] *) {
display: flex;
}

.group-aria-checked\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-checked="true"] *) {
.group-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.group)[aria-valuenow="1"] *) {
display: flex;
}

.group-aria-\\[valuenow\\=1\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-valuenow="1"] *) {
display: flex;
}

.peer-aria-\\[modal\\]\\:flex:is(:where(.peer)[aria-modal] ~ *) {
.peer-aria-checked\\:flex:is(:where(.peer)[aria-checked="true"] ~ *) {
display: flex;
}

.peer-aria-checked\\:flex:is(:where(.peer)[aria-checked="true"] ~ *) {
.peer-aria-checked\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-checked="true"] ~ *) {
display: flex;
}

.peer-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.peer)[aria-valuenow="1"] ~ *) {
.peer-aria-\\[modal\\]\\:flex:is(:where(.peer)[aria-modal] ~ *) {
display: flex;
}

.peer-aria-\\[modal\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[aria-modal] ~ *) {
.peer-aria-\\[modal\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-modal] ~ *) {
display: flex;
}

.peer-aria-checked\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[aria-checked="true"] ~ *) {
.peer-aria-\\[valuenow\\=1\\]\\:flex:is(:where(.peer)[aria-valuenow="1"] ~ *) {
display: flex;
}

.peer-aria-\\[valuenow\\=1\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[aria-valuenow="1"] ~ *) {
.peer-aria-\\[valuenow\\=1\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[aria-valuenow="1"] ~ *) {
display: flex;
}

Expand All @@ -2460,11 +2461,11 @@ test('aria', async () => {
display: flex;
}

.aria-\\[valuenow\\=1\\]\\:flex[aria-valuenow="1"] {
.aria-\\[valuenow_\\=_\\"1\\"\\]\\:flex[aria-valuenow="1"] {
display: flex;
}

.aria-\\[valuenow_\\=_\\"1\\"\\]\\:flex[aria-valuenow="1"] {
.aria-\\[valuenow\\=1\\]\\:flex[aria-valuenow="1"] {
display: flex;
}"
`)
Expand Down Expand Up @@ -2493,12 +2494,12 @@ test('data', async () => {
'group-data-[foo$=bar_baz_i]/parent-name:flex',

'peer-data-[disabled]:flex',
'peer-data-[disabled]/parent-name:flex',
'peer-data-[disabled]/sibling-name:flex',
'peer-data-[foo=1]:flex',
'peer-data-[foo=1]/parent-name:flex',
'peer-data-[foo=bar baz]/parent-name:flex',
"peer-data-[foo$='bar'_i]/parent-name:flex",
'peer-data-[foo$=bar_baz_i]/parent-name:flex',
'peer-data-[foo=1]/sibling-name:flex',
'peer-data-[foo=bar baz]/sibling-name:flex',
"peer-data-[foo$='bar'_i]/sibling-name:flex",
'peer-data-[foo$=bar_baz_i]/sibling-name:flex',
]),
).toMatchInlineSnapshot(`
".group-data-\\[disabled\\]\\:flex:is(:where(.group)[data-disabled] *) {
Expand All @@ -2509,87 +2510,87 @@ test('data', async () => {
display: flex;
}

.group-data-\\[foo\\=1\\]\\:flex:is(:where(.group)[data-foo="1"] *) {
.group-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar" i] *) {
display: flex;
}

.group-data-\\[foo\\=1\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="1"] *) {
.group-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar baz" i] *) {
display: flex;
}

.group-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="bar baz"] *) {
.group-data-\\[foo\\=1\\]\\:flex:is(:where(.group)[data-foo="1"] *) {
display: flex;
}

.group-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar" i] *) {
.group-data-\\[foo\\=1\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="1"] *) {
display: flex;
}

.group-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo$="bar baz" i] *) {
.group-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-foo="bar baz"] *) {
display: flex;
}

.peer-data-\\[disabled\\]\\:flex:is(:where(.peer)[data-disabled] ~ *) {
display: flex;
}

.peer-data-\\[disabled\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-disabled] ~ *) {
.peer-data-\\[disabled\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-disabled] ~ *) {
display: flex;
}

.peer-data-\\[foo\\=1\\]\\:flex:is(:where(.peer)[data-foo="1"] ~ *) {
.peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo$="bar" i] ~ *) {
display: flex;
}

.peer-data-\\[foo\\=1\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo="1"] ~ *) {
.peer-data-\\[foo\\$\\=bar_baz_i\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo$="bar baz" i] ~ *) {
display: flex;
}

.peer-data-\\[foo\\=bar\\ baz\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo="bar baz"] ~ *) {
.peer-data-\\[foo\\=1\\]\\:flex:is(:where(.peer)[data-foo="1"] ~ *) {
display: flex;
}

.peer-data-\\[foo\\$\\=\\'bar\\'_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar" i] ~ *) {
.peer-data-\\[foo\\=1\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo="1"] ~ *) {
display: flex;
}

.peer-data-\\[foo\\$\\=bar_baz_i\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-foo$="bar baz" i] ~ *) {
.peer-data-\\[foo\\=bar\\ baz\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name)[data-foo="bar baz"] ~ *) {
display: flex;
}

.data-disabled\\:flex[data-disabled] {
display: flex;
}

.data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
.data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i] {
display: flex;
}

.data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"] {
.data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i] {
display: flex;
}

.data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"] {
.data-\\[foo\\=1\\]\\:flex[data-foo="1"] {
display: flex;
}

.data-\\[potato\\=\\"\\^_\\=\\"\\]\\:flex[data-potato="^ ="] {
.data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"] {
display: flex;
}

.data-\\[foo\\=1\\]\\:flex[data-foo="1"] {
.data-\\[potato_\\=_\\"salad\\"\\]\\:flex[data-potato="salad"] {
display: flex;
}

.data-\\[foo\\=bar_baz\\]\\:flex[data-foo="bar baz"] {
.data-\\[potato_\\^\\=_\\"salad\\"\\]\\:flex[data-potato^="salad"] {
display: flex;
}

.data-\\[foo\\$\\=\\'bar\\'_i\\]\\:flex[data-foo$="bar" i] {
.data-\\[potato\\=\\"\\^_\\=\\"\\]\\:flex[data-potato="^ ="] {
display: flex;
}

.data-\\[foo\\$\\=bar_baz_i\\]\\:flex[data-foo$="bar baz" i] {
.data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
display: flex;
}"
`)
Expand Down Expand Up @@ -2881,6 +2882,7 @@ test('variant order', async () => {
'contrast-less:flex',
'contrast-more:flex',
'dark:flex',
'data-custom:flex',
'data-[custom=true]:flex',
'default:flex',
'disabled:flex',
Expand Down Expand Up @@ -3123,10 +3125,6 @@ test('variant order', async () => {
display: flex;
}

.aria-\\[custom\\=true\\]\\:flex[aria-custom="true"] {
display: flex;
}

.aria-busy\\:flex[aria-busy="true"] {
display: flex;
}
Expand Down Expand Up @@ -3163,6 +3161,14 @@ test('variant order', async () => {
display: flex;
}

.aria-\\[custom\\=true\\]\\:flex[aria-custom="true"] {
display: flex;
}

.data-custom\\:flex[data-custom] {
display: flex;
}

.data-\\[custom\\=true\\]\\:flex[data-custom="true"] {
display: flex;
}
Expand Down Expand Up @@ -3285,6 +3291,60 @@ test('variant order', async () => {
`)
})

test('variants with the same root are sorted deterministically', async () => {
function permute(arr: string[]): string[][] {
if (arr.length <= 1) return [arr]

return arr.flatMap((item, i) =>
permute([...arr.slice(0, i), ...arr.slice(i + 1)]).map((permutation) => [
item,
...permutation,
]),
)
}

let classLists = permute([
'data-hover:flex',
'data-focus:flex',
'data-active:flex',
'data-[foo]:flex',
'data-[bar]:flex',
'data-[baz]:flex',
])

for (let classList of classLists) {
let output = await compileCss('@tailwind utilities;', classList)

expect(output.trim()).toEqual(
dedent(css`
.data-active\:flex[data-active] {
display: flex;
}

.data-focus\:flex[data-focus] {
display: flex;
}

.data-hover\:flex[data-hover] {
display: flex;
}

.data-\[bar\]\:flex[data-bar] {
display: flex;
}

.data-\[baz\]\:flex[data-baz] {
display: flex;
}

.data-\[foo\]\:flex[data-foo] {
display: flex;
}
`),
)
}
})

test.each([
// These are style rules
[['.foo'], Compounds.StyleRules],
Expand Down
Loading