mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Discard invalid variants and utilities with modifiers (#13977)
* ensure that static utilities do not take a `modifier` * do not allow multiple segments for now Right now, `bg-red-1/2/3` should not parse * add tests for variants that don't accept a modifier * ensure static variants do not accept a modifier * do not accept a modifier for some variants * add tests for utilities that don't accept a modifier * do not accept a modifier for some utilities * update changelog * re-add sorting related test
This commit is contained in:
parent
de48a76b6d
commit
6c0c6a5941
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Discard invalid classes such as `bg-red-[#000]` ([#13970](https://github.com/tailwindlabs/tailwindcss/pull/13970))
|
||||
- Fix parsing body-less at-rule without terminating semicolon ([#13978](https://github.com/tailwindlabs/tailwindcss/pull/13978))
|
||||
- Ensure opacity modifier with variables work with `color-mix()` ([#13972](https://github.com/tailwindlabs/tailwindcss/pull/13972))
|
||||
- Discard invalid `variants` and `utilities` with modifiers ([#13977](https://github.com/tailwindlabs/tailwindcss/pull/13977))
|
||||
|
||||
## [4.0.0-alpha.17] - 2024-07-04
|
||||
|
||||
|
||||
@ -445,6 +445,27 @@ it('should not parse a partial utility', () => {
|
||||
expect(run('bg-', { utilities })).toMatchInlineSnapshot(`null`)
|
||||
})
|
||||
|
||||
it('should not parse static utilities with a modifier', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
||||
expect(run('flex/foo', { utilities })).toMatchInlineSnapshot(`null`)
|
||||
})
|
||||
|
||||
it('should not parse static utilities with multiple modifiers', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
||||
expect(run('flex/foo/bar', { utilities })).toMatchInlineSnapshot(`null`)
|
||||
})
|
||||
|
||||
it('should not parse functional utilities with multiple modifiers', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.functional('bg', () => [])
|
||||
|
||||
expect(run('bg-red-1/2/3', { utilities })).toMatchInlineSnapshot(`null`)
|
||||
})
|
||||
|
||||
it('should parse a utility with an arbitrary value', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.functional('bg', () => [])
|
||||
|
||||
@ -258,7 +258,14 @@ export function parseCandidate(input: string, designSystem: DesignSystem): Candi
|
||||
// ^^^^^^^^^^ -> Base without modifier
|
||||
// ^^ -> Modifier segment
|
||||
// ```
|
||||
let [baseWithoutModifier, modifierSegment = null] = segment(base, '/')
|
||||
let [baseWithoutModifier, modifierSegment = null, additionalModifier] = segment(base, '/')
|
||||
|
||||
// If there's more than one modifier, the utility is invalid.
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// - `bg-red-500/50/50`
|
||||
if (additionalModifier) return null
|
||||
|
||||
// Arbitrary properties
|
||||
if (baseWithoutModifier[0] === '[') {
|
||||
@ -373,8 +380,12 @@ export function parseCandidate(input: string, designSystem: DesignSystem): Candi
|
||||
let kind = designSystem.utilities.kind(root)
|
||||
|
||||
if (kind === 'static') {
|
||||
// Static utilities do not have a value
|
||||
if (value !== null) return null
|
||||
|
||||
// Static utilities do not have a modifier
|
||||
if (modifierSegment !== null) return null
|
||||
|
||||
return {
|
||||
kind: 'static',
|
||||
root,
|
||||
@ -557,8 +568,12 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia
|
||||
|
||||
switch (designSystem.variants.kind(root)) {
|
||||
case 'static': {
|
||||
// Static variants do not have a value
|
||||
if (value !== null) return null
|
||||
|
||||
// Static variants do not have a modifier
|
||||
if (modifier !== null) return null
|
||||
|
||||
return {
|
||||
kind: 'static',
|
||||
root,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -317,12 +317,15 @@ export function createUtilities(theme: Theme) {
|
||||
let value: string | null = null
|
||||
|
||||
if (!candidate.value) {
|
||||
if (candidate.modifier) return
|
||||
|
||||
// If the candidate has no value segment (like `rounded`), use the
|
||||
// `defaultValue` (for candidates like `grow` that have no theme values)
|
||||
// or a bare theme value (like `--radius` for `rounded`). No utility
|
||||
// will ever support both of these.
|
||||
value = desc.defaultValue ?? theme.resolve(null, desc.themeKeys ?? [])
|
||||
} else if (candidate.value.kind === 'arbitrary') {
|
||||
if (candidate.modifier) return
|
||||
value = candidate.value.value
|
||||
} else {
|
||||
value = theme.resolve(
|
||||
@ -333,6 +336,8 @@ export function createUtilities(theme: Theme) {
|
||||
// Automatically handle things like `w-1/2` without requiring `1/2` to
|
||||
// exist as a theme value.
|
||||
if (value === null && desc.supportsFractions && candidate.value.fraction) {
|
||||
let [lhs, rhs] = segment(candidate.value.fraction, '/')
|
||||
if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return
|
||||
value = `calc(${candidate.value.fraction} * 100%)`
|
||||
}
|
||||
|
||||
@ -340,6 +345,7 @@ export function createUtilities(theme: Theme) {
|
||||
// use the bare candidate value as the value.
|
||||
if (value === null && desc.handleBareValue) {
|
||||
value = desc.handleBareValue(candidate.value)
|
||||
if (!value?.includes('/') && candidate.modifier) return
|
||||
}
|
||||
}
|
||||
|
||||
@ -893,8 +899,7 @@ export function createUtilities(theme: Theme) {
|
||||
handleBareValue: ({ fraction }) => {
|
||||
if (fraction === null) return null
|
||||
let [lhs, rhs] = segment(fraction, '/')
|
||||
if (!Number.isInteger(Number(lhs))) return null
|
||||
if (!Number.isInteger(Number(rhs))) return null
|
||||
if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return null
|
||||
return fraction
|
||||
},
|
||||
handle: (value) => [decl('aspect-ratio', value)],
|
||||
@ -1058,18 +1063,23 @@ export function createUtilities(theme: Theme) {
|
||||
if (candidate.negative) return
|
||||
|
||||
if (!candidate.value) {
|
||||
if (candidate.modifier) return
|
||||
return [decl('display', 'flex')]
|
||||
}
|
||||
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
if (candidate.modifier) return
|
||||
return [decl('flex', candidate.value.value)]
|
||||
}
|
||||
|
||||
if (candidate.value.fraction) {
|
||||
let [lhs, rhs] = segment(candidate.value.fraction, '/')
|
||||
if (!Number.isInteger(Number(lhs)) || !Number.isInteger(Number(rhs))) return
|
||||
return [decl('flex', `calc(${candidate.value.fraction} * 100%)`)]
|
||||
}
|
||||
|
||||
if (Number.isInteger(Number(candidate.value.value))) {
|
||||
if (candidate.modifier) return
|
||||
return [decl('flex', candidate.value.value)]
|
||||
}
|
||||
})
|
||||
@ -1325,7 +1335,8 @@ export function createUtilities(theme: Theme) {
|
||||
*/
|
||||
staticUtility('scale-none', [['scale', 'none']])
|
||||
utilities.functional('scale', (candidate) => {
|
||||
if (!candidate.value) return
|
||||
if (!candidate.value || candidate.modifier) return
|
||||
|
||||
let value
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
value = candidate.value.value
|
||||
@ -1402,7 +1413,8 @@ export function createUtilities(theme: Theme) {
|
||||
*/
|
||||
staticUtility('rotate-none', [['rotate', 'none']])
|
||||
utilities.functional('rotate', (candidate) => {
|
||||
if (!candidate.value) return
|
||||
if (!candidate.value || candidate.modifier) return
|
||||
|
||||
let value
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
value = candidate.value.value
|
||||
@ -1556,7 +1568,7 @@ export function createUtilities(theme: Theme) {
|
||||
* @css `transform`
|
||||
*/
|
||||
utilities.functional('transform', (candidate) => {
|
||||
if (candidate.negative) return
|
||||
if (candidate.negative || candidate.modifier) return
|
||||
|
||||
let value: string | null = null
|
||||
if (!candidate.value) {
|
||||
@ -2238,6 +2250,7 @@ export function createUtilities(theme: Theme) {
|
||||
if (candidate.negative) return
|
||||
|
||||
if (!candidate.value) {
|
||||
if (candidate.modifier) return
|
||||
let value = theme.get(['--default-border-width']) ?? '1px'
|
||||
let decls = desc.width(value)
|
||||
if (!decls) return
|
||||
@ -2252,6 +2265,7 @@ export function createUtilities(theme: Theme) {
|
||||
switch (type) {
|
||||
case 'line-width':
|
||||
case 'length': {
|
||||
if (candidate.modifier) return
|
||||
let decls = desc.width(value)
|
||||
if (!decls) return
|
||||
return [borderProperties(), ...decls]
|
||||
@ -2275,6 +2289,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// `border-width` property
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--border-width'])
|
||||
if (value) {
|
||||
let decls = desc.width(value)
|
||||
@ -2510,7 +2525,7 @@ export function createUtilities(theme: Theme) {
|
||||
}
|
||||
|
||||
utilities.functional('bg-linear', (candidate) => {
|
||||
if (!candidate.value) return
|
||||
if (!candidate.value || candidate.modifier) return
|
||||
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
let value: string | null = candidate.value.value
|
||||
@ -2552,15 +2567,18 @@ export function createUtilities(theme: Theme) {
|
||||
switch (type) {
|
||||
case 'percentage':
|
||||
case 'position': {
|
||||
if (candidate.modifier) return
|
||||
return [decl('background-position', value)]
|
||||
}
|
||||
case 'bg-size':
|
||||
case 'length':
|
||||
case 'size': {
|
||||
if (candidate.modifier) return
|
||||
return [decl('background-size', value)]
|
||||
}
|
||||
case 'image':
|
||||
case 'url': {
|
||||
if (candidate.modifier) return
|
||||
return [decl('background-image', value)]
|
||||
}
|
||||
default: {
|
||||
@ -2582,6 +2600,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// `background-image` property
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--background-image'])
|
||||
if (value) {
|
||||
return [decl('background-image', value)]
|
||||
@ -2635,6 +2654,7 @@ export function createUtilities(theme: Theme) {
|
||||
switch (type) {
|
||||
case 'length':
|
||||
case 'percentage': {
|
||||
if (candidate.modifier) return
|
||||
return desc.position(value)
|
||||
}
|
||||
default: {
|
||||
@ -2656,6 +2676,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// Known values: Positions
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--gradient-color-stop-positions'])
|
||||
if (value) {
|
||||
return desc.position(value)
|
||||
@ -2808,6 +2829,7 @@ export function createUtilities(theme: Theme) {
|
||||
case 'number':
|
||||
case 'length':
|
||||
case 'percentage': {
|
||||
if (candidate.modifier) return
|
||||
return [decl('stroke-width', value)]
|
||||
}
|
||||
default: {
|
||||
@ -2937,7 +2959,7 @@ export function createUtilities(theme: Theme) {
|
||||
})
|
||||
|
||||
utilities.functional('font', (candidate) => {
|
||||
if (candidate.negative || !candidate.value) return
|
||||
if (candidate.negative || !candidate.value || candidate.modifier) return
|
||||
|
||||
if (candidate.value.kind === 'arbitrary') {
|
||||
let value = candidate.value.value
|
||||
@ -3105,6 +3127,7 @@ export function createUtilities(theme: Theme) {
|
||||
switch (type) {
|
||||
case 'length':
|
||||
case 'percentage': {
|
||||
if (candidate.modifier) return
|
||||
return [decl('text-decoration-thickness', value)]
|
||||
}
|
||||
default: {
|
||||
@ -3120,10 +3143,12 @@ export function createUtilities(theme: Theme) {
|
||||
{
|
||||
let value = theme.resolve(candidate.value.value, ['--text-decoration-thickness'])
|
||||
if (value) {
|
||||
if (candidate.modifier) return
|
||||
return [decl('text-decoration-thickness', value)]
|
||||
}
|
||||
|
||||
if (!Number.isNaN(Number(candidate.value.value))) {
|
||||
if (candidate.modifier) return
|
||||
return [decl('text-decoration-thickness', `${candidate.value.value}px`)]
|
||||
}
|
||||
}
|
||||
@ -3210,7 +3235,7 @@ export function createUtilities(theme: Theme) {
|
||||
}
|
||||
|
||||
utilities.functional('filter', (candidate) => {
|
||||
if (candidate.negative) return
|
||||
if (candidate.negative || candidate.modifier) return
|
||||
|
||||
if (candidate.value === null) {
|
||||
return [filterProperties(), decl('filter', cssFilterValue)]
|
||||
@ -3227,7 +3252,7 @@ export function createUtilities(theme: Theme) {
|
||||
})
|
||||
|
||||
utilities.functional('backdrop-filter', (candidate) => {
|
||||
if (candidate.negative) return
|
||||
if (candidate.negative || candidate.modifier) return
|
||||
|
||||
if (candidate.value === null) {
|
||||
return [
|
||||
@ -3671,6 +3696,9 @@ export function createUtilities(theme: Theme) {
|
||||
// This utility doesn't support negative values.
|
||||
if (candidate.negative) return
|
||||
|
||||
// This utility doesn't support modifiers.
|
||||
if (candidate.modifier) return
|
||||
|
||||
// This utility doesn't support `DEFAULT` values.
|
||||
if (!candidate.value) return
|
||||
|
||||
@ -3912,6 +3940,7 @@ export function createUtilities(theme: Theme) {
|
||||
if (candidate.negative) return
|
||||
|
||||
if (candidate.value === null) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
outlineProperties(),
|
||||
decl('outline-style', 'var(--tw-outline-style)'),
|
||||
@ -3929,6 +3958,7 @@ export function createUtilities(theme: Theme) {
|
||||
case 'length':
|
||||
case 'number':
|
||||
case 'percentage': {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
outlineProperties(),
|
||||
decl('outline-style', 'var(--tw-outline-style)'),
|
||||
@ -3954,6 +3984,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// `outline-width` property
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--outline-width'])
|
||||
if (value) {
|
||||
return [
|
||||
@ -4205,6 +4236,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
switch (candidate.value.value) {
|
||||
case 'none':
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-shadow', nullShadow),
|
||||
@ -4217,6 +4249,7 @@ export function createUtilities(theme: Theme) {
|
||||
{
|
||||
let value = theme.get([`--shadow-${candidate.value.value}`])
|
||||
if (value) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-shadow', value),
|
||||
@ -4302,6 +4335,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
switch (candidate.value.value) {
|
||||
case 'none':
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-inset-shadow', nullShadow),
|
||||
@ -4315,6 +4349,7 @@ export function createUtilities(theme: Theme) {
|
||||
let value = theme.get([`--inset-shadow-${candidate.value.value}`])
|
||||
|
||||
if (value) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-inset-shadow', value),
|
||||
@ -4364,6 +4399,7 @@ export function createUtilities(theme: Theme) {
|
||||
if (candidate.negative) return
|
||||
|
||||
if (!candidate.value) {
|
||||
if (candidate.modifier) return
|
||||
let value = theme.get(['--default-ring-width']) ?? '1px'
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
@ -4378,6 +4414,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
switch (type) {
|
||||
case 'length': {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-ring-shadow', ringShadowValue(value)),
|
||||
@ -4403,6 +4440,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// Ring width
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--ring-width'])
|
||||
if (value === null && !Number.isNaN(Number(candidate.value.value))) {
|
||||
value = `${candidate.value.value}px`
|
||||
@ -4438,6 +4476,7 @@ export function createUtilities(theme: Theme) {
|
||||
if (candidate.negative) return
|
||||
|
||||
if (!candidate.value) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-inset-ring-shadow', insetRingShadowValue('1px')),
|
||||
@ -4451,6 +4490,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
switch (type) {
|
||||
case 'length': {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
boxShadowProperties(),
|
||||
decl('--tw-inset-ring-shadow', insetRingShadowValue(value)),
|
||||
@ -4476,6 +4516,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
// Ring width
|
||||
{
|
||||
if (candidate.modifier) return
|
||||
let value = theme.resolve(candidate.value.value, ['--ring-width'])
|
||||
if (value === null && !Number.isNaN(Number(candidate.value.value))) {
|
||||
value = `${candidate.value.value}px`
|
||||
@ -4515,6 +4556,7 @@ export function createUtilities(theme: Theme) {
|
||||
|
||||
switch (type) {
|
||||
case 'length': {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
decl('--tw-ring-offset-width', value),
|
||||
decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
|
||||
@ -4533,11 +4575,13 @@ export function createUtilities(theme: Theme) {
|
||||
{
|
||||
let value = theme.resolve(candidate.value.value, ['--ring-offset-width'])
|
||||
if (value) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
decl('--tw-ring-offset-width', value),
|
||||
decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
|
||||
]
|
||||
} else if (!Number.isNaN(Number(candidate.value.value))) {
|
||||
if (candidate.modifier) return
|
||||
return [
|
||||
decl('--tw-ring-offset-width', `${candidate.value.value}px`),
|
||||
decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
|
||||
|
||||
@ -9,14 +9,16 @@ test('force', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['force/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('*', () => {
|
||||
expect(run(['*:flex'])).toMatchInlineSnapshot(`
|
||||
".\\*\\:flex > * {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
".\\*\\:flex > * {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['*/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('first-letter', () => {
|
||||
@ -25,6 +27,7 @@ test('first-letter', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['first-letter/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('first-line', () => {
|
||||
@ -33,6 +36,7 @@ test('first-line', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['first-line/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('marker', () => {
|
||||
@ -41,6 +45,7 @@ test('marker', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['marker/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('selection', () => {
|
||||
@ -49,6 +54,7 @@ test('selection', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['selection/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('file', () => {
|
||||
@ -57,6 +63,7 @@ test('file', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['file/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('placeholder', () => {
|
||||
@ -65,6 +72,7 @@ test('placeholder', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['placeholder/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('backdrop', () => {
|
||||
@ -73,6 +81,7 @@ test('backdrop', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['backdrop/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('before', () => {
|
||||
@ -96,6 +105,7 @@ test('before', () => {
|
||||
initial-value: "";
|
||||
}"
|
||||
`)
|
||||
expect(run(['before/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('after', () => {
|
||||
@ -119,6 +129,7 @@ test('after', () => {
|
||||
initial-value: "";
|
||||
}"
|
||||
`)
|
||||
expect(run(['after/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('first', () => {
|
||||
@ -135,6 +146,7 @@ test('first', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['first/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('last', () => {
|
||||
@ -151,6 +163,7 @@ test('last', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['last/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('only', () => {
|
||||
@ -167,6 +180,7 @@ test('only', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['only/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('odd', () => {
|
||||
@ -183,6 +197,7 @@ test('odd', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['odd/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('even', () => {
|
||||
@ -199,6 +214,7 @@ test('even', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['even/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('first-of-type', () => {
|
||||
@ -216,6 +232,7 @@ test('first-of-type', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['first-of-type/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('last-of-type', () => {
|
||||
@ -233,6 +250,7 @@ test('last-of-type', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['last-of-type/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('only-of-type', () => {
|
||||
@ -250,6 +268,7 @@ test('only-of-type', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['only-of-type/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('visited', () => {
|
||||
@ -266,6 +285,7 @@ test('visited', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['visited/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('target', () => {
|
||||
@ -282,6 +302,7 @@ test('target', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['target/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('open', () => {
|
||||
@ -298,6 +319,7 @@ test('open', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['open/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('default', () => {
|
||||
@ -314,6 +336,7 @@ test('default', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['default/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('checked', () => {
|
||||
@ -330,6 +353,7 @@ test('checked', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['checked/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('indeterminate', () => {
|
||||
@ -347,6 +371,7 @@ test('indeterminate', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['indeterminate/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('placeholder-shown', () => {
|
||||
@ -365,6 +390,7 @@ test('placeholder-shown', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['placeholder-shown/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('autofill', () => {
|
||||
@ -382,6 +408,7 @@ test('autofill', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['autofill/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('optional', () => {
|
||||
@ -399,6 +426,7 @@ test('optional', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['optional/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('required', () => {
|
||||
@ -416,6 +444,7 @@ test('required', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['required/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('valid', () => {
|
||||
@ -432,6 +461,7 @@ test('valid', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['valid/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('invalid', () => {
|
||||
@ -448,6 +478,7 @@ test('invalid', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['invalid/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('in-range', () => {
|
||||
@ -465,6 +496,7 @@ test('in-range', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['in-range/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('out-of-range', () => {
|
||||
@ -482,6 +514,7 @@ test('out-of-range', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['out-of-range/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('read-only', () => {
|
||||
@ -499,6 +532,7 @@ test('read-only', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['read-only/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('empty', () => {
|
||||
@ -515,6 +549,7 @@ test('empty', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['empty/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('focus-within', () => {
|
||||
@ -532,6 +567,7 @@ test('focus-within', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['focus-within/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('hover', () => {
|
||||
@ -548,6 +584,7 @@ test('hover', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['hover/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('focus', () => {
|
||||
@ -564,9 +601,10 @@ test('focus', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['focus/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('group-hover group-focus', () => {
|
||||
test('group-hover group-focus sorting', () => {
|
||||
expect(run(['group-hover:flex', 'group-focus:flex'])).toMatchInlineSnapshot(`
|
||||
".group-hover\\:flex:is(:where(.group):hover *) {
|
||||
display: flex;
|
||||
@ -602,6 +640,7 @@ test('focus-visible', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['focus-visible/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('active', () => {
|
||||
@ -618,6 +657,7 @@ test('active', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['active/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('enabled', () => {
|
||||
@ -634,6 +674,7 @@ test('enabled', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['enabled/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('disabled', () => {
|
||||
@ -651,6 +692,7 @@ test('disabled', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['disabled/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('group-[...]', () => {
|
||||
@ -777,6 +819,7 @@ test('ltr', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['ltr/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('rtl', () => {
|
||||
@ -785,6 +828,7 @@ test('rtl', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['rtl/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('motion-safe', () => {
|
||||
@ -795,6 +839,7 @@ test('motion-safe', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['motion-safe/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('motion-reduce', () => {
|
||||
@ -805,6 +850,7 @@ test('motion-reduce', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['motion-reduce/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('dark', () => {
|
||||
@ -815,6 +861,7 @@ test('dark', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['dark/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('starting', () => {
|
||||
@ -825,6 +872,7 @@ test('starting', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['starting/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('print', () => {
|
||||
@ -835,6 +883,7 @@ test('print', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['print/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('default breakpoints', () => {
|
||||
@ -892,6 +941,22 @@ test('default breakpoints', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@theme reference {
|
||||
/* Breakpoints */
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
--breakpoint-2xl: 1536px;
|
||||
}
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['sm/foo:flex', 'md/foo:flex', 'lg/foo:flex', 'xl/foo:flex', '2xl/foo:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('custom breakpoint', () => {
|
||||
@ -957,6 +1022,20 @@ test('max-*', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@theme reference {
|
||||
/* Explicitly ordered in a strange way */
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-md: 768px;
|
||||
}
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['max-lg/foo:flex', 'max-sm/foo:flex', 'max-md/foo:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('min-*', () => {
|
||||
@ -998,6 +1077,20 @@ test('min-*', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@theme reference {
|
||||
/* Explicitly ordered in a strange way */
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-md: 768px;
|
||||
}
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['min-lg/foo:flex', 'min-sm/foo:flex', 'min-md/foo:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('sorting stacked min-* and max-* variants', () => {
|
||||
@ -1356,6 +1449,17 @@ test('supports', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(
|
||||
run([
|
||||
'supports-gap/foo:grid',
|
||||
'supports-[display:grid]/foo:flex',
|
||||
'supports-[selector(A_>_B)]/foo:flex',
|
||||
'supports-[font-format(opentype)]/foo:grid',
|
||||
'supports-[(display:grid)_and_font-format(opentype)]/foo:grid',
|
||||
'supports-[font-tech(color-COLRv1)]/foo:flex',
|
||||
'supports-[--test]/foo:flex',
|
||||
]),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('not', () => {
|
||||
@ -1400,6 +1504,8 @@ test('not', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(run(['not-[:checked]/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('has', () => {
|
||||
@ -1444,6 +1550,7 @@ test('has', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['has-[:checked]/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('aria', () => {
|
||||
@ -1503,6 +1610,7 @@ test('aria', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['aria-checked/foo:flex', 'aria-[invalid=spelling]/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('data', () => {
|
||||
@ -1542,6 +1650,7 @@ test('data', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['data-disabled/foo:flex', 'data-[potato=salad]/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('portrait', () => {
|
||||
@ -1552,6 +1661,7 @@ test('portrait', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['portrait/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('landscape', () => {
|
||||
@ -1562,6 +1672,7 @@ test('landscape', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['landscape/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('contrast-more', () => {
|
||||
@ -1572,6 +1683,7 @@ test('contrast-more', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['contrast-more/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('contrast-less', () => {
|
||||
@ -1582,6 +1694,7 @@ test('contrast-less', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['contrast-less/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('forced-colors', () => {
|
||||
@ -1592,6 +1705,7 @@ test('forced-colors', () => {
|
||||
}
|
||||
}"
|
||||
`)
|
||||
expect(run(['forced-colors/foo:flex'])).toEqual('')
|
||||
})
|
||||
|
||||
test('nth', () => {
|
||||
@ -1653,6 +1767,20 @@ test('nth', () => {
|
||||
expect(
|
||||
run(['nth-foo:flex', 'nth-of-type-foo:flex', 'nth-last-foo:flex', 'nth-last-of-type-foo:flex']),
|
||||
).toEqual('')
|
||||
expect(
|
||||
run([
|
||||
'nth-3/foo:flex',
|
||||
'nth-[2n+1]/foo:flex',
|
||||
'nth-[2n+1_of_.foo]/foo:flex',
|
||||
'nth-last-3/foo:flex',
|
||||
'nth-last-[2n+1]/foo:flex',
|
||||
'nth-last-[2n+1_of_.foo]/foo:flex',
|
||||
'nth-of-type-3/foo:flex',
|
||||
'nth-of-type-[2n+1]/foo:flex',
|
||||
'nth-last-of-type-3/foo:flex',
|
||||
'nth-last-of-type-[2n+1]/foo:flex',
|
||||
]),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('container queries', () => {
|
||||
|
||||
@ -178,7 +178,8 @@ export function createVariants(theme: Theme): Variants {
|
||||
variants.static('force', () => {}, { compounds: false })
|
||||
staticVariant('*', ['& > *'], { compounds: false })
|
||||
|
||||
variants.compound('not', (ruleNode) => {
|
||||
variants.compound('not', (ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
ruleNode.selector = `&:not(${ruleNode.selector.replace('&', '*')})`
|
||||
})
|
||||
|
||||
@ -336,7 +337,8 @@ export function createVariants(theme: Theme): Variants {
|
||||
staticVariant(key, [value])
|
||||
}
|
||||
|
||||
variants.compound('has', (ruleNode) => {
|
||||
variants.compound('has', (ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
ruleNode.selector = `&:has(${ruleNode.selector.replace('&', '*')})`
|
||||
})
|
||||
|
||||
@ -347,7 +349,8 @@ export function createVariants(theme: Theme): Variants {
|
||||
})
|
||||
|
||||
variants.functional('aria', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
if (variant.value.kind === 'arbitrary') {
|
||||
ruleNode.nodes = [rule(`&[aria-${variant.value.value}]`, ruleNode.nodes)]
|
||||
} else {
|
||||
@ -368,13 +371,13 @@ export function createVariants(theme: Theme): Variants {
|
||||
])
|
||||
|
||||
variants.functional('data', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
ruleNode.nodes = [rule(`&[data-${variant.value.value}]`, ruleNode.nodes)]
|
||||
})
|
||||
|
||||
variants.functional('nth', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
// Only numeric bare values are allowed
|
||||
if (variant.value.kind === 'named' && Number.isNaN(Number(variant.value.value))) return null
|
||||
@ -383,7 +386,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
})
|
||||
|
||||
variants.functional('nth-last', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
// Only numeric bare values are allowed
|
||||
if (variant.value.kind === 'named' && Number.isNaN(Number(variant.value.value))) return null
|
||||
@ -392,7 +395,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
})
|
||||
|
||||
variants.functional('nth-of-type', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
// Only numeric bare values are allowed
|
||||
if (variant.value.kind === 'named' && Number.isNaN(Number(variant.value.value))) return null
|
||||
@ -401,7 +404,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
})
|
||||
|
||||
variants.functional('nth-last-of-type', (ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
// Only numeric bare values are allowed
|
||||
if (variant.value.kind === 'named' && Number.isNaN(Number(variant.value.value))) return null
|
||||
@ -412,7 +415,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
variants.functional(
|
||||
'supports',
|
||||
(ruleNode, variant) => {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
let value = variant.value.value
|
||||
if (value === null) return null
|
||||
@ -540,7 +543,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
}
|
||||
|
||||
case 'functional': {
|
||||
if (variant.value === null) return null
|
||||
if (!variant.value || variant.modifier) return null
|
||||
|
||||
let value: string | null = null
|
||||
|
||||
@ -567,6 +570,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
variants.functional(
|
||||
'max',
|
||||
(ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
let value = resolvedBreakpoints.get(variant)
|
||||
if (value === null) return null
|
||||
|
||||
@ -601,6 +605,7 @@ export function createVariants(theme: Theme): Variants {
|
||||
variants.functional(
|
||||
'min',
|
||||
(ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
let value = resolvedBreakpoints.get(variant)
|
||||
if (value === null) return null
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user