mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ensure CSS theme() functions are evaluated in media query ranges with collapsed whitespace (#14321)
Fixes #14320 This PR adds `>`, `<`, and `=` as separators into the CSS value parser. This is necessary because [`@media` range context](https://www.w3.org/TR/mediaqueries-4/#mq-range-context) does not require spaces around these operators so something like this is a valid value for the range syntax: ```css @media (40rem<width<=48rem) { /* ... */ } ``` If you add our CSS `theme()` function to the mix, this rule look like that: ```css @media (theme(--breakpoint-sm)<width<=theme(--breakpoint-md)) { /* ... */ } ``` --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
a1d56d8e24
commit
adc8dfb1a2
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314))
|
||||
- Ensure CSS `theme()` functions are evaluated in media query ranges with collapsed whitespace ((#14321)[https://github.com/tailwindlabs/tailwindcss/pull/14321])
|
||||
|
||||
## [4.0.0-alpha.21] - 2024-09-02
|
||||
|
||||
|
||||
@ -540,14 +540,15 @@ describe('theme function', () => {
|
||||
})
|
||||
|
||||
describe('in @media queries', () => {
|
||||
test('@media (min-width: theme(breakpoint.md)) and (max-width: theme(--breakpoint-lg))', async () => {
|
||||
test('@media (min-width:theme(breakpoint.md)) and (max-width: theme(--breakpoint-lg))', async () => {
|
||||
expect(
|
||||
await compileCss(css`
|
||||
@theme {
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
}
|
||||
@media (min-width: theme(breakpoint.md)) and (max-width: theme(--breakpoint-lg)) {
|
||||
/* prettier-ignore */
|
||||
@media (min-width:theme(breakpoint.md)) and (max-width: theme(--breakpoint-lg)) {
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
@ -566,5 +567,32 @@ describe('theme function', () => {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('@media (width >= theme(breakpoint.md)) and (width<theme(--breakpoint-lg))', async () => {
|
||||
expect(
|
||||
await compileCss(css`
|
||||
@theme {
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
}
|
||||
@media (width >= theme(breakpoint.md)) and (width<theme(--breakpoint-lg)) {
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`),
|
||||
).toMatchInlineSnapshot(`
|
||||
":root {
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
}
|
||||
|
||||
@media (width >= 48rem) and (width < 64rem) {
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -93,7 +93,11 @@ describe('parse', () => {
|
||||
})
|
||||
|
||||
it('should handle media query params with functions', () => {
|
||||
expect(parse('(min-width: 600px) and (max-width:theme(colors.red.500))')).toEqual([
|
||||
expect(
|
||||
parse(
|
||||
'(min-width: 600px) and (max-width:theme(colors.red.500)) and (theme(--breakpoint-sm)<width<=theme(--breakpoint-md))',
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
kind: 'function',
|
||||
value: '',
|
||||
@ -115,6 +119,20 @@ describe('parse', () => {
|
||||
{ kind: 'function', value: 'theme', nodes: [{ kind: 'word', value: 'colors.red.500' }] },
|
||||
],
|
||||
},
|
||||
{ kind: 'separator', value: ' ' },
|
||||
{ kind: 'word', value: 'and' },
|
||||
{ kind: 'separator', value: ' ' },
|
||||
{
|
||||
kind: 'function',
|
||||
value: '',
|
||||
nodes: [
|
||||
{ kind: 'function', value: 'theme', nodes: [{ kind: 'word', value: '--breakpoint-sm' }] },
|
||||
{ kind: 'separator', value: '<' },
|
||||
{ kind: 'word', value: 'width' },
|
||||
{ kind: 'separator', value: '<=' },
|
||||
{ kind: 'function', value: 'theme', nodes: [{ kind: 'word', value: '--breakpoint-md' }] },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -111,6 +111,9 @@ const DOUBLE_QUOTE = 0x22
|
||||
const OPEN_PAREN = 0x28
|
||||
const SINGLE_QUOTE = 0x27
|
||||
const SPACE = 0x20
|
||||
const LESS_THAN = 0x3c
|
||||
const GREATER_THAN = 0x3e
|
||||
const EQUALS = 0x3d
|
||||
|
||||
export function parse(input: string) {
|
||||
input = input.replaceAll('\r\n', '\n')
|
||||
@ -139,7 +142,10 @@ export function parse(input: string) {
|
||||
// ```
|
||||
case COLON:
|
||||
case COMMA:
|
||||
case SPACE: {
|
||||
case SPACE:
|
||||
case LESS_THAN:
|
||||
case GREATER_THAN:
|
||||
case EQUALS: {
|
||||
// 1. Handle everything before the separator as a word
|
||||
// Handle everything before the closing paren a word
|
||||
if (buffer.length > 0) {
|
||||
@ -157,7 +163,14 @@ export function parse(input: string) {
|
||||
let end = i + 1
|
||||
for (; end < input.length; end++) {
|
||||
peekChar = input.charCodeAt(end)
|
||||
if (peekChar !== COLON && peekChar !== COMMA && peekChar !== SPACE) {
|
||||
if (
|
||||
peekChar !== COLON &&
|
||||
peekChar !== COMMA &&
|
||||
peekChar !== SPACE &&
|
||||
peekChar !== LESS_THAN &&
|
||||
peekChar !== GREATER_THAN &&
|
||||
peekChar !== EQUALS
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user