From 811e97d61afabcda8858183add91772abd6b4cb6 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Apr 2025 11:18:47 +0200 Subject: [PATCH] Fix polyfill in combination with unused CSS variable removal (#17555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fixes an issue we noticed while investigating #17553, where the unused CSS variable removal didn't work properly when the theme variable it tried to remove was modified by a polyfill rule. The way the bookkeeping for the unused CSS variable worked was that it tired to find the declaration inside it's parent after the traversal. However, the `color-mix(…)` polyfill has since then made changes to the declaration so it can't find it's position correctly anymore and will thus instead delete the last declaration of the node (this caused unrelated CSS variables to be eliminated while the ones with `color-mix(…)` were unexpectedly kept). To fix this, we decided to apply the polyfills after any eventual deletions. This also ensures that no `@supports` query for the variables are created and simplifies the code a bit since all polyfills are now colocated. ## Test plan - Added a unit test for the example we discovered in #17553 - Luckily the conditions of this seemed rare enough so that it doesn't cause any other of our tests to update. --------- Co-authored-by: Robin Malfait --- CHANGELOG.md | 4 +- packages/tailwindcss/src/ast.ts | 142 +++--- .../tailwindcss/src/compat/config.test.ts | 26 +- .../tailwindcss/src/compat/plugin-api.test.ts | 90 ++-- .../tailwindcss/src/css-functions.test.ts | 16 +- packages/tailwindcss/src/index.test.ts | 144 ++++++ packages/tailwindcss/src/utilities.test.ts | 414 +++++++++--------- 7 files changed, 492 insertions(+), 344 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ecfd3de..3f7f160d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Ensure `color-mix(…)` polyfills do not cause used CSS variables to be removed ([#17555](https://github.com/tailwindlabs/tailwindcss/pull/17555)) ## [4.1.3] - 2025-04-04 diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index af76b0772..f15191118 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -266,10 +266,8 @@ export function optimizeAst( ) { let atRoots: AstNode[] = [] let seenAtProperties = new Set() - let cssThemeVariables = new DefaultMap< - Extract['nodes'], - Set - >(() => new Set()) + let cssThemeVariables = new DefaultMap>(() => new Set()) + let colorMixDeclarations = new DefaultMap>(() => new Set()) let keyframes = new Set() let usedKeyframeNames = new Set() @@ -280,7 +278,7 @@ export function optimizeAst( function transform( node: AstNode, - parent: Extract['nodes'], + parent: AstNode[], context: Record = {}, depth = 0, ) { @@ -326,71 +324,7 @@ export function optimizeAst( // Create fallback values for usages of the `color-mix(…)` function that reference variables // found in the theme config. if (polyfills & Polyfills.ColorMix && node.value.includes('color-mix(')) { - let ast = ValueParser.parse(node.value) - - let requiresPolyfill = false - ValueParser.walk(ast, (node, { replaceWith }) => { - if (node.kind !== 'function' || node.value !== 'color-mix') return - - let containsUnresolvableVars = false - let containsCurrentcolor = false - ValueParser.walk(node.nodes, (node, { replaceWith }) => { - if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') { - containsCurrentcolor = true - requiresPolyfill = true - return - } - if (node.kind !== 'function' || node.value !== 'var') return - let firstChild = node.nodes[0] - if (!firstChild || firstChild.kind !== 'word') return - - requiresPolyfill = true - - let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any]) - if (!inlinedColor) { - containsUnresolvableVars = true - return - } - - replaceWith({ kind: 'word', value: inlinedColor }) - }) - - if (containsUnresolvableVars || containsCurrentcolor) { - let separatorIndex = node.nodes.findIndex( - (node) => node.kind === 'separator' && node.value.trim().includes(','), - ) - if (separatorIndex === -1) return - let firstColorValue = - node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null - if (!firstColorValue) return - replaceWith(firstColorValue) - } else if (requiresPolyfill) { - // Change the colorspace to `srgb` since the fallback values should not be represented as - // `oklab(…)` functions again as their support in Safari <16 is very limited. - let colorspace = node.nodes[2] - if ( - colorspace.kind === 'word' && - (colorspace.value === 'oklab' || - colorspace.value === 'oklch' || - colorspace.value === 'lab' || - colorspace.value === 'lch') - ) { - colorspace.value = 'srgb' - } - } - }) - - if (requiresPolyfill) { - let fallback = { - ...node, - value: ValueParser.toCss(ast), - } - let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [node]) - - parent.push(fallback, colorMixQuery) - - return - } + colorMixDeclarations.get(parent).add(node) } parent.push(node) @@ -595,6 +529,74 @@ export function optimizeAst( newAst = newAst.concat(atRoots) // Fallbacks + // Create fallback values for usages of the `color-mix(…)` function that reference variables + // found in the theme config. + if (polyfills & Polyfills.ColorMix) { + for (let [parent, declarations] of colorMixDeclarations) { + for (let declaration of declarations) { + let idx = parent.indexOf(declaration) + // If the declaration is no longer present, we don't need to create a polyfill anymore + if (idx === -1 || declaration.value == null) continue + + let ast = ValueParser.parse(declaration.value) + let requiresPolyfill = false + ValueParser.walk(ast, (node, { replaceWith }) => { + if (node.kind !== 'function' || node.value !== 'color-mix') return + let containsUnresolvableVars = false + let containsCurrentcolor = false + ValueParser.walk(node.nodes, (node, { replaceWith }) => { + if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') { + containsCurrentcolor = true + requiresPolyfill = true + return + } + if (node.kind !== 'function' || node.value !== 'var') return + let firstChild = node.nodes[0] + if (!firstChild || firstChild.kind !== 'word') return + requiresPolyfill = true + let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any]) + if (!inlinedColor) { + containsUnresolvableVars = true + return + } + replaceWith({ kind: 'word', value: inlinedColor }) + }) + if (containsUnresolvableVars || containsCurrentcolor) { + let separatorIndex = node.nodes.findIndex( + (node) => node.kind === 'separator' && node.value.trim().includes(','), + ) + if (separatorIndex === -1) return + let firstColorValue = + node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null + if (!firstColorValue) return + replaceWith(firstColorValue) + } else if (requiresPolyfill) { + // Change the colorspace to `srgb` since the fallback values should not be represented as + // `oklab(…)` functions again as their support in Safari <16 is very limited. + let colorspace = node.nodes[2] + if ( + colorspace.kind === 'word' && + (colorspace.value === 'oklab' || + colorspace.value === 'oklch' || + colorspace.value === 'lab' || + colorspace.value === 'lch') + ) { + colorspace.value = 'srgb' + } + } + }) + if (!requiresPolyfill) continue + + let fallback = { + ...declaration, + value: ValueParser.toCss(ast), + } + let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [declaration]) + parent.splice(idx, 1, fallback, colorMixQuery) + } + } + } + if (polyfills & Polyfills.AtProperty) { let fallbackAst = [] diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index b68e6dcff..6bfccb1e3 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1164,21 +1164,21 @@ test('utilities must be prefixed', async () => { // Prefixed utilities are generated expect(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom'])) .toMatchInlineSnapshot(` - ".tw\\:custom { - color: red; - } - .tw\\:underline { - text-decoration-line: underline; - } - .tw\\:hover\\:line-through { - &:hover { - @media (hover: hover) { - text-decoration-line: line-through; + ".tw\\:custom { + color: red; + } + .tw\\:underline { + text-decoration-line: underline; + } + .tw\\:hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through; + } } } - } - " - `) + " + `) // Non-prefixed utilities are ignored compiler = await compile(input, { diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index dc415eb1a..ca7d96dee 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -243,14 +243,14 @@ describe('theme', async () => { expect(compiler.build(['animate-duration-316', 'animate-duration-slow'])) .toMatchInlineSnapshot(` - ".animate-duration-316 { - animation-duration: 316ms; - } - .animate-duration-slow { - animation-duration: 800ms; - } - " - `) + ".animate-duration-316 { + animation-duration: 316ms; + } + .animate-duration-slow { + animation-duration: 800ms; + } + " + `) }) test('plugin theme can have opacity modifiers', async () => { @@ -3340,16 +3340,16 @@ describe('matchUtilities()', () => { } expect(optimizeCss(await run(['@w-1', 'hover:@w-1'])).trim()).toMatchInlineSnapshot(` - ".\\@w-1 { + ".\\@w-1 { + width: 1px; + } + + @media (hover: hover) { + .hover\\:\\@w-1:hover { width: 1px; } - - @media (hover: hover) { - .hover\\:\\@w-1:hover { - width: 1px; - } - }" - `) + }" + `) }) test('custom functional utilities can return an array of rules', async () => { @@ -4153,30 +4153,30 @@ describe('addComponents()', () => { expect(optimizeCss(compiled.build(['btn', 'btn-blue', 'btn-red'])).trim()) .toMatchInlineSnapshot(` - ".btn { - border-radius: .25rem; - padding: .5rem 1rem; - font-weight: 600; - } + ".btn { + border-radius: .25rem; + padding: .5rem 1rem; + font-weight: 600; + } - .btn-blue { - color: #fff; - background-color: #3490dc; - } + .btn-blue { + color: #fff; + background-color: #3490dc; + } - .btn-blue:hover { - background-color: #2779bd; - } + .btn-blue:hover { + background-color: #2779bd; + } - .btn-red { - color: #fff; - background-color: #e3342f; - } + .btn-red { + color: #fff; + background-color: #e3342f; + } - .btn-red:hover { - background-color: #cc1f1a; - }" - `) + .btn-red:hover { + background-color: #cc1f1a; + }" + `) }) }) @@ -4212,16 +4212,16 @@ describe('matchComponents()', () => { expect(optimizeCss(compiled.build(['prose', 'sm:prose-sm', 'hover:prose-lg'])).trim()) .toMatchInlineSnapshot(` - ".prose { - --container-size: normal; - } - - @media (hover: hover) { - .hover\\:prose-lg:hover { - --container-size: lg; + ".prose { + --container-size: normal; } - }" - `) + + @media (hover: hover) { + .hover\\:prose-lg:hover { + --container-size: lg; + } + }" + `) }) }) diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts index 37747e32f..f63682f63 100644 --- a/packages/tailwindcss/src/css-functions.test.ts +++ b/packages/tailwindcss/src/css-functions.test.ts @@ -756,10 +756,10 @@ describe('theme(…)', () => { } `), ).toMatchInlineSnapshot(` - ".fam { - font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; - }" - `) + ".fam { + font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + }" + `) }) test('theme(fontFamily.sans) (config)', async () => { @@ -776,10 +776,10 @@ describe('theme(…)', () => { ) expect(optimizeCss(compiled.build([])).trim()).toMatchInlineSnapshot(` - ".fam { - font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; - }" - `) + ".fam { + font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + }" + `) }) }) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 14aaf18c7..707e8f02c 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -5047,6 +5047,150 @@ describe('`color-mix(…)` polyfill', () => { }" `) }) + + it('does not delete theme variables from the output', async () => { + await expect( + compileCss( + css` + @layer theme { + @theme { + --color-red-500: red; + --shadow-xl: 0 6px 18px 4px color-mix(in oklab, var(--color-red-500) 25%, transparent); + --opacity-disabled: 50%; + } + } + @tailwind utilities; + `, + ['text-red-500', 'shadow-xl', 'opacity-disabled'], + ), + ).resolves.toMatchInlineSnapshot(` + "@layer properties { + @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { + *, :before, :after, ::backdrop { + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + } + } + } + + @layer theme { + :root, :host { + --color-red-500: red; + --opacity-disabled: 50%; + } + } + + .text-red-500 { + color: var(--color-red-500); + } + + .opacity-disabled { + opacity: var(--opacity-disabled); + } + + .shadow-xl { + --tw-shadow: 0 6px 18px 4px var(--tw-shadow-color, #ff000040); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + + @supports (color: color-mix(in lab, red, red)) { + .shadow-xl { + --tw-shadow: 0 6px 18px 4px var(--tw-shadow-color, color-mix(in oklab, var(--color-red-500) 25%, transparent)); + } + } + + @property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + + @property --tw-shadow-color { + syntax: "*"; + inherits: false + } + + @property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; + } + + @property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + + @property --tw-inset-shadow-color { + syntax: "*"; + inherits: false + } + + @property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; + } + + @property --tw-ring-color { + syntax: "*"; + inherits: false + } + + @property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + + @property --tw-inset-ring-color { + syntax: "*"; + inherits: false + } + + @property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + + @property --tw-ring-inset { + syntax: "*"; + inherits: false + } + + @property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0; + } + + @property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; + } + + @property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + }" + `) + }) }) describe('`@property` polyfill', async () => { diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index c0286428f..c3af45d3b 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -1319,26 +1319,26 @@ test('row-start', async () => { expect( await run(['row-start-auto', 'row-start-4', 'row-start-99', 'row-start-[123]', '-row-start-4']), ).toMatchInlineSnapshot(` - ".-row-start-4 { - grid-row-start: calc(4 * -1); - } + ".-row-start-4 { + grid-row-start: calc(4 * -1); + } - .row-start-4 { - grid-row-start: 4; - } + .row-start-4 { + grid-row-start: 4; + } - .row-start-99 { - grid-row-start: 99; - } + .row-start-99 { + grid-row-start: 99; + } - .row-start-\\[123\\] { - grid-row-start: 123; - } + .row-start-\\[123\\] { + grid-row-start: 123; + } - .row-start-auto { - grid-row-start: auto; - }" - `) + .row-start-auto { + grid-row-start: auto; + }" + `) expect( await run([ 'row-start', @@ -4437,22 +4437,22 @@ test('translate-3d', async () => { test('rotate', async () => { expect(await run(['rotate-45', '-rotate-45', 'rotate-[123deg]', 'rotate-[0.3_0.7_1_45deg]'])) .toMatchInlineSnapshot(` - ".-rotate-45 { - rotate: -45deg; - } + ".-rotate-45 { + rotate: -45deg; + } - .rotate-45 { - rotate: 45deg; - } + .rotate-45 { + rotate: 45deg; + } - .rotate-\\[0\\.3_0\\.7_1_45deg\\] { - rotate: .3 .7 1 45deg; - } + .rotate-\\[0\\.3_0\\.7_1_45deg\\] { + rotate: .3 .7 1 45deg; + } - .rotate-\\[123deg\\] { - rotate: 123deg; - }" - `) + .rotate-\\[123deg\\] { + rotate: 123deg; + }" + `) expect( await run([ 'rotate', @@ -5316,42 +5316,42 @@ test('transform', async () => { 'backface-hidden', ]), ).toMatchInlineSnapshot(` - ".backface-hidden { - backface-visibility: hidden; - } + ".backface-hidden { + backface-visibility: hidden; + } - .backface-visible { - backface-visibility: visible; - } + .backface-visible { + backface-visibility: visible; + } - .transform-3d { - transform-style: preserve-3d; - } + .transform-3d { + transform-style: preserve-3d; + } - .transform-border { - transform-box: border-box; - } + .transform-border { + transform-box: border-box; + } - .transform-content { - transform-box: content-box; - } + .transform-content { + transform-box: content-box; + } - .transform-fill { - transform-box: fill-box; - } + .transform-fill { + transform-box: fill-box; + } - .transform-flat { - transform-style: flat; - } + .transform-flat { + transform-style: flat; + } - .transform-stroke { - transform-box: stroke-box; - } + .transform-stroke { + transform-box: stroke-box; + } - .transform-view { - transform-box: view-box; - }" - `) + .transform-view { + transform-box: view-box; + }" + `) expect( await run([ '-transform', @@ -5862,26 +5862,26 @@ test('touch-pinch-zoom', async () => { test('select', async () => { expect(await run(['select-none', 'select-text', 'select-all', 'select-auto'])) .toMatchInlineSnapshot(` - ".select-all { - -webkit-user-select: all; - user-select: all; - } + ".select-all { + -webkit-user-select: all; + user-select: all; + } - .select-auto { - -webkit-user-select: auto; - user-select: auto; - } + .select-auto { + -webkit-user-select: auto; + user-select: auto; + } - .select-none { - -webkit-user-select: none; - user-select: none; - } + .select-none { + -webkit-user-select: none; + user-select: none; + } - .select-text { - -webkit-user-select: text; - user-select: text; - }" - `) + .select-text { + -webkit-user-select: text; + user-select: text; + }" + `) expect( await run([ '-select-none', @@ -6006,22 +6006,22 @@ test('--tw-scroll-snap-strictness', async () => { test('scroll-snap-align', async () => { expect(await run(['snap-align-none', 'snap-start', 'snap-end', 'snap-center'])) .toMatchInlineSnapshot(` - ".snap-align-none { - scroll-snap-align: none; - } + ".snap-align-none { + scroll-snap-align: none; + } - .snap-center { - scroll-snap-align: center; - } + .snap-center { + scroll-snap-align: center; + } - .snap-end { - scroll-snap-align: end; - } + .snap-end { + scroll-snap-align: end; + } - .snap-start { - scroll-snap-align: start; - }" - `) + .snap-start { + scroll-snap-align: start; + }" + `) expect( await run([ '-snap-align-none', @@ -7375,22 +7375,22 @@ test('grid-rows', async () => { test('flex-direction', async () => { expect(await run(['flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse'])) .toMatchInlineSnapshot(` - ".flex-col { - flex-direction: column; - } + ".flex-col { + flex-direction: column; + } - .flex-col-reverse { - flex-direction: column-reverse; - } + .flex-col-reverse { + flex-direction: column-reverse; + } - .flex-row { - flex-direction: row; - } + .flex-row { + flex-direction: row; + } - .flex-row-reverse { - flex-direction: row-reverse; - }" - `) + .flex-row-reverse { + flex-direction: row-reverse; + }" + `) expect( await run([ '-flex-row', @@ -8363,31 +8363,31 @@ test('divide-style', async () => { expect( await run(['divide-solid', 'divide-dashed', 'divide-dotted', 'divide-double', 'divide-none']), ).toMatchInlineSnapshot(` - ":where(.divide-dashed > :not(:last-child)) { - --tw-border-style: dashed; - border-style: dashed; - } + ":where(.divide-dashed > :not(:last-child)) { + --tw-border-style: dashed; + border-style: dashed; + } - :where(.divide-dotted > :not(:last-child)) { - --tw-border-style: dotted; - border-style: dotted; - } + :where(.divide-dotted > :not(:last-child)) { + --tw-border-style: dotted; + border-style: dotted; + } - :where(.divide-double > :not(:last-child)) { - --tw-border-style: double; - border-style: double; - } + :where(.divide-double > :not(:last-child)) { + --tw-border-style: double; + border-style: double; + } - :where(.divide-none > :not(:last-child)) { - --tw-border-style: none; - border-style: none; - } + :where(.divide-none > :not(:last-child)) { + --tw-border-style: none; + border-style: none; + } - :where(.divide-solid > :not(:last-child)) { - --tw-border-style: solid; - border-style: solid; - }" - `) + :where(.divide-solid > :not(:last-child)) { + --tw-border-style: solid; + border-style: solid; + }" + `) expect( await run([ 'divide', @@ -9274,18 +9274,18 @@ test('overflow-y', async () => { test('overscroll', async () => { expect(await run(['overscroll-auto', 'overscroll-contain', 'overscroll-none'])) .toMatchInlineSnapshot(` - ".overscroll-auto { - overscroll-behavior: auto; - } + ".overscroll-auto { + overscroll-behavior: auto; + } - .overscroll-contain { - overscroll-behavior: contain; - } + .overscroll-contain { + overscroll-behavior: contain; + } - .overscroll-none { - overscroll-behavior: none; - }" - `) + .overscroll-none { + overscroll-behavior: none; + }" + `) expect( await run([ 'overscroll', @@ -9302,18 +9302,18 @@ test('overscroll', async () => { test('overscroll-x', async () => { expect(await run(['overscroll-x-auto', 'overscroll-x-contain', 'overscroll-x-none'])) .toMatchInlineSnapshot(` - ".overscroll-x-auto { - overscroll-behavior-x: auto; - } + ".overscroll-x-auto { + overscroll-behavior-x: auto; + } - .overscroll-x-contain { - overscroll-behavior-x: contain; - } + .overscroll-x-contain { + overscroll-behavior-x: contain; + } - .overscroll-x-none { - overscroll-behavior-x: none; - }" - `) + .overscroll-x-none { + overscroll-behavior-x: none; + }" + `) expect( await run([ 'overscroll-x', @@ -9330,18 +9330,18 @@ test('overscroll-x', async () => { test('overscroll-y', async () => { expect(await run(['overscroll-y-auto', 'overscroll-y-contain', 'overscroll-y-none'])) .toMatchInlineSnapshot(` - ".overscroll-y-auto { - overscroll-behavior-y: auto; - } + ".overscroll-y-auto { + overscroll-behavior-y: auto; + } - .overscroll-y-contain { - overscroll-behavior-y: contain; - } + .overscroll-y-contain { + overscroll-behavior-y: contain; + } - .overscroll-y-none { - overscroll-behavior-y: none; - }" - `) + .overscroll-y-none { + overscroll-behavior-y: none; + }" + `) expect( await run([ 'overscroll-y', @@ -9483,22 +9483,22 @@ test('whitespace', async () => { test('text-wrap', async () => { expect(await run(['text-wrap', 'text-nowrap', 'text-balance', 'text-pretty'])) .toMatchInlineSnapshot(` - ".text-balance { - text-wrap: balance; - } + ".text-balance { + text-wrap: balance; + } - .text-nowrap { - text-wrap: nowrap; - } + .text-nowrap { + text-wrap: nowrap; + } - .text-pretty { - text-wrap: pretty; - } + .text-pretty { + text-wrap: pretty; + } - .text-wrap { - text-wrap: wrap; - }" - `) + .text-wrap { + text-wrap: wrap; + }" + `) expect( await run([ '-text-wrap', @@ -18604,18 +18604,18 @@ test('bg-clip', async () => { test('bg-origin', async () => { expect(await run(['bg-origin-border', 'bg-origin-padding', 'bg-origin-content'])) .toMatchInlineSnapshot(` - ".bg-origin-border { - background-origin: border-box; - } + ".bg-origin-border { + background-origin: border-box; + } - .bg-origin-content { - background-origin: content-box; - } + .bg-origin-content { + background-origin: content-box; + } - .bg-origin-padding { - background-origin: padding-box; - }" - `) + .bg-origin-padding { + background-origin: padding-box; + }" + `) expect( await run([ 'bg-origin', @@ -20307,22 +20307,22 @@ test('font-stretch', async () => { test('text-decoration-line', async () => { expect(await run(['underline', 'overline', 'line-through', 'no-underline'])) .toMatchInlineSnapshot(` - ".line-through { - text-decoration-line: line-through; - } + ".line-through { + text-decoration-line: line-through; + } - .no-underline { - text-decoration-line: none; - } + .no-underline { + text-decoration-line: none; + } - .overline { - text-decoration-line: overline; - } + .overline { + text-decoration-line: overline; + } - .underline { - text-decoration-line: underline; - }" - `) + .underline { + text-decoration-line: underline; + }" + `) expect( await run([ '-underline', @@ -22158,14 +22158,14 @@ test('content', async () => { test('forced-color-adjust', async () => { expect(await run(['forced-color-adjust-none', 'forced-color-adjust-auto'])) .toMatchInlineSnapshot(` - ".forced-color-adjust-auto { - forced-color-adjust: auto; - } + ".forced-color-adjust-auto { + forced-color-adjust: auto; + } - .forced-color-adjust-none { - forced-color-adjust: none; - }" - `) + .forced-color-adjust-none { + forced-color-adjust: none; + }" + `) expect( await run([ 'forced', @@ -26588,22 +26588,22 @@ describe('custom utilities', () => { expect(await compileCss(input, ['example-1', 'example-0.5', 'example-20%', 'example-2/3'])) .toMatchInlineSnapshot(` - ".example-0\\.5 { - --value-as-number: .5; - } + ".example-0\\.5 { + --value-as-number: .5; + } - .example-1 { - --value-as-number: 1; - } + .example-1 { + --value-as-number: 1; + } - .example-2\\/3 { - --value-as-ratio: 2 / 3; - } + .example-2\\/3 { + --value-as-ratio: 2 / 3; + } - .example-20\\% { - --value-as-percentage: 20%; - }" - `) + .example-20\\% { + --value-as-percentage: 20%; + }" + `) expect( await compileCss(input, [ 'example-1.23',