Skip to content

fix: Continue timing when mouse moves away#383

Open
luozz1994 wants to merge 2 commits intoreact-component:masterfrom
luozz1994:fix/continue-timing-mouseout
Open

fix: Continue timing when mouse moves away#383
luozz1994 wants to merge 2 commits intoreact-component:masterfrom
luozz1994:fix/continue-timing-mouseout

Conversation

@luozz1994
Copy link

@luozz1994 luozz1994 commented Feb 28, 2026

问题描述

notification 关闭首个通知后,下方通知上移,鼠标移开时倒计时不自动继续

复现步骤

  1. 点击 “Show custom progress color” 按钮,生成多个堆叠的通知(例如 4 个)。
  2. 将鼠标悬浮在最上方的第一个通知上,触发其悬浮倒计时(进度条开始填充)。
  3. 在不移动鼠标的情况下,点击该通知的关闭(X)按钮,将其移除。此时下方的通知会上移,原第二个通知成为新的第一个通知。将鼠标光标从通知区域移开。
  4. 观察新的最上方通知的进度条:它会停留在关闭首个通知时的进度,不会自动继续倒计时直至关闭。
  5. 将鼠标再次移到该通知上,然后移开,倒计时会恢复正常,通知会按预期关闭。

关联Antd-Design Issue

ant-design/ant-design#57096

修复方案

当通知列表发生变化时,检查鼠标是否仍在通知区域内

Summary by CodeRabbit

发布说明

  • 新功能

    • 在通知堆叠模式中加入全局鼠标位置跟踪,提升对悬停目标的识别精度
    • 优化悬停交互逻辑,仅在实际变化时更新悬停状态,减少不必要的重渲染
  • 测试

    • 扩充堆叠通知的悬停行为与自动关闭定时器相关测试,覆盖快速创建、悬停与关闭场景

@vercel
Copy link

vercel bot commented Feb 28, 2026

Someone is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

Walkthrough

添加全局鼠标位置追踪并在堆栈模式下基于元素边界计算鼠标下的通知,从而更新悬停状态;同时为堆栈通知添加两个测试用例,验证悬停与计时器交互行为。

Changes

Cohort / File(s) Summary
鼠标悬停检测
src/NoticeList.tsx
新增 mousePositionRefmousemove 监听器,在堆栈模式激活时按下一帧计算鼠标坐标并比较每个通知元素的 getBoundingClientRect() 来确定并更新 hoverKeys;将 latestNotice 类型改为 `HTMLDivElement
堆栈模式测试
tests/stack.test.tsx
新增两个测试用例(使用 vi.useFakeTimersact)以覆盖快速创建通知、悬停暂停计时器、关闭通知与悬停状态持久化等场景;包含对 getBoundingClientRect 的模拟。

Sequence Diagram(s)

(无须生成序列图)

Estimated code review effort

🎯 3 (中等复杂度) | ⏱️ ~20 分钟

Poem

🐰✨
鼠标轻点我记牢,
堆栈通知细如毛,
悬停暂停时光缓,
测试守护不忘跑,
兔子跳跃代码好!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地反映了主要变更:修复在鼠标移离通知区域后计时器继续工作的问题。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在解决一个用户体验问题:在堆叠通知模式下,当用户关闭一个鼠标悬停的通知时,即使鼠标已移开新出现的顶部通知区域,该通知的自动关闭倒计时仍会意外暂停。通过在通知列表结构发生变化时,利用全局鼠标位置信息重新计算并更新通知的悬停状态,确保了倒计时逻辑能够正确地恢复,从而提升了通知组件的响应性和用户交互的流畅性。

Highlights

  • 修复通知倒计时问题: 修复了堆叠通知中,当鼠标悬停的通知被关闭后,下方通知上移但倒计时未自动继续的问题。
  • 引入全局鼠标位置跟踪: 引入了全局鼠标位置跟踪机制,以便在通知列表更新时能准确重新评估鼠标悬停状态。
  • 新增测试用例: 新增了全面的测试用例,以验证通知关闭后悬停状态的正确处理和计时器的预期恢复。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/NoticeList.tsx
    • 新增了 mousePositionRef 来存储全局鼠标坐标。
    • 添加了一个 useEffect 钩子,用于在 stack 模式下监听全局 mousemove 事件,实时更新鼠标位置。
    • 优化了清理悬停键的 useEffect 逻辑,以避免在无实际变化时触发不必要的 setHoverKeys 调用。
    • 新增了一个 useEffect 钩子,当通知列表 (keys) 变化时,它会利用 requestAnimationFrame 机制,根据当前鼠标位置和通知元素的边界矩形,重新计算并更新 hoverKeys 状态。
  • tests/stack.test.tsx
    • 引入了 act 辅助函数,以确保 React 状态更新在测试环境中得到正确处理。
    • 新增了两个测试用例,分别验证了在关闭悬停通知后,计时器能够正确恢复自动关闭,以及当鼠标在通知区域内时,悬停状态能够保持,从而暂停计时器。
Activity
  • 目前没有检测到此拉取请求的任何活动(例如评论、审查或进度更新)。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 Pull Request 旨在修复一个在堆叠通知中关闭一个通知后,其余通知的计时器没有正确恢复的问题。修复方案通过跟踪全局鼠标位置,并在通知列表变化时重新计算鼠标悬停的通知,这是合理的。实现上使用了 useEffectrequestAnimationFrame,这是处理 DOM 更新后逻辑的良好实践。同时,PR 也添加了相应的单元测试来覆盖修复的场景,这非常好。

我发现了一个小问题,在 src/NoticeList.tsx 中,useEffect hook 内部使用了可能过期的状态值,这可能会导致潜在的 bug。我已经在代码中提出了具体的修改建议。除此之外,整体修改看起来很不错。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NoticeList.tsx (1)

87-119: 建议使用函数式更新模式避免潜在的闭包陈旧问题。

第 113 行的条件判断使用了闭包中的 hoverKeys,但依赖数组 [keys, stack] 不包含 hoverKeys。这可能导致 hoverKeys 值陈旧,在某些边界情况下跳过必要的状态更新。

建议使用函数式更新,同时进行数组比较以避免不必要的重渲染:

♻️ 建议的优化
     // Only update if there's a change to avoid unnecessary re-renders
-    if (newHoverKeys.length > 0 || hoverKeys.length > 0) {
-      setHoverKeys(newHoverKeys);
-    }
+    setHoverKeys((prev) => {
+      // Skip update if both are empty
+      if (newHoverKeys.length === 0 && prev.length === 0) {
+        return prev;
+      }
+      // Skip update if arrays are identical
+      if (
+        newHoverKeys.length === prev.length &&
+        newHoverKeys.every((key, i) => key === prev[i])
+      ) {
+        return prev;
+      }
+      return newHoverKeys;
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NoticeList.tsx` around lines 87 - 119, The effect that computes hover
keys uses the closed-over hoverKeys variable but does not list it in the
dependency array (useEffect(...) with dependencies [keys, stack]), which can
cause stale reads and skipped updates; change the setHoverKeys call inside the
requestAnimationFrame callback to use the functional updater form
(setHoverKeys(prev => { compare prev to newHoverKeys with an array equality
check and return prev if identical, otherwise return newHoverKeys })) and ensure
you reference mousePositionRef, dictRef and keys as you already do so the state
update is correct and avoids unnecessary re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NoticeList.tsx`:
- Around line 87-119: The effect that computes hover keys uses the closed-over
hoverKeys variable but does not list it in the dependency array (useEffect(...)
with dependencies [keys, stack]), which can cause stale reads and skipped
updates; change the setHoverKeys call inside the requestAnimationFrame callback
to use the functional updater form (setHoverKeys(prev => { compare prev to
newHoverKeys with an array equality check and return prev if identical,
otherwise return newHoverKeys })) and ensure you reference mousePositionRef,
dictRef and keys as you already do so the state update is correct and avoids
unnecessary re-renders.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 477e95f and ca3d678.

📒 Files selected for processing (2)
  • src/NoticeList.tsx
  • tests/stack.test.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NoticeList.tsx`:
- Around line 96-109: The hit-testing currently collects all elements whose
bounding rect contains the mouse, which causes multiple overlapping notices to
be marked hovered and leaves stale keys; change the logic to only keep the
topmost hit: use document.elementFromPoint(mousePos.x, mousePos.y) (or otherwise
pick the last/highest z-order element) and map that single element back to your
dictRef entries (keys in dictRef.current) to push only that key into
newHoverKeys so only the topmost notice becomes hovered (adjust any
mapping/attribute lookup as needed and remove the multi-push behavior in the
block using getBoundingClientRect).
- Around line 78-83: The cleanup currently only runs when stack &&
hoverKeys.length > 1, which misses the case where a single hover key becomes
invalid; update the logic in the same block that calls setHoverKeys so it always
filters invalid keys (remove the hoverKeys.length > 1 guard or change it to run
unconditionally when stack is truthy), using the existing setHoverKeys((prev) =>
{ const filtered = prev.filter((key) => keys.some(({ key: dataKey }) => key ===
dataKey)); return filtered.length === prev.length ? prev : filtered; }) pattern
to ensure a removed single key clears and stops hovering/timers; keep the same
filtered comparison to avoid unnecessary re-renders.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca3d678 and a21610f.

📒 Files selected for processing (1)
  • src/NoticeList.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant