971ff6b67e
We recently landed a fix for `Popover`s not closing correctly when using the `transition` prop (#3448). Once this fix was published, some users still ran into issues using Firefox on Windows (see: https://github.com/tailwindlabs/tailwindui-issues/issues/1625). One fun thing I discovered is that transitions somehow behave differently based on where they are triggered from (?). What I mean by this is that holding down the <kbd>space</kbd> key on the button does properly open/close the `Popover`. But if you rapidly click the button, the `Popover` will eventually get stuck. > Note: when testing this, I made sure that the handling of the `space` key (in a `keydown` handler) and the clicking of the mouse (handled in a `click` handler) called the exact same code. It still happened. The debugging continues… One thing I noticed is that when the `Popover` gets stuck, it meant that a transition didn't properly complete. The current implementation of the internal `useTransition(…)` hook has to wait for all the transitions to finish. This is done using a `waitForTransition(…)` helper. This helper sets up some event listeners (`transitionstart`, `transitionend`, …) and waits for them to fire. This seems to be unreliable on Firefox for some unknown reason. I knew the code for waiting for transitions wasn't ideal, so I wanted to see if using the native `node.getAnimations()` simplifies this and makes it work in general. Lo and behold, it did! 🎉 This now has multiple benefits: 1. It works as expected on Firefox 2. The code is much much simpler 3. Uses native features The `getAnimations(…)` function is supported in all modern browsers (since 2020). At the time it was too early to rely on it, but right now it should be safe to use. Fixes: https://github.com/tailwindlabs/tailwindui-issues/issues/1625
47 lines
1.5 KiB
JavaScript
47 lines
1.5 KiB
JavaScript
globalThis.IS_REACT_ACT_ENVIRONMENT = true
|
|
|
|
// These are not 1:1 perfect polyfills, but they implement the parts we need for
|
|
// testing. The implementation of the `getAnimations` uses the `setTimeout`
|
|
// approach we used in the past.
|
|
//
|
|
// This is only necessary because JSDOM does not implement `getAnimations` or
|
|
// `CSSTransition` yet. This is a temporary solution until JSDOM implements
|
|
// these features. Or, until we use proper browser tests using Puppeteer or
|
|
// Playwright.
|
|
{
|
|
if (typeof CSSTransition === 'undefined') {
|
|
globalThis.CSSTransition = class CSSTransition {
|
|
constructor(duration) {
|
|
this.duration = duration
|
|
}
|
|
|
|
finished = new Promise((resolve) => {
|
|
setTimeout(resolve, this.duration)
|
|
})
|
|
}
|
|
}
|
|
|
|
if (typeof Element.prototype.getAnimations !== 'function') {
|
|
Element.prototype.getAnimations = function () {
|
|
let { transitionDuration, transitionDelay } = getComputedStyle(this)
|
|
|
|
let [durationMs, delayMs] = [transitionDuration, transitionDelay].map((value) => {
|
|
let [resolvedValue = 0] = value
|
|
.split(',')
|
|
// Remove falsy we can't work with
|
|
.filter(Boolean)
|
|
// Values are returned as `0.3s` or `75ms`
|
|
.map((v) => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000))
|
|
.sort((a, z) => z - a)
|
|
|
|
return resolvedValue
|
|
})
|
|
|
|
let totalDuration = durationMs + delayMs
|
|
if (totalDuration === 0) return []
|
|
|
|
return [new CSSTransition(totalDuration)]
|
|
}
|
|
}
|
|
}
|