-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
101 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
'use client'; | ||
import * as React from 'react'; | ||
import { useEventCallback } from './useEventCallback'; | ||
import { ownerWindow } from './owner'; | ||
|
||
/** | ||
* Executes a function only if the given element has no CSS animations or transitions. | ||
* @ignore - internal hook. | ||
*/ | ||
export function useExecuteIfNotAnimated(getElement: () => Element | null | undefined) { | ||
const frame1Ref = React.useRef(-1); | ||
const frame2Ref = React.useRef(-1); | ||
|
||
const cancelFrames = useEventCallback(() => { | ||
cancelAnimationFrame(frame1Ref.current); | ||
cancelAnimationFrame(frame2Ref.current); | ||
}); | ||
|
||
React.useEffect(() => cancelFrames, [cancelFrames]); | ||
|
||
return useEventCallback((fnToExecute: () => void) => { | ||
cancelFrames(); | ||
|
||
const element = getElement(); | ||
|
||
if (!element) { | ||
return; | ||
} | ||
|
||
// Wait for the CSS styles to be applied to determine if the animation has been removed in the | ||
// [data-instant] state. This allows the close animation to play if the `delay` instantType is | ||
// applying to the same element. | ||
// Notes: | ||
// - A single `requestAnimationFrame` is sometimes unreliable. | ||
// - `queueMicrotask` does not work. | ||
frame1Ref.current = requestAnimationFrame(() => { | ||
frame2Ref.current = requestAnimationFrame(() => { | ||
const computedStyles = ownerWindow(element).getComputedStyle(element); | ||
const hasNoAnimation = | ||
['', 'none'].includes(computedStyles.animationName) || | ||
['', '0s'].includes(computedStyles.animationDuration); | ||
const hasNoTransition = ['', '0s'].includes(computedStyles.transitionDuration); | ||
if (hasNoAnimation && hasNoTransition) { | ||
fnToExecute(); | ||
} | ||
}); | ||
}); | ||
}); | ||
} |
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 |
---|---|---|
@@ -0,0 +1,52 @@ | ||
'use client'; | ||
import * as React from 'react'; | ||
import { useEnhancedEffect } from './useEnhancedEffect'; | ||
|
||
export type TransitionStatus = 'entering' | 'exiting' | undefined; | ||
|
||
/** | ||
* Provides a status string for CSS animations. | ||
* @param open - a boolean that determines if the element is open. | ||
* @ignore - internal hook. | ||
*/ | ||
export function useTransitionStatus(open: boolean) { | ||
const [transitionStatus, setTransitionStatus] = React.useState<TransitionStatus>(); | ||
const [mounted, setMounted] = React.useState(open); | ||
|
||
if (open && !mounted) { | ||
setMounted(true); | ||
} | ||
|
||
if (!open && mounted && transitionStatus !== 'exiting') { | ||
setTransitionStatus('exiting'); | ||
} | ||
|
||
if (!open && !mounted && transitionStatus === 'exiting') { | ||
setTransitionStatus(undefined); | ||
} | ||
|
||
useEnhancedEffect(() => { | ||
if (!open) { | ||
return undefined; | ||
} | ||
|
||
setTransitionStatus('entering'); | ||
|
||
const frame = requestAnimationFrame(() => { | ||
setTransitionStatus(undefined); | ||
}); | ||
|
||
return () => { | ||
cancelAnimationFrame(frame); | ||
}; | ||
}, [open]); | ||
|
||
return React.useMemo( | ||
() => ({ | ||
mounted, | ||
setMounted, | ||
transitionStatus, | ||
}), | ||
[mounted, transitionStatus], | ||
); | ||
} |