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 <4323180+adamwathan@users.noreply.github.com>
  • Loading branch information
thecrypticace and adamwathan committed Jan 5, 2024
1 parent 78fedd5 commit 3fb57e5
Show file tree
Hide file tree
Showing 20 changed files with 446 additions and 134 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ 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))

### Added

- Add new `selector` and `variant` strategies for dark mode ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))

### Changed

- Support `rtl` and `ltr` variants on same element as `dir` attribute ([#12717](https://github.com/tailwindlabs/tailwindcss/pull/12717))

## [3.4.0] - 2023-12-19

Expand Down
49 changes: 44 additions & 5 deletions src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,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 @@ -217,7 +217,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 @@ -228,10 +228,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 @@ -767,14 +767,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
44 changes: 22 additions & 22 deletions tests/apply.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand Down Expand Up @@ -216,14 +216,14 @@ crosscheck(({ stable, oxide }) => {
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 @@ -513,14 +513,14 @@ crosscheck(({ stable, oxide }) => {
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 @@ -755,7 +755,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand All @@ -775,7 +775,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand All @@ -799,7 +799,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand All @@ -823,7 +823,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand All @@ -846,7 +846,7 @@ crosscheck(({ stable, oxide }) => {
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 @@ -868,7 +868,7 @@ crosscheck(({ stable, oxide }) => {

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

Expand All @@ -891,7 +891,7 @@ crosscheck(({ stable, oxide }) => {
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 @@ -2360,7 +2360,7 @@ crosscheck(({ stable, oxide }) => {

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 @@ -2404,18 +2404,18 @@ crosscheck(({ stable, oxide }) => {

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;
}
: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;
}
: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 @@ -2430,7 +2430,7 @@ crosscheck(({ stable, oxide }) => {

stable.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 @@ -3,7 +3,7 @@ import { crosscheck, run, html, css } from './util/run'
crosscheck(() => {
test('custom separator', () => {
let config = {
darkMode: 'class',
darkMode: 'selector',
content: [
{
raw: html`
Expand Down Expand Up @@ -33,10 +33,10 @@ crosscheck(() => {
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 @@ -45,7 +45,7 @@ crosscheck(() => {

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 3fb57e5

Please sign in to comment.