Skip to content

Conversation

@zombieJ
Copy link
Member

@zombieJ zombieJ commented Dec 5, 2025

触发 blur 有两种情况:

  1. 点击了外侧,导致 Select 没有任何元素能获得焦点
  2. 点击了弹层或者边框之类不能获得焦点的元素,导致失去焦点

case 1 需要触发关闭,case 2 仍应该认为是在元素内部不需要关闭。

该 PR 额外做了一层监听,来处理 case 2 的情况。

TODO: 如果失去焦点,应该将焦点还原给 Select。但是这也是一个行为变化,暂时先不加。有人提 issue 再加。

Summary by CodeRabbit

发布说明

  • 错误修复
    • 改进了选择组件的鼠标交互和焦点模糊行为,防止在内部交互期间意外关闭下拉菜单。
    • 优化了在特定场景下的组件交互响应时序,增强了用户体验的稳定性。

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 5, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
select Ready Ready Preview Comment Dec 5, 2025 8:02am

@gemini-code-assist
Copy link

Summary of Changes

Hello @zombieJ, 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!

此拉取请求旨在解决Select组件中一个特定的失焦行为问题。它通过引入更精细的逻辑来判断何时应关闭组件,从而避免了当用户点击组件内部的非聚焦元素时,组件意外关闭的情况。这一改进提升了用户体验,确保了组件在内部交互时的预期行为。

Highlights

  • 优化失焦关闭逻辑: 此PR改进了Select组件的失焦处理逻辑,区分了两种失焦情况:点击外部导致失焦(应关闭)和点击内部不可聚焦元素导致失焦(不应关闭)。
  • 引入内部鼠标按下状态跟踪: 新增了internalMouseDownRef来追踪鼠标按下事件是否发生在组件内部,这对于判断失焦的来源至关重要。
  • 新增测试用例: 添加了一个新的测试用例,专门验证点击内部不可聚焦元素时,Select组件不会意外关闭的行为,确保了修复的正确性。
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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

演练

此PR通过引入内部鼠标按下追踪机制(internalMouseDownRef)和macroTask来防止在内部交互期间意外关闭下拉菜单。模糊事件的关闭逻辑由此标志把守,同时添加测试以验证该行为。

变更

内聚体 / 文件 变更摘要
BaseSelect 实现
src/BaseSelect/index.tsx
导入useOpen及macroTask辅助函数。引入internalMouseDownRef跟踪真实鼠标按下状态,用于防护blur关闭行为。onMouseDown和onRootMouseDown设置该ref并计划macroTask重置,确保关闭逻辑能区分外部点击和内部交互。
测试用例更新
tests/Combobox.test.tsx, tests/focus.test.tsx
Combobox测试中在mouseDown事件后添加延迟(await delay(100))。focus测试新增用例验证内部无焦点元素的点击不会触发弹出关闭。

预计代码审查工作量

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

  • 需重点关注的领域:
    • internalMouseDownRef与macroTask的协调逻辑及其与blur处理器的交互
    • 新增测试用例的时序断言和timer推进机制

可能关联的PR

建议审查者

  • afc163

诗句

🐰 鼠标在按下时悄悄做记号,
宏任务重置它莫要遗落,
模糊事件来临先查内心动,
外部点击才关闭真真的,
内部交互现在安全多了呢~ ✨

Pre-merge checks and finishing touches

✅ 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.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch all-align

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.

@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

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

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1184   +/-   ##
=======================================
  Coverage   99.42%   99.42%           
=======================================
  Files          31       31           
  Lines        1209     1213    +4     
  Branches      428      409   -19     
=======================================
+ Hits         1202     1206    +4     
  Misses          7        7           

☔ View full report in Codecov by Sentry.
📢 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.

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

这个 PR 很好地解决了点击 Select 内部非可聚焦元素导致下拉框意外关闭的问题。通过 internalMouseDownRefmacroTask 来判断 blur 事件是否由内部点击触发,逻辑清晰且有效。

我注意到 tests/Combobox.test.tsx 中的修改,虽然能让测试通过,但 delay(100) 放在循环内部会显著拖慢测试速度。我提供了一个建议,将延迟移到循环外部,这样既能保证测试的正确性,又能维持测试效率。

除此之外,代码的其余部分看起来不错,新加的 focus.test.tsx 测试用例也很好地覆盖了核心场景。

Comment on lines 500 to 507
for (let i = 0; i < 10; i += 1) {
fireEvent.mouseDown(container.querySelector('input')!);
expectOpen(container);

await delay(100);
}

fireEvent.blur(container.querySelector('input')!);

Choose a reason for hiding this comment

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

medium

await delay(100) 放在循环内部会导致测试运行时间不必要地增加(在这个例子中超过1秒)。这个 delay 的目的是为了确保在触发 blur 事件之前,由最后一次 mouseDown 事件触发的 macroTask 已经执行完毕,将 internalMouseDownRef 重置为 false

因此,这个延迟只需要在循环之后、blur 事件之前执行一次即可。另外,使用 delay(0) 通常足以等待下一个宏任务,比 delay(100) 更高效,可以避免测试中的硬编码等待时间。

建议将 delay 移出循环,以提高测试效率。

Suggested change
for (let i = 0; i < 10; i += 1) {
fireEvent.mouseDown(container.querySelector('input')!);
expectOpen(container);
await delay(100);
}
fireEvent.blur(container.querySelector('input')!);
for (let i = 0; i < 10; i += 1) {
fireEvent.mouseDown(container.querySelector('input')!);
expectOpen(container);
}
await delay(0);
fireEvent.blur(container.querySelector('input')!);

@zombieJ zombieJ merged commit e39d054 into master Dec 5, 2025
11 of 12 checks passed
@zombieJ zombieJ deleted the all-align branch December 5, 2025 08:04
Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
tests/Combobox.test.tsx (1)

500-505: 在循环中加入 await delay(100) 有助于稳定与宏任务相关的时序

  • 每次 mouseDown 后等待一个短暂的真实时间片,能让内部 macroTask/blur 逻辑完整执行,避免因为事件过于紧凑导致的非预期状态。
  • 该测试未启用 fake timers,累计约 1 秒的延时对整体测试时长影响有限,但换来更稳的时序行为验证是值得的。

如后续发现整包测试时间偏长,可以考虑把间隔适当调小(例如 50ms)再评估一次稳定性。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c67f9f4 and d9f8f8e.

📒 Files selected for processing (3)
  • src/BaseSelect/index.tsx (4 hunks)
  • tests/Combobox.test.tsx (1 hunks)
  • tests/focus.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/BaseSelect/index.tsx (1)
src/hooks/useOpen.ts (1)
  • macroTask (10-19)
🔇 Additional comments (2)
tests/focus.test.tsx (1)

122-141: 新增用例很好地覆盖了“内部非可聚焦元素点击不应关闭弹层”的场景

  • popupRender 渲染纯 div.bamboo,再触发 mouseDown + blur 后断言 onPopupVisibleChange 没被调用,和 BaseSelect 新的 blur 处理逻辑相吻合。
  • 计时器配合 beforeEachjest.useFakeTimers()act(() => jest.runAllTimers()) 使用方式也与现有测试保持一致。

这段用例整体设计合理,可读性也不错。

src/BaseSelect/index.tsx (1)

24-25: 利用 internalMouseDownRef 区分内部/外部 blur 的思路合理

  • 新增的 internalMouseDownRefmacroTask 组合,使得:
    • 组件或弹层内部 onRootMouseDown 时先将标记置为 true,在一个宏任务后再重置为 false
    • onRootBlur 中只有在 mergedOpen && !internalMouseDownRef.current 时才尝试关闭,并继续通过 cancelFun 校验 document.activeElement 是否仍在 getSelectElements() 内。
  • 这样可以:
    • 避免点击弹层内部(包括非可聚焦区域)时,由于 activeElement 暂时指向 body 而误触发关闭;
    • 保留键盘 Tab 等纯 blur 场景以及真正外部点击时的关闭行为。
  • macroTask 的异步重置也避免了 blur 与 mousedown 同步竞争条件,在现有单测(focus / combobox)下覆盖到了典型时序。

整体改动与 PR 目标一致,目前看不到明显的回归风险。

Also applies to: 550-552, 567-572, 596-612

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.

2 participants