From 745d8410719ff21c6c7839129af47af82e00131d Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:50:07 -0500 Subject: [PATCH 1/5] Support opacity values in increments of 0.25 by default --- .../src/__snapshots__/utilities.test.ts.snap | 108 +++++++++ packages/tailwindcss/src/utilities.test.ts | 208 +++++++++++++++++- packages/tailwindcss/src/utilities.ts | 11 +- .../tailwindcss/src/utils/infer-data-type.ts | 8 +- 4 files changed, 328 insertions(+), 7 deletions(-) diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap index f5ed7321b3e5..721a5d2976a4 100644 --- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap @@ -98,6 +98,18 @@ exports[`border-* 1`] = ` border-color: var(--color-red-500); } +.border-red-500\\/2\\.5 { + border-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-red-500\\/2\\.25 { + border-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-red-500\\/2\\.75 { + border-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-red-500\\/50 { border-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -219,6 +231,18 @@ exports[`border-b-* 1`] = ` border-bottom-color: var(--color-red-500); } +.border-b-red-500\\/2\\.5 { + border-bottom-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-b-red-500\\/2\\.25 { + border-bottom-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-b-red-500\\/2\\.75 { + border-bottom-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-b-red-500\\/50 { border-bottom-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -340,6 +364,18 @@ exports[`border-e-* 1`] = ` border-inline-end-color: var(--color-red-500); } +.border-e-red-500\\/2\\.5 { + border-inline-end-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-e-red-500\\/2\\.25 { + border-inline-end-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-e-red-500\\/2\\.75 { + border-inline-end-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-e-red-500\\/50 { border-inline-end-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -461,6 +497,18 @@ exports[`border-l-* 1`] = ` border-left-color: var(--color-red-500); } +.border-l-red-500\\/2\\.5 { + border-left-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-l-red-500\\/2\\.25 { + border-left-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-l-red-500\\/2\\.75 { + border-left-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-l-red-500\\/50 { border-left-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -582,6 +630,18 @@ exports[`border-r-* 1`] = ` border-right-color: var(--color-red-500); } +.border-r-red-500\\/2\\.5 { + border-right-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-r-red-500\\/2\\.25 { + border-right-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-r-red-500\\/2\\.75 { + border-right-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-r-red-500\\/50 { border-right-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -703,6 +763,18 @@ exports[`border-s-* 1`] = ` border-inline-start-color: var(--color-red-500); } +.border-s-red-500\\/2\\.5 { + border-inline-start-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-s-red-500\\/2\\.25 { + border-inline-start-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-s-red-500\\/2\\.75 { + border-inline-start-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-s-red-500\\/50 { border-inline-start-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -824,6 +896,18 @@ exports[`border-t-* 1`] = ` border-top-color: var(--color-red-500); } +.border-t-red-500\\/2\\.5 { + border-top-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-t-red-500\\/2\\.25 { + border-top-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-t-red-500\\/2\\.75 { + border-top-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-t-red-500\\/50 { border-top-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -945,6 +1029,18 @@ exports[`border-x-* 1`] = ` border-inline-color: var(--color-red-500); } +.border-x-red-500\\/2\\.5 { + border-inline-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-x-red-500\\/2\\.25 { + border-inline-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-x-red-500\\/2\\.75 { + border-inline-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-x-red-500\\/50 { border-inline-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -1066,6 +1162,18 @@ exports[`border-y-* 1`] = ` border-block-color: var(--color-red-500); } +.border-y-red-500\\/2\\.5 { + border-block-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); +} + +.border-y-red-500\\/2\\.25 { + border-block-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); +} + +.border-y-red-500\\/2\\.75 { + border-block-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); +} + .border-y-red-500\\/50 { border-block-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index b40f08782ee7..6415306ec00a 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -8022,6 +8022,9 @@ test('accent', async () => { [ 'accent-red-500', 'accent-red-500/50', + 'accent-red-500/2.25', + 'accent-red-500/2.5', + 'accent-red-500/2.75', 'accent-red-500/[0.5]', 'accent-red-500/[50%]', 'accent-current', @@ -8065,6 +8068,18 @@ test('accent', async () => { accent-color: var(--color-red-500); } + .accent-red-500\\/2\\.5 { + accent-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .accent-red-500\\/2\\.25 { + accent-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .accent-red-500\\/2\\.75 { + accent-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .accent-red-500\\/50, .accent-red-500\\/\\[0\\.5\\], .accent-red-500\\/\\[50\\%\\] { accent-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -8122,6 +8137,9 @@ test('caret', async () => { [ 'caret-red-500', 'caret-red-500/50', + 'caret-red-500/2.25', + 'caret-red-500/2.5', + 'caret-red-500/2.75', 'caret-red-500/[0.5]', 'caret-red-500/[50%]', 'caret-current', @@ -8165,6 +8183,18 @@ test('caret', async () => { caret-color: var(--color-red-500); } + .caret-red-500\\/2\\.5 { + caret-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .caret-red-500\\/2\\.25 { + caret-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .caret-red-500\\/2\\.75 { + caret-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .caret-red-500\\/50, .caret-red-500\\/\\[0\\.5\\], .caret-red-500\\/\\[50\\%\\] { caret-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -8220,6 +8250,9 @@ test('divide-color', async () => { [ 'divide-red-500', 'divide-red-500/50', + 'divide-red-500/2.25', + 'divide-red-500/2.5', + 'divide-red-500/2.75', 'divide-red-500/[0.5]', 'divide-red-500/[50%]', 'divide-current', @@ -8263,6 +8296,18 @@ test('divide-color', async () => { border-color: var(--color-red-500); } + :where(.divide-red-500\\/2\\.5 > :not(:last-child)) { + border-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + :where(.divide-red-500\\/2\\.25 > :not(:last-child)) { + border-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + :where(.divide-red-500\\/2\\.75 > :not(:last-child)) { + border-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + :where(.divide-red-500\\/50 > :not(:last-child)), :where(.divide-red-500\\/\\[0\\.5\\] > :not(:last-child)), :where(.divide-red-500\\/\\[50\\%\\] > :not(:last-child)) { border-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -9889,6 +9934,9 @@ for (let prefix of prefixes) { // Color classes.push(`${prefix}-red-500`) classes.push(`${prefix}-red-500/50`) + classes.push(`${prefix}-red-500/2.25`) + classes.push(`${prefix}-red-500/2.5`) + classes.push(`${prefix}-red-500/2.75`) classes.push(`${prefix}-[#0088cc]`) classes.push(`${prefix}-[#0088cc]/50`) classes.push(`${prefix}-current`) @@ -9988,6 +10036,9 @@ test('bg', async () => { // background-color 'bg-red-500', 'bg-red-500/50', + 'bg-red-500/2.25', + 'bg-red-500/2.5', + 'bg-red-500/2.75', 'bg-red-500/[0.5]', 'bg-red-500/[50%]', 'bg-current', @@ -10132,6 +10183,18 @@ test('bg', async () => { background-color: var(--color-red-500); } + .bg-red-500\\/2\\.5 { + background-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .bg-red-500\\/2\\.25 { + background-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .bg-red-500\\/2\\.75 { + background-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .bg-red-500\\/50, .bg-red-500\\/\\[0\\.5\\], .bg-red-500\\/\\[50\\%\\] { background-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -11578,6 +11641,9 @@ test('fill', async () => { [ 'fill-red-500', 'fill-red-500/50', + 'fill-red-500/2.25', + 'fill-red-500/2.5', + 'fill-red-500/2.75', 'fill-red-500/[0.5]', 'fill-red-500/[50%]', 'fill-current', @@ -11621,6 +11687,18 @@ test('fill', async () => { fill: var(--color-red-500); } + .fill-red-500\\/2\\.5 { + fill: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .fill-red-500\\/2\\.25 { + fill: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .fill-red-500\\/2\\.75 { + fill: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .fill-red-500\\/50, .fill-red-500\\/\\[0\\.5\\], .fill-red-500\\/\\[50\\%\\] { fill: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -11664,6 +11742,9 @@ test('stroke', async () => { // Color 'stroke-red-500', 'stroke-red-500/50', + 'stroke-red-500/2.25', + 'stroke-red-500/2.5', + 'stroke-red-500/2.75', 'stroke-red-500/[0.5]', 'stroke-red-500/[50%]', 'stroke-current', @@ -11747,6 +11828,18 @@ test('stroke', async () => { stroke: var(--color-red-500); } + .stroke-red-500\\/2\\.5 { + stroke: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .stroke-red-500\\/2\\.25 { + stroke: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .stroke-red-500\\/2\\.75 { + stroke: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .stroke-red-500\\/50, .stroke-red-500\\/\\[0\\.5\\], .stroke-red-500\\/\\[50\\%\\] { stroke: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -12667,6 +12760,9 @@ test('placeholder', async () => { [ 'placeholder-red-500', 'placeholder-red-500/50', + 'placeholder-red-500/2.25', + 'placeholder-red-500/2.5', + 'placeholder-red-500/2.75', 'placeholder-red-500/[0.5]', 'placeholder-red-500/[50%]', 'placeholder-current', @@ -12710,6 +12806,18 @@ test('placeholder', async () => { color: var(--color-red-500); } + .placeholder-red-500\\/2\\.5::placeholder { + color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .placeholder-red-500\\/2\\.25::placeholder { + color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .placeholder-red-500\\/2\\.75::placeholder { + color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .placeholder-red-500\\/50::placeholder, .placeholder-red-500\\/\\[0\\.5\\]::placeholder, .placeholder-red-500\\/\\[50\\%\\]::placeholder { color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -14720,8 +14828,28 @@ test('outline-offset', async () => { }) test('opacity', async () => { - expect(await run(['opacity-15', 'opacity-[var(--value)]'])).toMatchInlineSnapshot(` - ".opacity-15 { + expect( + await run([ + 'opacity-15', + 'opacity-2.5', + 'opacity-3.25', + 'opacity-4.75', + 'opacity-[var(--value)]', + ]), + ).toMatchInlineSnapshot(` + ".opacity-2\\.5 { + opacity: .025; + } + + .opacity-3\\.25 { + opacity: .0325; + } + + .opacity-4\\.75 { + opacity: .0475; + } + + .opacity-15 { opacity: .15; } @@ -14733,6 +14861,7 @@ test('opacity', async () => { await run([ 'opacity', 'opacity--15', + 'opacity-1.125', '-opacity-15', '-opacity-[var(--value)]', 'opacity-unknown', @@ -14822,6 +14951,9 @@ test('text', async () => { // color 'text-red-500', 'text-red-500/50', + 'text-red-500/2.25', + 'text-red-500/2.5', + 'text-red-500/2.75', 'text-red-500/[0.5]', 'text-red-500/[50%]', 'text-current', @@ -14985,6 +15117,18 @@ test('text', async () => { color: var(--color-red-500); } + .text-red-500\\/2\\.5 { + color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .text-red-500\\/2\\.25 { + color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .text-red-500\\/2\\.75 { + color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .text-red-500\\/50, .text-red-500\\/\\[0\\.5\\], .text-red-500\\/\\[50\\%\\] { color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -15044,6 +15188,9 @@ test('shadow', async () => { // Colors 'shadow-red-500', 'shadow-red-500/50', + 'shadow-red-500/2.25', + 'shadow-red-500/2.5', + 'shadow-red-500/2.75', 'shadow-red-500/[0.5]', 'shadow-red-500/[50%]', 'shadow-current', @@ -15131,6 +15278,18 @@ test('shadow', async () => { --tw-shadow-color: var(--color-red-500); } + .shadow-red-500\\/2\\.5 { + --tw-shadow-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .shadow-red-500\\/2\\.25 { + --tw-shadow-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .shadow-red-500\\/2\\.75 { + --tw-shadow-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .shadow-red-500\\/50, .shadow-red-500\\/\\[0\\.5\\], .shadow-red-500\\/\\[50\\%\\] { --tw-shadow-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -15272,6 +15431,9 @@ test('inset-shadow', async () => { // Colors 'inset-shadow-red-500', 'inset-shadow-red-500/50', + 'inset-shadow-red-500/2.25', + 'inset-shadow-red-500/2.5', + 'inset-shadow-red-500/2.75', 'inset-shadow-red-500/[0.5]', 'inset-shadow-red-500/[50%]', 'inset-shadow-current', @@ -15359,6 +15521,18 @@ test('inset-shadow', async () => { --tw-inset-shadow-color: var(--color-red-500); } + .inset-shadow-red-500\\/2\\.5 { + --tw-inset-shadow-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .inset-shadow-red-500\\/2\\.25 { + --tw-inset-shadow-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .inset-shadow-red-500\\/2\\.75 { + --tw-inset-shadow-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .inset-shadow-red-500\\/50, .inset-shadow-red-500\\/\\[0\\.5\\], .inset-shadow-red-500\\/\\[50\\%\\] { --tw-inset-shadow-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -15490,6 +15664,9 @@ test('ring', async () => { 'ring-inset', 'ring-red-500', 'ring-red-500/50', + 'ring-red-500/2.25', + 'ring-red-500/2.5', + 'ring-red-500/2.75', 'ring-red-500/[0.5]', 'ring-red-500/[50%]', 'ring-current', @@ -15601,6 +15778,18 @@ test('ring', async () => { --tw-ring-color: var(--color-red-500); } + .ring-red-500\\/2\\.5 { + --tw-ring-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .ring-red-500\\/2\\.25 { + --tw-ring-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .ring-red-500\\/2\\.75 { + --tw-ring-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .ring-red-500\\/50, .ring-red-500\\/\\[0\\.5\\], .ring-red-500\\/\\[50\\%\\] { --tw-ring-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } @@ -15750,6 +15939,9 @@ test('inset-ring', async () => { // ring color 'inset-ring-red-500', 'inset-ring-red-500/50', + 'inset-ring-red-500/2.25', + 'inset-ring-red-500/2.5', + 'inset-ring-red-500/2.75', 'inset-ring-red-500/[0.5]', 'inset-ring-red-500/[50%]', 'inset-ring-current', @@ -15861,6 +16053,18 @@ test('inset-ring', async () => { --tw-inset-ring-color: var(--color-red-500); } + .inset-ring-red-500\\/2\\.5 { + --tw-inset-ring-color: color-mix(in oklch, var(--color-red-500) 2.5%, transparent); + } + + .inset-ring-red-500\\/2\\.25 { + --tw-inset-ring-color: color-mix(in oklch, var(--color-red-500) 2.25%, transparent); + } + + .inset-ring-red-500\\/2\\.75 { + --tw-inset-ring-color: color-mix(in oklch, var(--color-red-500) 2.75%, transparent); + } + .inset-ring-red-500\\/50, .inset-ring-red-500\\/\\[0\\.5\\], .inset-ring-red-500\\/\\[50\\%\\] { --tw-inset-ring-color: color-mix(in oklch, var(--color-red-500) 50%, transparent); } diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index c554a9cc5a41..d2ba54ae5d06 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -2,7 +2,12 @@ import { atRoot, atRule, decl, styleRule, type AstNode } from './ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate' import type { Theme, ThemeKey } from './theme' import { DefaultMap } from './utils/default-map' -import { inferDataType, isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type' +import { + inferDataType, + isMultipleOf, + isPositiveInteger, + isValidSpacingMultiplier, +} from './utils/infer-data-type' import { replaceShadowColors } from './utils/replace-shadow-colors' import { segment } from './utils/segment' @@ -125,7 +130,7 @@ export function asColor(value: string, modifier: CandidateModifier | null): stri return withAlpha(value, modifier.value) } - if (!isPositiveInteger(modifier.value)) { + if (!isMultipleOf(modifier.value, 0.25)) { return null } @@ -3849,7 +3854,7 @@ export function createUtilities(theme: Theme) { functionalUtility('opacity', { themeKeys: ['--opacity'], handleBareValue: ({ value }) => { - if (!isPositiveInteger(value)) return null + if (!isMultipleOf(value, 0.25)) return null return `${value}%` }, handle: (value) => [decl('opacity', value)], diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts index 6aa34fbe1d7e..8a2ea92493b1 100644 --- a/packages/tailwindcss/src/utils/infer-data-type.ts +++ b/packages/tailwindcss/src/utils/infer-data-type.ts @@ -322,7 +322,7 @@ function isVector(value: string) { } /** - * Returns true of the value can be parsed as a positive whole number. + * Returns true if the value can be parsed as a positive whole number. */ export function isPositiveInteger(value: any) { let num = Number(value) @@ -333,6 +333,10 @@ export function isPositiveInteger(value: any) { * Returns true if the value is either a positive whole number or a multiple of 0.25. */ export function isValidSpacingMultiplier(value: any) { + return isMultipleOf(value, 0.25) +} + +export function isMultipleOf(value: string | number, divisor: number) { let num = Number(value) - return num >= 0 && num % 0.25 === 0 && String(num) === String(value) + return num >= 0 && num % divisor === 0 && String(num) === String(value) } From c19f7795e29e48c09d8ce2b307a7459a48519dec Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:51:14 -0500 Subject: [PATCH 2/5] Handle backdrop-opacity --- packages/tailwindcss/src/utilities.test.ts | 21 +++++++++++++++++++++ packages/tailwindcss/src/utilities.ts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 6415306ec00a..88f57077b490 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -13485,6 +13485,9 @@ test('backdrop-filter', async () => { 'backdrop-invert-[var(--value)]', 'backdrop-opacity-50', 'backdrop-opacity-71', + 'backdrop-opacity-1.25', + 'backdrop-opacity-2.5', + 'backdrop-opacity-3.75', 'backdrop-opacity-[0.5]', 'backdrop-saturate-0', 'backdrop-saturate-[1.75]', @@ -13602,6 +13605,24 @@ test('backdrop-filter', async () => { backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); } + .backdrop-opacity-1\\.25 { + --tw-backdrop-opacity: opacity(1.25%); + -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + } + + .backdrop-opacity-2\\.5 { + --tw-backdrop-opacity: opacity(2.5%); + -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + } + + .backdrop-opacity-3\\.75 { + --tw-backdrop-opacity: opacity(3.75%); + -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); + } + .backdrop-opacity-50 { --tw-backdrop-opacity: opacity(50%); -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, ); diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index d2ba54ae5d06..17ace2c86d45 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -3400,7 +3400,7 @@ export function createUtilities(theme: Theme) { functionalUtility('backdrop-opacity', { themeKeys: ['--backdrop-opacity', '--opacity'], handleBareValue: ({ value }) => { - if (!isPositiveInteger(value)) return null + if (!isMultipleOf(value, 0.25)) return null return `${value}%` }, handle: (value) => [ From f240e18f3e58e53f54b49469b9d7cc4345739555 Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:53:58 -0500 Subject: [PATCH 3/5] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad5fafc3eda..5881b3558475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Added + +- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980)) ## [4.0.0-alpha.33] - 2024-11-11 From dbc9d236ff6b8a211659890c5c191b653860bfc8 Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:57:29 -0500 Subject: [PATCH 4/5] Add isValidOpacityValue function --- packages/tailwindcss/src/utilities.ts | 8 ++++---- packages/tailwindcss/src/utils/infer-data-type.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 17ace2c86d45..968f629e37ce 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -4,8 +4,8 @@ import type { Theme, ThemeKey } from './theme' import { DefaultMap } from './utils/default-map' import { inferDataType, - isMultipleOf, isPositiveInteger, + isValidOpacityValue, isValidSpacingMultiplier, } from './utils/infer-data-type' import { replaceShadowColors } from './utils/replace-shadow-colors' @@ -130,7 +130,7 @@ export function asColor(value: string, modifier: CandidateModifier | null): stri return withAlpha(value, modifier.value) } - if (!isMultipleOf(modifier.value, 0.25)) { + if (!isValidOpacityValue(modifier.value)) { return null } @@ -3400,7 +3400,7 @@ export function createUtilities(theme: Theme) { functionalUtility('backdrop-opacity', { themeKeys: ['--backdrop-opacity', '--opacity'], handleBareValue: ({ value }) => { - if (!isMultipleOf(value, 0.25)) return null + if (!isValidOpacityValue(value)) return null return `${value}%` }, handle: (value) => [ @@ -3854,7 +3854,7 @@ export function createUtilities(theme: Theme) { functionalUtility('opacity', { themeKeys: ['--opacity'], handleBareValue: ({ value }) => { - if (!isMultipleOf(value, 0.25)) return null + if (!isValidOpacityValue(value)) return null return `${value}%` }, handle: (value) => [decl('opacity', value)], diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts index 8a2ea92493b1..fc29ec8b6837 100644 --- a/packages/tailwindcss/src/utils/infer-data-type.ts +++ b/packages/tailwindcss/src/utils/infer-data-type.ts @@ -336,7 +336,14 @@ export function isValidSpacingMultiplier(value: any) { return isMultipleOf(value, 0.25) } -export function isMultipleOf(value: string | number, divisor: number) { +/** + * Returns true if the value is either a positive whole number or a multiple of 0.25. + */ +export function isValidOpacityValue(value: any) { + return isMultipleOf(value, 0.25) +} + +function isMultipleOf(value: string | number, divisor: number) { let num = Number(value) return num >= 0 && num % divisor === 0 && String(num) === String(value) } From e30a775883e3e78434466a676ba61ceb703a50c1 Mon Sep 17 00:00:00 2001 From: Adam Wathan <4323180+adamwathan@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:14:18 -0500 Subject: [PATCH 5/5] Update comment --- packages/tailwindcss/src/utils/infer-data-type.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts index fc29ec8b6837..c6a0881a8228 100644 --- a/packages/tailwindcss/src/utils/infer-data-type.ts +++ b/packages/tailwindcss/src/utils/infer-data-type.ts @@ -329,20 +329,18 @@ export function isPositiveInteger(value: any) { return Number.isInteger(num) && num >= 0 && String(num) === String(value) } -/** - * Returns true if the value is either a positive whole number or a multiple of 0.25. - */ export function isValidSpacingMultiplier(value: any) { return isMultipleOf(value, 0.25) } -/** - * Returns true if the value is either a positive whole number or a multiple of 0.25. - */ export function isValidOpacityValue(value: any) { return isMultipleOf(value, 0.25) } +/** + * Ensures a number (or numeric string) is a multiple of another number, and + * that it has no unnecessary leading or trailing zeros. + */ function isMultipleOf(value: string | number, divisor: number) { let num = Number(value) return num >= 0 && num % divisor === 0 && String(num) === String(value)