Skip to content

Conversation

@zombieJ
Copy link
Member

@zombieJ zombieJ commented Nov 26, 2025

fix ant-design/ant-design#55928

Summary by CodeRabbit

发布说明

  • Bug Fixes(缺陷修复)

    • 改进了下拉菜单的焦点管理和失焦关闭逻辑
    • 优化了选择输入框的模糊事件处理机制
  • Refactor(重构)

    • 简化了内部焦点控制流程

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

@vercel
Copy link

vercel bot commented Nov 26, 2025

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

Project Deployment Preview Comments Updated (UTC)
select Ready Ready Preview Comment Nov 26, 2025 10:08am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

总体概括

通过移除 BaseSelect 中的自动关闭行为并在 SelectInput 中使用 macroTask 延迟执行模糊处理逻辑来重构模糊事件处理。导出 macroTask 工具函数,从 TriggerOpenType 配置中移除 lazy 选项,确保下拉框在自定义输入组件获得焦点后不会自动关闭。

变更清单

结群 / 文件 变更摘要
核心模糊事件处理重构
src/BaseSelect/index.tsx
在 onInternalBlur 中移除 triggerOpen(false, { lazy: true }) 调用,改为无操作注释,防止模糊事件自动关闭下拉框
SelectInput 延迟模糊处理
src/SelectInput/index.tsx
导入 macroTask 工具,使用 macroTask 包装模糊处理逻辑,延迟 toggleOpen(false) 执行直至下一个宏任务,添加条件检查确保仅在输入框非活跃时执行
useOpen 钩子 API 调整
src/hooks/useOpen.ts
导出 macroTask 函数供外部使用;从 TriggerOpenType 配置中移除 lazy?: boolean 属性;简化 toggleOpen 条件逻辑以移除懒加载处理
测试用例完善
tests/Combobox.test.tsx, tests/Select.test.tsx
在模糊事件测试中添加显式 blur() 调用,增强测试确定性,确保模糊处理逻辑完整执行

序列流程图

sequenceDiagram
    participant User
    participant Input as SelectInput
    participant MacroTask as 宏任务队列
    participant OpenHook as useOpen Hook
    
    User->>Input: 触发 blur 事件
    activate Input
    Input->>MacroTask: 通过 macroTask 延迟执行
    deactivate Input
    
    Note over MacroTask: 等待当前同步代码执行完毕
    
    MacroTask->>Input: 检查当前活跃元素
    alt 输入框仍为活跃元素
        Input->>Input: 跳过 toggleOpen(false)
    else 输入框不是活跃元素
        Input->>OpenHook: 调用 toggleOpen(false)
        activate OpenHook
        OpenHook->>OpenHook: 更新 open 状态
        deactivate OpenHook
    end
Loading

代码审查工作量评估

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

需要重点关注的区域:

  • src/hooks/useOpen.ts - TriggerOpenType 的公开 API 变更(移除 lazy 属性)可能影响其他依赖此配置的调用方
  • src/SelectInput/index.tsx - macroTask 延迟逻辑与活跃元素检查的时序依赖,需确保在不同浏览器/环境中的一致性
  • 测试覆盖 - 验证新增的显式 blur() 调用是否充分测试了修复后的聚焦行为,特别是自定义输入组件场景

相关 PR

建议审阅者

  • afc163

诗赋

🐰 模糊何须急着关,

宏任务来帮忙承担,

检查焦点是否在,

下拉框平静不消散,

自定义输入配合妙,

选项显示永不偷懒! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确描述了主要改动:解决了blur事件在activeElement仍为input ref时不应该关闭dropdown的问题。
Linked Issues check ✅ Passed 代码改动直接解决了#55928中的问题:通过延迟检查activeElement并只在input不是活动元素时才关闭dropdown,防止了blur事件错误地关闭打开的下拉框。
Out of Scope Changes check ✅ Passed 所有改动都围绕修复blur关闭dropdown的问题展开,包括导出macroTask、更新TriggerOpenType配置和修改测试用例,均在#55928的范围内。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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 blur-close

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

This pull request addresses a bug in Ant Design's select components where the dropdown would close unexpectedly when the input field lost focus, even if the focus was still within a related element of the component. The fix introduces a more robust blur handling mechanism that defers the decision to close the dropdown until it's confirmed that the focus has truly left the component, thereby improving the user experience and preventing premature closures.

Highlights

  • Refined Blur Handling in Select Components: The core change prevents the select dropdown from closing prematurely when the input field loses focus but the document.activeElement remains within the component's input reference. This is achieved by introducing a macroTask to check the active element after a blur event.
  • Conditional Dropdown Closure: The onInternalBlur handler in SelectInput now conditionally calls toggleOpen(false). It only closes the dropdown if the focus has truly moved outside the input element or its contained elements, preventing unintended closures.
  • Simplified BaseSelect Blur Logic: The BaseSelect component no longer directly calls triggerOpen(false) on blur, delegating this responsibility to the more granular SelectInput component's refined blur logic.
  • Exported macroTask Utility: A macroTask utility function has been exported from useOpen.ts to allow for deferred execution, which is crucial for checking the document.activeElement reliably after a blur event.
  • Updated Test Cases: Existing test cases for Combobox and Select components have been updated to explicitly trigger blur() on the input element, ensuring comprehensive testing of the new blur handling logic.
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.

@codecov
Copy link

codecov bot commented Nov 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.41%. Comparing base (6b3a9fa) to head (dd860f6).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1175   +/-   ##
=======================================
  Coverage   99.41%   99.41%           
=======================================
  Files          31       31           
  Lines        1196     1198    +2     
  Branches      425      404   -21     
=======================================
+ Hits         1189     1191    +2     
  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

This pull request effectively resolves an issue where the select dropdown would close prematurely on blur. The solution, which involves deferring the close logic using a macroTask to check the document.activeElement after focus has shifted, is a solid approach. The related refactoring in useOpen to always handle closing asynchronously simplifies the logic. The test updates are also good, as they more accurately simulate real browser behavior for blur events. I have one minor suggestion to remove some commented-out code to keep the codebase clean.

Comment on lines +573 to +575
// triggerOpen(false, {
// lazy: true,
// });

Choose a reason for hiding this comment

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

medium

This commented-out code is no longer necessary since the logic for closing the dropdown on blur has been moved to the SelectInput component. It should be removed to improve code clarity.

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: 1

🧹 Nitpick comments (1)
src/BaseSelect/index.tsx (1)

557-578: 建议移除注释代码或添加说明。

triggerOpen(false, { lazy: true }) 调用被注释掉,因为 blur 关闭逻辑已移至 SelectInput 中处理。这是正确的架构调整,但代码库中留下注释代码会降低可维护性。

建议:

  1. 直接移除注释代码(首选),或
  2. 添加详细的 TODO 注释说明为何保留,例如:
  const onInternalBlur: React.FocusEventHandler<HTMLElement> = (event) => {
    setFocused(false);

    if (mergedSearchValue) {
      // `tags` mode should move `searchValue` into values
      if (mode === 'tags') {
        onSearch(mergedSearchValue, { source: 'submit' });
      } else if (mode === 'multiple') {
        // `multiple` mode only clean the search value but not trigger event
        onSearch('', {
          source: 'blur',
        });
      }
    }

    if (!disabled) {
-     // triggerOpen(false, {
-     //   lazy: true,
-     // });
+     // Note: Blur close logic moved to SelectInput to check activeElement before closing
+     // This prevents closing when focus moves to another input within the component
      onBlur?.(event);
    }
  };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b3a9fa and dd860f6.

📒 Files selected for processing (5)
  • src/BaseSelect/index.tsx (1 hunks)
  • src/SelectInput/index.tsx (2 hunks)
  • src/hooks/useOpen.ts (2 hunks)
  • tests/Combobox.test.tsx (2 hunks)
  • tests/Select.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/SelectInput/index.tsx (2)
src/hooks/useOpen.ts (1)
  • macroTask (10-19)
tests/utils/common.ts (1)
  • toggleOpen (22-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
src/hooks/useOpen.ts (4)

10-19: 导出 macroTask 工具函数是合理的。

macroTask 导出使得其他模块(如 SelectInput)可以使用延迟执行机制来处理异步逻辑,这与 PR 修复 blur 关闭逻辑的目标一致。


70-76: 解构更新与类型定义保持一致。

移除 lazy 参数的解构与 TriggerOpenType 的类型更新一致。


79-93: 简化的 toggleOpen 逻辑符合新的 blur 处理机制。

移除 lazy 条件后,关闭操作始终通过 macroTask 延迟执行,这允许在 SelectInput 的 blur 处理中正确检查 document.activeElement,确保焦点仍在输入框内时不会关闭下拉框。


21-31: The review comment is incorrect—lazy was not removed from the type definition.

The lazy property still exists in TriggerOpenType at line 29 of src/hooks/useOpen.ts. While it was never actually implemented (the only reference is commented-out code in src/BaseSelect/index.tsx line 574), it remains in the type signature. This is dead code that should be cleaned up, not a breaking change being introduced. Since lazy was never functional, no external consumers could be depending on it.

Likely an incorrect or invalid review comment.

src/SelectInput/index.tsx (1)

14-14: 导入 macroTask 用于延迟 blur 处理。

useOpen 导入 macroTask 工具函数,用于实现下方的延迟关闭逻辑。

tests/Select.test.tsx (1)

808-815: 测试更新正确模拟了新的 blur 行为。

显式调用 input.blur() 并在 act 块中运行所有定时器,确保 blur 处理逻辑(包括 macroTask 延迟的关闭检查)在断言之前完成。这使测试更具确定性,并正确验证了 blur 不会在焦点保持时关闭下拉框的修复。

tests/Combobox.test.tsx (2)

505-510: 测试正确模拟了 blur 事件处理。

显式调用 input.blur() 确保浏览器的 activeElement 状态更新,随后的 delay(100) 等待 macroTask 延迟的关闭逻辑执行完成。


594-599: 测试更新与其他测试文件保持一致。

act 块中调用 input.blur()jest.runAllTimers() 的模式与 Select.test.tsx 中的更新一致,确保 blur 处理逻辑完整执行后再进行断言。

Comment on lines 200 to 209
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
toggleOpen(false);
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});

onBlur?.(event);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

blur 处理逻辑修复了焦点转移问题,但需要添加空值检查。

使用 macroTask 延迟关闭逻辑确保在 document.activeElement 更新后再检查焦点位置,这正确修复了当焦点转移到同一组件内的其他输入元素时错误关闭下拉框的问题。

但是,getDOM(inputRef.current) 可能返回 null,应添加空值检查以避免潜在的运行时错误。

应用以下 diff 添加空值检查:

  const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
    macroTask(() => {
      const inputNode = getDOM(inputRef.current);
-     if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
+     if (inputNode && inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
        toggleOpen(false);
      }
    });

    onBlur?.(event);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
toggleOpen(false);
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});
onBlur?.(event);
};
const onInternalBlur: SelectInputProps['onBlur'] = (event) => {
macroTask(() => {
const inputNode = getDOM(inputRef.current);
if (inputNode && inputNode !== document.activeElement && !inputNode.contains(document.activeElement)) {
toggleOpen(false);
}
});
onBlur?.(event);
};
🤖 Prompt for AI Agents
In src/SelectInput/index.tsx around lines 200 to 209, the blur handler uses
getDOM(inputRef.current) without checking for null which can cause a runtime
error; update the macroTask callback to first check that inputNode is not null
(or undefined) before comparing it to document.activeElement or calling
inputNode.contains(...), and only call toggleOpen(false) when inputNode exists
and the activeElement is outside it; keep the existing onBlur?.(event) call
unchanged.

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.

AutoComplete自定义输入组件中的input获取焦点后下拉框会隐藏

2 participants