Skip to content

Commit f530642

Browse files
committed
fix: toast issue in firefox
1 parent 11d4959 commit f530642

File tree

4 files changed

+41
-5
lines changed

4 files changed

+41
-5
lines changed

.changeset/neat-pigs-dance.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
fix(toast): prevent toasts from collapsing when pointer is hovering
66

77
Fixed an issue where dismissing a toast by clicking the close button while hovering over the toast group would cause
8-
toasts to immediately collapse, even though the cursor was still within the group. This was caused by focus restoration
9-
triggering browser hover state recalculation.
8+
toasts to immediately collapse, even though the cursor was still within the group.
109

11-
The fix adds pointer tracking to only restore focus and collapse toasts when the pointer has actually left the toast
12-
group, maintaining the expected hover behavior.
10+
**Root causes:**
11+
12+
1. **All browsers:** Focus restoration to the trigger button causes the browser to recalculate hover state, removing the
13+
`:hover` pseudo-class momentarily
14+
2. **Some browsers (particularly Firefox):** DOM mutations (removing toasts) can trigger spurious `mouseleave`/
15+
`mouseenter` events even when the mouse hasn't moved, causing flickering when multiple toasts are dismissed rapidly
16+
17+
**Solution:**
18+
19+
- Add `isPointerWithin` tracking to only restore focus when pointer has actually left the toast group
20+
- Add `ignoringMouseEvents` flag that blocks all mouse events for 100ms after toast removal
21+
- This prevents spurious event cycles during DOM mutations from triggering unwanted expand/collapse actions
22+
23+
This maintains the expected hover behavior across all browsers while preserving accessibility for keyboard users.

packages/machines/toast/src/toast-group.connect.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,25 @@ export function groupConnect<T extends PropTypes, O = any>(
3939
"aria-live": "polite",
4040
role: "region",
4141
style: getGroupPlacementStyle(service, placement),
42+
onMouseEnter() {
43+
// Ignore mouse events briefly after toast removal to prevent spurious events during DOM mutations
44+
if (refs.get("ignoringMouseEvents")) {
45+
return
46+
}
47+
send({ type: "REGION.POINTER_ENTER", placement })
48+
},
4249
onMouseMove() {
50+
// Ignore mouse events briefly after toast removal to prevent spurious events during DOM mutations
51+
if (refs.get("ignoringMouseEvents")) {
52+
return
53+
}
4354
send({ type: "REGION.POINTER_ENTER", placement })
4455
},
4556
onMouseLeave() {
57+
// Ignore mouse events briefly after toast removal to prevent spurious events during DOM mutations
58+
if (refs.get("ignoringMouseEvents")) {
59+
return
60+
}
4661
send({ type: "REGION.POINTER_LEAVE", placement })
4762
},
4863
onFocus(event) {

packages/machines/toast/src/toast-group.machine.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const groupMachine = createMachine<ToastGroupSchema>({
2424
lastFocusedEl: null,
2525
isFocusWithin: false,
2626
isPointerWithin: false,
27+
ignoringMouseEvents: false,
2728
dismissableCleanup: undefined,
2829
}
2930
},
@@ -80,7 +81,7 @@ export const groupMachine = createMachine<ToastGroupSchema>({
8081
},
8182
],
8283
"TOAST.REMOVE": {
83-
actions: ["removeToast", "removeHeight"],
84+
actions: ["removeToast", "removeHeight", "ignoreMouseEventsTemporarily"],
8485
},
8586
"TOAST.PAUSE": {
8687
actions: ["pauseToasts"],
@@ -259,6 +260,14 @@ export const groupMachine = createMachine<ToastGroupSchema>({
259260
refs.set("lastFocusedEl", null)
260261
refs.set("isFocusWithin", false)
261262
},
263+
ignoreMouseEventsTemporarily({ refs }) {
264+
// Ignore mouse events briefly after toast removal to prevent spurious events
265+
// during DOM mutations (particularly in Firefox, but applied universally for consistency)
266+
refs.set("ignoringMouseEvents", true)
267+
setTimeout(() => {
268+
refs.set("ignoringMouseEvents", false)
269+
}, 100)
270+
},
262271
},
263272
},
264273
})

packages/machines/toast/src/toast.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export type ToastGroupSchema = {
247247
lastFocusedEl: HTMLElement | null
248248
isFocusWithin: boolean
249249
isPointerWithin: boolean
250+
ignoringMouseEvents: boolean
250251
}
251252
guard: string
252253
effect: string

0 commit comments

Comments
 (0)