Skip to content

Commit 6cd19f0

Browse files
authored
fix(toast): resume timers if pointer left region due to region shrinking (#7771)
1 parent 90bc6c0 commit 6cd19f0

File tree

3 files changed

+38
-5
lines changed

3 files changed

+38
-5
lines changed

packages/@react-aria/toast/src/useToastRegion.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,32 @@ export function useToastRegion<T>(props: AriaToastRegionProps, state: ToastState
4949
onHoverEnd: state.resumeAll
5050
});
5151

52+
let prevToastCount = useRef(state.visibleToasts.length);
53+
useEffect(() => {
54+
// Resume timers if the user's pointer left the region due to a toast being removed and the region shrinking.
55+
// Waits until the next pointermove after a toast is removed.
56+
let onPointerMove = (e: PointerEvent) => {
57+
if (!ref.current) {
58+
document.removeEventListener('pointermove', onPointerMove);
59+
return;
60+
}
61+
let regionRect = ref.current.getBoundingClientRect();
62+
const isPointerOverRegion = e.clientX >= regionRect.left && e.clientX <= regionRect.right && e.clientY >= regionRect.top && e.clientY <= regionRect.bottom;
63+
if (!isPointerOverRegion) {
64+
state.resumeAll();
65+
}
66+
document.removeEventListener('pointermove', onPointerMove);
67+
};
68+
69+
if (state.visibleToasts.length < prevToastCount.current && state.visibleToasts.length > 0) {
70+
document.addEventListener('pointermove', onPointerMove);
71+
}
72+
prevToastCount.current = state.visibleToasts.length;
73+
return () => {
74+
document.removeEventListener('pointermove', onPointerMove);
75+
};
76+
}, [state.visibleToasts, ref, state]);
77+
5278
// Manage focus within the toast region.
5379
// If a focused containing toast is removed, move focus to the next toast, or the previous toast if there is no next toast.
5480
// We might be making an assumption with how this works if someone implements the priority queue differently, or

packages/@react-aria/toast/stories/Example.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function ToastRegion() {
3232
let ref = useRef(null);
3333
let {regionProps} = useToastRegion({}, state, ref);
3434
return (
35-
<div {...regionProps} ref={ref} style={{position: 'fixed', bottom: 0, right: 0}}>
35+
<div {...regionProps} ref={ref} style={{position: 'fixed', bottom: 0, right: 0, display: 'flex', flexDirection: 'column', gap: 10}}>
3636
{state.visibleToasts.map(toast => (
3737
<Toast key={toast.key} toast={toast} />
3838
))}

packages/@react-aria/toast/stories/useToast.stories.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ import {ToastContainer} from './Example';
1616
export default {
1717
title: 'useToast',
1818
args: {
19-
maxVisibleToasts: 1
19+
maxVisibleToasts: 1,
20+
timeout: null
21+
},
22+
argTypes: {
23+
timeout: {
24+
control: 'radio',
25+
options: [null, 5000]
26+
}
2027
}
2128
};
2229

@@ -25,9 +32,9 @@ let count = 0;
2532
export const Default = args => (
2633
<ToastContainer {...args}>
2734
{state => (<>
28-
<button onClick={() => state.add('High ' + ++count, {priority: 10})}>Add high priority toast</button>
29-
<button onClick={() => state.add('Medium ' + ++count, {priority: 5})}>Add medium priority toast</button>
30-
<button onClick={() => state.add('Low ' + ++count, {priority: 1})}>Add low priority toast</button>
35+
<button onClick={() => state.add('High ' + ++count, {priority: 10, timeout: args.timeout})}>Add high priority toast</button>
36+
<button onClick={() => state.add('Medium ' + ++count, {priority: 5, timeout: args.timeout})}>Add medium priority toast</button>
37+
<button onClick={() => state.add('Low ' + ++count, {priority: 1, timeout: args.timeout})}>Add low priority toast</button>
3138
</>)}
3239
</ToastContainer>
3340
);

0 commit comments

Comments
 (0)