Skip to content

fix: close tooltip on Escape - #540

Closed
nrps9909 wants to merge 2 commits into
react-component:masterfrom
nrps9909:codex/close-tooltip-on-escape
Closed

fix: close tooltip on Escape#540
nrps9909 wants to merge 2 commits into
react-component:masterfrom
nrps9909:codex/close-tooltip-on-escape

Conversation

@nrps9909

@nrps9909 nrps9909 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • dismiss an open tooltip when its document window receives Escape, matching the ARIA tooltip keyboard interaction
  • route trigger changes and Escape dismissal through one controlled/uncontrolled visibility state
  • preserve controlled behavior by requesting false through onVisibleChange while leaving the rendered state under the caller's control
  • listen on the trigger element's own window so iframe-hosted tooltips use the correct document context

Validation

  • Exact-base regression opened the tooltip but received no false callback after Escape
  • Focused Escape scope: 2 passed, covering uncontrolled close/reopen and controlled callback semantics
  • Complete suite: 2 files, 30 tests passed
  • pnpm tsc
  • focused ESLint, Prettier check, and git diff --check
  • complete ES/CJS/declaration and Less compilation

Fixes #510.

AI assistance disclosure: Codex was used to trace rc-trigger visibility ownership, construct the exact-base keyboard regression, and run validation. The submitted behavior and test results were verified locally.

Summary by CodeRabbit

  • 新功能

    • Tooltip 支持通过按下 Escape 键关闭弹层。
    • 支持非受控和受控可见状态下的关闭行为。
  • 测试

    • 新增 Escape 键关闭及受控状态行为测试。

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

@nrps9909 is attempting to deploy a commit to the afc163's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

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 Plus

Run ID: a20ce674-e761-4c79-a86e-68570ff0744b

📥 Commits

Reviewing files that changed from the base of the PR and between ba2d690 and 8a0625c.

📒 Files selected for processing (2)
  • src/Tooltip.tsx
  • tests/index.test.tsx

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


Walkthrough

Tooltip 统一管理 visiblepopupVisible 和内部可见状态。Tooltip 打开时监听目标窗口的 Escape,并在关闭或卸载时移除监听器。新增受控和非受控场景测试。

Changes

Tooltip 可见状态与 Escape 关闭

Layer / File(s) Summary
可见状态与 Escape 处理
src/Tooltip.tsx
Tooltip 合并受控及内部可见状态,并通过统一回调更新状态和调用 onVisibleChange。Tooltip 打开时监听目标窗口的 Escape,并在关闭或卸载时清理监听器。
Escape 行为验证
tests/index.test.tsx
测试覆盖非受控 Tooltip 的 Escape 关闭及再次打开。测试也验证受控 Tooltip 仅触发关闭回调,并在 visible={false} 后隐藏。

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 8a062

This localized change adds Escape dismissal for tooltips while preserving controlled visibility behavior, with reported tests and validation checks passing; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Trigger
  participant Tooltip
  participant TargetWindow
  Trigger->>Tooltip: 传入 mergedVisible 和 onInternalVisibleChange
  Tooltip->>TargetWindow: 注册 keydown 监听器
  TargetWindow-->>Tooltip: 发送 Escape 事件
  Tooltip->>Trigger: 请求可见状态变为 false
Loading

Suggested reviewers: zombiej

Poem

小兔按下 Escape,耳朵轻轻摇,
Tooltip 收起身影,状态已同步好。
受控模式先回调,显示仍由外部调,
非受控模式随即关,再次触发又能跑。
监听清理不遗留,月光下代码笑。

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题“fix: close tooltip on Escape”准确概括了本次主要变更。标题简洁、明确,并与 Tooltip 的 Escape 关闭行为直接相关。
Linked Issues check ✅ Passed PR 实现了 Issue #510 的核心要求。打开的 Tooltip 会监听所属窗口的 Escape 按键并请求关闭;非受控 Tooltip 会关闭,受控 Tooltip 会通过 onVisibleChange(false) 通知调用方。新增测试覆盖了两种行为。
Out of Scope Changes check ✅ Passed 变更均服务于 Issue #510。Tooltip 可见状态管理、Escape 事件监听、监听器清理、Trigger 适配和相关测试都属于实现或验证 Escape 关闭行为所需的范围。未发现无关代码变更。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2 files.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@yoyo837 yoyo837 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few focused issues around Escape handling and visibility ownership.

Comment thread src/Tooltip.tsx Outdated
}
};

targetWindow?.addEventListener('keydown', onKeyDown);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Trigger/Portal already handles Escape with a topmost-overlay guard. This listener runs for every visible tooltip, so one keypress can close background tooltips and call onVisibleChange(false) twice in controlled mode. Could we reuse Trigger's existing Escape path?

Comment thread src/Tooltip.tsx Outdated

const targetWindow = triggerRef.current?.nativeElement?.ownerDocument.defaultView;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This bypasses Portal's IME guards (event.isComposing and the post-compositionend lock), so Escape used to cancel CJK composition also closes the tooltip.

Comment thread src/Tooltip.tsx Outdated
popupAlign={align}
getPopupContainer={getTooltipContainer}
onOpenChange={onVisibleChange}
popupVisible={mergedVisible}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Always passing popupVisible forces Trigger into controlled mode. With unique and enter/leave delays, Trigger delays before onOpenChange, then UniqueProvider applies the delay again, effectively doubling it.

Comment thread src/Tooltip.tsx Outdated
extraProps.popupVisible = props.visible;
}
React.useEffect(() => {
if (!mergedVisible) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While disabled, Trigger hides the popup but preserves its raw open state. This effect still listens because mergedVisible is true, so Escape clears that state and the popup no longer returns when re-enabled.

@nrps9909

Copy link
Copy Markdown
Contributor Author

Thank you for catching these ownership and interaction issues. I verified the current Trigger and Portal path already owns Escape ordering, IME guards, controlled-state requests, disabled-state preservation, and UniqueProvider timing. Signed commit a005347 fully restores the upstream implementation; the resulting branch has no diff from the exact base and the focused 27-test file passes. I am closing this PR rather than keeping a redundant listener that would weaken those guarantees.

@nrps9909 nrps9909 closed this Aug 27, 2026
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (ba2d690) to head (a005347).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #540   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines           36        36           
  Branches        14        14           
=========================================
  Hits            36        36           

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

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.

Tooltip cannot be closed with Escape key

2 participants