Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore old behavior for class dark mode, add new selector and variant options for dark mode #12717

Merged
merged 24 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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