|
1 | 1 | import { useRef, useState, type MutableRefObject } from 'react'
|
2 | 2 | import { disposables } from '../utils/disposables'
|
3 |
| -import { once } from '../utils/once' |
4 | 3 | import { useDisposables } from './use-disposables'
|
5 | 4 | import { useFlags } from './use-flags'
|
6 | 5 | import { useIsoMorphicEffect } from './use-iso-morphic-effect'
|
@@ -211,84 +210,43 @@ function transition(
|
211 | 210 | // This means that no transition happens at all. To fix this, we delay the
|
212 | 211 | // actual transition by one frame.
|
213 | 212 | d.nextFrame(() => {
|
214 |
| - // Wait for the transition, once the transition is complete we can cleanup. |
215 |
| - // This is registered first to prevent race conditions, otherwise it could |
216 |
| - // happen that the transition is already done before we start waiting for |
217 |
| - // the actual event. |
218 |
| - d.add(waitForTransition(node, done)) |
219 |
| - |
220 | 213 | // Initiate the transition by applying the new classes.
|
221 | 214 | run()
|
| 215 | + |
| 216 | + // Wait for the transition, once the transition is complete we can cleanup. |
| 217 | + // We wait for a frame such that the browser has time to flush the changes |
| 218 | + // to the DOM. |
| 219 | + d.requestAnimationFrame(() => { |
| 220 | + d.add(waitForTransition(node, done)) |
| 221 | + }) |
222 | 222 | })
|
223 | 223 |
|
224 | 224 | return d.dispose
|
225 | 225 | }
|
226 | 226 |
|
227 |
| -function waitForTransition(node: HTMLElement, _done: () => void) { |
228 |
| - let done = once(_done) |
| 227 | +function waitForTransition(node: HTMLElement | null, done: () => void) { |
229 | 228 | let d = disposables()
|
230 |
| - |
231 | 229 | if (!node) return d.dispose
|
232 | 230 |
|
233 |
| - // Safari returns a comma separated list of values, so let's sort them and take the highest value. |
234 |
| - let { transitionDuration, transitionDelay } = getComputedStyle(node) |
235 |
| - |
236 |
| - let [durationMs, delayMs] = [transitionDuration, transitionDelay].map((value) => { |
237 |
| - let [resolvedValue = 0] = value |
238 |
| - .split(',') |
239 |
| - // Remove falsy we can't work with |
240 |
| - .filter(Boolean) |
241 |
| - // Values are returned as `0.3s` or `75ms` |
242 |
| - .map((v) => (v.includes('ms') ? parseFloat(v) : parseFloat(v) * 1000)) |
243 |
| - .sort((a, z) => z - a) |
244 |
| - |
245 |
| - return resolvedValue |
| 231 | + let cancelled = false |
| 232 | + d.add(() => { |
| 233 | + cancelled = true |
246 | 234 | })
|
247 | 235 |
|
248 |
| - let totalDuration = durationMs + delayMs |
249 |
| - |
250 |
| - if (totalDuration !== 0) { |
251 |
| - if (process.env.NODE_ENV === 'test') { |
252 |
| - let dispose = d.setTimeout(() => { |
253 |
| - done() |
254 |
| - dispose() |
255 |
| - }, totalDuration) |
256 |
| - } else { |
257 |
| - let disposeGroup = d.group((d) => { |
258 |
| - // Mark the transition as done when the timeout is reached. This is a fallback in case the |
259 |
| - // transitionrun event is not fired. |
260 |
| - let cancelTimeout = d.setTimeout(() => { |
261 |
| - done() |
262 |
| - d.dispose() |
263 |
| - }, totalDuration) |
264 |
| - |
265 |
| - // The moment the transitionrun event fires, we should cleanup the timeout fallback, because |
266 |
| - // then we know that we can use the native transition events because something is |
267 |
| - // transitioning. |
268 |
| - d.addEventListener(node, 'transitionrun', (event) => { |
269 |
| - if (event.target !== event.currentTarget) return |
270 |
| - cancelTimeout() |
271 |
| - |
272 |
| - d.addEventListener(node, 'transitioncancel', (event) => { |
273 |
| - if (event.target !== event.currentTarget) return |
274 |
| - done() |
275 |
| - disposeGroup() |
276 |
| - }) |
277 |
| - }) |
278 |
| - }) |
279 |
| - |
280 |
| - d.addEventListener(node, 'transitionend', (event) => { |
281 |
| - if (event.target !== event.currentTarget) return |
282 |
| - done() |
283 |
| - d.dispose() |
284 |
| - }) |
285 |
| - } |
286 |
| - } else { |
287 |
| - // No transition is happening, so we should cleanup already. Otherwise we have to wait until we |
288 |
| - // get disposed. |
| 236 | + let transitions = node.getAnimations().filter((animation) => animation instanceof CSSTransition) |
| 237 | + // If there are no transitions, we can stop early. |
| 238 | + if (transitions.length === 0) { |
289 | 239 | done()
|
| 240 | + return d.dispose |
290 | 241 | }
|
291 | 242 |
|
| 243 | + // Wait for all the transitions to complete. |
| 244 | + Promise.allSettled(transitions.map((transition) => transition.finished)).then(() => { |
| 245 | + if (!cancelled) { |
| 246 | + done() |
| 247 | + } |
| 248 | + }) |
| 249 | + |
292 | 250 | return d.dispose
|
293 | 251 | }
|
294 | 252 |
|
|
0 commit comments