-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix transition bug on Firefox, triggered by clicking the `PopoverButt…
…on` in rapid succession (#3452) 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: tailwindlabs/tailwindui-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: tailwindlabs/tailwindui-issues#1625
- Loading branch information
1 parent
75619ee
commit 971ff6b
Showing
4 changed files
with
74 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,46 @@ | ||
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)] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters