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>
This commit is contained in:
Robin Malfait 2024-11-11 16:46:53 +01:00 • committed by GitHub
parent d325e70496
commit 292efa5daa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 369 additions and 382 deletions

View file

@ -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

View file

@ -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",

View file

@ -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": [

View file

@ -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,
}

View file

@ -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<Candidate, { kind: 'functional' }>) {
// 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<Candidate, { kind: 'functional' }>) => {
// 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
}
let isColor = types.includes('color')
// 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,
)
// 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
}
}
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
let isColor = types.includes('color')
// 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,
)
}
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
{
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
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 ?? {}

View file

@ -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

View file

@ -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<Candidate, { kind: 'static' | 'functional' }>,
) {
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<Candidate, { kind: 'functional' }>) => {
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 ?? [],
)
// 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%)`
}
// 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 (value === null && desc.handleBareValue) {
value = desc.handleBareValue(candidate.value)
if (!value?.includes('/') && candidate.modifier) return
}
}
// 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 there is no value, don't generate any rules.
if (value === null) return
if (value === null && desc.handleBareValue) {
value = desc.handleBareValue(candidate.value)
if (!value?.includes('/') && candidate.modifier) return
}
// Negate the value if the candidate has a negative prefix.
return desc.handle(negative ? `calc(${value} * -1)` : value)
}
}
// 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(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<Candidate, { kind: 'functional' }>) => {
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)]
function handleRotate({ negative }: { negative: boolean }) {
return (candidate: Extract<Candidate, { kind: 'functional' }>) => {
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
}
} else {
value = theme.resolve(candidate.value.value, ['--rotate'])
if (!value && isPositiveInteger(candidate.value.value)) {
value = `${candidate.value.value}deg`
}
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<Candidate, { kind: 'functional' }>) => {
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}))`),
]
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}))`),
]
}
}
default: {
if (candidate.negative) return
} else {
if (!isPositiveInteger(value)) return
return [
decl('--tw-gradient-position', `${value},`),
decl('background-image', `linear-gradient(var(--tw-gradient-stops,${value}))`),
]
}
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))`),
]
}
} else {
if (!isPositiveInteger(value)) return
value = withNegative(`${value}deg`, candidate)
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<Candidate, { kind: 'functional' }>) => {
if (candidate.modifier) return
if (!candidate.value) {
return [
decl('--tw-gradient-position', `in oklch,`),
decl('background-image', `conic-gradient(var(--tw-gradient-stops))`),
]
}
let value = candidate.value.value
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
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))`),
]
}
}
}
let value = candidate.value.value
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
value = withNegative(`${value}deg`, candidate)
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'