diff --git a/packages/@react-aria/toast/src/useToastRegion.ts b/packages/@react-aria/toast/src/useToastRegion.ts index fc09c822317..28138eff761 100644 --- a/packages/@react-aria/toast/src/useToastRegion.ts +++ b/packages/@react-aria/toast/src/useToastRegion.ts @@ -49,6 +49,32 @@ export function useToastRegion(props: AriaToastRegionProps, state: ToastState onHoverEnd: state.resumeAll }); + let prevToastCount = useRef(state.visibleToasts.length); + useEffect(() => { + // Resume timers if the user's pointer left the region due to a toast being removed and the region shrinking. + // Waits until the next pointermove after a toast is removed. + let onPointerMove = (e: PointerEvent) => { + if (!ref.current) { + document.removeEventListener('pointermove', onPointerMove); + return; + } + let regionRect = ref.current.getBoundingClientRect(); + const isPointerOverRegion = e.clientX >= regionRect.left && e.clientX <= regionRect.right && e.clientY >= regionRect.top && e.clientY <= regionRect.bottom; + if (!isPointerOverRegion) { + state.resumeAll(); + } + document.removeEventListener('pointermove', onPointerMove); + }; + + if (state.visibleToasts.length < prevToastCount.current && state.visibleToasts.length > 0) { + document.addEventListener('pointermove', onPointerMove); + } + prevToastCount.current = state.visibleToasts.length; + return () => { + document.removeEventListener('pointermove', onPointerMove); + }; + }, [state.visibleToasts, ref, state]); + // Manage focus within the toast region. // If a focused containing toast is removed, move focus to the next toast, or the previous toast if there is no next toast. // We might be making an assumption with how this works if someone implements the priority queue differently, or diff --git a/packages/@react-aria/toast/stories/Example.tsx b/packages/@react-aria/toast/stories/Example.tsx index bf337271c83..1d79d62cad4 100644 --- a/packages/@react-aria/toast/stories/Example.tsx +++ b/packages/@react-aria/toast/stories/Example.tsx @@ -32,7 +32,7 @@ function ToastRegion() { let ref = useRef(null); let {regionProps} = useToastRegion({}, state, ref); return ( -
+
{state.visibleToasts.map(toast => ( ))} diff --git a/packages/@react-aria/toast/stories/useToast.stories.tsx b/packages/@react-aria/toast/stories/useToast.stories.tsx index 985a8b305b8..6e71d8423e2 100644 --- a/packages/@react-aria/toast/stories/useToast.stories.tsx +++ b/packages/@react-aria/toast/stories/useToast.stories.tsx @@ -16,7 +16,14 @@ import {ToastContainer} from './Example'; export default { title: 'useToast', args: { - maxVisibleToasts: 1 + maxVisibleToasts: 1, + timeout: null + }, + argTypes: { + timeout: { + control: 'radio', + options: [null, 5000] + } } }; @@ -25,9 +32,9 @@ let count = 0; export const Default = args => ( {state => (<> - - - + + + )} );