Skip to content

Commit

Permalink
Restore old behavior for class dark mode, add new selector and `v…
Browse files Browse the repository at this point in the history
…ariant` options for dark mode (#12717)

* Add dark mode variant option

* Tweak warning messages

* Add legacy dark mode option

* wip

* Use `class` for legacy behavior, `selector` for new behavior

* Add simplified failing apply/where test case

* Switch to `where` list, apply changes to `dir` variants

* Don’t let `:where`, `:is:`, or `:has` be attached to pseudo elements

* Updating tests...

* Finish updating tests

* Remove `variant` dark mode strategy

* Update types

* Update comments

* Update changelog

* Revert "Remove `variant` dark mode strategy"

This reverts commit 1852504.

* Add variant back to types

* wip

* Update comments

* Update tests

* Rename variable

* Update changelog

* Update changelog

* Update changelog

* Fix CS

---------

Co-authored-by: Adam Wathan <[email protected]>
  • Loading branch information
thecrypticace and adamwathan authored Jan 5, 2024
1 parent a5ae318 commit fb1743b
Show file tree
Hide file tree
Showing 23 changed files with 375 additions and 100 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Don't remove keyframe stops when using important utilities ([#12639](https://github.com/tailwindlabs/tailwindcss/pull/12639))
- Don't add spaces to gradients and grid track names when followed by `calc()` ([#12704](https://github.com/tailwindlabs/tailwindcss/pull/12704))
- Restore old behavior for `class` dark mode strategy ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
- Improve glob handling for folders with `(`, `)`, `[` or `]` in the file path ([#12715](https://github.com/tailwindlabs/tailwindcss/pull/12715))

### Added

- Add new `selector` and `variant` strategies for dark mode ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
- [Oxide] New Rust template parsing engine ([#10252](https://github.com/tailwindlabs/tailwindcss/pull/10252))
- [Oxide] Support `@import "tailwindcss"` using top-level `index.css` file ([#11205](https://github.com/tailwindlabs/tailwindcss/pull/11205), ([#11260](https://github.com/tailwindlabs/tailwindcss/pull/11260)))
- [Oxide] Use `lightningcss` for nesting and vendor prefixes in PostCSS plugin ([#10399](https://github.com/tailwindlabs/tailwindcss/pull/10399))
Expand All @@ -25,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Support `rtl` and `ltr` variants on same element as `dir` attribute ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))
- [Oxide] Deprecate `--no-autoprefixer` flag in the CLI ([#11280](https://github.com/tailwindlabs/tailwindcss/pull/11280))
- [Oxide] Make the Rust based parser the default ([#11394](https://github.com/tailwindlabs/tailwindcss/pull/11394))

Expand Down
49 changes: 44 additions & 5 deletions src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ export let variantPlugins = {
},

directionVariants: ({ addVariant }) => {
addVariant('ltr', ':is(:where([dir="ltr"]) &)')
addVariant('rtl', ':is(:where([dir="rtl"]) &)')
addVariant('ltr', '&:where([dir="ltr"], [dir="ltr"] *)')
addVariant('rtl', '&:where([dir="rtl"], [dir="rtl"] *)')
},

reducedMotionVariants: ({ addVariant }) => {
Expand All @@ -216,7 +216,7 @@ export let variantPlugins = {
},

darkVariants: ({ config, addVariant }) => {
let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'))
let [mode, selector = '.dark'] = [].concat(config('darkMode', 'media'))

if (mode === false) {
mode = 'media'
Expand All @@ -227,10 +227,49 @@ export let variantPlugins = {
])
}

if (mode === 'class') {
addVariant('dark', `:is(:where(${className}) &)`)
if (mode === 'variant') {
let formats
if (Array.isArray(selector)) {
formats = selector
} else if (typeof selector === 'function') {
formats = selector
} else if (typeof selector === 'string') {
formats = [selector]
}

// TODO: We could also add these warnings if the user passes a function that returns string | string[]
// But this is an advanced enough use case that it's probably not necessary
if (Array.isArray(formats)) {
for (let format of formats) {
if (format === '.dark') {
mode = false
log.warn('darkmode-variant-without-selector', [
'When using `variant` for `darkMode`, you must provide a selector.',
'Example: `darkMode: ["variant", ".your-selector &"]`',
])
} else if (!format.includes('&')) {
mode = false
log.warn('darkmode-variant-without-ampersand', [
'When using `variant` for `darkMode`, your selector must contain `&`.',
'Example `darkMode: ["variant", ".your-selector &"]`',
])
}
}
}

selector = formats
}

if (mode === 'selector') {
// New preferred behavior
addVariant('dark', `&:where(${selector}, ${selector} *)`)
} else if (mode === 'media') {
addVariant('dark', '@media (prefers-color-scheme: dark)')
} else if (mode === 'variant') {
addVariant('dark', selector)
} else if (mode === 'class') {
// Old behavior
addVariant('dark', `:is(${selector} &)`)
}
},

Expand Down
23 changes: 22 additions & 1 deletion src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,14 +757,35 @@ function resolvePlugins(context, root) {
variantPlugins['supportsVariants'],
variantPlugins['reducedMotionVariants'],
variantPlugins['prefersContrastVariants'],
variantPlugins['printVariant'],
variantPlugins['screenVariants'],
variantPlugins['orientationVariants'],
variantPlugins['directionVariants'],
variantPlugins['darkVariants'],
variantPlugins['forcedColorsVariants'],
variantPlugins['printVariant'],
]

// This is a compatibility fix for the pre 3.4 dark mode behavior
// `class` retains the old behavior, but `selector` keeps the new behavior
let isLegacyDarkMode =
context.tailwindConfig.darkMode === 'class' ||
(Array.isArray(context.tailwindConfig.darkMode) &&
context.tailwindConfig.darkMode[0] === 'class')

if (isLegacyDarkMode) {
afterVariants = [
variantPlugins['supportsVariants'],
variantPlugins['reducedMotionVariants'],
variantPlugins['prefersContrastVariants'],
variantPlugins['darkVariants'],
variantPlugins['screenVariants'],
variantPlugins['orientationVariants'],
variantPlugins['directionVariants'],
variantPlugins['forcedColorsVariants'],
variantPlugins['printVariant'],
]
}

return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
}

Expand Down
4 changes: 4 additions & 0 deletions src/util/pseudoElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ let elementProperties = {
':first-letter': ['terminal', 'jumpable'],
':first-line': ['terminal', 'jumpable'],

':where': [],
':is': [],
':has': [],

// The default value is used when the pseudo-element is not recognized
// Because it's not recognized, we don't know if it's terminal or not
// So we assume it can be moved AND can have user-action pseudo classes attached to it
Expand Down
50 changes: 26 additions & 24 deletions tests/apply.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ let sharedHtml = html`

test('@apply', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: sharedHtml }],
}

Expand Down Expand Up @@ -215,14 +215,14 @@ test('@apply', () => {
text-align: left;
}
}
:is(:where(.dark) .apply-dark-variant) {
.apply-dark-variant:where(.dark, .dark *) {
text-align: center;
}
:is(:where(.dark) .apply-dark-variant:hover) {
.apply-dark-variant:hover:where(.dark, .dark *) {
text-align: right;
}
@media (min-width: 1024px) {
:is(:where(.dark) .apply-dark-variant) {
.apply-dark-variant:where(.dark, .dark *) {
text-align: left;
}
}
Expand Down Expand Up @@ -452,7 +452,7 @@ test('@apply', () => {

test('@apply error with unknown utility', async () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: sharedHtml }],
}

Expand All @@ -472,7 +472,7 @@ test('@apply error with unknown utility', async () => {

test('@apply error with nested @screen', async () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: sharedHtml }],
}

Expand All @@ -496,7 +496,7 @@ test('@apply error with nested @screen', async () => {

test('@apply error with nested @anyatrulehere', async () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: sharedHtml }],
}

Expand All @@ -520,7 +520,7 @@ test('@apply error with nested @anyatrulehere', async () => {

test('@apply error when using .group utility', async () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: '<div class="foo"></div>' }],
}

Expand All @@ -543,7 +543,7 @@ test('@apply error when using .group utility', async () => {
test('@apply error when using a prefixed .group utility', async () => {
let config = {
prefix: 'tw-',
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: html`<div class="foo"></div>` }],
}

Expand All @@ -565,7 +565,7 @@ test('@apply error when using a prefixed .group utility', async () => {

test('@apply error when using .peer utility', async () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: '<div class="foo"></div>' }],
}

Expand All @@ -588,7 +588,7 @@ test('@apply error when using .peer utility', async () => {
test('@apply error when using a prefixed .peer utility', async () => {
let config = {
prefix: 'tw-',
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: html`<div class="foo"></div>` }],
}

Expand Down Expand Up @@ -1972,7 +1972,7 @@ it('should maintain the correct selector when applying other utilities', () => {

it('pseudo elements inside apply are moved outside of :is() or :has()', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [
{
raw: html` <div class="foo bar baz qux steve bob"></div> `,
Expand Down Expand Up @@ -2016,28 +2016,30 @@ it('pseudo elements inside apply are moved outside of :is() or :has()', () => {

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(:where(.dark) .foo):before,
:is(:where([dir='rtl']) :is(:where(.dark) .bar)):before,
:is(:where([dir='rtl']) :is(:where(.dark) .baz:hover)):before {
.foo:where(.dark, .dark *):before,
.bar:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *):before,
.baz:hover:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *):before {
background-color: #000;
}
:-webkit-any(
:where([dir='rtl']) :-webkit-any(:where(.dark) .qux)
.qux:where(.dark, .dark *):where(
[dir='rtl'],
[dir='rtl'] *
)::-webkit-file-upload-button:hover {
background-color: #000;
}
:is(:where([dir='rtl']) :is(:where(.dark) .qux))::file-selector-button:hover {
.qux:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::file-selector-button:hover {
background-color: #000;
}
:is(:where([dir='rtl']) :is(:where(.dark) .steve):hover):before {
.steve:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *):before {
background-color: #000;
}
:-webkit-any(
:where([dir='rtl']) :-webkit-any(:where(.dark) .bob)
)::-webkit-file-upload-button:hover {
.bob:where(.dark, .dark *):hover:where(
[dir='rtl'],
[dir='rtl'] *
)::-webkit-file-upload-button {
background-color: #000;
}
:is(:where([dir='rtl']) :is(:where(.dark) .bob))::file-selector-button:hover {
.bob:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *)::file-selector-button {
background-color: #000;
}
:has([dir='rtl'] .foo:hover):before {
Expand All @@ -2055,7 +2057,7 @@ it('pseudo elements inside apply are moved outside of :is() or :has()', () => {

test('::ng-deep, ::deep, ::v-deep pseudo elements are left alone', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [
{
raw: html` <div class="foo bar"></div> `,
Expand Down
8 changes: 4 additions & 4 deletions tests/custom-separator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { run, html, css } from './util/run'

test('custom separator', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [
{
raw: html`
Expand Down Expand Up @@ -32,10 +32,10 @@ test('custom separator', () => {
text-align: right;
}
}
:is(:where([dir='rtl']) .rtl_active_text-center:active) {
.rtl_active_text-center:active:where([dir='rtl'], [dir='rtl'] *) {
text-align: center;
}
:is(:where(.dark) .dark_focus_text-left:focus) {
.dark_focus_text-left:focus:where(.dark, .dark *) {
text-align: left;
}
`)
Expand All @@ -44,7 +44,7 @@ test('custom separator', () => {

test('dash is not supported', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [{ raw: 'lg-hover-font-bold' }],
separator: '-',
}
Expand Down
Loading

0 comments on commit fb1743b

Please sign in to comment.