From b661614265bac75ba5f9bc6fd8116cf5f122fe25 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 6 Oct 2021 17:45:26 +0200 Subject: [PATCH] Enable optimize universal defaults by default (#5635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enabled `optimizeUniversalDefaults` by default This PR is done in a way so that the default is set to `true`, but you can still disable it if it causes issues. In this case we do appreciate an issue in that case 😅. * update tests to use optimized universal selector * update integration tests * add dedicated tests for the optimized universal selector * improve minimumImpactSelector algorithm I think I cracked the algorithm, but I will probably need another pair of eyes on the subject. The current implementation works like this: Prerequisites: - The selector should already have been parsed using the selectorParser from 'postcss-selector-parser'. Algorithm: 1. Remove all of the pseudo classes from the list of nodes. 1.1. We do want to keep pseudo elements (E.g.: `::before`, `::first-line`, ...) 1.2. We do want to keep pseudo classes that contain nodes (E.g.: `:not(...)`) 2. Reverse the list of nodes. This will make it easier to search from the end to the start. For example `.group:hover .group-hover` should result in `.group-hover` not `.group`. 2.1. Find the index of the best match (class, id, attribute), and convert the node if required. (E.g.: `span#app` -> `#app` => `[id="app"]`) 2.2. Remove the rest of the selector that is not important anymore 2.3. Re-join the left-over nodes together * update tests using new algorithm * also look for `tag` types * take `tag` into account * simplify logic * add test to prove `rest.reverse()` in first case is required In case we don't find a match (idx === -1), we use `rest.reverse()`. However, it looks like you can just use `nodes` instead. This is not entirely true, because the `rest` variable will contain only the nodes that are not pseudo elements. `*:hover` would result in `*:hover` instead of just `*` * replace all nodes after > with a single universal selector --- .../tailwindcss-cli/tests/cli.test.js | 7 +- src/featureFlags.js | 14 ++- src/lib/resolveDefaultsAtRules.js | 76 ++++++------ tests/basic-usage.test.css | 64 +++++++++- tests/experimental.test.js | 6 +- tests/kitchen-sink.test.css | 15 ++- tests/match-components.test.js | 4 +- tests/minimum-impact-selector.test.js | 29 +++++ tests/plugins/divide.test.js | 21 +--- tests/resolve-defaults-at-rules.test.js | 59 +++++----- tests/variants.test.css | 110 +++++++++++++++++- 11 files changed, 298 insertions(+), 107 deletions(-) create mode 100644 tests/minimum-impact-selector.test.js diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index f6bb7aa60..973758ffa 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -21,10 +21,9 @@ describe('Build command', () => { // `-i` is omitted, therefore the default `@tailwind base; @tailwind // components; @tailwind utilities` is used. However `preflight` is // disabled. I still want to verify that the `base` got included. - expect(contents).toContain('*') - expect(contents).toContain('::before') - expect(contents).toContain('::after') - expect(contents).toContain('--tw-shadow') + expect(contents).toContain('--tw-ring-offset-shadow: 0 0 #0000') + expect(contents).toContain('--tw-ring-shadow: 0 0 #0000') + expect(contents).toContain('--tw-shadow: 0 0 #0000') // Verify `utilities` output is correct expect(contents).toIncludeCss( diff --git a/src/featureFlags.js b/src/featureFlags.js index 189efb36f..7e521f95b 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -1,18 +1,24 @@ import chalk from 'chalk' import log from './util/log' -const featureFlags = { +let defaults = { + optimizeUniversalDefaults: true, +} + +let featureFlags = { future: [], experimental: ['optimizeUniversalDefaults'], } export function flagEnabled(config, flag) { if (featureFlags.future.includes(flag)) { - return config.future === 'all' || (config?.future?.[flag] ?? false) + return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false) } if (featureFlags.experimental.includes(flag)) { - return config.experimental === 'all' || (config?.experimental?.[flag] ?? false) + return ( + config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false) + ) } return false @@ -34,7 +40,7 @@ export function issueFlagNotices(config) { } if (experimentalFlagsEnabled(config).length > 0) { - const changes = experimentalFlagsEnabled(config) + let changes = experimentalFlagsEnabled(config) .map((s) => chalk.yellow(s)) .join(', ') diff --git a/src/lib/resolveDefaultsAtRules.js b/src/lib/resolveDefaultsAtRules.js index f83e28ce1..59b769264 100644 --- a/src/lib/resolveDefaultsAtRules.js +++ b/src/lib/resolveDefaultsAtRules.js @@ -2,60 +2,56 @@ import postcss from 'postcss' import selectorParser from 'postcss-selector-parser' import { flagEnabled } from '../featureFlags' -function isPseudoElement(n) { - if (n.type !== 'pseudo') { - return false - } - - return ( - n.value.startsWith('::') || - [':before', ':after', ':first-line', ':first-letter'].includes(n.value) - ) +let getNode = { + id(node) { + return selectorParser.attribute({ + attribute: 'id', + operator: '=', + value: node.value, + quoteMark: '"', + }) + }, } function minimumImpactSelector(nodes) { let rest = nodes - // Keep all pseudo & combinator types (:not([hidden]) ~ :not([hidden])) - .filter((n) => n.type === 'pseudo' || n.type === 'combinator') - // Remove leading pseudo's (:hover, :focus, ...) - .filter((n, idx, all) => { - // Keep pseudo elements - if (isPseudoElement(n)) return true + .filter((node) => { + // Keep non-pseudo nodes + if (node.type !== 'pseudo') return true - if (idx === 0 && n.type === 'pseudo') return false - if (idx > 0 && n.type === 'pseudo' && all[idx - 1].type === 'pseudo') return false + // Keep pseudo nodes that have subnodes + // E.g.: `:not()` contains subnodes inside the parentheses + if (node.nodes.length > 0) return true - return true + // Keep pseudo `elements` + // This implicitly means that we ignore pseudo `classes` + return ( + node.value.startsWith('::') || + [':before', ':after', ':first-line', ':first-letter'].includes(node.value) + ) }) + .reverse() - let [bestNode] = nodes + let searchFor = new Set(['tag', 'class', 'id', 'attribute']) - for (let [type, getNode = (n) => n] of [ - ['class'], - [ - 'id', - (n) => - selectorParser.attribute({ - attribute: 'id', - operator: '=', - value: n.value, - quoteMark: '"', - }), - ], - ['attribute'], - ]) { - let match = nodes.find((n) => n.type === type) + let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type)) + if (splitPointIdx === -1) return rest.reverse().join('').trim() - if (match) { - bestNode = getNode(match) - break - } + let node = rest[splitPointIdx] + let bestNode = getNode[node.type] ? getNode[node.type](node) : node + + rest = rest.slice(0, splitPointIdx) + + let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>') + if (combinatorIdx !== -1) { + rest.splice(0, combinatorIdx) + rest.unshift(selectorParser.universal()) } - return [bestNode, ...rest].join('').trim() + return [bestNode, ...rest.reverse()].join('').trim() } -let elementSelectorParser = selectorParser((selectors) => { +export let elementSelectorParser = selectorParser((selectors) => { return selectors.map((s) => { let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop() return minimumImpactSelector(nodes) diff --git a/tests/basic-usage.test.css b/tests/basic-usage.test.css index 187b174d9..acbfbe80e 100644 --- a/tests/basic-usage.test.css +++ b/tests/basic-usage.test.css @@ -1,6 +1,12 @@ -*, -::before, -::after { +.translate-x-5, +.-translate-x-4, +.translate-y-6, +.-translate-x-3, +.rotate-3, +.skew-y-12, +.skew-x-12, +.scale-95, +.transform { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -11,12 +17,36 @@ --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.snap-x { --tw-scroll-snap-strictness: proximity; +} + +.divide-x-2 > *, +.divide-y-4 > *, +.divide-x-0 > *, +.divide-y-0 > *, +.border, +.border-2, +.border-x-4, +.border-y-4, +.border-t, +.border-b-4 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.shadow, +.shadow-md, +.shadow-lg { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; +} + +.ring, +.ring-4 { --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; @@ -24,6 +54,18 @@ --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; +} + +.blur-md, +.brightness-150, +.contrast-50, +.drop-shadow-md, +.grayscale, +.hue-rotate-60, +.invert, +.saturate-200, +.sepia, +.filter { --tw-blur: var(--tw-empty, /*!*/ /*!*/); --tw-brightness: var(--tw-empty, /*!*/ /*!*/); --tw-contrast: var(--tw-empty, /*!*/ /*!*/); @@ -35,6 +77,18 @@ --tw-drop-shadow: var(--tw-empty, /*!*/ /*!*/); --tw-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.backdrop-blur-lg, +.backdrop-brightness-50, +.backdrop-contrast-0, +.backdrop-grayscale, +.backdrop-hue-rotate-90, +.backdrop-invert, +.backdrop-opacity-75, +.backdrop-saturate-150, +.backdrop-sepia, +.backdrop-filter { --tw-backdrop-blur: var(--tw-empty, /*!*/ /*!*/); --tw-backdrop-brightness: var(--tw-empty, /*!*/ /*!*/); --tw-backdrop-contrast: var(--tw-empty, /*!*/ /*!*/); @@ -351,10 +405,10 @@ .columns-md { columns: 28rem; } -.break-before-page { +.break-before-page { break-before: page; } -.break-inside-avoid-column { +.break-inside-avoid-column { break-inside: avoid-column; } .break-after-auto { diff --git a/tests/experimental.test.js b/tests/experimental.test.js index 6404c0013..16d346555 100644 --- a/tests/experimental.test.js +++ b/tests/experimental.test.js @@ -113,7 +113,7 @@ test('experimental universal selector improvements (child selectors: divide-y)', return run(input, config).then((result) => { expect(result.css).toMatchCss(css` - .divide-y > :not([hidden]) ~ :not([hidden]) { + .divide-y > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -145,7 +145,7 @@ test('experimental universal selector improvements (hover:divide-y)', () => { return run(input, config).then((result) => { expect(result.css).toMatchCss(css` - .hover\\:divide-y > :not([hidden]) ~ :not([hidden]) { + .hover\\:divide-y > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -178,7 +178,7 @@ test('experimental universal selector improvements (#app important)', () => { return run(input, config).then((result) => { expect(result.css).toMatchCss(css` - .divide-y > :not([hidden]) ~ :not([hidden]) { + .divide-y > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } diff --git a/tests/kitchen-sink.test.css b/tests/kitchen-sink.test.css index 6de98168b..b4a2e3fd4 100644 --- a/tests/kitchen-sink.test.css +++ b/tests/kitchen-sink.test.css @@ -126,9 +126,9 @@ } } } -*, -::before, -::after { +.scale-50, +.transform, +.hover\:scale-75 { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -139,9 +139,18 @@ --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.shadow-sm, +.shadow-md, +.hover\:shadow-lg, +.md\:shadow-sm { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; +} + +.focus\:ring-2 { --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; diff --git a/tests/match-components.test.js b/tests/match-components.test.js index f32c8f662..5390a502d 100644 --- a/tests/match-components.test.js +++ b/tests/match-components.test.js @@ -41,9 +41,7 @@ it('should be possible to matchComponents', () => { return run('@tailwind base; @tailwind components; @tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .shadow { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; diff --git a/tests/minimum-impact-selector.test.js b/tests/minimum-impact-selector.test.js new file mode 100644 index 000000000..d8292e467 --- /dev/null +++ b/tests/minimum-impact-selector.test.js @@ -0,0 +1,29 @@ +import { elementSelectorParser } from '../src/lib/resolveDefaultsAtRules' + +it.each` + before | after + ${'*'} | ${'*'} + ${'*:hover'} | ${'*'} + ${'* > *'} | ${'* > *'} + ${'.foo'} | ${'.foo'} + ${'.foo:hover'} | ${'.foo'} + ${'.foo:focus:hover'} | ${'.foo'} + ${'li:first-child'} | ${'li'} + ${'li:before'} | ${'li:before'} + ${'li::before'} | ${'li::before'} + ${'#app .foo'} | ${'.foo'} + ${'#app'} | ${'[id=app]'} + ${'#app.other'} | ${'.other'} + ${'input[type="text"]'} | ${'[type="text"]'} + ${'input[type="text"].foo'} | ${'.foo'} + ${'.group .group\\:foo'} | ${'.group\\:foo'} + ${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'} + ${'.owl > * + *'} | ${'.owl > *'} + ${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'} + ${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'} + ${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'} + ${'.whats ~ .next > span:hover'} | ${'span'} + ${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'} +`('should generate "$after" from "$before"', ({ before, after }) => { + expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after) +}) diff --git a/tests/plugins/divide.test.js b/tests/plugins/divide.test.js index b63c0bc8d..00735f8cd 100644 --- a/tests/plugins/divide.test.js +++ b/tests/plugins/divide.test.js @@ -8,9 +8,7 @@ it('should add the divide styles for divide-y and a default border color', () => return run('@tailwind base; @tailwind utilities;', config).then((result) => { expect(result.css).toMatchCss(css` - *, - ::before, - ::after { + .divide-y > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -32,9 +30,7 @@ it('should add the divide styles for divide-x and a default border color', () => return run('@tailwind base; @tailwind utilities;', config).then((result) => { expect(result.css).toMatchCss(css` - *, - ::before, - ::after { + .divide-x > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -56,9 +52,7 @@ it('should add the divide styles for divide-y-reverse and a default border color return run('@tailwind base; @tailwind utilities;', config).then((result) => { expect(result.css).toMatchCss(css` - *, - ::before, - ::after { + .divide-y-reverse > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -78,9 +72,7 @@ it('should add the divide styles for divide-x-reverse and a default border color return run('@tailwind base; @tailwind utilities;', config).then((result) => { expect(result.css).toMatchCss(css` - *, - ::before, - ::after { + .divide-x-reverse > * { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -100,9 +92,8 @@ it('should only inject the base styles once if we use divide and border at the s return run('@tailwind base; @tailwind utilities;', config).then((result) => { expect(result.css).toMatchCss(css` - *, - ::before, - ::after { + .divide-y > *, + .border-r { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js index e4d29af02..e88319502 100644 --- a/tests/resolve-defaults-at-rules.test.js +++ b/tests/resolve-defaults-at-rules.test.js @@ -14,9 +14,9 @@ test('basic utilities', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .rotate-3, + .skew-y-6, + .scale-x-110 { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -61,9 +61,9 @@ test('with pseudo-class variants', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .hover\\:scale-x-110, + .focus\\:rotate-3, + .hover\\:focus\\:skew-y-6 { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -106,9 +106,8 @@ test('with pseudo-element variants', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .before\\:scale-x-110::before, + .after\\:rotate-3::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -149,9 +148,8 @@ test('with multi-class variants', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .group-hover\\:scale-x-110, + .peer-focus\\:rotate-3 { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -192,9 +190,8 @@ test('with multi-class pseudo-element variants', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .group-hover\\:before\\:scale-x-110::before, + .peer-focus\\:after\\:rotate-3::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -241,9 +238,8 @@ test('with multi-class pseudo-element and pseudo-class variants', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .group-hover\\:hover\\:before\\:scale-x-110::before, + .peer-focus\\:focus\\:after\\:rotate-3::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -327,9 +323,17 @@ test('with apply', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .foo, + .bar::before, + .baz::before, + span, + .media-queries, + .a, + .b, + .c, + .a::before, + .b::after, + .recursive { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; @@ -480,9 +484,8 @@ test('with borders', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .border, + .md\\:border-2 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity)); } @@ -517,12 +520,14 @@ test('with shadows', async () => { return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - *, - ::before, - ::after { + .shadow, + .md\\:shadow-xl { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; + } + + .ring-1 { --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; diff --git a/tests/variants.test.css b/tests/variants.test.css index 6e6f5da4d..5a844fabd 100644 --- a/tests/variants.test.css +++ b/tests/variants.test.css @@ -1,6 +1,110 @@ -*, -::before, -::after { +.first\:shadow-md, +.last\:shadow-md, +.only\:shadow-md, +.odd\:shadow-md:nth-child(odd), +.even\:shadow-md:nth-child(even), +.first-of-type\:shadow-md, +.last-of-type\:shadow-md, +.only-of-type\:shadow-md, +.visited\:shadow-md, +.target\:shadow-md, +.default\:shadow-md, +.checked\:shadow-md, +.indeterminate\:shadow-md, +.placeholder-shown\:shadow-md, +.autofill\:shadow-md, +.required\:shadow-md, +.valid\:shadow-md, +.invalid\:shadow-md, +.in-range\:shadow-md, +.out-of-range\:shadow-md, +.read-only\:shadow-md, +.empty\:shadow-md, +.focus-within\:shadow-md, +.hover\:shadow-md, +.focus\:shadow-md, +.focus\:hover\:shadow-md, +.focus-visible\:shadow-md, +.active\:shadow-md, +.disabled\:shadow-md, +.group-first\:shadow-md, +.group-last\:shadow-md, +.group-only\:shadow-md, +.group-odd\:shadow-md, +.group-even\:shadow-md, +.group-first-of-type\:shadow-md, +.group-last-of-type\:shadow-md, +.group-only-of-type\:shadow-md, +.group-visited\:shadow-md, +.group-target\:shadow-md, +.group-default\:shadow-md, +.group-checked\:shadow-md, +.group-indeterminate\:shadow-md, +.group-placeholder-shown\:shadow-md, +.group-autofill\:shadow-md, +.group-required\:shadow-md, +.group-valid\:shadow-md, +.group-invalid\:shadow-md, +.group-in-range\:shadow-md, +.group-out-of-range\:shadow-md, +.group-read-only\:shadow-md, +.group-empty\:shadow-md, +.group-focus-within\:shadow-md, +.group-hover\:shadow-md, +.group-focus\:shadow-md, +.group-focus\:group-hover\:shadow-md, +.group-focus-visible\:shadow-md, +.group-active\:shadow-md, +.group-disabled\:shadow-md, +.group-disabled\:group-focus\:group-hover\:shadow-md, +.group-disabled\:group-focus\:group-hover\:first\:shadow-md, +.peer-first\:shadow-md, +.peer-last\:shadow-md, +.peer-only\:shadow-md, +.peer-odd\:shadow-md, +.peer-even\:shadow-md, +.peer-first-of-type\:shadow-md, +.peer-last-of-type\:shadow-md, +.peer-only-of-type\:shadow-md, +.peer-visited\:shadow-md, +.peer-target\:shadow-md, +.peer-default\:shadow-md, +.peer-checked\:shadow-md, +.peer-indeterminate\:shadow-md, +.peer-placeholder-shown\:shadow-md, +.peer-autofill\:shadow-md, +.peer-required\:shadow-md, +.peer-valid\:shadow-md, +.peer-invalid\:shadow-md, +.peer-in-range\:shadow-md, +.peer-out-of-range\:shadow-md, +.peer-read-only\:shadow-md, +.peer-empty\:shadow-md, +.peer-focus-within\:shadow-md, +.peer-hover\:shadow-md, +.peer-focus\:shadow-md, +.peer-focus\:peer-hover\:shadow-md, +.peer-focus-visible\:shadow-md, +.peer-active\:shadow-md, +.peer-disabled\:shadow-md, +.peer-disabled\:peer-focus\:peer-hover\:shadow-md, +.peer-disabled\:peer-focus\:peer-hover\:first\:shadow-md, +.ltr\:shadow-md, +.rtl\:shadow-md, +.motion-safe\:shadow-md, +.motion-reduce\:shadow-md, +.dark\:shadow-md, +.dark\:group-disabled\:group-focus\:group-hover\:shadow-md, +.dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md, +.sm\:shadow-md, +.sm\:active\:shadow-md, +.md\:shadow-md, +.md\:group-focus\:shadow-md, +.lg\:shadow-md, +.lg\:dark\:shadow-md, +.xl\:shadow-md, +.\32xl\:shadow-md, +.\32xl\:dark\:motion-safe\:focus-within\:shadow-md { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000;