From 292efa5daaabec7c0f5a3c9e04634398e99edc34 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 11 Nov 2024 16:46:53 +0100 Subject: [PATCH] Remove the negative flag from the Candidate AST (#14938) This PR removes the `negative` flag from the `Candidate` AST. The system itself doesn't this information at all, but it's up to each plugin to handle the `negative` flag themselves. This also means that if you _don't_ handle it, that `foo` and `-foo` results in the same CSS output. To make sure that the negative version of utilities that supported it still work, this PR also adds the negative versions as separate utilities. E.g.: `-scale` is registered in addition to `scale`. This is an internal refactor only, and doesn't change any behavior. --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> --- .../src/template/candidates.ts | 7 - .../__snapshots__/intellisense.test.ts.snap | 46 ++ packages/tailwindcss/src/candidate.test.ts | 44 +- packages/tailwindcss/src/candidate.ts | 12 - packages/tailwindcss/src/compat/plugin-api.ts | 186 ++++---- packages/tailwindcss/src/index.ts | 5 +- packages/tailwindcss/src/utilities.ts | 443 +++++++++--------- 7 files changed, 365 insertions(+), 378 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.ts b/packages/@tailwindcss-upgrade/src/template/candidates.ts index 5e0ec6eaaae8..e4849a6be2a5 100644 --- a/packages/@tailwindcss-upgrade/src/template/candidates.ts +++ b/packages/@tailwindcss-upgrade/src/template/candidates.ts @@ -31,13 +31,6 @@ export function printCandidate(designSystem: DesignSystem, candidate: Candidate) let base: string = '' - // Handle negative - if (candidate.kind === 'static' || candidate.kind === 'functional') { - if (candidate.negative) { - base += '-' - } - } - // Handle static if (candidate.kind === 'static') { base += candidate.root diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index ac0a70fa693d..d96b027f3425 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -36,6 +36,8 @@ exports[`getClassList 1`] = ` "-bottom-80", "-bottom-9", "-bottom-96", + "-bottom-full", + "-bottom-px", "-col-end-1", "-col-end-10", "-col-end-11", @@ -96,6 +98,8 @@ exports[`getClassList 1`] = ` "-end-80", "-end-9", "-end-96", + "-end-full", + "-end-px", "-indent-0", "-indent-0.5", "-indent-1", @@ -130,6 +134,7 @@ exports[`getClassList 1`] = ` "-indent-80", "-indent-9", "-indent-96", + "-indent-px", "-inset-0", "-inset-0.5", "-inset-1", @@ -164,6 +169,8 @@ exports[`getClassList 1`] = ` "-inset-80", "-inset-9", "-inset-96", + "-inset-full", + "-inset-px", "-inset-x-0", "-inset-x-0.5", "-inset-x-1", @@ -198,6 +205,8 @@ exports[`getClassList 1`] = ` "-inset-x-80", "-inset-x-9", "-inset-x-96", + "-inset-x-full", + "-inset-x-px", "-inset-y-0", "-inset-y-0.5", "-inset-y-1", @@ -232,6 +241,8 @@ exports[`getClassList 1`] = ` "-inset-y-80", "-inset-y-9", "-inset-y-96", + "-inset-y-full", + "-inset-y-px", "-left-0", "-left-0.5", "-left-1", @@ -266,6 +277,8 @@ exports[`getClassList 1`] = ` "-left-80", "-left-9", "-left-96", + "-left-full", + "-left-px", "-m-0", "-m-0.5", "-m-1", @@ -300,6 +313,7 @@ exports[`getClassList 1`] = ` "-m-80", "-m-9", "-m-96", + "-m-px", "-mb-0", "-mb-0.5", "-mb-1", @@ -334,6 +348,7 @@ exports[`getClassList 1`] = ` "-mb-80", "-mb-9", "-mb-96", + "-mb-px", "-me-0", "-me-0.5", "-me-1", @@ -368,6 +383,7 @@ exports[`getClassList 1`] = ` "-me-80", "-me-9", "-me-96", + "-me-px", "-ml-0", "-ml-0.5", "-ml-1", @@ -402,6 +418,7 @@ exports[`getClassList 1`] = ` "-ml-80", "-ml-9", "-ml-96", + "-ml-px", "-mr-0", "-mr-0.5", "-mr-1", @@ -436,6 +453,7 @@ exports[`getClassList 1`] = ` "-mr-80", "-mr-9", "-mr-96", + "-mr-px", "-ms-0", "-ms-0.5", "-ms-1", @@ -470,6 +488,7 @@ exports[`getClassList 1`] = ` "-ms-80", "-ms-9", "-ms-96", + "-ms-px", "-mt-0", "-mt-0.5", "-mt-1", @@ -504,6 +523,7 @@ exports[`getClassList 1`] = ` "-mt-80", "-mt-9", "-mt-96", + "-mt-px", "-mx-0", "-mx-0.5", "-mx-1", @@ -538,6 +558,7 @@ exports[`getClassList 1`] = ` "-mx-80", "-mx-9", "-mx-96", + "-mx-px", "-my-0", "-my-0.5", "-my-1", @@ -572,6 +593,7 @@ exports[`getClassList 1`] = ` "-my-80", "-my-9", "-my-96", + "-my-px", "-order-1", "-order-10", "-order-11", @@ -618,6 +640,8 @@ exports[`getClassList 1`] = ` "-right-80", "-right-9", "-right-96", + "-right-full", + "-right-px", "-rotate-0", "-rotate-1", "-rotate-12", @@ -758,6 +782,7 @@ exports[`getClassList 1`] = ` "-scroll-m-80", "-scroll-m-9", "-scroll-m-96", + "-scroll-m-px", "-scroll-mb-0", "-scroll-mb-0.5", "-scroll-mb-1", @@ -792,6 +817,7 @@ exports[`getClassList 1`] = ` "-scroll-mb-80", "-scroll-mb-9", "-scroll-mb-96", + "-scroll-mb-px", "-scroll-me-0", "-scroll-me-0.5", "-scroll-me-1", @@ -826,6 +852,7 @@ exports[`getClassList 1`] = ` "-scroll-me-80", "-scroll-me-9", "-scroll-me-96", + "-scroll-me-px", "-scroll-ml-0", "-scroll-ml-0.5", "-scroll-ml-1", @@ -860,6 +887,7 @@ exports[`getClassList 1`] = ` "-scroll-ml-80", "-scroll-ml-9", "-scroll-ml-96", + "-scroll-ml-px", "-scroll-mr-0", "-scroll-mr-0.5", "-scroll-mr-1", @@ -894,6 +922,7 @@ exports[`getClassList 1`] = ` "-scroll-mr-80", "-scroll-mr-9", "-scroll-mr-96", + "-scroll-mr-px", "-scroll-ms-0", "-scroll-ms-0.5", "-scroll-ms-1", @@ -928,6 +957,7 @@ exports[`getClassList 1`] = ` "-scroll-ms-80", "-scroll-ms-9", "-scroll-ms-96", + "-scroll-ms-px", "-scroll-mt-0", "-scroll-mt-0.5", "-scroll-mt-1", @@ -962,6 +992,7 @@ exports[`getClassList 1`] = ` "-scroll-mt-80", "-scroll-mt-9", "-scroll-mt-96", + "-scroll-mt-px", "-scroll-mx-0", "-scroll-mx-0.5", "-scroll-mx-1", @@ -996,6 +1027,7 @@ exports[`getClassList 1`] = ` "-scroll-mx-80", "-scroll-mx-9", "-scroll-mx-96", + "-scroll-mx-px", "-scroll-my-0", "-scroll-my-0.5", "-scroll-my-1", @@ -1030,6 +1062,7 @@ exports[`getClassList 1`] = ` "-scroll-my-80", "-scroll-my-9", "-scroll-my-96", + "-scroll-my-px", "-skew-0", "-skew-1", "-skew-12", @@ -1082,6 +1115,7 @@ exports[`getClassList 1`] = ` "-space-x-80", "-space-x-9", "-space-x-96", + "-space-x-px", "-space-y-0", "-space-y-0.5", "-space-y-1", @@ -1116,6 +1150,7 @@ exports[`getClassList 1`] = ` "-space-y-80", "-space-y-9", "-space-y-96", + "-space-y-px", "-start-0", "-start-0.5", "-start-1", @@ -1150,6 +1185,8 @@ exports[`getClassList 1`] = ` "-start-80", "-start-9", "-start-96", + "-start-full", + "-start-px", "-top-0", "-top-0.5", "-top-1", @@ -1184,6 +1221,8 @@ exports[`getClassList 1`] = ` "-top-80", "-top-9", "-top-96", + "-top-full", + "-top-px", "-translate-0", "-translate-0.5", "-translate-1", @@ -1218,6 +1257,8 @@ exports[`getClassList 1`] = ` "-translate-80", "-translate-9", "-translate-96", + "-translate-full", + "-translate-px", "-translate-x-0", "-translate-x-0.5", "-translate-x-1", @@ -1252,6 +1293,8 @@ exports[`getClassList 1`] = ` "-translate-x-80", "-translate-x-9", "-translate-x-96", + "-translate-x-full", + "-translate-x-px", "-translate-y-0", "-translate-y-0.5", "-translate-y-1", @@ -1286,6 +1329,8 @@ exports[`getClassList 1`] = ` "-translate-y-80", "-translate-y-9", "-translate-y-96", + "-translate-y-full", + "-translate-y-px", "-translate-z-0", "-translate-z-0.5", "-translate-z-1", @@ -1320,6 +1365,7 @@ exports[`getClassList 1`] = ` "-translate-z-80", "-translate-z-9", "-translate-z-96", + "-translate-z-px", "-underline-offset-0", "-underline-offset-1", "-underline-offset-2", diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts index 9837e3471cf6..570a3424977a 100644 --- a/packages/tailwindcss/src/candidate.test.ts +++ b/packages/tailwindcss/src/candidate.test.ts @@ -41,7 +41,6 @@ it('should parse a simple utility', () => { { "important": false, "kind": "static", - "negative": false, "raw": "flex", "root": "flex", "variants": [], @@ -59,7 +58,6 @@ it('should parse a simple utility that should be important', () => { { "important": true, "kind": "static", - "negative": false, "raw": "flex!", "root": "flex", "variants": [], @@ -70,7 +68,7 @@ it('should parse a simple utility that should be important', () => { it('should parse a simple utility that can be negative', () => { let utilities = new Utilities() - utilities.functional('translate-x', () => []) + utilities.functional('-translate-x', () => []) expect(run('-translate-x-4', { utilities })).toMatchInlineSnapshot(` [ @@ -78,9 +76,8 @@ it('should parse a simple utility that can be negative', () => { "important": false, "kind": "functional", "modifier": null, - "negative": true, "raw": "-translate-x-4", - "root": "translate-x", + "root": "-translate-x", "value": { "fraction": null, "kind": "named", @@ -104,7 +101,6 @@ it('should parse a simple utility with a variant', () => { { "important": false, "kind": "static", - "negative": false, "raw": "hover:flex", "root": "flex", "variants": [ @@ -131,7 +127,6 @@ it('should parse a simple utility with stacked variants', () => { { "important": false, "kind": "static", - "negative": false, "raw": "focus:hover:flex", "root": "flex", "variants": [ @@ -158,7 +153,6 @@ it('should parse a simple utility with an arbitrary variant', () => { { "important": false, "kind": "static", - "negative": false, "raw": "[&_p]:flex", "root": "flex", "variants": [ @@ -185,7 +179,6 @@ it('should parse a simple utility with a parameterized variant', () => { { "important": false, "kind": "static", - "negative": false, "raw": "data-[disabled]:flex", "root": "flex", "variants": [ @@ -216,7 +209,6 @@ it('should parse compound variants with an arbitrary value as an arbitrary varia { "important": false, "kind": "static", - "negative": false, "raw": "group-[&_p]/parent-name:flex", "root": "flex", "variants": [ @@ -253,7 +245,6 @@ it('should parse a simple utility with a parameterized variant and a modifier', { "important": false, "kind": "static", - "negative": false, "raw": "group-aria-[disabled]/parent-name:flex", "root": "flex", "variants": [ @@ -294,7 +285,6 @@ it('should parse compound group with itself group-group-*', () => { { "important": false, "kind": "static", - "negative": false, "raw": "group-group-group-hover/parent-name:flex", "root": "flex", "variants": [ @@ -335,7 +325,6 @@ it('should parse a simple utility with an arbitrary media variant', () => { { "important": false, "kind": "static", - "negative": false, "raw": "[@media(width>=123px)]:flex", "root": "flex", "variants": [ @@ -370,7 +359,6 @@ it('should parse a utility with a modifier', () => { "kind": "named", "value": "50", }, - "negative": false, "raw": "bg-red-500/50", "root": "bg", "value": { @@ -397,7 +385,6 @@ it('should parse a utility with an arbitrary modifier', () => { "kind": "arbitrary", "value": "50%", }, - "negative": false, "raw": "bg-red-500/[50%]", "root": "bg", "value": { @@ -424,7 +411,6 @@ it('should parse a utility with a modifier that is important', () => { "kind": "named", "value": "50", }, - "negative": false, "raw": "bg-red-500/50!", "root": "bg", "value": { @@ -454,7 +440,6 @@ it('should parse a utility with a modifier and a variant', () => { "kind": "named", "value": "50", }, - "negative": false, "raw": "hover:bg-red-500/50", "root": "bg", "value": { @@ -513,7 +498,6 @@ it('should parse a utility with an arbitrary value', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[#0088cc]", "root": "bg", "value": { @@ -537,7 +521,6 @@ it('should parse a utility with an arbitrary value including a typehint', () => "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[color:var(--value)]", "root": "bg", "value": { @@ -564,7 +547,6 @@ it('should parse a utility with an arbitrary value with a modifier', () => { "kind": "named", "value": "50", }, - "negative": false, "raw": "bg-[#0088cc]/50", "root": "bg", "value": { @@ -591,7 +573,6 @@ it('should parse a utility with an arbitrary value with an arbitrary modifier', "kind": "arbitrary", "value": "50%", }, - "negative": false, "raw": "bg-[#0088cc]/[50%]", "root": "bg", "value": { @@ -615,7 +596,6 @@ it('should parse a utility with an arbitrary value that is important', () => { "important": true, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[#0088cc]!", "root": "bg", "value": { @@ -639,7 +619,6 @@ it('should parse a utility with an implicit variable as the arbitrary value', () "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[var(--value)]", "root": "bg", "value": { @@ -663,7 +642,6 @@ it('should parse a utility with an implicit variable as the arbitrary value that "important": true, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[var(--value)]!", "root": "bg", "value": { @@ -687,7 +665,6 @@ it('should parse a utility with an explicit variable as the arbitrary value', () "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[var(--value)]", "root": "bg", "value": { @@ -711,7 +688,6 @@ it('should parse a utility with an explicit variable as the arbitrary value that "important": true, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[var(--value)]!", "root": "bg", "value": { @@ -768,7 +744,6 @@ it('should parse a utility with an implicit variable as the modifier', () => { "kind": "arbitrary", "value": "var(--value)", }, - "negative": false, "raw": "bg-red-500/[var(--value)]", "root": "bg", "value": { @@ -795,7 +770,6 @@ it('should parse a utility with an implicit variable as the modifier that is imp "kind": "arbitrary", "value": "var(--value)", }, - "negative": false, "raw": "bg-red-500/[var(--value)]!", "root": "bg", "value": { @@ -822,7 +796,6 @@ it('should parse a utility with an explicit variable as the modifier', () => { "kind": "arbitrary", "value": "var(--value)", }, - "negative": false, "raw": "bg-red-500/[var(--value)]", "root": "bg", "value": { @@ -849,7 +822,6 @@ it('should parse a utility with an explicit variable as the modifier that is imp "kind": "arbitrary", "value": "var(--value)", }, - "negative": false, "raw": "bg-red-500/[var(--value)]!", "root": "bg", "value": { @@ -875,7 +847,6 @@ it('should parse a static variant starting with @', () => { { "important": false, "kind": "static", - "negative": false, "raw": "@lg:flex", "root": "flex", "variants": [ @@ -901,7 +872,6 @@ it('should parse a functional variant with a modifier', () => { { "important": false, "kind": "static", - "negative": false, "raw": "foo-bar/50:flex", "root": "flex", "variants": [ @@ -935,7 +905,6 @@ it('should parse a functional variant starting with @', () => { { "important": false, "kind": "static", - "negative": false, "raw": "@lg:flex", "root": "flex", "variants": [ @@ -966,7 +935,6 @@ it('should parse a functional variant starting with @ and a modifier', () => { { "important": false, "kind": "static", - "negative": false, "raw": "@lg/name:flex", "root": "flex", "variants": [ @@ -998,7 +966,6 @@ it('should replace `_` with ` `', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "content-["hello_world"]", "root": "content", "value": { @@ -1022,7 +989,6 @@ it('should not replace `\\_` with ` ` (when it is escaped)', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "content-["hello\\_world"]", "root": "content", "value": { @@ -1047,7 +1013,6 @@ it('should not replace `_` inside of `url()`', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "bg-[no-repeat_url(https://example.com/some_page)]", "root": "bg", "value": { @@ -1072,7 +1037,6 @@ it('should not replace `_` in the first argument to `var()`', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "ml-[var(--spacing-1_5,_var(--spacing-2_5,_1rem))]", "root": "ml", "value": { @@ -1097,7 +1061,6 @@ it('should not replace `_` in the first argument to `theme()`', () => { "important": false, "kind": "functional", "modifier": null, - "negative": false, "raw": "ml-[theme(--spacing-1_5,_theme(--spacing-2_5,_1rem))]", "root": "ml", "value": { @@ -1272,7 +1235,6 @@ it('should parse a variant containing an arbitrary string with unbalanced parens { "important": false, "kind": "static", - "negative": false, "raw": "string-['}[("\\'']:flex", "root": "flex", "variants": [ @@ -1307,7 +1269,6 @@ it('should parse candidates with a prefix', () => { { "important": false, "kind": "static", - "negative": false, "raw": "tw:flex", "root": "flex", "variants": [], @@ -1319,7 +1280,6 @@ it('should parse candidates with a prefix', () => { { "important": false, "kind": "static", - "negative": false, "raw": "tw:hover:flex", "root": "flex", "variants": [ diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index 91d6e1bb60bf..669ef4f7c103 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -183,7 +183,6 @@ export type Candidate = kind: 'static' root: string variants: Variant[] - negative: boolean important: boolean raw: string } @@ -203,7 +202,6 @@ export type Candidate = value: ArbitraryUtilityValue | NamedUtilityValue | null modifier: ArbitraryModifier | NamedModifier | null variants: Variant[] - negative: boolean important: boolean raw: string } @@ -240,7 +238,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter } let important = false - let negative = false // Candidates that end with an exclamation mark are the important version with // higher specificity of the non-important candidate, e.g. `mx-4!`. @@ -255,13 +252,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter base = base.slice(1) } - // Candidates that start with a dash are the negative versions of another - // candidate, e.g. `-mx-4`. - if (base[0] === '-') { - negative = true - base = base.slice(1) - } - // Check for an exact match of a static utility first as long as it does not // look like an arbitrary value. if (designSystem.utilities.has(base, 'static') && !base.includes('[')) { @@ -269,7 +259,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter kind: 'static', root: base, variants: parsedCandidateVariants, - negative, important, raw: input, } @@ -384,7 +373,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter modifier: modifierSegment === null ? null : parseModifier(modifierSegment), value: null, variants: parsedCandidateVariants, - negative, important, raw: input, } diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index e348da5c0c5c..9afb93140092 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -4,7 +4,7 @@ import type { Candidate, CandidateModifier, NamedUtilityValue } from '../candida import { substituteFunctions } from '../css-functions' import * as CSS from '../css-parser' import type { DesignSystem } from '../design-system' -import { withAlpha, withNegative } from '../utilities' +import { withAlpha } from '../utilities' import { inferDataType } from '../utils/infer-data-type' import { segment } from '../utils/segment' import { toKeyPath } from '../utils/to-key-path' @@ -228,8 +228,7 @@ export function buildPluginApi( ) } - designSystem.utilities.static(name.slice(1), (candidate) => { - if (candidate.negative) return + designSystem.utilities.static(name.slice(1), () => { let ast = objectToAst(css) substituteAtApply(ast, designSystem) return ast @@ -251,112 +250,117 @@ export function buildPluginApi( ) } - function compileFn(candidate: Extract) { - // A negative utility was provided but is unsupported - if (!options?.supportsNegativeValues && candidate.negative) return - - // Throw out any candidate whose value is not a supported type - if (candidate.value?.kind === 'arbitrary' && types.length > 0 && !types.includes('any')) { - // The candidate has an explicit data type but it's not in the list - // of supported types by this utility. For example, a `scrollbar` - // utility that is only used to change the scrollbar color but is - // used with a `length` value: `scrollbar-[length:var(--whatever)]` - if (candidate.value.dataType && !types.includes(candidate.value.dataType)) { - return - } - - // The candidate does not have an explicit data type and the value - // cannot be inferred as one of the supported types. For example, a - // `scrollbar` utility that is only used to change the scrollbar - // color but is used with a `length` value: `scrollbar-[33px]` + function compileFn({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + // Throw out any candidate whose value is not a supported type if ( - !candidate.value.dataType && - !inferDataType(candidate.value.value, types as any[]) + candidate.value?.kind === 'arbitrary' && + types.length > 0 && + !types.includes('any') ) { - return + // The candidate has an explicit data type but it's not in the list + // of supported types by this utility. For example, a `scrollbar` + // utility that is only used to change the scrollbar color but is + // used with a `length` value: `scrollbar-[length:var(--whatever)]` + if (candidate.value.dataType && !types.includes(candidate.value.dataType)) { + return + } + + // The candidate does not have an explicit data type and the value + // cannot be inferred as one of the supported types. For example, a + // `scrollbar` utility that is only used to change the scrollbar + // color but is used with a `length` value: `scrollbar-[33px]` + if ( + !candidate.value.dataType && + !inferDataType(candidate.value.value, types as any[]) + ) { + return + } } - } - let isColor = types.includes('color') + let isColor = types.includes('color') - // Resolve the candidate value - let value: string | null = null - let ignoreModifier = false + // Resolve the candidate value + let value: string | null = null + let ignoreModifier = false - { - let values = options?.values ?? {} - - if (isColor) { - // Color utilities implicitly support `inherit`, `transparent`, and `currentColor` - // for backwards compatibility but still allow them to be overridden - values = Object.assign( - { - inherit: 'inherit', - transparent: 'transparent', - current: 'currentColor', - }, - values, - ) - } + { + let values = options?.values ?? {} + + if (isColor) { + // Color utilities implicitly support `inherit`, `transparent`, and `currentColor` + // for backwards compatibility but still allow them to be overridden + values = Object.assign( + { + inherit: 'inherit', + transparent: 'transparent', + current: 'currentColor', + }, + values, + ) + } - if (!candidate.value) { - value = values.DEFAULT ?? null - } else if (candidate.value.kind === 'arbitrary') { - value = candidate.value.value - } else if (candidate.value.fraction && values[candidate.value.fraction]) { - value = values[candidate.value.fraction] - ignoreModifier = true - } else if (values[candidate.value.value]) { - value = values[candidate.value.value] - } else if (values.__BARE_VALUE__) { - value = values.__BARE_VALUE__(candidate.value) ?? null - ignoreModifier = (candidate.value.fraction !== null && value?.includes('/')) ?? false + if (!candidate.value) { + value = values.DEFAULT ?? null + } else if (candidate.value.kind === 'arbitrary') { + value = candidate.value.value + } else if (candidate.value.fraction && values[candidate.value.fraction]) { + value = values[candidate.value.fraction] + ignoreModifier = true + } else if (values[candidate.value.value]) { + value = values[candidate.value.value] + } else if (values.__BARE_VALUE__) { + value = values.__BARE_VALUE__(candidate.value) ?? null + ignoreModifier = + (candidate.value.fraction !== null && value?.includes('/')) ?? false + } } - } - if (value === null) return + if (value === null) return - // Resolve the modifier value - let modifier: string | null + // Resolve the modifier value + let modifier: string | null - { - let modifiers = options?.modifiers ?? null - - if (!candidate.modifier) { - modifier = null - } else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') { - modifier = candidate.modifier.value - } else if (modifiers?.[candidate.modifier.value]) { - modifier = modifiers[candidate.modifier.value] - } else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) { - modifier = `${candidate.modifier.value}%` - } else { - modifier = null + { + let modifiers = options?.modifiers ?? null + + if (!candidate.modifier) { + modifier = null + } else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') { + modifier = candidate.modifier.value + } else if (modifiers?.[candidate.modifier.value]) { + modifier = modifiers[candidate.modifier.value] + } else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) { + modifier = `${candidate.modifier.value}%` + } else { + modifier = null + } } - } - // A modifier was provided but is invalid - if (candidate.modifier && modifier === null && !ignoreModifier) { - // For arbitrary values, return `null` to avoid falling through to the next utility - return candidate.value?.kind === 'arbitrary' ? null : undefined - } + // A modifier was provided but is invalid + if (candidate.modifier && modifier === null && !ignoreModifier) { + // For arbitrary values, return `null` to avoid falling through to the next utility + return candidate.value?.kind === 'arbitrary' ? null : undefined + } - if (isColor && modifier !== null) { - value = withAlpha(value, modifier) - } + if (isColor && modifier !== null) { + value = withAlpha(value, modifier) + } - if (candidate.negative) { - value = withNegative(value, candidate) - } + if (negative) { + value = `calc(${value} * -1)` + } - let ast = objectToAst(fn(value, { modifier })) - substituteAtApply(ast, designSystem) - return ast + let ast = objectToAst(fn(value, { modifier })) + substituteAtApply(ast, designSystem) + return ast + } } - designSystem.utilities.functional(name, compileFn, { - types, - }) + if (options?.supportsNegativeValues) { + designSystem.utilities.functional(`-${name}`, compileFn({ negative: true }), { types }) + } + designSystem.utilities.functional(name, compileFn({ negative: false }), { types }) designSystem.utilities.suggest(name, () => { let values = options?.values ?? {} diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 40663f4883b8..04e9bb99d9e4 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -161,10 +161,7 @@ async function parseCss( } customUtilities.push((designSystem) => { - designSystem.utilities.static(name, (candidate) => { - if (candidate.negative) return - return structuredClone(node.nodes) - }) + designSystem.utilities.static(name, () => structuredClone(node.nodes)) }) return diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 2248f507deee..96ceea6b5261 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -133,16 +133,6 @@ export function asColor(value: string, modifier: CandidateModifier | null): stri return withAlpha(value, `${modifier.value}%`) } -/** - * Negate a numeric value — literals get simplified by Lightning CSS. - */ -export function withNegative( - value: string, - candidate: Extract, -) { - return candidate.negative ? `calc(${value} * -1)` : value -} - /** * Finds a color in the theme under one of the given theme keys that matches * `candidateValue`. @@ -232,9 +222,7 @@ export function createUtilities(theme: Theme) { * Register a static utility class like `justify-center`. */ function staticUtility(className: string, declarations: ([string, string] | (() => AstNode))[]) { - utilities.static(className, (candidate) => { - if (candidate.negative) return - + utilities.static(className, () => { return declarations.map((node) => { return typeof node === 'function' ? node() : decl(node[0], node[1]) }) @@ -256,62 +244,64 @@ export function createUtilities(theme: Theme) { * user's theme. */ function functionalUtility(classRoot: string, desc: UtilityDescription) { - utilities.functional(classRoot, (candidate) => { - // If the class candidate has a negative prefix (like `-mx-2`) but this - // utility doesn't support negative values (like the `width` utility), - // don't generate any rules. - if (candidate.negative && !desc.supportsNegative) return + function handleFunctionalUtility({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + let value: string | null = null - let value: string | null = null + if (!candidate.value) { + if (candidate.modifier) return - if (!candidate.value) { - if (candidate.modifier) return + // If the candidate has no value segment (like `rounded`), use the + // `defaultValue` (for candidates like `grow` that have no theme + // values) or a bare theme value (like `--radius` for `rounded`). No + // utility will ever support both of these. + value = + desc.defaultValue !== undefined + ? desc.defaultValue + : theme.resolve(null, desc.themeKeys ?? []) + } else if (candidate.value.kind === 'arbitrary') { + if (candidate.modifier) return + value = candidate.value.value + } else { + value = theme.resolve( + candidate.value.fraction ?? candidate.value.value, + desc.themeKeys ?? [], + ) + + // Automatically handle things like `w-1/2` without requiring `1/2` to + // exist as a theme value. + if (value === null && desc.supportsFractions && candidate.value.fraction) { + let [lhs, rhs] = segment(candidate.value.fraction, '/') + if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return + value = `calc(${candidate.value.fraction} * 100%)` + } - // If the candidate has no value segment (like `rounded`), use the - // `defaultValue` (for candidates like `grow` that have no theme values) - // or a bare theme value (like `--radius` for `rounded`). No utility - // will ever support both of these. - value = - desc.defaultValue !== undefined - ? desc.defaultValue - : theme.resolve(null, desc.themeKeys ?? []) - } else if (candidate.value.kind === 'arbitrary') { - if (candidate.modifier) return - value = candidate.value.value - } else { - value = theme.resolve( - candidate.value.fraction ?? candidate.value.value, - desc.themeKeys ?? [], - ) - - // Automatically handle things like `w-1/2` without requiring `1/2` to - // exist as a theme value. - if (value === null && desc.supportsFractions && candidate.value.fraction) { - let [lhs, rhs] = segment(candidate.value.fraction, '/') - if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return - value = `calc(${candidate.value.fraction} * 100%)` - } + // If there is still no value but the utility supports bare values, + // then use the bare candidate value as the value. + if (value === null && negative && desc.handleNegativeBareValue) { + value = desc.handleNegativeBareValue(candidate.value) + if (!value?.includes('/') && candidate.modifier) return + if (value !== null) return desc.handle(value) + } - // If there is still no value but the utility supports bare values, then - // use the bare candidate value as the value. - if (value === null && candidate.negative && desc.handleNegativeBareValue) { - value = desc.handleNegativeBareValue(candidate.value) - if (!value?.includes('/') && candidate.modifier) return - if (value !== null) return desc.handle(value) + if (value === null && desc.handleBareValue) { + value = desc.handleBareValue(candidate.value) + if (!value?.includes('/') && candidate.modifier) return + } } - if (value === null && desc.handleBareValue) { - value = desc.handleBareValue(candidate.value) - if (!value?.includes('/') && candidate.modifier) return - } - } + // If there is no value, don't generate any rules. + if (value === null) return - // If there is no value, don't generate any rules. - if (value === null) return + // Negate the value if the candidate has a negative prefix. + return desc.handle(negative ? `calc(${value} * -1)` : value) + } + } - // Negate the value if the candidate has a negative prefix. - return desc.handle(withNegative(value, candidate)) - }) + if (desc.supportsNegative) { + utilities.functional(`-${classRoot}`, handleFunctionalUtility({ negative: true })) + } + utilities.functional(classRoot, handleFunctionalUtility({ negative: false })) suggest(classRoot, () => [ { @@ -337,10 +327,6 @@ export function createUtilities(theme: Theme) { // `bg-red-500`, otherwise they would be static utilities. if (!candidate.value) return - // Color utilities never support negative values as there's no sensible - // way to negate a color. - if (candidate.negative) return - // Find the actual CSS value that the candidate value maps to. let value: string | null = null @@ -381,10 +367,10 @@ export function createUtilities(theme: Theme) { supportsFractions?: boolean } = {}, ) { - utilities.static(`${name}-px`, (candidate) => { - if (!supportsNegative && candidate.negative) return - return handle(candidate.negative ? '-1px' : '1px') - }) + if (supportsNegative) { + utilities.static(`-${name}-px`, () => handle('-1px')) + } + utilities.static(`${name}-px`, () => handle('1px')) let themeKeys = ([] as ThemeKey[]).concat(themeNamespace, '--spacing') functionalUtility(name, { themeKeys, @@ -515,10 +501,8 @@ export function createUtilities(theme: Theme) { ['left', 'left'], ] as const) { staticUtility(`${name}-auto`, [[property, 'auto']]) - utilities.static(`${name}-full`, (candidate) => { - let value = candidate.negative ? '-100%' : '100%' - return [decl(property, value)] - }) + staticUtility(`${name}-full`, [[property, '100%']]) + staticUtility(`-${name}-full`, [[property, '-100%']]) spacingUtility(name, '--inset', (value) => [decl(property, value)], { supportsNegative: true, supportsFractions: true, @@ -917,8 +901,6 @@ export function createUtilities(theme: Theme) { // generate `flex: 1`. Our `functionalUtility` helper can't handle two properties // using the same namespace, so we handle this one manually. utilities.functional('flex', (candidate) => { - if (candidate.negative) return - if (!candidate.value) { if (candidate.modifier) return return [decl('display', 'flex')] @@ -1087,16 +1069,18 @@ export function createUtilities(theme: Theme) { * @css `translate` */ staticUtility('translate-none', [['translate', 'none']]) - utilities.static('translate-full', (candidate) => { - let value = candidate.negative ? '-100%' : '100%' - - return [ - translateProperties(), - decl('--tw-translate-x', value), - decl('--tw-translate-y', value), - decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'), - ] - }) + staticUtility('-translate-full', [ + translateProperties, + ['--tw-translate-x', '-100%'], + ['--tw-translate-y', '-100%'], + ['translate', 'var(--tw-translate-x) var(--tw-translate-y)'], + ]) + staticUtility('translate-full', [ + translateProperties, + ['--tw-translate-x', '100%'], + ['--tw-translate-y', '100%'], + ['translate', 'var(--tw-translate-x) var(--tw-translate-y)'], + ]) spacingUtility( 'translate', @@ -1111,19 +1095,29 @@ export function createUtilities(theme: Theme) { ) for (let axis of ['x', 'y']) { - let handle = (value: string) => [ - translateProperties(), - decl(`--tw-translate-${axis}`, value), - decl('translate', `var(--tw-translate-x) var(--tw-translate-y)`), - ] - - spacingUtility(`translate-${axis}`, ['--translate'], (value) => handle(value), { - supportsNegative: true, - supportsFractions: true, - }) - utilities.static(`translate-${axis}-full`, (candidate) => { - return handle(candidate.negative ? '-100%' : '100%') - }) + staticUtility(`-translate-${axis}-full`, [ + translateProperties, + [`--tw-translate-${axis}`, '-100%'], + ['translate', `var(--tw-translate-x) var(--tw-translate-y)`], + ]) + staticUtility(`translate-${axis}-full`, [ + translateProperties, + [`--tw-translate-${axis}`, '100%'], + ['translate', `var(--tw-translate-x) var(--tw-translate-y)`], + ]) + spacingUtility( + `translate-${axis}`, + ['--translate'], + (value) => [ + translateProperties(), + decl(`--tw-translate-${axis}`, value), + decl('translate', `var(--tw-translate-x) var(--tw-translate-y)`), + ], + { + supportsNegative: true, + supportsFractions: true, + }, + ) } spacingUtility( @@ -1138,13 +1132,16 @@ export function createUtilities(theme: Theme) { supportsNegative: true, }, ) - utilities.static(`translate-z-px`, (candidate) => { - return [ - translateProperties(), - decl(`--tw-translate-z`, candidate.negative ? '-1px' : '1px'), - decl('translate', 'var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z)'), - ] - }) + staticUtility(`-translate-z-px`, [ + translateProperties, + [`--tw-translate-z`, '-1px'], + ['translate', 'var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z)'], + ]) + staticUtility(`translate-z-px`, [ + translateProperties, + [`--tw-translate-z`, '1px'], + ['translate', 'var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z)'], + ]) staticUtility('translate-3d', [ translateProperties, @@ -1162,29 +1159,33 @@ export function createUtilities(theme: Theme) { * @css `scale` */ staticUtility('scale-none', [['scale', 'none']]) - utilities.functional('scale', (candidate) => { - if (!candidate.value || candidate.modifier) return + function handleScale({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + if (!candidate.value || candidate.modifier) return - let value - if (candidate.value.kind === 'arbitrary') { - value = candidate.value.value - return [decl('scale', value)] - } else { - value = theme.resolve(candidate.value.value, ['--scale']) - if (!value && isPositiveInteger(candidate.value.value)) { - value = `${candidate.value.value}%` + let value + if (candidate.value.kind === 'arbitrary') { + value = candidate.value.value + return [decl('scale', value)] + } else { + value = theme.resolve(candidate.value.value, ['--scale']) + if (!value && isPositiveInteger(candidate.value.value)) { + value = `${candidate.value.value}%` + } + if (!value) return } - if (!value) return + value = negative ? `calc(${value} * -1)` : value + return [ + scaleProperties(), + decl('--tw-scale-x', value), + decl('--tw-scale-y', value), + decl('--tw-scale-z', value), + decl('scale', `var(--tw-scale-x) var(--tw-scale-y)`), + ] } - value = withNegative(value, candidate) - return [ - scaleProperties(), - decl('--tw-scale-x', value), - decl('--tw-scale-y', value), - decl('--tw-scale-z', value), - decl('scale', `var(--tw-scale-x) var(--tw-scale-y)`), - ] - }) + } + utilities.functional('-scale', handleScale({ negative: true })) + utilities.functional('scale', handleScale({ negative: false })) suggest('scale', () => [ { @@ -1240,28 +1241,33 @@ export function createUtilities(theme: Theme) { * `rotate-[1_2_3_45deg]` => `rotate: 1 2 3 45deg` */ staticUtility('rotate-none', [['rotate', 'none']]) - utilities.functional('rotate', (candidate) => { - if (!candidate.value || candidate.modifier) return - let value - if (candidate.value.kind === 'arbitrary') { - value = candidate.value.value - let type = candidate.value.dataType ?? inferDataType(value, ['angle', 'vector']) - if (type === 'vector') { - return [decl('rotate', `${value} var(--tw-rotate)`)] - } else if (type !== 'angle') { - return [decl('rotate', value)] - } - } else { - value = theme.resolve(candidate.value.value, ['--rotate']) - if (!value && isPositiveInteger(candidate.value.value)) { - value = `${candidate.value.value}deg` + function handleRotate({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + if (!candidate.value || candidate.modifier) return + + let value + if (candidate.value.kind === 'arbitrary') { + value = candidate.value.value + let type = candidate.value.dataType ?? inferDataType(value, ['angle', 'vector']) + if (type === 'vector') { + return [decl('rotate', `${value} var(--tw-rotate)`)] + } else if (type !== 'angle') { + return [decl('rotate', value)] + } + } else { + value = theme.resolve(candidate.value.value, ['--rotate']) + if (!value && isPositiveInteger(candidate.value.value)) { + value = `${candidate.value.value}deg` + } + if (!value) return } - if (!value) return + return [decl('rotate', negative ? `calc(${value} * -1)` : value)] } - value = withNegative(value, candidate) - return [decl('rotate', value)] - }) + } + + utilities.functional('-rotate', handleRotate({ negative: true })) + utilities.functional('rotate', handleRotate({ negative: false })) suggest('rotate', () => [ { @@ -1396,7 +1402,7 @@ export function createUtilities(theme: Theme) { * @css `transform` */ utilities.functional('transform', (candidate) => { - if (candidate.negative || candidate.modifier) return + if (candidate.modifier) return let value: string | null = null if (!candidate.value) { @@ -2000,8 +2006,6 @@ export function createUtilities(theme: Theme) { function borderSideUtility(classRoot: string, desc: BorderDescription) { utilities.functional(classRoot, (candidate) => { - if (candidate.negative) return - if (!candidate.value) { if (candidate.modifier) return let value = theme.get(['--default-border-width']) ?? '1px' @@ -2271,72 +2275,82 @@ export function createUtilities(theme: Theme) { ]) } - utilities.functional('bg-linear', (candidate) => { - if (!candidate.value || candidate.modifier) return + function handleBgLinear({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + if (!candidate.value || candidate.modifier) return - let value = candidate.value.value + let value = candidate.value.value - if (candidate.value.kind === 'arbitrary') { - let type = candidate.value.dataType ?? inferDataType(value, ['angle']) + if (candidate.value.kind === 'arbitrary') { + let type = candidate.value.dataType ?? inferDataType(value, ['angle']) - switch (type) { - case 'angle': { - value = withNegative(value, candidate) + switch (type) { + case 'angle': { + value = negative ? `calc(${value} * -1)` : `${value}` - return [ - decl('--tw-gradient-position', `${value},`), - decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), - ] - } - default: { - if (candidate.negative) return + return [ + decl('--tw-gradient-position', `${value},`), + decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), + ] + } + default: { + if (negative) return - return [ - decl('--tw-gradient-position', `${value},`), - decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), - ] + return [ + decl('--tw-gradient-position', `${value},`), + decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`), + ] + } } - } - } else { - if (!isPositiveInteger(value)) return + } else { + if (!isPositiveInteger(value)) return - value = withNegative(`${value}deg`, candidate) + value = negative ? `calc(${value}deg * -1)` : `${value}deg` - return [ - decl('--tw-gradient-position', `${value} in oklch,`), - decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), - ] + return [ + decl('--tw-gradient-position', `${value} in oklch,`), + decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), + ] + } } - }) + } - utilities.functional('bg-conic', (candidate) => { - if (candidate.modifier) return + utilities.functional('-bg-linear', handleBgLinear({ negative: true })) + utilities.functional('bg-linear', handleBgLinear({ negative: false })) - if (!candidate.value) { - return [ - decl('--tw-gradient-position', `in oklch,`), - decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), - ] - } + function handleBgConic({ negative }: { negative: boolean }) { + return (candidate: Extract) => { + if (candidate.modifier) return - let value = candidate.value.value + if (!candidate.value) { + return [ + decl('--tw-gradient-position', `in oklch,`), + decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), + ] + } - if (candidate.value.kind === 'arbitrary') { - return [ - decl('--tw-gradient-position', `${value},`), - decl('background-image', `conic-gradient(var(--tw-gradient-stops,${value}))`), - ] - } else { - if (!isPositiveInteger(value)) return + let value = candidate.value.value - value = withNegative(`${value}deg`, candidate) + if (candidate.value.kind === 'arbitrary') { + return [ + decl('--tw-gradient-position', `${value},`), + decl('background-image', `conic-gradient(var(--tw-gradient-stops,${value}))`), + ] + } else { + if (!isPositiveInteger(value)) return - return [ - decl('--tw-gradient-position', `from ${value} in oklch,`), - decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), - ] + value = negative ? `calc(${value} * -1)` : `${value}deg` + + return [ + decl('--tw-gradient-position', `from ${value} in oklch,`), + decl('background-image', `conic-gradient(var(--tw-gradient-stops))`), + ] + } } - }) + } + + utilities.functional('-bg-conic', handleBgConic({ negative: true })) + utilities.functional('bg-conic', handleBgConic({ negative: false })) utilities.functional('bg-radial', (candidate) => { if (candidate.modifier) return @@ -2358,7 +2372,7 @@ export function createUtilities(theme: Theme) { }) utilities.functional('bg', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return // Arbitrary values if (candidate.value.kind === 'arbitrary') { @@ -2452,7 +2466,7 @@ export function createUtilities(theme: Theme) { function gradientStopUtility(classRoot: string, desc: GradientStopDescription) { utilities.functional(classRoot, (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return // Arbitrary values if (candidate.value.kind === 'arbitrary') { @@ -2601,7 +2615,7 @@ export function createUtilities(theme: Theme) { staticUtility('fill-none', [['fill', 'none']]) utilities.functional('fill', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return if (candidate.value.kind === 'arbitrary') { let value = asColor(candidate.value.value, candidate.modifier) @@ -2625,7 +2639,7 @@ export function createUtilities(theme: Theme) { staticUtility('stroke-none', [['stroke', 'none']]) utilities.functional('stroke', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return if (candidate.value.kind === 'arbitrary') { let value: string | null = candidate.value.value @@ -2738,7 +2752,7 @@ export function createUtilities(theme: Theme) { }) utilities.functional('font', (candidate) => { - if (candidate.negative || !candidate.value || candidate.modifier) return + if (!candidate.value || candidate.modifier) return if (candidate.value.kind === 'arbitrary') { let value = candidate.value.value @@ -2872,7 +2886,7 @@ export function createUtilities(theme: Theme) { staticUtility('decoration-from-font', [['text-decoration-thickness', 'from-font']]) utilities.functional('decoration', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return if (candidate.value.kind === 'arbitrary') { let value: string | null = candidate.value.value @@ -2988,7 +3002,7 @@ export function createUtilities(theme: Theme) { } utilities.functional('filter', (candidate) => { - if (candidate.negative || candidate.modifier) return + if (candidate.modifier) return if (candidate.value === null) { return [filterProperties(), decl('filter', cssFilterValue)] @@ -3005,7 +3019,7 @@ export function createUtilities(theme: Theme) { }) utilities.functional('backdrop-filter', (candidate) => { - if (candidate.negative || candidate.modifier) return + if (candidate.modifier) return if (candidate.value === null) { return [ @@ -3460,9 +3474,6 @@ export function createUtilities(theme: Theme) { staticUtility('duration-initial', [transitionDurationProperty, ['--tw-duration', 'initial']]) utilities.functional('duration', (candidate) => { - // This utility doesn't support negative values. - if (candidate.negative) return - // This utility doesn't support modifiers. if (candidate.modifier) return @@ -3739,8 +3750,6 @@ export function createUtilities(theme: Theme) { ]) utilities.functional('outline', (candidate) => { - if (candidate.negative) return - if (candidate.value === null) { if (candidate.modifier) return return [ @@ -3871,7 +3880,7 @@ export function createUtilities(theme: Theme) { ]) utilities.functional('text', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return if (candidate.value.kind === 'arbitrary') { let value: string | null = candidate.value.value @@ -4007,8 +4016,6 @@ export function createUtilities(theme: Theme) { staticUtility('shadow-initial', [boxShadowProperties, ['--tw-shadow-color', 'initial']]) utilities.functional('shadow', (candidate) => { - if (candidate.negative) return - if (!candidate.value) { let value = theme.get(['--shadow']) if (value === null) return @@ -4101,8 +4108,6 @@ export function createUtilities(theme: Theme) { ]) utilities.functional('inset-shadow', (candidate) => { - if (candidate.negative) return - if (!candidate.value) { let value = theme.get(['--inset-shadow']) if (value === null) return @@ -4197,8 +4202,6 @@ export function createUtilities(theme: Theme) { return `var(--tw-ring-inset,) 0 0 0 calc(${value} + var(--tw-ring-offset-width)) var(--tw-ring-color, ${defaultRingColor})` } utilities.functional('ring', (candidate) => { - if (candidate.negative) return - if (!candidate.value) { if (candidate.modifier) return let value = theme.get(['--default-ring-width']) ?? '1px' @@ -4273,8 +4276,6 @@ export function createUtilities(theme: Theme) { return `inset 0 0 0 ${value} var(--tw-inset-ring-color, currentColor)` } utilities.functional('inset-ring', (candidate) => { - if (candidate.negative) return - if (!candidate.value) { if (candidate.modifier) return return [ @@ -4347,7 +4348,7 @@ export function createUtilities(theme: Theme) { let ringOffsetShadowValue = 'var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)' utilities.functional('ring-offset', (candidate) => { - if (candidate.negative || !candidate.value) return + if (!candidate.value) return if (candidate.value.kind === 'arbitrary') { let value: string | null = candidate.value.value @@ -4411,8 +4412,6 @@ export function createUtilities(theme: Theme) { ]) utilities.functional('@container', (candidate) => { - if (candidate.negative) return - let value: string | null = null if (candidate.value === null) { value = 'inline-size'