Skip to content

feat: add safe hover polygon handling#625

Open
li-jia-nan wants to merge 3 commits into
masterfrom
feat-safe-hover-polygon
Open

feat: add safe hover polygon handling#625
li-jia-nan wants to merge 3 commits into
masterfrom
feat-safe-hover-polygon

Conversation

@li-jia-nan
Copy link
Copy Markdown
Member

@li-jia-nan li-jia-nan commented Jun 7, 2026

Kapture.2026-06-08.at.01.55.05.mp4

Summary by CodeRabbit

  • 新功能

    • 添加“安全悬停”行为:在目标与弹层间的安全区域内移动时保持弹层打开,离开安全区域后按延迟关闭。
  • 文档

    • 新增安全悬停演示页面与示例,展示行为与可视化区域。
  • 测试

    • 扩展单元/集成测试,覆盖多种安全悬停与延迟关闭场景。
  • 其他

    • 增加对样式导入的类型声明以改善构建体验。

Copilot AI review requested due to automatic review settings June 7, 2026 14:04
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 7, 2026

Codecov Report

❌ Patch coverage is 99.31507% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.54%. Comparing base (97703ff) to head (ba5359b).

Files with missing lines Patch % Lines
src/util/safeHover.ts 98.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #625      +/-   ##
==========================================
+ Coverage   97.28%   97.54%   +0.26%     
==========================================
  Files          17       18       +1     
  Lines         956     1100     +144     
  Branches      268      313      +45     
==========================================
+ Hits          930     1073     +143     
- Misses         26       27       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edeaddf9-4ea6-420b-a460-fadb69a3f8d8

📥 Commits

Reviewing files that changed from the base of the PR and between 67829e5 and ba5359b.

📒 Files selected for processing (3)
  • docs/examples/safe-hover.tsx
  • src/util/safeHover.ts
  • typings.d.ts
✅ Files skipped from review due to trivial changes (1)
  • typings.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/util/safeHover.ts
  • docs/examples/safe-hover.tsx

Walkthrough

新增“安全悬停区域”功能:通过几何多边形判定鼠标在目标与弹层之间的安全走廊,Trigger 在离开时可注册文档级监听以延迟或阻止弹层关闭,并配套单元/集成测试与示例文档。

Changes

Safe Hover Area Detection Feature

Layer / File(s) Summary
Safe Hover Geometric Calculation
src/util/safeHover.ts
类型与基础几何判定、getSafeHoverSide、gap/intent 多边形生成,以及 isPointInSafeHoverArea 的综合判定。
Trigger Component Integration
src/index.tsx
导入安全悬停工具,新增 safeHoverRef、start/clear 逻辑与文档级监听;在 target leave 时基于安全区决定延迟或关闭;在 popup enter/卸载时清理监听。
Geometry Unit Tests
tests/safeHover.test.ts
rect 辅助、getSafeHoverSide 与 isPointInSafeHoverArea 在多种间隙与意图场景下的断言测试。
Integration Tests
tests/basic.test.jsx
mockRect 辅助与多条用例,验证鼠标在安全区移动保持弹层打开、离开安全区后延迟关闭、安全区消失触发关闭,以及 zero 延迟立即关闭。
Documentation and Example
docs/examples/safe-hover.tsx, docs/demos/safe-hover.md
SafeHoverDemo 示例渲染安全多边形并展示 Trigger 配置,文档页面嵌入示例并配置导航。
Type Declarations
typings.d.ts
新增 declare module '*.less'; 声明以支持 Less 导入。

Sequence Diagram

sequenceDiagram
  participant Trigger as Trigger Component
  participant Document as Document
  participant SafeHover as isPointInSafeHoverArea
  Trigger->>Trigger: onMouseLeave (leavePoint)
  Trigger->>SafeHover: isPointInSafeHoverArea(leavePoint, targetRect, popupRect)
  alt 在安全区域
    Trigger->>Document: register mousemove/pointermove monitor
    Document->>SafeHover: check pointer vs polygons
    alt 仍在安全区域
      Document->>Trigger: defer close until mouseLeaveDelay
    else 离开安全区域
      Document->>Trigger: clearSafeHover() then close popup
    end
  else 不在安全区域
    Trigger->>Trigger: 直接关闭 popup
  end
  Trigger->>Trigger: onPopupMouseEnter -> clearSafeHover()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • zombieJ

Poem

我是小兔守悬停,
画出多边护空庭,
光标过道不惊慌,
延迟判定保弹层,
几何走廊笑眯眯 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地总结了拉取请求的主要变更:添加安全悬停区域多边形处理功能,与 src/index.tsx、src/util/safeHover.ts 等文件的核心改动高度相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-safe-hover-polygon

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

docs/examples/safe-hover.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/util/safeHover.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

typings.d.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


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.

Copy link
Copy Markdown
Contributor

@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

This pull request introduces a safe hover area feature to prevent popups from closing prematurely when the cursor moves between the target element and the popup. It adds a new utility file src/safeHover.ts to calculate the safe polygon area, integrates event listeners in src/index.tsx to monitor mouse movements, and includes corresponding unit tests. Feedback on the changes highlights a critical bug where the popup closes prematurely upon re-entering the target or popup because the mousemove listener is not stopped, and suggests simplifying nested ternaries in the safe hover calculations to improve readability.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/index.tsx Outdated
Comment thread src/safeHover.ts Outdated
Copy link
Copy Markdown

@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 (4)
src/index.tsx (2)

527-533: 💤 Low value

两个分支都调用 triggerOpen(false, mouseLeaveDelay) 的设计值得关注。

当前逻辑:

  • 在安全区域内:持续重置关闭计时器,保持弹层显示
  • 离开安全区域:停止监控并重新触发延迟关闭

这个模式可以正常工作,但离开安全区域时重置计时器意味着用户需要额外等待 mouseLeaveDelay 才能关闭弹层。如果期望离开安全区域后立即(或更快)关闭,可考虑使用更短的延迟或直接触发关闭。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.tsx` around lines 527 - 533, The code calls triggerOpen(false,
mouseLeaveDelay) in both branches, causing an unnecessary full mouseLeaveDelay
when leaving the safe area; update the else branch that uses
stopSafeHoverMonitor() to call triggerOpen with a shorter delay or immediate
close (e.g., triggerOpen(false, 0) or a new constant like immediateCloseDelay)
instead of mouseLeaveDelay so the popup closes faster when nextInSafeArea is
false; locate the logic around nextInSafeArea, stopSafeHoverMonitor,
triggerOpen, and mouseLeaveDelay in src/index.tsx and change only the else
branch call to use the faster/zero delay.

494-501: 💤 Low value

mouseLeaveDelay 为 0 时,安全悬停监控将被静默禁用。

由于条件 !mouseLeaveDelay 包含 mouseLeaveDelay === 0 的情况,用户设置 mouseLeaveDelay={0} 时 safe hover 功能不会生效。如果这是预期行为,建议添加注释说明;否则应改为显式的 undefined 检查。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.tsx` around lines 494 - 501, The guard incorrectly treats
mouseLeaveDelay === 0 as falsy and disables safe hover; in the conditional
inside the function that checks currentTargetEle, currentPopupEle,
openRef.current, and mouseLeaveDelay, replace the falsy check "!mouseLeaveDelay"
with an explicit undefined check (e.g. "mouseLeaveDelay === undefined") so
mouseLeaveDelay=0 remains valid (or if 0 should disable behavior, add a
clarifying comment next to the condition); update the condition that references
currentTargetEle/currentPopupEle/openRef.current accordingly.
src/safeHover.ts (2)

145-165: ⚡ Quick win

缺少对 targetpopup 元素的空值校验。

getRect 内部调用 element.getBoundingClientRect(),若 targetpopupnull/undefined 会抛出运行时异常。虽然调用方应保证元素存在,但在此处添加防御性检查可提高鲁棒性。

💡 建议的修复
 export function isInSafeHoverArea(options: SafeHoverAreaOptions) {
   const {
     clientX,
     clientY,
     originX,
     originY,
     target,
     popup,
     align,
     buffer = 0.5,
   } = options;

+  if (!target || !popup) {
+    return false;
+  }
+
   const clientPoint: Point = [clientX, clientY];
   const refRect = getRect(target);
   const popupRect = getRect(popup);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/safeHover.ts` around lines 145 - 165, In isInSafeHoverArea, add defensive
null/undefined checks for the DOM elements before calling getRect: verify that
options.target and options.popup are non-null (or throw a clear error) and if
either is missing return false early to avoid getRect calling
element.getBoundingClientRect() on null; update the start of isInSafeHoverArea
(before calling getRect on target/popup) to perform these checks and document
the behavior so callers know missing elements result in a safe "not inside"
result.

231-377: ⚖️ Poor tradeoff

getPolygon 函数嵌套三元运算符过深,可读性和可维护性较差。

当前每个 case 分支内使用多层嵌套三元表达式计算多边形顶点,建议拆分为独立的辅助函数或使用 if-else 语句提升可读性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/safeHover.ts` around lines 231 - 377, The getPolygon function uses deeply
nested ternaries per side ('top','bottom','left','right'), hurting readability;
refactor by extracting a helper per side (e.g., computeTopPolygon,
computeBottomPolygon, computeLeftPolygon, computeRightPolygon) that accepts the
same inputs (Point, buffer, popupRect and flags like
isFloatingWider/isFloatingTaller/cursorLeaveFromRight/cursorLeaveFromBottom) and
computes named intermediate values via if/else (e.g., anchorX, anchorY,
edgeY/edgeX) instead of nested ?:, then have getPolygon simply switch on side
and return the corresponding helper result—this keeps behavior identical but
replaces nested ternaries with clear, testable logic located around those helper
function names.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/index.tsx`:
- Around line 527-533: The code calls triggerOpen(false, mouseLeaveDelay) in
both branches, causing an unnecessary full mouseLeaveDelay when leaving the safe
area; update the else branch that uses stopSafeHoverMonitor() to call
triggerOpen with a shorter delay or immediate close (e.g., triggerOpen(false, 0)
or a new constant like immediateCloseDelay) instead of mouseLeaveDelay so the
popup closes faster when nextInSafeArea is false; locate the logic around
nextInSafeArea, stopSafeHoverMonitor, triggerOpen, and mouseLeaveDelay in
src/index.tsx and change only the else branch call to use the faster/zero delay.
- Around line 494-501: The guard incorrectly treats mouseLeaveDelay === 0 as
falsy and disables safe hover; in the conditional inside the function that
checks currentTargetEle, currentPopupEle, openRef.current, and mouseLeaveDelay,
replace the falsy check "!mouseLeaveDelay" with an explicit undefined check
(e.g. "mouseLeaveDelay === undefined") so mouseLeaveDelay=0 remains valid (or if
0 should disable behavior, add a clarifying comment next to the condition);
update the condition that references
currentTargetEle/currentPopupEle/openRef.current accordingly.

In `@src/safeHover.ts`:
- Around line 145-165: In isInSafeHoverArea, add defensive null/undefined checks
for the DOM elements before calling getRect: verify that options.target and
options.popup are non-null (or throw a clear error) and if either is missing
return false early to avoid getRect calling element.getBoundingClientRect() on
null; update the start of isInSafeHoverArea (before calling getRect on
target/popup) to perform these checks and document the behavior so callers know
missing elements result in a safe "not inside" result.
- Around line 231-377: The getPolygon function uses deeply nested ternaries per
side ('top','bottom','left','right'), hurting readability; refactor by
extracting a helper per side (e.g., computeTopPolygon, computeBottomPolygon,
computeLeftPolygon, computeRightPolygon) that accepts the same inputs (Point,
buffer, popupRect and flags like
isFloatingWider/isFloatingTaller/cursorLeaveFromRight/cursorLeaveFromBottom) and
computes named intermediate values via if/else (e.g., anchorX, anchorY,
edgeY/edgeX) instead of nested ?:, then have getPolygon simply switch on side
and return the corresponding helper result—this keeps behavior identical but
replaces nested ternaries with clear, testable logic located around those helper
function names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 334bae19-24f8-49d4-8b58-a1207526ec5e

📥 Commits

Reviewing files that changed from the base of the PR and between 220358d and a7847b7.

📒 Files selected for processing (4)
  • package.json
  • src/index.tsx
  • src/safeHover.ts
  • tests/basic.test.jsx

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds “safe hover polygon” logic to keep hover-triggered popups open while the cursor moves from the trigger toward the popup, and includes a regression test for the new hover-close behavior.

Changes:

  • Add a new isInSafeHoverArea utility to compute whether the cursor remains within a safe hover region between trigger and popup.
  • Start/stop a document-level pointer tracking monitor on hover leave to refresh mouseLeaveDelay while the cursor remains in the safe region.
  • Add a test validating that mouseLeaveDelay is refreshed while moving through the safe hover area, and bump @rc-component/util minimum version.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/basic.test.jsx Adds a test to verify safe-hover keeps popup open while moving through the safe area.
src/safeHover.ts Introduces safe hover polygon/rectangle computations (isInSafeHoverArea).
src/index.tsx Hooks safe-hover monitoring into hover leave/enter lifecycle to refresh close delay.
package.json Raises the minimum required version of @rc-component/util.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/index.tsx Outdated
Comment thread src/index.tsx Outdated
Comment thread src/safeHover.ts Outdated
Comment thread src/index.tsx Fixed
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.tsx (1)

505-522: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

修复 safe-hover 原点坐标传参错误(originY 被误传为 originX

这里把 originY 传成了 originX,会让 isInSafeHoverArea 的几何判断丢失垂直方向信息;同时这也是当前 CI 报错的直接原因(originY 未使用)。

🔧 建议修复
         const originX = event.clientX;
         const originY = event.clientY;

         const onMouseMove = (mouseEvent: MouseEvent) => {
@@
           const inSafeHoverArea = isInSafeHoverArea({
             clientX: mouseEvent.clientX,
             clientY: mouseEvent.clientY,
             originX: originX,
-            originY: originX,
+            originY: originY,
             target: nextTargetEle,
             popup: nextPopupEle,
             align: alignInfo,
           });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.tsx` around lines 505 - 522, The safe-hover origin Y coordinate is
passed incorrectly in the onMouseMove handler: originY was passed as originX to
isInSafeHoverArea, causing vertical checks to be lost; update the
isInSafeHoverArea call inside onMouseMove to pass originY for the originY
parameter (keep originX as-is for originX), referencing the variables originX
and originY and the isInSafeHoverArea invocation to locate the change.

Source: Pipeline failures

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/index.tsx`:
- Around line 505-522: The safe-hover origin Y coordinate is passed incorrectly
in the onMouseMove handler: originY was passed as originX to isInSafeHoverArea,
causing vertical checks to be lost; update the isInSafeHoverArea call inside
onMouseMove to pass originY for the originY parameter (keep originX as-is for
originX), referencing the variables originX and originY and the
isInSafeHoverArea invocation to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d023304d-57e0-4505-b4d0-eeea2c5d601c

📥 Commits

Reviewing files that changed from the base of the PR and between a7847b7 and b5c7026.

📒 Files selected for processing (3)
  • src/index.tsx
  • src/safeHover.ts
  • tests/safeHover.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/safeHover.ts

@li-jia-nan li-jia-nan closed this Jun 7, 2026
@li-jia-nan li-jia-nan force-pushed the feat-safe-hover-polygon branch from b5c7026 to 97703ff Compare June 7, 2026 14:22
@li-jia-nan li-jia-nan reopened this Jun 7, 2026
Copy link
Copy Markdown

@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 (3)
src/util/safeHover.ts (1)

83-90: 💤 Low value

变量命名可以更清晰。

verticalRecthorizontalRect 的命名容易引起误解 — verticalRect 实际用于获取水平方向的边界(left/right),而 horizontalRect 用于获取垂直方向的边界(top/bottom)。考虑重命名为 narrowerWidthRectnarrowerHeightRect 以更准确地表达其用途。

♻️ 建议的重命名
-  const verticalRect =
+  const narrowerWidthRect =
     popupRect.width > targetRect.width ? targetRect : popupRect;
-  const horizontalRect =
+  const narrowerHeightRect =
     popupRect.height > targetRect.height ? targetRect : popupRect;
-  const left = verticalRect.left - buffer;
-  const right = verticalRect.right + buffer;
-  const top = horizontalRect.top - buffer;
-  const bottom = horizontalRect.bottom + buffer;
+  const left = narrowerWidthRect.left - buffer;
+  const right = narrowerWidthRect.right + buffer;
+  const top = narrowerHeightRect.top - buffer;
+  const bottom = narrowerHeightRect.bottom + buffer;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/safeHover.ts` around lines 83 - 90, The variables verticalRect and
horizontalRect are misleading because verticalRect is used to compute horizontal
bounds (left/right) and horizontalRect is used for vertical bounds (top/bottom);
rename verticalRect to narrowerWidthRect and horizontalRect to
narrowerHeightRect and update all references (left, right, top, bottom) where
popupRect, targetRect and buffer are used so the names clearly reflect "narrower
width" and "narrower height" selection logic in the block that computes left =
... right = ... top = ... bottom = ....
tests/basic.test.jsx (1)

139-148: 💤 Low value

mockRect 辅助函数可以考虑在测试后恢复原始方法。

虽然每个测试都创建新元素,当前实现不会导致测试间污染,但作为最佳实践,可以考虑保存并恢复原始的 getBoundingClientRect

🔄 可选的改进方案
 function mockRect(element, rect) {
+  const original = element.getBoundingClientRect;
   element.getBoundingClientRect = jest.fn(() => ({
     left: rect.left,
     top: rect.top,
     width: rect.width,
     height: rect.height,
     right: rect.left + rect.width,
     bottom: rect.top + rect.height,
   }));
+  return () => {
+    element.getBoundingClientRect = original;
+  };
 }

不过考虑到当前测试结构(每个测试使用独立的 DOM 元素且有 cleanup()),这只是一个可选的防御性改进。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/basic.test.jsx` around lines 139 - 148, The helper mockRect overwrites
element.getBoundingClientRect without restoring it; change mockRect (and tests
that use it) to save the original method (e.g., const original =
element.getBoundingClientRect) before replacing it and provide a restore step
after the test (either return a restore function from mockRect or call the saved
original in an afterEach/cleanup), ensuring the original getBoundingClientRect
is reattached to the element; reference: mockRect function and tests that call
it.
tests/safeHover.test.ts (1)

7-21: 💤 Low value

考虑为 rect 辅助函数添加类型注解。

虽然 TypeScript 可以推断返回类型,但为参数和返回值显式添加类型注解能提升可读性和类型安全性:

-function rect(
-  left: number,
-  top: number,
-  width: number,
-  height: number,
-): SafeHoverRect {
+function rect(
+  left: number,
+  top: number,
+  width: number,
+  height: number,
+): SafeHoverRect {

实际上当前代码已经有返回类型注解,这很好。不过可以考虑为 rect 对象字面量添加类型断言以获得更早的类型检查。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/safeHover.test.ts` around lines 7 - 21, The rect helper already has
parameter and return type annotations but the returned object literal should be
asserted as SafeHoverRect to enable earlier shape checking; update the return
statement in the rect function (the object literal) to include a type assertion
to SafeHoverRect (e.g., assert the literal as SafeHoverRect) so TypeScript
validates the object properties against SafeHoverRect at the return site while
keeping the existing function signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/util/safeHover.ts`:
- Around line 83-90: The variables verticalRect and horizontalRect are
misleading because verticalRect is used to compute horizontal bounds
(left/right) and horizontalRect is used for vertical bounds (top/bottom); rename
verticalRect to narrowerWidthRect and horizontalRect to narrowerHeightRect and
update all references (left, right, top, bottom) where popupRect, targetRect and
buffer are used so the names clearly reflect "narrower width" and "narrower
height" selection logic in the block that computes left = ... right = ... top =
... bottom = ....

In `@tests/basic.test.jsx`:
- Around line 139-148: The helper mockRect overwrites
element.getBoundingClientRect without restoring it; change mockRect (and tests
that use it) to save the original method (e.g., const original =
element.getBoundingClientRect) before replacing it and provide a restore step
after the test (either return a restore function from mockRect or call the saved
original in an afterEach/cleanup), ensuring the original getBoundingClientRect
is reattached to the element; reference: mockRect function and tests that call
it.

In `@tests/safeHover.test.ts`:
- Around line 7-21: The rect helper already has parameter and return type
annotations but the returned object literal should be asserted as SafeHoverRect
to enable earlier shape checking; update the return statement in the rect
function (the object literal) to include a type assertion to SafeHoverRect
(e.g., assert the literal as SafeHoverRect) so TypeScript validates the object
properties against SafeHoverRect at the return site while keeping the existing
function signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 224ec2fe-f413-4eb0-9a74-ffedb11d5980

📥 Commits

Reviewing files that changed from the base of the PR and between b5c7026 and 67829e5.

📒 Files selected for processing (6)
  • docs/demos/safe-hover.md
  • docs/examples/safe-hover.tsx
  • src/index.tsx
  • src/util/safeHover.ts
  • tests/basic.test.jsx
  • tests/safeHover.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/demos/safe-hover.md

@li-jia-nan li-jia-nan requested review from MadCcc, afc163 and zombieJ June 7, 2026 17:57
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.

3 participants