From f33d6a5d751e562e641654395f669183d5ca5065 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 19 Dec 2023 10:25:04 -0500 Subject: [PATCH 1/7] Update CI --- .github/workflows/ci-stable.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/integration-tests-oxide.yml | 4 ++-- .github/workflows/integration-tests-stable.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-stable.yml b/.github/workflows/ci-stable.yml index 80d19dca07c1..9c5f6ff8f298 100644 --- a/.github/workflows/ci-stable.yml +++ b/.github/workflows/ci-stable.yml @@ -7,7 +7,7 @@ on: push: branches: [master] pull_request: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0153ad227ace..015923ef1c9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI — Oxide on: push: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] pull_request: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] permissions: contents: read diff --git a/.github/workflows/integration-tests-oxide.yml b/.github/workflows/integration-tests-oxide.yml index e647b9fb321c..3768d4ac8c88 100644 --- a/.github/workflows/integration-tests-oxide.yml +++ b/.github/workflows/integration-tests-oxide.yml @@ -2,9 +2,9 @@ name: Integration Tests — Oxide on: push: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] pull_request: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] permissions: contents: read diff --git a/.github/workflows/integration-tests-stable.yml b/.github/workflows/integration-tests-stable.yml index b2820719656b..8f2594adf026 100644 --- a/.github/workflows/integration-tests-stable.yml +++ b/.github/workflows/integration-tests-stable.yml @@ -2,9 +2,9 @@ name: Integration Tests — Stable on: push: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] pull_request: - branches: [master, 3.3] + branches: [master, 3.3, 3.4] permissions: contents: read From 88907757c1c538e1c3dac3d0250ba2ac6cc63ed9 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 21 Dec 2023 12:17:12 -0500 Subject: [PATCH 2/7] Don't remove keyframe stops when using important utilities (#12639) * Don't remove keyframe stops when using important utilities * Fix test * fix linting --- src/lib/generateRules.js | 10 ++++++++++ tests/important-modifier.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 57bb1d140cd3..05bdd784e95e 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -119,10 +119,20 @@ function applyImportant(matches, classCandidate) { let result = [] + function isInKeyframes(rule) { + return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes' + } + for (let [meta, rule] of matches) { let container = postcss.root({ nodes: [rule.clone()] }) container.walkRules((r) => { + // Declarations inside keyframes cannot be marked as important + // They will be ignored by the browser + if (isInKeyframes(r)) { + return + } + let ast = selectorParser().astSync(r.selector) // Remove extraneous selectors that do not include the base candidate diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js index 6d30fd0bceb3..6f6f1a8de54a 100644 --- a/tests/important-modifier.test.js +++ b/tests/important-modifier.test.js @@ -150,4 +150,34 @@ crosscheck(() => { `) }) }) + + test('the important modifier does not break keyframes', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes pulse { + 50% { + opacity: 0.5; + } + } + + .\!animate-pulse { + animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important; + } + `) + }) + }) }) + From 08a0a6c9664eafb6f5d95af89fd69c79d8607913 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 21 Dec 2023 12:19:39 -0500 Subject: [PATCH 3/7] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f33ccdf79d..b056adc86ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Don't remove keyframe stops when using important utilities ([#12639](https://github.com/tailwindlabs/tailwindcss/pull/12639)) + ## [3.4.0] - 2023-12-19 ### Added From 78fedd5cc0fd21d0573ec0733d583f68f66cc7fd Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 3 Jan 2024 13:03:16 -0500 Subject: [PATCH 4/7] Don't add spaces to gradients and grid track names when followed by `calc()` (#12704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don’t break gradient functions when following `calc()` * Don’t break CSS grid track names * Update changelog --- CHANGELOG.md | 1 + src/util/dataTypes.js | 12 ++++++++++++ tests/normalize-data-types.test.js | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b056adc86ced..993e3594fc2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - 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)) ## [3.4.0] - 2023-12-19 diff --git a/src/util/dataTypes.js b/src/util/dataTypes.js index 06ccb9524303..5e2541e5e969 100644 --- a/src/util/dataTypes.js +++ b/src/util/dataTypes.js @@ -106,6 +106,13 @@ function normalizeMathOperatorSpacing(value) { 'keyboard-inset-left', 'keyboard-inset-width', 'keyboard-inset-height', + + 'radial-gradient', + 'linear-gradient', + 'conic-gradient', + 'repeating-radial-gradient', + 'repeating-linear-gradient', + 'repeating-conic-gradient', ] return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => { @@ -161,6 +168,11 @@ function normalizeMathOperatorSpacing(value) { result += consumeUntil([')']) } + // Don't break CSS grid track names + else if (peek('[')) { + result += consumeUntil([']']) + } + // Handle operators else if ( ['+', '-', '*', '/'].includes(char) && diff --git a/tests/normalize-data-types.test.js b/tests/normalize-data-types.test.js index b249f787450a..356798ed3d33 100644 --- a/tests/normalize-data-types.test.js +++ b/tests/normalize-data-types.test.js @@ -87,6 +87,16 @@ let table = [ // Prevent formatting keywords ['minmax(min-content,25%)', 'minmax(min-content,25%)'], + // Prevent formatting keywords + [ + 'radial-gradient(calc(1+2)),radial-gradient(calc(1+2))', + 'radial-gradient(calc(1 + 2)),radial-gradient(calc(1 + 2))', + ], + [ + '[content-start]_calc(100%-1px)_[content-end]_minmax(1rem,1fr)', + '[content-start] calc(100% - 1px) [content-end] minmax(1rem,1fr)', + ], + // Misc ['color(0_0_0/1.0)', 'color(0 0 0/1.0)'], ['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'], From 3fb57e55abdba6fa68da6cb605fcaad5ce94764a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 5 Jan 2024 14:39:34 -0500 Subject: [PATCH 5/7] Restore old behavior for `class` dark mode, add new `selector` and `variant` options for dark mode (#12717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 185250438ccb2f61ba876d4676823c1807891122. * 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> --- CHANGELOG.md | 9 + src/corePlugins.js | 49 ++++- src/lib/setupContextUtils.js | 23 ++- src/util/pseudoElements.js | 4 + tests/apply.test.js | 44 ++--- tests/custom-separator.test.js | 8 +- tests/dark-mode.test.js | 204 +++++++++++++++++++- tests/important-boolean.test.js | 6 +- tests/important-modifier-prefix.test.js | 2 +- tests/important-modifier.test.js | 2 +- tests/important-selector.test.js | 19 +- tests/kitchen-sink.test.js | 26 +-- tests/modify-selectors.test.js | 2 +- tests/opacity.test.js | 4 +- tests/prefix.test.js | 10 +- tests/util/apply-important-selector.test.js | 36 ++-- tests/variants.oxide.test.css | 54 ++++-- tests/variants.test.css | 58 +++--- tests/variants.test.js | 13 +- types/config.d.ts | 7 + 20 files changed, 446 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 993e3594fc2b..837728b7df24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/corePlugins.js b/src/corePlugins.js index a04dce82e3bd..01afcec269d3 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -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 }) => { @@ -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' @@ -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} &)`) } }, diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 91d5f66bfc8e..72aa8f56aa68 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -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] } diff --git a/src/util/pseudoElements.js b/src/util/pseudoElements.js index 5795cdd42045..e518801f42ba 100644 --- a/src/util/pseudoElements.js +++ b/src/util/pseudoElements.js @@ -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 diff --git a/tests/apply.test.js b/tests/apply.test.js index fe2b572496ba..ca5416e9261a 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -35,7 +35,7 @@ crosscheck(({ stable, oxide }) => { test('@apply', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: sharedHtml }], } @@ -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; } } @@ -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; } } @@ -755,7 +755,7 @@ crosscheck(({ stable, oxide }) => { test('@apply error with unknown utility', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: sharedHtml }], } @@ -775,7 +775,7 @@ crosscheck(({ stable, oxide }) => { test('@apply error with nested @screen', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: sharedHtml }], } @@ -799,7 +799,7 @@ crosscheck(({ stable, oxide }) => { test('@apply error with nested @anyatrulehere', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: sharedHtml }], } @@ -823,7 +823,7 @@ crosscheck(({ stable, oxide }) => { test('@apply error when using .group utility', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: '
' }], } @@ -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`
` }], } @@ -868,7 +868,7 @@ crosscheck(({ stable, oxide }) => { test('@apply error when using .peer utility', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: '
' }], } @@ -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`
` }], } @@ -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`
`, @@ -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 { @@ -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`
`, diff --git a/tests/custom-separator.test.js b/tests/custom-separator.test.js index 6546f4ed2c63..aee13e67e2f3 100644 --- a/tests/custom-separator.test.js +++ b/tests/custom-separator.test.js @@ -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` @@ -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; } `) @@ -45,7 +45,7 @@ crosscheck(() => { test('dash is not supported', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [{ raw: 'lg-hover-font-bold' }], separator: '-', } diff --git a/tests/dark-mode.test.js b/tests/dark-mode.test.js index 9845e0fdfe61..6fbea28d915b 100644 --- a/tests/dark-mode.test.js +++ b/tests/dark-mode.test.js @@ -1,6 +1,6 @@ import { crosscheck, run, html, css, defaults } from './util/run' -crosscheck(() => { +crosscheck(({ oxide, stable }) => { it('should be possible to use the darkMode "class" mode', () => { let config = { darkMode: 'class', @@ -17,7 +17,7 @@ crosscheck(() => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` ${defaults} - :is(:where(.dark) .dark\:font-bold) { + :is(.dark .dark\:font-bold) { font-weight: 700; } `) @@ -40,7 +40,7 @@ crosscheck(() => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` ${defaults} - :is(:where(.test-dark) .dark\:font-bold) { + :is(.test-dark .dark\:font-bold) { font-weight: 700; } `) @@ -120,4 +120,202 @@ crosscheck(() => { `) }) }) + + it('should support the deprecated `class` dark mode behavior', () => { + let config = { + darkMode: 'class', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + :is(.dark .dark\:font-bold) { + font-weight: 700; + } + `) + }) + }) + + it('should support custom classes with deprecated `class` dark mode', () => { + let config = { + darkMode: ['class', '.my-dark'], + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + :is(.my-dark .dark\:font-bold) { + font-weight: 700; + } + `) + }) + }) + + it('should use legacy sorting when using `darkMode: class`', () => { + let config = { + darkMode: 'class', + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + stable.expect(result.css).toMatchFormattedCss(css` + .hover\:text-green-200:hover { + --tw-text-opacity: 1; + color: rgb(187 247 208 / var(--tw-text-opacity)); + } + :is(.dark .dark\:text-green-100) { + --tw-text-opacity: 1; + color: rgb(220 252 231 / var(--tw-text-opacity)); + } + @media (min-width: 1024px) { + .lg\:text-green-300 { + --tw-text-opacity: 1; + color: rgb(134 239 172 / var(--tw-text-opacity)); + } + } + `) + oxide.expect(result.css).toMatchFormattedCss(css` + .hover\:text-green-200:hover { + color: #bbf7d0; + } + :is(.dark .dark\:text-green-100) { + color: #dcfce7; + } + @media (min-width: 1024px) { + .lg\:text-green-300 { + color: #86efac; + } + } + `) + }) + }) + + it('should use modern sorting otherwise', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + stable.expect(result.css).toMatchFormattedCss(css` + .hover\:text-green-200:hover { + --tw-text-opacity: 1; + color: rgb(187 247 208 / var(--tw-text-opacity)); + } + @media (min-width: 1024px) { + .lg\:text-green-300 { + --tw-text-opacity: 1; + color: rgb(134 239 172 / var(--tw-text-opacity)); + } + } + .dark\:text-green-100:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(220 252 231 / var(--tw-text-opacity)); + } + `) + oxide.expect(result.css).toMatchFormattedCss(css` + .hover\:text-green-200:hover { + color: #bbf7d0; + } + @media (min-width: 1024px) { + .lg\:text-green-300 { + color: #86efac; + } + } + .dark\:text-green-100:where(.dark, .dark *) { + color: #dcfce7; + } + `) + }) + }) + + it('should allow customization of the dark mode variant', () => { + let config = { + darkMode: ['variant', '&:not(.light *)'], + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .dark\:font-bold:not(.light *) { + font-weight: 700; + } + `) + }) + }) + + it('should support parallel selectors for the dark mode variant', () => { + let config = { + darkMode: ['variant', ['&:not(.light *)', '&:not(.extralight *)']], + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .dark\:font-bold:not(.light *), + .dark\:font-bold:not(.extralight *) { + font-weight: 700; + } + `) + }) + }) + + it('should support fn selectors for the dark mode variant', () => { + let config = { + darkMode: ['variant', () => ['&:not(.light *)', '&:not(.extralight *)']], + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .dark\:font-bold:not(.light *), + .dark\:font-bold:not(.extralight *) { + font-weight: 700; + } + `) + }) + }) }) diff --git a/tests/important-boolean.test.js b/tests/important-boolean.test.js index 6b028736c441..4853f2a1f1db 100644 --- a/tests/important-boolean.test.js +++ b/tests/important-boolean.test.js @@ -8,7 +8,7 @@ crosscheck(() => { test('important boolean', () => { let config = { important: true, - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` @@ -148,10 +148,10 @@ crosscheck(() => { text-align: right !important; } } - :is(:where([dir='rtl']) .rtl\:active\:text-center:active) { + .rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *) { text-align: center !important; } - :is(:where(.dark) .dark\:focus\:text-left:focus) { + .dark\:focus\:text-left:focus:where(.dark, .dark *) { text-align: left !important; } `) diff --git a/tests/important-modifier-prefix.test.js b/tests/important-modifier-prefix.test.js index 782ec809e417..1e9f2bd22d68 100644 --- a/tests/important-modifier-prefix.test.js +++ b/tests/important-modifier-prefix.test.js @@ -5,7 +5,7 @@ crosscheck(() => { let config = { important: false, prefix: 'tw-', - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js index 6f6f1a8de54a..a4da55dd4ca5 100644 --- a/tests/important-modifier.test.js +++ b/tests/important-modifier.test.js @@ -4,7 +4,7 @@ crosscheck(() => { test('important modifier', () => { let config = { important: false, - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js index 24533f02f34d..840c572864c6 100644 --- a/tests/important-selector.test.js +++ b/tests/important-selector.test.js @@ -4,7 +4,7 @@ crosscheck(({ stable, oxide }) => { test('important selector', () => { let config = { important: '#app', - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` @@ -146,19 +146,22 @@ crosscheck(({ stable, oxide }) => { text-align: right; } } - #app :is(:is(:where([dir='rtl']) .rtl\:active\:text-center:active)) { + #app :is(.rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *)) { text-align: center; } - #app :is(:where(.dark) .dark\:before\:underline):before { + #app :is(.dark\:before\:underline:where(.dark, .dark *)):before { content: var(--tw-content); text-decoration-line: underline; } - #app :is(:is(:where(.dark) .dark\:focus\:text-left:focus)) { + #app :is(.dark\:focus\:text-left:focus:where(.dark, .dark *)) { text-align: left; } #app :is( - :where([dir='rtl']) :is(:where(.dark) .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100) + .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100:where( + .dark, + .dark * + ):where([dir='rtl'], [dir='rtl'] *) )::file-selector-button:hover { background-color: #000; } @@ -169,7 +172,7 @@ crosscheck(({ stable, oxide }) => { test('pseudo-elements are appended after the `:is()`', () => { let config = { important: '#app', - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html`
`, @@ -187,7 +190,7 @@ crosscheck(({ stable, oxide }) => { return run(input, config).then((result) => { stable.expect(result.css).toMatchFormattedCss(css` ${defaults} - #app :is(:where(.dark) .dark\:before\:bg-black)::before { + #app .dark\:before\:bg-black:where(.dark, .dark *)::before { content: var(--tw-content); --tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity)); @@ -195,7 +198,7 @@ crosscheck(({ stable, oxide }) => { `) oxide.expect(result.css).toMatchFormattedCss(css` ${defaults} - #app :is(:where(.dark) .dark\:before\:bg-black)::before { + #app .dark\:before\:bg-black:where(.dark, .dark *)::before { content: var(--tw-content); background-color: #000; } diff --git a/tests/kitchen-sink.test.js b/tests/kitchen-sink.test.js index 44665ffab4a6..27dedb46a8ef 100644 --- a/tests/kitchen-sink.test.js +++ b/tests/kitchen-sink.test.js @@ -3,7 +3,7 @@ import { crosscheck, run, html, css, defaults } from './util/run' crosscheck(({ stable, oxide }) => { test('it works', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` @@ -304,8 +304,10 @@ crosscheck(({ stable, oxide }) => { margin-right: auto; } .drop-empty-rules:hover, - .group:hover .apply-group, - :is(:where(.dark) .apply-dark-mode) { + .group:hover .apply-group { + font-weight: 700; + } + .apply-dark-mode:where(.dark, .dark *) { font-weight: 700; } .apply-with-existing:hover { @@ -340,7 +342,7 @@ crosscheck(({ stable, oxide }) => { .apply-order-b { margin: 1.5rem 1.25rem 1.25rem; } - :is(:where(.dark) .group:hover .apply-dark-group-example-a) { + .group:hover .apply-dark-group-example-a:where(.dark, .dark *) { --tw-bg-opacity: 1; background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } @@ -802,12 +804,12 @@ crosscheck(({ stable, oxide }) => { text-align: left; } } - :is(:where(.dark) .dark\:custom-util) { + .dark\:custom-util:where(.dark, .dark *) { background: #abcdef; } @media (min-width: 768px) { @media (prefers-reduced-motion: no-preference) { - :is(:where(.dark) .md\:dark\:motion-safe\:foo\:active\:custom-util:active) { + .md\:dark\:motion-safe\:foo\:active\:custom-util:active:where(.dark, .dark *) { background: #abcdef !important; } } @@ -877,8 +879,10 @@ crosscheck(({ stable, oxide }) => { margin-right: auto; } .drop-empty-rules:hover, - .group:hover .apply-group, - :is(:where(.dark) .apply-dark-mode) { + .group:hover .apply-group { + font-weight: 700; + } + .apply-dark-mode:where(.dark, .dark *) { font-weight: 700; } .apply-with-existing:hover { @@ -912,7 +916,7 @@ crosscheck(({ stable, oxide }) => { .apply-order-b { margin: 1.5rem 1.25rem 1.25rem; } - :is(:where(.dark) .group:hover .apply-dark-group-example-a) { + .group:hover .apply-dark-group-example-a:where(.dark, .dark *) { background-color: #22c55e; } @media (min-width: 640px) { @@ -1364,12 +1368,12 @@ crosscheck(({ stable, oxide }) => { text-align: left; } } - :is(:where(.dark) .dark\:custom-util) { + .dark\:custom-util:where(.dark, .dark *) { background: #abcdef; } @media (min-width: 768px) { @media (prefers-reduced-motion: no-preference) { - :is(:where(.dark) .md\:dark\:motion-safe\:foo\:active\:custom-util:active) { + .md\:dark\:motion-safe\:foo\:active\:custom-util:active:where(.dark, .dark *) { background: #abcdef !important; } } diff --git a/tests/modify-selectors.test.js b/tests/modify-selectors.test.js index 3176b17f3da5..2efbe206a11f 100644 --- a/tests/modify-selectors.test.js +++ b/tests/modify-selectors.test.js @@ -5,7 +5,7 @@ import { crosscheck, run, html, css } from './util/run' crosscheck(() => { test('modify selectors', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` diff --git a/tests/opacity.test.js b/tests/opacity.test.js index 88b88bd1539a..b5a1a53cfee7 100644 --- a/tests/opacity.test.js +++ b/tests/opacity.test.js @@ -3,7 +3,7 @@ import { crosscheck, run, html, css } from './util/run' crosscheck(({ stable, oxide }) => { test('opacity', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` @@ -43,7 +43,7 @@ crosscheck(({ stable, oxide }) => { test('colors defined as functions work when opacity plugins are disabled', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` diff --git a/tests/prefix.test.js b/tests/prefix.test.js index a1fb6733ebc8..a93e570ebbf7 100644 --- a/tests/prefix.test.js +++ b/tests/prefix.test.js @@ -5,7 +5,7 @@ crosscheck(({ stable, oxide }) => { stable.test('prefix', () => { let config = { prefix: 'tw-', - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html` @@ -128,7 +128,7 @@ crosscheck(({ stable, oxide }) => { .custom-component { font-weight: 700; } - :is(:where(.tw-dark) .tw-group:hover .custom-component) { + .tw-group:hover .custom-component:where(.tw-dark, .tw-dark *) { font-weight: 400; } .tw--ml-4 { @@ -171,14 +171,14 @@ crosscheck(({ stable, oxide }) => { text-align: right; } } - :is(:where([dir='rtl']) .rtl\:active\:tw-text-center:active) { + .rtl\:active\:tw-text-center:active:where([dir='rtl'], [dir='rtl'] *) { text-align: center; } - :is(:where(.tw-dark) .dark\:tw-bg-\[rgb\(255\,0\,0\)\]) { + .dark\:tw-bg-\[rgb\(255\,0\,0\)\]:where(.tw-dark, .tw-dark *) { --tw-bg-opacity: 1; background-color: rgb(255 0 0 / var(--tw-bg-opacity)); } - :is(:where(.tw-dark) .dark\:focus\:tw-text-left:focus) { + .dark\:focus\:tw-text-left:focus:where(.tw-dark, .tw-dark *) { text-align: left; } `) diff --git a/tests/util/apply-important-selector.test.js b/tests/util/apply-important-selector.test.js index cc2cacf524fd..ddbb86a1c08e 100644 --- a/tests/util/apply-important-selector.test.js +++ b/tests/util/apply-important-selector.test.js @@ -3,25 +3,25 @@ import { applyImportantSelector } from '../../src/util/applyImportantSelector' crosscheck(() => { it.each` - before | after - ${'.foo'} | ${'#app :is(.foo)'} - ${'.foo .bar'} | ${'#app :is(.foo .bar)'} - ${'.foo:hover'} | ${'#app :is(.foo:hover)'} - ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'} - ${'.foo::before'} | ${'#app :is(.foo)::before'} - ${'.foo::before'} | ${'#app :is(.foo)::before'} - ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'} - ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'} - ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'} + before | after + ${'.foo'} | ${'#app :is(.foo)'} + ${'.foo .bar'} | ${'#app :is(.foo .bar)'} + ${'.foo:hover'} | ${'#app :is(.foo:hover)'} + ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'} + ${'.foo::before'} | ${'#app :is(.foo)::before'} + ${'.foo::before'} | ${'#app :is(.foo)::before'} + ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'} + ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'} + ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'} ${':is(:where(.dark) :is(:where([dir="rtl"]) .foo::before))'} | ${'#app :is(:where(.dark) :is(:where([dir="rtl"]) .foo))::before'} - ${':is(:where(.dark) .foo) .bar'} | ${'#app :is(:is(:where(.dark) .foo) .bar)'} - ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'} - ${':is(.foo)::before'} | ${'#app :is(.foo)::before'} - ${'.foo:before'} | ${'#app :is(.foo):before'} - ${'.foo::some-uknown-pseudo'} | ${'#app :is(.foo)::some-uknown-pseudo'} - ${'.foo::some-uknown-pseudo:hover'} | ${'#app :is(.foo)::some-uknown-pseudo:hover'} - ${'.foo:focus::some-uknown-pseudo:hover'} | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'} - ${'.foo:hover::some-uknown-pseudo:focus'} | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'} + ${':is(:where(.dark) .foo) .bar'} | ${'#app :is(:is(:where(.dark) .foo) .bar)'} + ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'} + ${':is(.foo)::before'} | ${'#app :is(.foo)::before'} + ${'.foo:before'} | ${'#app :is(.foo):before'} + ${'.foo::some-uknown-pseudo'} | ${'#app :is(.foo)::some-uknown-pseudo'} + ${'.foo::some-uknown-pseudo:hover'} | ${'#app :is(.foo)::some-uknown-pseudo:hover'} + ${'.foo:focus::some-uknown-pseudo:hover'} | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'} + ${'.foo:hover::some-uknown-pseudo:focus'} | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'} `('should generate "$after" from "$before"', ({ before, after }) => { expect(applyImportantSelector(before, '#app')).toEqual(after) }) diff --git a/tests/variants.oxide.test.css b/tests/variants.oxide.test.css index 2aaab5e70730..c04721895c07 100644 --- a/tests/variants.oxide.test.css +++ b/tests/variants.oxide.test.css @@ -319,11 +319,6 @@ background-color: #fde047; } } -@media print { - .print\:bg-yellow-300 { - background-color: #fde047; - } -} @media (min-width: 640px) { .sm\:shadow-md, .sm\:active\:shadow-md:active { @@ -389,26 +384,38 @@ background-color: #fde047; } } -:is(:where([dir="ltr"]) .ltr\:shadow-md), -:is(:where([dir="rtl"]) .rtl\:shadow-md), -:is(:where(.dark) .dark\:shadow-md), -:is( - :where(.dark) - .group:disabled:focus:hover - .dark\:group-disabled\:group-focus\:group-hover\:shadow-md - ), -:is( - :where(.dark) - .peer:disabled:focus:hover - ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md - ) { +.ltr\:shadow-md:where([dir="ltr"], [dir="ltr"] *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.rtl\:shadow-md:where([dir="rtl"], [dir="rtl"] *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.dark\:shadow-md:where(.dark, .dark *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:shadow-md:where(.dark, .dark *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } @media (min-width: 1024px) { - :is(:where(.dark) .lg\:dark\:shadow-md) { + .lg\:dark\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -417,7 +424,7 @@ } } @media (min-width: 1280px) { - :is(:where(.dark) .xl\:dark\:disabled\:shadow-md:disabled) { + .xl\:dark\:disabled\:shadow-md:disabled:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -427,7 +434,7 @@ } @media (min-width: 1536px) { @media (prefers-reduced-motion: no-preference) { - :is(:where(.dark) .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within) { + .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -441,3 +448,8 @@ display: flex; } } +@media print { + .print\:bg-yellow-300 { + background-color: #fde047; + } +} diff --git a/tests/variants.test.css b/tests/variants.test.css index c2fadabcbab7..95404e1cbe39 100644 --- a/tests/variants.test.css +++ b/tests/variants.test.css @@ -337,12 +337,6 @@ background-color: rgb(253 224 71 / var(--tw-bg-opacity)); } } -@media print { - .print\:bg-yellow-300 { - --tw-bg-opacity: 1; - background-color: rgb(253 224 71 / var(--tw-bg-opacity)); - } -} @media (min-width: 640px) { .sm\:shadow-md, .sm\:active\:shadow-md:active { @@ -410,26 +404,38 @@ background-color: rgb(253 224 71 / var(--tw-bg-opacity)); } } -:is(:where([dir="ltr"]) .ltr\:shadow-md), -:is(:where([dir="rtl"]) .rtl\:shadow-md), -:is(:where(.dark) .dark\:shadow-md), -:is( - :where(.dark) - .group:disabled:focus:hover - .dark\:group-disabled\:group-focus\:group-hover\:shadow-md - ), -:is( - :where(.dark) - .peer:disabled:focus:hover - ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md - ) { +.ltr\:shadow-md:where([dir="ltr"], [dir="ltr"] *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.rtl\:shadow-md:where([dir="rtl"], [dir="rtl"] *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.dark\:shadow-md:where(.dark, .dark *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:shadow-md:where(.dark, .dark *) { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } @media (min-width: 1024px) { - :is(:where(.dark) .lg\:dark\:shadow-md) { + .lg\:dark\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -438,7 +444,7 @@ } } @media (min-width: 1280px) { - :is(:where(.dark) .xl\:dark\:disabled\:shadow-md:disabled) { + .xl\:dark\:disabled\:shadow-md:disabled:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -448,7 +454,7 @@ } @media (min-width: 1536px) { @media (prefers-reduced-motion: no-preference) { - :is(:where(.dark) .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within) { + .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -461,4 +467,10 @@ .forced-colors\:flex { display: flex; } -} \ No newline at end of file +} +@media print { + .print\:bg-yellow-300 { + --tw-bg-opacity: 1; + background-color: rgb(253 224 71 / var(--tw-bg-opacity)); + } +} diff --git a/tests/variants.test.js b/tests/variants.test.js index 14535dfd0ce1..6cf246476acf 100644 --- a/tests/variants.test.js +++ b/tests/variants.test.js @@ -6,7 +6,7 @@ import { crosscheck, run, html, css, defaults } from './util/run' crosscheck(({ stable, oxide }) => { test('variants', () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [path.resolve(__dirname, './variants.test.html')], corePlugins: { preflight: false }, } @@ -1156,7 +1156,7 @@ crosscheck(({ stable, oxide }) => { test('stacking dark and rtl variants', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html`
`, @@ -1172,7 +1172,7 @@ crosscheck(({ stable, oxide }) => { let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` - :is(:where(.dark) :is(:where([dir='rtl']) .dark\:rtl\:italic)) { + .dark\:rtl\:italic:where([dir='rtl'], [dir='rtl'] *):where(.dark, .dark *) { font-style: italic; } `) @@ -1180,7 +1180,7 @@ crosscheck(({ stable, oxide }) => { test('stacking dark and rtl variants with pseudo elements', async () => { let config = { - darkMode: 'class', + darkMode: 'selector', content: [ { raw: html`
`, @@ -1196,7 +1196,10 @@ crosscheck(({ stable, oxide }) => { let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` - :is(:where(.dark) :is(:where([dir='rtl']) .dark\:rtl\:placeholder\:italic))::placeholder { + .dark\:rtl\:placeholder\:italic:where([dir='rtl'], [dir='rtl'] *):where( + .dark, + .dark * + )::placeholder { font-style: italic; } `) diff --git a/types/config.d.ts b/types/config.d.ts index b5d9ddc802b8..80b58d07028a 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -74,6 +74,13 @@ type DarkModeConfig = | 'class' // Use the `class` strategy with a custom class instead of `.dark`. | ['class', string] + // Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior + | 'selector' + // Use the `selector` strategy with a custom selector instead of `.dark`. + | ['selector', string] + // Use the `variant` strategy, which allows you to completely customize the selector + // It takes a string or an array of strings, which are passed directly to `addVariant()` + | ['variant', string | string[]] type Screen = { raw: string } | { min: string } | { max: string } | { min: string; max: string } type ScreensConfig = string[] | KeyValuePair From 508e7f2349e56d19f8d3091d583f2ad65b95f68c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 5 Jan 2024 15:23:38 -0500 Subject: [PATCH 6/7] Fix code style --- tests/format-variant-selector.test.js | 4 ++-- tests/important-modifier.test.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/format-variant-selector.test.js b/tests/format-variant-selector.test.js index ea0b92741d13..3ce3d1322c94 100644 --- a/tests/format-variant-selector.test.js +++ b/tests/format-variant-selector.test.js @@ -348,8 +348,8 @@ crosscheck(() => { ${'.parent::before &:hover'} | ${'.parent &:hover::before'} ${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'} ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} - ${'#app :is(:where(.dark) &::before)'} | ${'#app :is(:where(.dark) &)::before'} - ${'#app :is(:is(:where(.dark) &)::before)'} | ${'#app :is(:is(:where(.dark) &))::before'} + ${'#app :is(:where(.dark) &::before)'} | ${'#app :is(:where(.dark) &)::before'} + ${'#app :is(:is(:where(.dark) &)::before)'} | ${'#app :is(:is(:where(.dark) &))::before'} ${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'} ${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'} ${'.parent::marker li'} | ${'.parent li::marker'} diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js index a4da55dd4ca5..0cfa659d8d7e 100644 --- a/tests/important-modifier.test.js +++ b/tests/important-modifier.test.js @@ -180,4 +180,3 @@ crosscheck(() => { }) }) }) - From 7361468f77500105b0559e879e121f34306e8da2 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 5 Jan 2024 15:30:22 -0500 Subject: [PATCH 7/7] 3.4.1 --- CHANGELOG.md | 7 ++++++- package-lock.json | 4 ++-- package-lock.stable.json | 4 ++-- package.json | 2 +- package.stable.json | 2 +- standalone-cli/package-lock.json | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 837728b7df24..3a37f414d69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Nothing yet! + +## [3.4.1] - 2014-01-05 + ### Fixed - Don't remove keyframe stops when using important utilities ([#12639](https://github.com/tailwindlabs/tailwindcss/pull/12639)) @@ -2347,7 +2351,8 @@ No release notes - Everything! -[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.0...HEAD +[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.1...HEAD +[3.4.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.0...v3.4.1 [3.4.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.7...v3.4.0 [3.3.7]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.6...v3.3.7 [3.3.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.5...v3.3.6 diff --git a/package-lock.json b/package-lock.json index 22e90644cad8..b79bc523889d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "license": "MIT", "workspaces": [ "integrations/*", diff --git a/package-lock.stable.json b/package-lock.stable.json index 8c2f5023a9d6..874d2d14bfe2 100644 --- a/package-lock.stable.json +++ b/package-lock.stable.json @@ -1,12 +1,12 @@ { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", diff --git a/package.json b/package.json index fee562349ead..993c2d7276e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "lib/index.js", diff --git a/package.stable.json b/package.stable.json index f5ef9ce064d1..93ca124210e2 100644 --- a/package.stable.json +++ b/package.stable.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "3.4.0", + "version": "3.4.1", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "lib/index.js", diff --git a/standalone-cli/package-lock.json b/standalone-cli/package-lock.json index 4d4049dbc651..b455e85044b8 100644 --- a/standalone-cli/package-lock.json +++ b/standalone-cli/package-lock.json @@ -24,7 +24,7 @@ } }, "..": { - "version": "3.4.0", + "version": "3.4.1", "dev": true, "license": "MIT", "workspaces": [