diff --git a/CHANGELOG.md b/CHANGELOG.md index 51425dadadab..1bcda15312b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Preserve explicit `leading-*`, `tracking-*`, and `font-{weight}` value when overriding font-size ([#14403](https://github.com/tailwindlabs/tailwindcss/pull/14403)) - Disallow negative bare values in core utilities and variants ([#14453](https://github.com/tailwindlabs/tailwindcss/pull/14453)) - Preserve explicit shadow color when overriding shadow size ([#14458](https://github.com/tailwindlabs/tailwindcss/pull/14458)) +- Preserve explicit transition duration and timing function when overriding transition property ([#14490](https://github.com/tailwindlabs/tailwindcss/pull/14490)) ## [4.0.0-alpha.24] - 2024-09-11 diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 8ae6e5dcb607..28b443d2491a 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -1908,6 +1908,8 @@ exports[`getClassList 1`] = ` "duration-500", "duration-700", "duration-75", + "duration-initial", + "ease-initial", "end-0.5", "end-1", "end-3", diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 1a518fc3aa8c..20dbe123d22b 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -12898,47 +12898,47 @@ test('transition', async () => { .transition { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, -webkit-backdrop-filter, backdrop-filter; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-\\[--value\\] { transition-property: var(--value); - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-all { transition-property: all; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-opacity { transition-property: opacity; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); transition-property: var(--transition-property-opacity, opacity); - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-shadow { transition-property: box-shadow; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-transform { transition-property: transform, translate, scale, rotate; - transition-timing-function: var(--default-transition-timing-function, ease); - transition-duration: var(--default-transition-duration, .1s); + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function, ease)); + transition-duration: var(--tw-duration, var(--default-transition-duration, .1s)); } .transition-none { @@ -12965,20 +12965,20 @@ test('transition', async () => { .transition { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, -webkit-backdrop-filter, backdrop-filter; - transition-duration: .1s; - transition-timing-function: ease; + transition-timing-function: var(--tw-ease, ease); + transition-duration: var(--tw-duration, .1s); } .transition-all { transition-property: all; - transition-duration: .1s; - transition-timing-function: ease; + transition-timing-function: var(--tw-ease, ease); + transition-duration: var(--tw-duration, .1s); } .transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; - transition-duration: .1s; - transition-timing-function: ease; + transition-timing-function: var(--tw-ease, ease); + transition-duration: var(--tw-duration, .1s); }" `) @@ -12992,8 +12992,8 @@ test('transition', async () => { ).toMatchInlineSnapshot(` ".transition-all { transition-property: all; - transition-duration: 0s; - transition-timing-function: ease; + transition-timing-function: var(--tw-ease, ease); + transition-duration: var(--tw-duration, 0s); }" `) @@ -13047,15 +13047,31 @@ test('delay', async () => { test('duration', async () => { expect(await run(['duration-123', 'duration-200', 'duration-[300ms]'])).toMatchInlineSnapshot(` ".duration-123 { + --tw-duration: .123s; transition-duration: .123s; } .duration-200 { + --tw-duration: .2s; transition-duration: .2s; } .duration-\\[300ms\\] { + --tw-duration: .3s; transition-duration: .3s; + } + + @supports (-moz-orient: inline) { + @layer base { + *, :before, :after, ::backdrop { + --tw-duration: initial; + } + } + } + + @property --tw-duration { + syntax: "*"; + inherits: false }" `) expect( @@ -13090,15 +13106,31 @@ test('ease', async () => { } .ease-\\[--value\\] { + --tw-ease: var(--value); transition-timing-function: var(--value); } .ease-in { + --tw-ease: var(--transition-timing-function-in, cubic-bezier(.4, 0, 1, 1)); transition-timing-function: var(--transition-timing-function-in, cubic-bezier(.4, 0, 1, 1)); } .ease-out { + --tw-ease: var(--transition-timing-function-out, cubic-bezier(0, 0, .2, 1)); transition-timing-function: var(--transition-timing-function-out, cubic-bezier(0, 0, .2, 1)); + } + + @supports (-moz-orient: inline) { + @layer base { + *, :before, :after, ::backdrop { + --tw-ease: initial; + } + } + } + + @property --tw-ease { + syntax: "*"; + inherits: false }" `) expect( diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 6f0feb35e514..6fec8020f8b1 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -3701,9 +3701,8 @@ export function createUtilities(theme: Theme) { } { - let defaultTimingFunction = - theme.resolve(null, ['--default-transition-timing-function']) ?? 'ease' - let defaultDuration = theme.resolve(null, ['--default-transition-duration']) ?? '0s' + let defaultTimingFunction = `var(--tw-ease, ${theme.resolve(null, ['--default-transition-timing-function']) ?? 'ease'})` + let defaultDuration = `var(--tw-duration, ${theme.resolve(null, ['--default-transition-duration']) ?? '0s'})` staticUtility('transition-none', [['transition-property', 'none']]) staticUtility('transition-all', [ @@ -3755,37 +3754,49 @@ export function createUtilities(theme: Theme) { handle: (value) => [decl('transition-delay', value)], }) - utilities.functional('duration', (candidate) => { - // This utility doesn't support negative values. - if (candidate.negative) return + { + let transitionDurationProperty = () => { + return atRoot([property('--tw-duration')]) + } - // This utility doesn't support modifiers. - if (candidate.modifier) return + staticUtility('duration-initial', [transitionDurationProperty, ['--tw-duration', 'initial']]) - // This utility doesn't support `DEFAULT` values. - if (!candidate.value) return + utilities.functional('duration', (candidate) => { + // This utility doesn't support negative values. + if (candidate.negative) return - // Find the actual CSS value that the candidate value maps to. - let value: string | null = null + // This utility doesn't support modifiers. + if (candidate.modifier) return - if (candidate.value.kind === 'arbitrary') { - value = candidate.value.value - } else { - value = theme.resolve(candidate.value.fraction ?? candidate.value.value, [ - '--transition-duration', - ]) + // This utility doesn't support `DEFAULT` values. + if (!candidate.value) return - if (value === null && isPositiveInteger(candidate.value.value)) { - value = `${candidate.value.value}ms` + // Find the actual CSS value that the candidate value maps to. + let value: string | null = null + + if (candidate.value.kind === 'arbitrary') { + value = candidate.value.value + } else { + value = theme.resolve(candidate.value.fraction ?? candidate.value.value, [ + '--transition-duration', + ]) + + if (value === null && isPositiveInteger(candidate.value.value)) { + value = `${candidate.value.value}ms` + } } - } - // If the candidate value (like the `sm` in `max-w-sm`) doesn't resolve to - // an actual value, don't generate any rules. - if (value === null) return + // If the candidate value (like the `sm` in `max-w-sm`) doesn't resolve to + // an actual value, don't generate any rules. + if (value === null) return - return [decl('transition-duration', value)] - }) + return [ + transitionDurationProperty(), + decl('--tw-duration', value), + decl('transition-duration', value), + ] + }) + } suggest('delay', () => [ { @@ -3802,10 +3813,22 @@ export function createUtilities(theme: Theme) { ]) } - functionalUtility('ease', { - themeKeys: ['--transition-timing-function'], - handle: (value) => [decl('transition-timing-function', value)], - }) + { + let transitionTimingFunctionProperty = () => { + return atRoot([property('--tw-ease')]) + } + + staticUtility('ease-initial', [transitionTimingFunctionProperty, ['--tw-ease', 'initial']]) + + functionalUtility('ease', { + themeKeys: ['--transition-timing-function'], + handle: (value) => [ + transitionTimingFunctionProperty(), + decl('--tw-ease', value), + decl('transition-timing-function', value), + ], + }) + } staticUtility('will-change-auto', [['will-change', 'auto']]) staticUtility('will-change-scroll', [['will-change', 'scroll-position']]) diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index 92cbfa3b513f..b7580d2f310a 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -604,6 +604,28 @@ test('explicit font-weight utilities are respected when overriding font-size', a expect(await getPropertyValue('#z', 'font-weight')).toEqual('900') }) +test('explicit duration and ease utilities are respected when overriding transition-property', async ({ + page, +}) => { + let { getPropertyValue } = await render( + page, + html` +