Skip to content

Commit 7677c59

Browse files
authored
Implement the supports variant (#9453)
* implement a `supports` variant * update changelog * use `--tw` instead of `--tw-empty`
1 parent 22cd14e commit 7677c59

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Add `@config` support ([#9405](https://github.com/tailwindlabs/tailwindcss/pull/9405))
2121
- Add `fill-none` and `stroke-none` utilities by default ([#9403](https://github.com/tailwindlabs/tailwindcss/pull/9403))
2222
- Support `sort` function in `matchVariant` ([#9423](https://github.com/tailwindlabs/tailwindcss/pull/9423))
23+
- Implement the `supports` variant ([#9453](https://github.com/tailwindlabs/tailwindcss/pull/9453))
2324

2425
### Fixed
2526

src/corePlugins.js

+32
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { normalizeScreens } from './util/normalizeScreens'
1616
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
1717
import { removeAlphaVariables } from './util/removeAlphaVariables'
1818
import { flagEnabled } from './featureFlags'
19+
import { normalize } from './util/dataTypes'
1920

2021
export let variantPlugins = {
2122
pseudoElementVariants: ({ addVariant }) => {
@@ -215,6 +216,37 @@ export let variantPlugins = {
215216
}
216217
},
217218

219+
supportsVariants: ({ matchVariant, theme, config }) => {
220+
if (!flagEnabled(config(), 'matchVariant')) return
221+
222+
matchVariant(
223+
'supports',
224+
({ value = '' }) => {
225+
let check = normalize(value)
226+
let isRaw = /^\w*\s*\(/.test(check)
227+
228+
// Chrome has a bug where `(condtion1)or(condition2)` is not valid
229+
// But `(condition1) or (condition2)` is supported.
230+
check = isRaw ? check.replace(/\b(and|or|not)\b/g, ' $1 ') : check
231+
232+
if (isRaw) {
233+
return `@supports ${check}`
234+
}
235+
236+
if (!check.includes(':')) {
237+
check = `${check}: var(--tw)`
238+
}
239+
240+
if (!(check.startsWith('(') && check.endsWith(')'))) {
241+
check = `(${check})`
242+
}
243+
244+
return `@supports ${check} `
245+
},
246+
{ values: theme('supports') ?? {} }
247+
)
248+
},
249+
218250
orientationVariants: ({ addVariant }) => {
219251
addVariant('portrait', '@media (orientation: portrait)')
220252
addVariant('landscape', '@media (orientation: landscape)')

src/lib/setupContextUtils.js

+1
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ function resolvePlugins(context, root) {
655655
variantPlugins['pseudoClassVariants'],
656656
]
657657
let afterVariants = [
658+
variantPlugins['supportsVariants'],
658659
variantPlugins['directionVariants'],
659660
variantPlugins['reducedMotionVariants'],
660661
variantPlugins['prefersContrastVariants'],

tests/arbitrary-variants.test.js

+92
Original file line numberDiff line numberDiff line change
@@ -615,3 +615,95 @@ test('classes in the same arbitrary variant should not be prefixed', () => {
615615
`)
616616
})
617617
})
618+
619+
it('should support supports', () => {
620+
let config = {
621+
experimental: { matchVariant: true },
622+
theme: {
623+
supports: {
624+
grid: 'display: grid',
625+
},
626+
},
627+
content: [
628+
{
629+
raw: html`
630+
<div>
631+
<!-- Property check -->
632+
<div class="supports-[display:grid]:grid"></div>
633+
<!-- Value with spaces, needs to be normalized -->
634+
<div class="supports-[transform-origin:5%_5%]:underline"></div>
635+
<!-- Selectors (raw) -->
636+
<div class="supports-[selector(A>B)]:underline"></div>
637+
<!-- 'not' check (raw) -->
638+
<div class="supports-[not(foo:bar)]:underline"></div>
639+
<!-- 'or' check (raw) -->
640+
<div class="supports-[(foo:bar)or(bar:baz)]:underline"></div>
641+
<!-- 'and' check (raw) -->
642+
<div class="supports-[(foo:bar)and(bar:baz)]:underline"></div>
643+
<!-- No value give for the property, defaulting to prop: var(--tw) -->
644+
<div class="supports-[container-type]:underline"></div>
645+
<!-- Named supports usage -->
646+
<div class="supports-grid:underline"></div>
647+
</div>
648+
`,
649+
},
650+
],
651+
corePlugins: { preflight: false },
652+
}
653+
654+
let input = css`
655+
@tailwind utilities;
656+
`
657+
658+
return run(input, config).then((result) => {
659+
expect(result.css).toMatchFormattedCss(css`
660+
@supports (display: grid) {
661+
.supports-grid\:underline {
662+
text-decoration-line: underline;
663+
}
664+
}
665+
666+
@supports (display: grid) {
667+
.supports-\[display\:grid\]\:grid {
668+
display: grid;
669+
}
670+
}
671+
672+
@supports (transform-origin: 5% 5%) {
673+
.supports-\[transform-origin\:5\%_5\%\]\:underline {
674+
text-decoration-line: underline;
675+
}
676+
}
677+
678+
@supports selector(A > B) {
679+
.supports-\[selector\(A\>B\)\]\:underline {
680+
text-decoration-line: underline;
681+
}
682+
}
683+
684+
@supports not (foo: bar) {
685+
.supports-\[not\(foo\:bar\)\]\:underline {
686+
text-decoration-line: underline;
687+
}
688+
}
689+
690+
@supports (foo: bar) or (bar: baz) {
691+
.supports-\[\(foo\:bar\)or\(bar\:baz\)\]\:underline {
692+
text-decoration-line: underline;
693+
}
694+
}
695+
696+
@supports (foo: bar) and (bar: baz) {
697+
.supports-\[\(foo\:bar\)and\(bar\:baz\)\]\:underline {
698+
text-decoration-line: underline;
699+
}
700+
}
701+
702+
@supports (container-type: var(--tw)) {
703+
.supports-\[container-type\]\:underline {
704+
text-decoration-line: underline;
705+
}
706+
}
707+
`)
708+
})
709+
})

0 commit comments

Comments
 (0)