Preserve explicit transition duration and timing function when overriding transition property (#14490)
This PR changes the behavior of the `transition-{property}` utilities to
respect any explicit timing function or duration set by the user using
the `ease-*` and `duration-*` utilities.
Say you have this HTML:
```html
<div class="transition-colors duration-500 ease-out lg:transition-all">
```
Currently, the `transition-duration` and `transition-timing-functions`
will be reset to their default values at the `lg` breakpoint even though
you've provided explicit values for them.
After this PR is merged, those values will be preserved at the `lg`
breakpoint.
This PR also adds `duration-initial` and `ease-initial` utilities to
"unset" explicit duration/timing-function values so that the defaults
from classes like `transition-all` will kick in, without having to
specify their explicit values.
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
222a93c0a3
commit
6d43a8be99
5 changed files with 139 additions and 59 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1908,6 +1908,8 @@ exports[`getClassList 1`] = `
|
|||
"duration-500",
|
||||
"duration-700",
|
||||
"duration-75",
|
||||
"duration-initial",
|
||||
"ease-initial",
|
||||
"end-0.5",
|
||||
"end-1",
|
||||
"end-3",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
// This utility doesn't support modifiers.
|
||||
if (candidate.modifier) return
|
||||
|
||||
// This utility doesn't support `DEFAULT` values.
|
||||
if (!candidate.value) return
|
||||
|
||||
// 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`
|
||||
}
|
||||
{
|
||||
let transitionDurationProperty = () => {
|
||||
return atRoot([property('--tw-duration')])
|
||||
}
|
||||
|
||||
// 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
|
||||
staticUtility('duration-initial', [transitionDurationProperty, ['--tw-duration', 'initial']])
|
||||
|
||||
return [decl('transition-duration', value)]
|
||||
})
|
||||
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
|
||||
|
||||
// This utility doesn't support `DEFAULT` values.
|
||||
if (!candidate.value) return
|
||||
|
||||
// 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
|
||||
|
||||
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']])
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
<div
|
||||
id="x"
|
||||
class="ease-[linear] duration-500 transition-[opacity] hover:transition-[background-color]"
|
||||
>
|
||||
Hello world
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
|
||||
expect(await getPropertyValue('#x', 'transition-timing-function')).toEqual('linear')
|
||||
expect(await getPropertyValue('#x', 'transition-duration')).toEqual('0.5s')
|
||||
await page.locator('#x').hover()
|
||||
expect(await getPropertyValue('#x', 'transition-timing-function')).toEqual('linear')
|
||||
expect(await getPropertyValue('#x', 'transition-duration')).toEqual('0.5s')
|
||||
})
|
||||
|
||||
// ---
|
||||
|
||||
const preflight = fs.readFileSync(path.resolve(__dirname, '..', 'preflight.css'), 'utf-8')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue