- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 478
refactor: Semantic structure re-layout #1166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| The latest updates on your projects. Learn more about Vercel for GitHub. 
 | 
| Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough将旧的 Selector 实现替换为可组合的 SelectInput 子系统,重构 BaseSelect 渲染与打开控制,新增/调整多个 hooks 与样式,并同步更新示例与大量测试以匹配新的 DOM 和 API 命名。 Changes
 Sequence Diagram(s)sequenceDiagram
  autonumber
  actor User
  participant Select as Select (外层)
  participant BaseSelect as BaseSelect
  participant SI as SelectInput
  participant Trigger as SelectTrigger
  participant Hook as useOpen
  User->>SI: click / keydown / input
  SI->>Hook: toggleOpen(nextOpen?)
  Hook-->>SI: mergedOpen
  SI->>BaseSelect: onSearch / onSelectorRemove / onClear 等回调
  BaseSelect->>Trigger: 更新弹层 (open=mergedOpen)
  Trigger-->>User: 展示/隐藏下拉
sequenceDiagram
  autonumber
  actor User
  participant SI as SelectInput
  participant Ctx as SelectInputContext
  participant Content as Single/MultipleContent
  participant Input as Input
  Note over SI,Content: 组件化渲染路径(上下文驱动)
  SI->>Ctx: 提供上下文(prefixCls, mode, handlers)
  SI->>Content: 渲染 Single/MultipleContent
  Content->>Input: 渲染受控输入(事件/宽度同步)
  Input-->>Content: onChange/onKeyDown/onBlur 事件
  Content-->>SI: 搜索值/项移除/提交 事件
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
 Suggested reviewers
 Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
 ✅ Passed checks (1 passed)
 ✨ Finishing touches
 🧪 Generate unit tests (beta)
 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. Comment  | 
| Summary of ChangesHello @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 introduces a significant internal restructuring of the  Highlights
 Using Gemini Code AssistThe 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  
 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  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
 | 
There was a problem hiding this 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 is a significant and impressive refactoring of the Select component's internal structure. It replaces the monolithic Selector component with a more modular and semantic SelectInput component, which is a great improvement for maintainability and extensibility. The introduction of custom hooks like useOpen and useComponents cleans up the logic for state management and component customization significantly. The API changes, such as replacing suffixIcon with suffix, are also a step towards a more consistent and flexible component API. Overall, this is a high-quality refactor that modernizes the component's architecture. My review includes a few suggestions to remove some leftover debugging code and clarify some implementation details.
| display: inline-flex; | ||
| align-items: center; | ||
| user-select: none; | ||
| border: 1px solid blue; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| keyDown(container.querySelector('input'), KeyCode.L); | ||
| fireEvent.change(container.querySelector('input'), { target: { value: 'l' } }); | ||
|  | ||
| console.log('clear'); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| expect(onChange).not.toHaveBeenCalled(); | ||
|  | ||
| jest.runAllTimers(); | ||
| console.log('after 200ms'); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // This is used for semantic refactoring | ||
| @import (reference) url('./index.less'); | ||
|  | ||
| .@{select-prefix}.@{select-prefix} { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a duplicated class selector (.@{select-prefix}.@{select-prefix}) to increase specificity can be a bit of a hack and might make the CSS harder to maintain. If this is necessary to override existing styles, please add a comment explaining why. A better long-term solution might be to refactor the base styles to avoid needing such high specificity.
| onClear?.(); | ||
|  | ||
| selectorRef.current?.focus(); | ||
| containerRef.current?.focus(); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| style={styles?.clear} | ||
| onMouseDown={(e) => { | ||
| // Mark to tell not trigger open or focus | ||
| (e.nativeEvent as any)._select_lazy = true; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying the native event object with a custom property (_select_lazy) to pass state between event handlers is generally discouraged as it can be fragile and hard to follow. While it might be a pragmatic solution for complex event interactions, have you considered alternatives like using a React ref to store this transient state? If this is the best approach, please add a comment explaining why this flag is necessary and how it's used in the onInternalMouseDown handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (1)
tests/Multiple.test.tsx (1)
413-426: 移除调试用的 console.log 语句。测试代码中的
console.log语句应该在提交前移除,以保持测试输出的整洁。应用此差异移除调试语句:
- console.log('clear'); // Backspace keyDown(container.querySelector('input'), KeyCode.BACKSPACE); fireEvent.change(container.querySelector('input'), { target: { value: '' } }); onChange.mockReset(); keyDown(container.querySelector('input'), KeyCode.BACKSPACE); expect(onChange).not.toHaveBeenCalled(); - console.log('after 200ms'); act(() => { jest.runAllTimers(); });
🧹 Nitpick comments (19)
src/utils/keyUtil.ts (1)
25-29: 更新箭头键注释
注释当前写“箭头键 – 在输入中导航时不应触发打开”,但实际仅排除了 Up/Left/Right ,保留 Down 键以打开下拉。请将注释改为“除 Down 键外的箭头键 …”。tests/setup.ts (1)
1-28: 测试环境的 MessageChannel 模拟实现正确。这个 mock 实现通过
_target属性实现了双向端口通信,使用setTimeout(0)模拟异步消息传递,符合测试需求。可选优化:考虑为端口对象定义更具体的类型接口,而不是使用
any:+interface MockPort { + onmessage: ((event: { data: any }) => void) | null; + postMessage: (message: any) => void; + _target: MockPort | null; +} + window.MessageChannel = class { - port1: any; - port2: any; + port1: MockPort; + port2: MockPort; constructor() { - const createPort = () => { - const port = { + const createPort = (): MockPort => { + const port: MockPort = { onmessage: null, postMessage: (message: any) => { setTimeout(() => { if (port._target && typeof port._target.onmessage === 'function') { port._target.onmessage({ data: message }); } }, 0); }, _target: null, }; return port; };docs/examples/multiple.tsx (1)
102-102: Prop 重命名正确。将
suffixIcon更新为suffix,与 BaseSelect API 重构保持一致。可选优化:为保持一致性,考虑同时更新相关的 state 变量名:
state = { useAnim: false, - suffixIcon: null, + suffix: null, loading: false, value: ['a10'], searchValue: '', }; showArrow = (e) => { this.setState({ - suffixIcon: e.target.checked ? <div>arrow</div> : null, + suffix: e.target.checked ? <div>arrow</div> : null, }); }; render() { - const { useAnim, loading, value, suffixIcon } = this.state; + const { useAnim, loading, value, suffix } = this.state; return ( <div> {/* ... */} <input id="showArrow" - checked={!!suffixIcon} + checked={!!suffix} type="checkbox" onChange={this.showArrow} /> {/* ... */} - suffix={suffixIcon} + suffix={suffix}src/SelectInput/Affix.tsx (1)
1-16: LGTM! 简洁且设计良好的辅助组件。
Affix组件实现简单清晰:
- 正确扩展了 HTML 属性以支持标准 DOM 属性
- 使用提前返回模式优化性能
- 属性展开使用得当
- 注释说明了组件不读取上下文或逻辑属性的设计意图
可以考虑添加
displayName以改善开发体验:export default function Affix(props: AffixProps) { const { children, ...restProps } = props; if (!children) { return null; } return <div {...restProps}>{children}</div>; } + +Affix.displayName = 'Affix';tests/Tags.test.tsx (1)
516-516: 测试只渲染单个 input,通用选择器安全
当前测试中的组件实例仅包含一个 input,因此querySelector('input')不会选错元素。如果未来组件引入多个 input,请改用更具特异性的选择器或 RTL 的getByRole('textbox')等查询方法。src/SelectInput/Content/Placeholder.tsx (1)
19-21: 建议使用display: none代替visibility: hidden。当
show为false时,使用display: none可以避免元素占用布局空间,提升渲染性能。应用此差异来改进样式控制:
- style={{ - visibility: show ? 'visible' : 'hidden', - }} + style={{ + display: show ? 'block' : 'none', + }}tests/shared/maxTagRenderTest.tsx (1)
17-21: 测试断言仍然依赖快照。虽然改为只对文本内容进行快照比较比完整 DOM 快照更好,但仍然存在维护负担。建议使用显式的期望值来提高测试的可读性和稳定性。
应用此差异来使用显式断言:
- expect( - Array.from(container.querySelectorAll('.rc-select-selection-item-content')).map( - (ele) => ele.textContent, - ), - ).toMatchSnapshot(); + const contents = Array.from( + container.querySelectorAll('.rc-select-selection-item-content') + ).map((ele) => ele.textContent); + expect(contents).toEqual(['On', 'Tw']); // maxTagTextLength=2src/SelectInput/context.ts (1)
6-6: 添加对 useSelectInputContext 的返回值校验
建议在useSelectInputContext中加入防御性检查,未被<SelectInputContext.Provider>包裹时抛错:export function useSelectInputContext() { - return React.useContext(SelectInputContext); + const context = React.useContext(SelectInputContext); + if (!context) { + throw new Error('useSelectInputContext must be used within SelectInputContext.Provider'); + } + return context; }当前所有钩子调用均位于 Provider 范围内,此改动为可选重构。
src/SelectInput/Content/SingleContent.tsx (3)
41-55: 基于 value 的 option 匹配可能不稳(引用相等 vs. 值相等)flattenOptions 使用
opt.value === displayValue.value做严格相等查找。若 value 允许为对象或存在类型漂移(数字/字符串),可能出现匹配失败,导致 title/className/style 合并缺失。建议改为使用内部的值映射(如 rawValue → option 的 Map),或在上游保证 value 的稳定标识(基本类型且一致化)。
56-63: title 覆盖顺序 OK,但请确认覆盖意图当前顺序为:option.data → displayValue → rootTitle(rootTitle 若有则最终覆盖)。这会无条件覆盖来自 option 的 title。请确认产品语义是否期望 rootTitle 始终最高优先级;若只在无 title 时兜底,可调整为仅在无 title 时使用 rootTitle。
67-71: combobox 下重置 inputChanged 的副作用activeValue 变化时在 combobox 模式下重置
inputChanged为 false 符合旧逻辑。但这会在频繁高亮移动时反复重置。若仅需在打开或选中后重置,可考虑收窄触发条件以减少无效重渲染。src/SelectInput/Input.tsx (2)
196-206: 合并自定义 Input 的函数型 props 时应限于事件处理器当前对所有函数型 prop 进行“先现有后共享”的合并,可能误合并非事件类函数(如 render 回调),引入双调用副作用。建议仅合并以
on开头的事件处理器键。可按如下方式约束:
- Object.keys(existingProps).forEach((key) => { - const existingValue = (existingProps as any)[key]; - if (typeof existingValue === 'function') { + Object.keys(existingProps).forEach((key) => { + const existingValue = (existingProps as any)[key]; + if (typeof existingValue === 'function' && /^on[A-Z]/.test(key)) { (mergedProps as any)[key] = (...args: any[]) => { existingValue(...args); (sharedInputProps as any)[key]?.(...args); }; } });
161-166: CSS 自定义属性建议带单位,增强兼容性
--select-input-width直接传 number,某些场景下期望为带单位的字符串。建议在设置时转为${scrollWidth}px,并在消费处确保用作长度值。src/hooks/useOpen.ts (2)
4-20: MessageChannel 不可用时的降级在少数运行环境(老旧浏览器、极简测试环境)可能缺少 MessageChannel。建议在不可用时回退到
setTimeout(fn, 0),提升健壮性。
36-41: 入参类型建议更宽以兼容非受控用法
propOpen、onOpen、postOpen当前为必填且精确类型。实际调用场景常传undefined(非受控)。建议将propOpen?: boolean、onOpen?: (nextOpen: boolean) => void、postOpen?: (nextOpen: boolean) => boolean,并在内部提供默认实现,减少上游断言与兜底代码。tests/Select.test.tsx (1)
38-45: 计时器模式切换重复文件顶层 beforeEach/afterEach 已全局启用/还原 fake timers;部分用例内仍显式
useFakeTimers/useRealTimers。这虽不影响正确性,但会增加认知成本。建议统一由顶层控制,单测内仅在确有差异时覆盖。src/SelectInput/Content/MultipleContent.tsx (1)
93-102: 避免删除按钮重复渲染 “×”TransBtn 已通过 customizeIcon={removeIcon} 传入图标,同时又在子节点渲染了字符 “×”。当 removeIcon 为默认 “×” 或用户自定义图标时,可能出现重复显示。
建议移除子节点 “×”,仅依赖 customizeIcon:
{closable && ( <TransBtn className={`${selectionItemPrefixCls}-remove`} onMouseDown={onPreventMouseDown} onClick={onClose} customizeIcon={removeIcon} > - × </TransBtn> )}注:上方 removeIcon 已提供默认值 '×'(见行 60-66),不会导致图标缺失。
Also applies to: 59-66
src/BaseSelect/index.tsx (2)
607-611: 条件判断中误用函数标识,建议改为检查状态或移除冗余判断此处
triggerOpen是函数而非布尔值,&& triggerOpen恒为真,易混淆:if (popupElement?.contains(target as HTMLElement) && triggerOpen) { triggerOpen(true, true); }建议改为基于状态判断或直接去掉冗余条件:
- if (popupElement?.contains(target as HTMLElement) && triggerOpen) { + if (popupElement?.contains(target as HTMLElement)) { triggerOpen(true, true); }或(若仅在已打开时加锁):
- if (popupElement?.contains(target as HTMLElement) && triggerOpen) { + if (popupElement?.contains(target as HTMLElement) && mergedOpen) { triggerOpen(true, true); }
640-666: 移除 BaseSelectContext 中冗余的 boolean triggerOpen,统一使用 open 与 toggleOpen
全局检索确认所有组件均正确将triggerOpen作为布尔值使用,context 中的 setter 已命名为toggleOpen,未发现误用。建议在下个大版本中废弃context.triggerOpen,统一使用open;若需兼容短期保留,请添加@deprecated标注并规划后续移除。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
- tests/__snapshots__/Combobox.test.tsx.snapis excluded by- !**/*.snap
- tests/__snapshots__/Multiple.test.tsx.snapis excluded by- !**/*.snap
- tests/__snapshots__/Select.test.tsx.snapis excluded by- !**/*.snap
- tests/__snapshots__/Tags.test.tsx.snapis excluded by- !**/*.snap
- tests/__snapshots__/ssr.test.tsx.snapis excluded by- !**/*.snap
📒 Files selected for processing (46)
- assets/index.less(1 hunks)
- assets/patch.less(1 hunks)
- docs/examples/multiple-with-maxCount.tsx(1 hunks)
- docs/examples/multiple.tsx(1 hunks)
- examples/InputExample.tsx(1 hunks)
- examples/SelectContentExample.tsx(1 hunks)
- jest.config.js(1 hunks)
- src/BaseSelect/index.tsx(24 hunks)
- src/Select.tsx(1 hunks)
- src/SelectInput/Affix.tsx(1 hunks)
- src/SelectInput/Content/MultipleContent.tsx(1 hunks)
- src/SelectInput/Content/Placeholder.tsx(1 hunks)
- src/SelectInput/Content/SingleContent.tsx(1 hunks)
- src/SelectInput/Content/index.tsx(1 hunks)
- src/SelectInput/Input.tsx(1 hunks)
- src/SelectInput/context.ts(1 hunks)
- src/SelectInput/index.tsx(1 hunks)
- src/SelectTrigger.tsx(3 hunks)
- src/Selector/Input.tsx(0 hunks)
- src/Selector/MultipleSelector.tsx(0 hunks)
- src/Selector/SingleSelector.tsx(0 hunks)
- src/Selector/index.tsx(0 hunks)
- src/TransBtn.tsx(1 hunks)
- src/hooks/useAllowClear.tsx(1 hunks)
- src/hooks/useBaseProps.ts(1 hunks)
- src/hooks/useComponents.ts(1 hunks)
- src/hooks/useOpen.ts(1 hunks)
- src/hooks/useSearchConfig.ts(2 hunks)
- src/hooks/useSelectTriggerControl.ts(1 hunks)
- src/utils/keyUtil.ts(1 hunks)
- tests/Accessibility.test.tsx(1 hunks)
- tests/BaseSelect.test.tsx(2 hunks)
- tests/Combobox.test.tsx(10 hunks)
- tests/Custom.test.tsx(3 hunks)
- tests/Multiple.test.tsx(14 hunks)
- tests/Select.test.tsx(24 hunks)
- tests/Tags.test.tsx(3 hunks)
- tests/focus.test.tsx(0 hunks)
- tests/placeholder.test.tsx(1 hunks)
- tests/setup.ts(1 hunks)
- tests/shared/allowClearTest.tsx(1 hunks)
- tests/shared/blurTest.tsx(3 hunks)
- tests/shared/inputFilterTest.tsx(2 hunks)
- tests/shared/maxTagRenderTest.tsx(4 hunks)
- tests/shared/removeSelectedTest.tsx(1 hunks)
- tests/utils/common.ts(3 hunks)
💤 Files with no reviewable changes (5)
- tests/focus.test.tsx
- src/Selector/MultipleSelector.tsx
- src/Selector/SingleSelector.tsx
- src/Selector/Input.tsx
- src/Selector/index.tsx
🧰 Additional context used
🧬 Code graph analysis (21)
src/SelectInput/context.ts (1)
src/SelectInput/index.tsx (1)
SelectInputProps(21-50)
src/SelectInput/Content/index.tsx (1)
src/SelectInput/context.ts (1)
useSelectInputContext(8-10)
src/hooks/useSearchConfig.ts (1)
src/Select.tsx (3)
SearchConfig(114-121)
DefaultOptionType(99-103)
SelectProps(122-182)
src/hooks/useComponents.ts (2)
src/SelectInput/index.tsx (2)
SelectInputProps(21-50)
SelectInputRef(15-19)src/BaseSelect/index.tsx (1)
BaseSelectProps(136-230)
examples/SelectContentExample.tsx (1)
src/BaseSelect/index.tsx (1)
DisplayValueType(42-42)
src/SelectInput/Content/MultipleContent.tsx (6)
src/BaseSelect/index.tsx (3)
DisplayValueType(42-42)
CustomTagProps(69-77)
RawValueType(47-47)src/SelectInput/Content/index.tsx (1)
SharedContentProps(8-10)src/SelectInput/context.ts (1)
useSelectInputContext(8-10)src/utils/commonUtil.ts (1)
getTitle(29-40)src/Select.tsx (1)
RawValueType(71-71)src/SelectInput/Content/Placeholder.tsx (1)
Placeholder(8-26)
src/SelectInput/Content/Placeholder.tsx (1)
src/SelectInput/context.ts (1)
useSelectInputContext(8-10)
src/Select.tsx (1)
src/hooks/useSearchConfig.ts (1)
useSearchConfig(5-49)
tests/Tags.test.tsx (1)
src/index.ts (1)
Option(9-9)
tests/Combobox.test.tsx (1)
tests/utils/common.ts (1)
expectOpen(4-20)
tests/BaseSelect.test.tsx (3)
src/BaseSelect/index.tsx (1)
RefOptionListProps(63-67)src/OptionList.tsx (1)
OptionListProps(20-20)tests/utils/common.ts (1)
waitFakeTimer(138-148)
src/BaseSelect/index.tsx (9)
src/interface.ts (1)
RenderNode(22-22)src/hooks/useComponents.ts (2)
ComponentsConfig(5-8)
useComponents(19-42)src/SelectInput/index.tsx (1)
SelectInputRef(15-19)src/SelectTrigger.tsx (1)
RefTriggerProps(52-54)src/OptionList.tsx (1)
RefOptionListProps(22-26)src/hooks/useOpen.ts (1)
useOpen(36-93)src/hooks/useSelectTriggerControl.ts (1)
useSelectTriggerControl(4-37)src/hooks/useBaseProps.ts (1)
BaseSelectContext(16-16)src/BaseSelect/Polite.tsx (1)
Polite(9-32)
tests/Multiple.test.tsx (1)
tests/utils/common.ts (1)
toggleOpen(22-31)
tests/Custom.test.tsx (1)
tests/utils/common.ts (1)
waitFakeTimer(138-148)
src/hooks/useAllowClear.tsx (1)
src/interface.ts (2)
DisplayValueType(13-20)
Mode(26-26)
tests/Select.test.tsx (1)
tests/utils/common.ts (2)
keyDown(113-122)
expectOpen(4-20)
src/hooks/useOpen.ts (1)
tests/utils/common.ts (1)
toggleOpen(22-31)
src/SelectInput/Input.tsx (3)
src/SelectInput/context.ts (1)
useSelectInputContext(8-10)src/hooks/useBaseProps.ts (1)
useBaseProps(18-20)src/hooks/useLayoutEffect.ts (1)
useLayoutEffect(8-16)
tests/placeholder.test.tsx (1)
tests/utils/common.ts (1)
toggleOpen(22-31)
src/SelectInput/index.tsx (4)
src/hooks/useComponents.ts (1)
ComponentsConfig(5-8)src/utils/keyUtil.ts (1)
isValidateOpenKey(4-45)tests/utils/common.ts (1)
toggleOpen(22-31)src/SelectInput/Affix.tsx (1)
Affix(8-16)
src/SelectInput/Content/SingleContent.tsx (4)
src/SelectInput/Content/index.tsx (1)
SharedContentProps(8-10)src/SelectInput/context.ts (1)
useSelectInputContext(8-10)src/utils/commonUtil.ts (1)
getTitle(29-40)src/SelectInput/Content/Placeholder.tsx (1)
Placeholder(8-26)
🪛 GitHub Actions: ✅ test
examples/SelectContentExample.tsx
[error] 22-24: Type '{ prefixCls: string; multiple: boolean; value: DisplayValueType[]; }' is not assignable to type 'IntrinsicAttributes & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & RefAttributes'.
[error] 27-27: Type '{ prefixCls: string; multiple: boolean; value: DisplayValueType[]; }' is not assignable to type 'IntrinsicAttributes & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & RefAttributes'.
[error] 32-32: Type '{ prefixCls: string; multiple: boolean; value: DisplayValueType[]; }' is not assignable to type 'IntrinsicAttributes & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & RefAttributes'.
[error] 37-37: Type '{ prefixCls: string; multiple: boolean; value: DisplayValueType[]; }' is not assignable to type 'IntrinsicAttributes & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & RefAttributes'.
examples/InputExample.tsx
[error] 13-13: Type '{ prefixCls: string; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & InputProps & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & InputProps & RefAttributes'.
[error] 19-19: Type '{ prefixCls: string; value: string; onChange: (e: ChangeEvent) => void; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & InputProps & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & InputProps & RefAttributes'.
[error] 30-30: Type '{ prefixCls: string; disabled: true; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & InputProps & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & InputProps & RefAttributes'.
[error] 39-39: Type '{ prefixCls: string; className: string; style: { border: string; borderRadius: string; }; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & InputProps & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & InputProps & RefAttributes'.
src/SelectInput/index.tsx
[error] 207-207: Argument of type 'readonly ["value", "onChange", "removeIcon", "placeholder", "maxTagCount", "maxTagTextLength", "maxTagPlaceholder", "choiceTransitionName", "onInputKeyDown", "onPopupScroll", "tabIndex", "activeValue", "onSelectorRemove", "focused"]' is not assignable to parameter of type '("hidden" | "color" | "content" | "contextMenu" | "slot" | "title" | "onMouseEnter" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | ... 249 more ... | "focused")[] | readonly ("hidden" | ... 259 more ... | "focused")[]'. Type '"value" | "tabIndex" | "onChange" | "placeholder" | "activeValue" | "choiceTransitionName" | "maxTagTextLength" | "maxTagCount" | "maxTagPlaceholder" | "removeIcon" | "onPopupScroll" | "onInputKeyDown" | "onSelectorRemove" | "focused"' is not assignable to type '"hidden" | "color" | "content" | "contextMenu" | "slot" | "title" | "onMouseEnter" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | ... 249 more ... | "focused"'.
🔇 Additional comments (48)
src/SelectTrigger.tsx (3)
106-106: 解构实现正确。从 props 中解构
onPopupMouseDown的实现遵循了现有模式,代码结构清晰。
167-171: 事件处理器正确连接到弹出层。将
onMouseDown事件处理器添加到弹出层包装 div 的实现正确,与现有的onMouseEnter处理器保持一致。类型安全性得到保证(React.MouseEventHandler<HTMLDivElement>)。需要注意的是,包装 div 上的
onMouseDown可能会影响弹出内容内部的事件传播。请确保这不会干扰弹出内容中现有的 mousedown 处理器。
79-79: 保持onPopupMouseDown为必需属性。BaseSelect 组件在所有渲染中都会传入该回调,与onPopupMouseEnter保持一致,无需改为可选。Likely an incorrect or invalid review comment.
src/hooks/useSelectTriggerControl.ts (3)
1-2: LGTM!引入 useEvent 改进了代码质量。使用
useEvent替代之前基于 ref 的状态追踪是一个很好的重构,它提供了稳定的回调引用同时能访问最新的闭包变量,避免了不必要的重新渲染。
33-36: LGTM!useEffect 依赖项简化正确。由于
onGlobalMouseDown通过useEvent包装提供了稳定的引用,将其作为唯一依赖项是正确的。这避免了不必要的监听器重新注册,同时确保回调始终访问最新的闭包值。
4-9: 请运行以下脚本以确认useSelectTriggerControl的所有调用是否与新签名兼容:#!/bin/bash # 查找所有调用 useSelectTriggerControl 的地方,检查传入的 elements 函数的兼容性 rg -n -C3 'useSelectTriggerControl\(' -g '*.ts' -g '*.tsx'src/TransBtn.tsx (1)
15-24: 文档补充到位注释准确描述了组件作用与交互行为,符合现有实现。
tests/shared/removeSelectedTest.tsx (1)
45-45: 更新选择器以匹配新的类名命名规范改为 .rc-select-item-remove 看起来与本次语义化重构一致。请同时确认测试工具 removeSelection/其它测试中的旧类名也已统一替换,避免选择器漂移导致的假阳性/假阴性。
jest.config.js (1)
1-3: Jest 环境初始化配置合理在环境后加载 tests/setup.ts 符合预期,便于全局 mock 与定制断言。
tests/shared/inputFilterTest.tsx (1)
4-4: 基于计时器的异步断言处理正确引入 act 包裹 runAllTimers,并在用例末尾切回真实计时器,避免测试泄漏,写法稳健。
Also applies to: 8-8, 27-29, 31-31
examples/SelectContentExample.tsx (1)
22-22: 示例不应直接使用 SelectContent 传入 prefixCls/multiple/value
示例中 SelectContent 为内部组件,仅接受inputProps和ref,其余属性需通过SelectInputContext或上层Select组件提供,请修改示例以正确展示用法。Likely an incorrect or invalid review comment.
assets/index.less (1)
2-2: 样式模块导入正确。引入
patch.less为新的选择器组件提供样式支持,与 PR 中提到的样式重构保持一致。tests/Custom.test.tsx (1)
17-26: 测试更新符合新的异步测试模式。将测试函数改为
async并在交互后使用await waitFakeTimer()确保定时器驱动的 UI 更新完成后再进行断言,与 PR 中其他测试的更新模式一致。tests/shared/allowClearTest.tsx (1)
9-9: 清除按钮选择器更新正确。类名从
.rc-select-clear-icon更新为.rc-select-clear,与组件重构后的新 DOM 结构保持一致。tests/Accessibility.test.tsx (1)
65-68: 定时器处理正确包裹在 act 中。将
jest.runAllTimers()包裹在act()块中确保所有 React 状态更新在断言之前完成,这是 React 测试的最佳实践。docs/examples/multiple-with-maxCount.tsx (1)
17-17: 启用搜索功能的 prop 添加正确。为多选示例添加
showSearch属性,启用搜索 UI 功能。src/Select.tsx (1)
241-241: 搜索配置钩子调用更新正确。将
mode参数传递给useSearchConfig,使搜索配置能够根据不同的选择模式(combobox、tags、multiple)进行适配,与src/hooks/useSearchConfig.ts中更新的签名一致。src/hooks/useBaseProps.ts (1)
13-13: LGTM! 增强了可访问性支持。添加可选的
role属性为 BaseSelect 组件提供了 ARIA 角色自定义能力,这是一个良好的可访问性改进。属性定义符合 TypeScript 和 React 规范。tests/BaseSelect.test.tsx (2)
5-5: LGTM! 测试的定时器管理得到改进。引入
waitFakeTimer工具函数并在beforeEach/afterEach中统一管理假定时器,确保了测试环境的一致性和可靠性。Also applies to: 12-18
20-51: LGTM! 正确处理了异步测试行为。将测试转换为异步函数并在用户交互后使用
await waitFakeTimer()等待定时器驱动的 UI 更新,这确保了断言在正确的时机执行。tests/Tags.test.tsx (1)
68-68: LGTM! 简化了代码结构。移除未使用的
option2常量并将其内联使用,减少了不必要的变量声明。tests/shared/blurTest.tsx (2)
5-5: LGTM! 正确使用 act() 包装定时器操作。将
jest.runAllTimers()包装在act()中确保了 React 状态更新的正确同步,这是 React 测试的最佳实践。Also applies to: 37-39
68-70: LGTM! 选择器与新的组件结构保持一致。将选择器从
.rc-select-selector更新为.rc-select反映了 PR 中组件 DOM 结构的变化。tests/placeholder.test.tsx (2)
28-32: LGTM! 选择器已更新以匹配新的 DOM 结构。从
.rc-select-selection-item更新为.rc-select-content-value反映了组件内容渲染结构的变化。
9-15: 手动验证受控 searchValue 时的 placeholder 可见性
自动化搜索未能定位到相关渲染或样式逻辑,请在组件实现中手动确认当searchValue受控时.rc-select-placeholder的可见性行为是否符合预期。tests/Combobox.test.tsx (4)
77-80: LGTM! 选择器已更新以匹配新的 CSS 类名。测试选择器已统一更新:
.rc-select-selection-placeholder→.rc-select-placeholder
.rc-select-clear-icon→.rc-select-clear这些更改与 PR 中的组件结构调整保持一致。
Also applies to: 338-339, 349-350, 361-364
115-115: LGTM! 使用了更符合语义的断言匹配器。从
.value属性访问改为使用toHaveValue()匹配器,这是 testing-library 推荐的更具表达性的断言方式。Also applies to: 322-322
470-487: LGTM! 正确管理了测试中的假定时器。添加了
jest.useFakeTimers()和jest.useRealTimers()调用以及jest.runAllTimers()包装在act()中,确保了定时器驱动的测试行为的可预测性。
620-621: LGTM! 选择器与组件结构变化同步。从
.rc-select-selector更新为.rc-select,反映了组件 DOM 结构的简化。Also applies to: 637-638
tests/utils/common.ts (4)
6-8: LGTM! 测试工具函数正确处理定时器同步。在
expectOpen、toggleOpen和selectItem中将定时器操作包装在act()中,确保了所有 React 状态更新在断言执行前完成。这是关键的测试可靠性改进。Also applies to: 25-27, 36-38
24-24: LGTM! 选择器已更新以匹配新的组件结构。从
.rc-select-selector更新为.rc-select,与 PR 中的组件 DOM 重构保持一致。Also applies to: 30-30
47-49: LGTM! 增强了选择器的健壮性。使用可选链操作符并添加
.rc-select-content-value作为后备选择器,提高了在不同 DOM 结构下查找元素的容错能力。
57-58: LGTM! 非 HTMLElement 分支的选择器保持一致。更新了 Enzyme 风格测试的选择器以匹配新的类名结构。
tests/shared/maxTagRenderTest.tsx (1)
58-59: LGTM!显式检查
.rc-select-content-item的数量和.rc-select-content-item-rest的不存在,这比快照测试更加清晰和稳定。tests/Multiple.test.tsx (4)
36-42: LGTM!在
beforeEach和afterEach中添加计时器生命周期管理是良好的测试实践,确保了测试的隔离性和可预测性。
371-383: LGTM!将
suffixIcon重命名为suffix并更新相应的选择器(从.rc-select-arrow到.rc-select-suffix)与组件重构保持一致。
437-443: LGTM!使用
toHaveStyle断言代替直接属性访问提高了测试的可读性和健壮性。
518-521: LGTM!验证
tabIndex正确应用到input元素而不是根元素,符合可访问性最佳实践。assets/patch.less (3)
12-22: LGTM!内容区域的 flex 布局配置合理,使用
min-width: 0允许 flex 项收缩,结合text-overflow: ellipsis实现了良好的文本截断效果。
32-36: LGTM!使用
\00a0(不间断空格)作为占位符的::after伪元素是一个巧妙的技巧,可以在占位符为空时保持布局稳定性。
78-81: LGTM!使用 CSS 自定义属性
--select-input-width来动态控制输入框宽度是一个灵活的方案,最小宽度 4px 确保了输入框始终可见。src/SelectInput/Content/index.tsx (1)
12-31: LGTM!
SelectContent组件的实现清晰且职责单一:
- 正确使用
forwardRef传递引用- 使用
pickAttrs提取 ARIA 属性确保可访问性- 根据
showSearch设置readOnly属性符合预期- 根据
multiple标志路由到相应的内容组件src/hooks/useComponents.ts (2)
5-17: LGTM!接口设计良好:
ComponentsConfig提供灵活的组件替换选项
FilledComponentsConfig使用强类型的ForwardRefExoticComponent确保类型安全- 类型定义涵盖了输入元素的多种可能类型(input 和 textarea)
19-41: LGTM!钩子实现正确:
- 优先使用函数式元素获取器(
getRawInputElement和getInputElement)而非直接传入的组件- 正确使用
useMemo进行记忆化优化- 依赖数组包含所有相关值,避免闭包陷阱
src/hooks/useSearchConfig.ts (1)
30-38: multiple 模式下默认启用 showSearch 符合预期经排查,仅测试和示例中使用了
mode="multiple"且未显式传入showSearch,与文档保持一致。tests/Select.test.tsx (1)
1572-1579: loading 图标断言覆盖单选与多选,命名与语义一致断言基于
.rc-select-suffix-loading与.rc-select-suffix-icon的存在性,与新语义一致,测试用例更新合理。src/SelectInput/index.tsx (2)
124-155: 键盘打开逻辑与旧行为对齐,防止 TextArea 方向键误开
- UP/DOWN 在非 TextArea 且已打开时
preventDefault,避免滚动冲突;- 通过
isValidateOpenKey(which)控制开合,过滤系统键;
此段逻辑清晰,符合预期交互。
171-196: 鼠标交互与惰性标记(_select_lazy)对 root 的
onMouseDown中使用_select_lazy标记避免清除按钮触发 focus/open,且在单选 +(combobox|showSearch)时阻止关闭,细节处理到位。
| display: inline-flex; | ||
| align-items: center; | ||
| user-select: none; | ||
| border: 1px solid blue; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
移除或配置化硬编码的蓝色边框。
硬编码的 border: 1px solid blue; 看起来像是用于调试或演示目的。应该将其移除或改为可配置的变量,以便在生产环境中使用。
应用此差异移除硬编码边框:
   display: inline-flex;
   align-items: center;
   user-select: none;
-  border: 1px solid blue;
   position: relative;或者使用变量:
-  border: 1px solid blue;
+  border: @select-border-width @select-border-style @select-border-color;🤖 Prompt for AI Agents
In assets/patch.less around line 8 (line 8 only), there is a hard-coded debug
style "border: 1px solid blue;" — remove this hard-coded rule or replace it with
a configurable Less variable (e.g. use a border declaration that references a
variable like @patch-border or @debug-border with a sensible default or none)
and update your variables/settings file to define that variable; ensure any
consumers fallback when the variable is unset and remove any remaining
debug-only styling before production.
        
          
                examples/InputExample.tsx
              
                Outdated
          
        
      |  | ||
| <div> | ||
| <h3>Basic Input</h3> | ||
| <Input prefixCls="rc-select" placeholder="Type something..." /> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
示例使用了 Input 未声明的 prop(prefixCls),导致编译失败
InputProps 未包含 prefixCls,四处用法均触发 TS 报错。请移除 prefixCls 或改用组件实际支持的 props。
可先用以下修复解 unblock:
-        <Input prefixCls="rc-select" placeholder="Type something..." />
+        <Input placeholder="Type something..." />
-        <Input 
-          prefixCls="rc-select" 
+        <Input 
           value={value} 
           onChange={(e) => setValue(e.target.value)}
           placeholder="Controlled input..."
         />
-        <Input 
-          prefixCls="rc-select" 
+        <Input 
           disabled 
           placeholder="Disabled input..."
         />
-        <Input 
-          prefixCls="rc-select" 
+        <Input 
           className="custom-input"
           style={{ border: '2px solid #1890ff', borderRadius: '4px' }}
           placeholder="Styled input..."
         />如需核对 InputProps 是否确实不含 prefixCls,可运行:
Also applies to: 19-19, 30-30, 39-39
🏁 Script executed:
#!/bin/bash
# 查找 InputProps 定义与是否包含 prefixCls
rg -nP --type=ts -C2 '(interface|type)\s+InputProps\b|prefixCls' src/SelectInputLength of output: 6027
为 InputProps 接口添加 prefixCls 声明
src/SelectInput/Input.tsx 中 InputProps 缺少 prefixCls,导致示例 <Input prefixCls="rc-select" /> TS 报错。请补充:
 export interface InputProps {
   id?: string;
   readOnly?: boolean;
+  /** CSS 类名前缀 */
+  prefixCls: string;
   // … 其他已有属性
 }📝 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.
| <Input prefixCls="rc-select" placeholder="Type something..." /> | |
| // In src/SelectInput/Input.tsx | |
| export interface InputProps { | |
| id?: string; | |
| readOnly?: boolean; | |
| /** CSS 类名前缀 */ | |
| prefixCls: string; | |
| // … 其他已有属性 | |
| } | 
🧰 Tools
🪛 GitHub Actions: ✅ test
[error] 13-13: Type '{ prefixCls: string; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & InputProps & RefAttributes'. Property 'prefixCls' does not exist on type 'IntrinsicAttributes & InputProps & RefAttributes'.
🤖 Prompt for AI Agents
In examples/InputExample.tsx around line 13 the example uses <Input
prefixCls="rc-select" /> but InputProps in src/SelectInput/Input.tsx lacks a
prefixCls declaration; update src/SelectInput/Input.tsx by adding prefixCls?:
string to the InputProps interface and ensure the component accepts and forwards
that prop to the rendered element (e.g., include prefixCls in the props
destructuring and pass it into the root element or className computation) so the
example type-checks.
| const onGlobalMouseDown = useEvent((event: MouseEvent) => { | ||
| // If trigger is customized, Trigger will take control of popupVisible | ||
| if (customizedTrigger) { | ||
| return; | ||
| } | ||
|  | ||
| let target = event.target as HTMLElement; | ||
| let target = event.target as HTMLElement; | ||
|  | ||
| if (target.shadowRoot && event.composed) { | ||
| target = (event.composedPath()[0] || target) as HTMLElement; | ||
| } | ||
| if (target.shadowRoot && event.composed) { | ||
| target = (event.composedPath()[0] || target) as HTMLElement; | ||
| } | ||
|  | ||
| if ( | ||
| propsRef.current.open && | ||
| elements() | ||
| .filter((element) => element) | ||
| .every((element) => !element.contains(target) && element !== target) | ||
| ) { | ||
| // Should trigger close | ||
| propsRef.current.triggerOpen(false); | ||
| } | ||
| if ( | ||
| open && | ||
| elements() | ||
| .filter((element) => element) | ||
| .every((element) => !element.contains(target) && element !== target) | ||
| ) { | ||
| // Should trigger close | ||
| triggerOpen(false); | ||
| } | ||
| }); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
类型转换与新的 SVGElement 支持不一致。
useEvent 的重构看起来不错,逻辑正确。但是,第 16 行和第 19 行将 target 强制转换为 HTMLElement,这与新增的 SVGElement 支持不一致。如果用户点击了 SVG 元素,event.target 可能是 SVGElement,类型转换会不准确。
应用以下差异修复类型安全问题:
-    let target = event.target as HTMLElement;
+    let target = event.target as Element;
     if (target.shadowRoot && event.composed) {
-      target = (event.composedPath()[0] || target) as HTMLElement;
+      target = (event.composedPath()[0] || target) as Element;
     }注意:使用 Element 类型(HTMLElement 和 SVGElement 的公共父类)更准确,且 contains() 方法在 Element 上可用。
🤖 Prompt for AI Agents
In src/hooks/useSelectTriggerControl.ts around lines 10 to 31, the code
currently casts event targets to HTMLElement which breaks type-safety for
SVGElement clicks; change the target typing and casts to the more general
Element type (use Element for target and when reading composedPath entries) so
contains() calls remain valid for both HTMLElement and SVGElement, and preserve
the composedPath handling and early return for customizedTrigger while ensuring
TypeScript uses Element rather than HTMLElement.
| const customizeRenderSelector = ( | ||
| value: RawValueType, | ||
| content: React.ReactNode, | ||
| itemDisabled: boolean, | ||
| closable?: boolean, | ||
| onClose?: React.MouseEventHandler, | ||
| isMaxTag?: boolean, | ||
| info?: { index: number }, | ||
| ) => { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
customizeRenderSelector 的参数类型与调用不一致,编译期错误
renderRest 调用 customizeRenderSelector(undefined, …) 时传入了 undefined,但 value 的类型声明为 RawValueType(string | number,不允许 undefined)。这会导致 TS 报错并阻断编译。建议放宽 value 参数类型以覆盖 “更多标签” 的场景。
可按以下方式修改:
-  const customizeRenderSelector = (
-    value: RawValueType,
+  const customizeRenderSelector = (
+    value?: RawValueType,
     content: React.ReactNode,
     itemDisabled: boolean,
     closable?: boolean,
     onClose?: React.MouseEventHandler,
     isMaxTag?: boolean,
     info?: { index: number },
   ) => {说明:CustomTagProps 的 value 是 any(参见 BaseSelect 中的定义),放宽为可选或 RawValueType | undefined 均可满足使用方。Based on learnings
Also applies to: 170-182
🤖 Prompt for AI Agents
In src/SelectInput/Content/MultipleContent.tsx around lines 106-114, the
customizeRenderSelector signature declares value as RawValueType (string|number)
but callers (e.g., renderRest) pass undefined for the "more tags" case, causing
TS compile errors; change the value parameter to be optional or accept undefined
(e.g., value?: RawValueType or RawValueType | undefined) so calls with undefined
are valid, and apply the same relaxation to the other occurrence around lines
170-182.
| 'aria-owns': `${id}_list`, | ||
| 'aria-autocomplete': 'list' as const, | ||
| 'aria-controls': `${id}_list`, | ||
| 'aria-activedescendant': open ? activeDescendantId : undefined, | ||
| }; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当未提供 id 时避免输出无效 ARIA 关联
aria-owns / aria-controls 在 id 缺失时会变成 undefined_list。建议按有无 id 条件赋值,避免无效可访问性属性。
应用如下修改:
-    'aria-owns': `${id}_list`,
+    'aria-owns': id ? `${id}_list` : undefined,
...
-    'aria-controls': `${id}_list`,
+    'aria-controls': id ? `${id}_list` : undefined,📝 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.
| 'aria-owns': `${id}_list`, | |
| 'aria-autocomplete': 'list' as const, | |
| 'aria-controls': `${id}_list`, | |
| 'aria-activedescendant': open ? activeDescendantId : undefined, | |
| }; | |
| 'aria-owns': id ? `${id}_list` : undefined, | |
| 'aria-autocomplete': 'list' as const, | |
| 'aria-controls': id ? `${id}_list` : undefined, | |
| 'aria-activedescendant': open ? activeDescendantId : undefined, | 
🤖 Prompt for AI Agents
In src/SelectInput/Input.tsx around lines 181 to 185, aria-owns and
aria-controls are currently built using `${id}_list` which produces invalid
values like `undefined_list` when id is not provided; change the logic to only
set 'aria-owns' and 'aria-controls' when id is truthy (e.g., id ? `${id}_list` :
undefined) so those ARIA attributes are omitted if no id exists, keep
'aria-autocomplete' and 'aria-activedescendant' behavior unchanged.
There was a problem hiding this 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
♻️ Duplicate comments (1)
src/SelectInput/index.tsx (1)
264-267: 避免在原生事件对象上打补丁_select_lazy此前已有类似反馈。建议用 ref 标记或
data-属性判断点击来源,替代修改event.nativeEvent。如无法避免,请补充注释说明必要性与作用域。
🧹 Nitpick comments (7)
src/SelectInput/index.tsx (5)
52-67: 修复 omit 的类型宽化方式,避免any失去类型约束当前用
as any绕过类型系统。建议将常量声明为string[]并最小化断言范围,保留更多类型信息。-const DEFAULT_OMIT_PROPS = [ +const DEFAULT_OMIT_PROPS: string[] = [ 'value', 'onChange', 'removeIcon', 'placeholder', 'maxTagCount', 'maxTagTextLength', 'maxTagPlaceholder', 'choiceTransitionName', 'onInputKeyDown', 'onPopupScroll', 'tabIndex', 'activeValue', 'onSelectorRemove', 'focused', -] as const; +] ; @@ - const domProps = omit(restProps, DEFAULT_OMIT_PROPS as any); + const domProps = omit(restProps as any, DEFAULT_OMIT_PROPS);这与先前流水线失败的建议一致,同时尽量减少
any的扩散。Based on learningsAlso applies to: 207-207
121-121: inputRef 类型过窄,建议支持 TextArea代码里用到了
instanceof HTMLTextAreaElement判断,但inputRef声明为HTMLInputElement。建议改为联合类型以匹配实际:- const inputRef = React.useRef<HTMLInputElement>(null); + const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>(null);
124-155: 键盘处理使用了过时的which与位运算技巧,建议现代化与可读性改进
- 使用
event.key/event.keyCode替代which。- 用
includes()替代~indexOf提升可读性。示例改写(要点):
- const { which } = event; + const { key, keyCode } = event; @@ - if (!isTextAreaElement && triggerOpen && (which === KeyCode.UP || which === KeyCode.DOWN)) { + if (!isTextAreaElement && triggerOpen && (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN)) { event.preventDefault(); } @@ - if ( - isTextAreaElement && - !triggerOpen && - ~[KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].indexOf(which) - ) { + if ( + isTextAreaElement && + !triggerOpen && + [KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].includes(keyCode) + ) { return; } @@ - if (isValidateOpenKey(which)) { + if (isValidateOpenKey(keyCode)) { toggleOpen(true); }
10-10: 统一 clsx 引入方式本文件使用默认导入
import clsx from 'clsx',而 BaseSelect 使用命名导入import { clsx } from 'clsx'。为一致性与类型友好(TS/exports map),建议统一为命名导入:-import clsx from 'clsx'; +import { clsx } from 'clsx';
15-19: SelectInputRef.nativeElement 可为 null,类型建议放宽在
useImperativeHandle初次建立时rootRef.current可能为null。建议将类型放宽为可空,避免潜在的严格空检查报错:-export interface SelectInputRef { +export interface SelectInputRef { focus: (options?: FocusOptions) => void; blur: () => void; - nativeElement: HTMLDivElement; + nativeElement: HTMLDivElement | null; }BaseSelect 中相应的
BaseSelectRef['nativeElement']也应放宽(见对 BaseSelect 的单独评论)。Also applies to: 167-168
src/BaseSelect/index.tsx (2)
321-326: BaseSelectRef.nativeElement 可能为空;类型与实现建议放宽
getDOM(containerRef.current)在未挂载或 SSR 场景可能返回null。当前强制断言为HTMLElement。
- 建议将
BaseSelectRef['nativeElement']放宽为HTMLElement | null。- 赋值时不做强断言,保持一致。
示例(接口与实现同步调整):
// interface: export interface BaseSelectRef { focus: (options?: FocusOptions) => void; blur: () => void; scrollTo: ScrollTo; nativeElement: HTMLElement | null; } // useImperativeHandle: nativeElement: getDOM(containerRef.current),
688-699: class 计算良好,但clsx使用风格需与其他文件一致本文件采用命名导入
{ clsx },SelectInput 采用默认导入。建议统一(推荐命名导入)。参见对 SelectInput 的建议。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
- src/BaseSelect/index.tsx(23 hunks)
- src/SelectInput/index.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/SelectInput/index.tsx (3)
src/hooks/useComponents.ts (1)
ComponentsConfig(5-8)src/utils/keyUtil.ts (1)
isValidateOpenKey(4-45)src/SelectInput/Affix.tsx (1)
Affix(8-16)
src/BaseSelect/index.tsx (9)
src/interface.ts (2)
RenderNode(22-22)
DisplayValueType(13-20)src/hooks/useComponents.ts (2)
ComponentsConfig(5-8)
useComponents(19-42)src/SelectInput/index.tsx (1)
SelectInputRef(15-19)src/SelectTrigger.tsx (1)
RefTriggerProps(52-54)src/OptionList.tsx (1)
RefOptionListProps(22-26)src/hooks/useOpen.ts (1)
useOpen(36-93)src/hooks/useSelectTriggerControl.ts (1)
useSelectTriggerControl(4-37)src/hooks/useBaseProps.ts (1)
BaseSelectContext(16-16)src/BaseSelect/Polite.tsx (1)
Polite(9-32)
🔇 Additional comments (2)
src/BaseSelect/index.tsx (2)
701-740: 属性透传与事件链路清晰,SelectInput 注入合理(小建议:onInputKeyDown 贯通)当前通过 Context 包装了
onInternalInputKeyDown,对外部onInputKeyDown无需在此处额外传递。若需要让外部onInputKeyDown同时作用于输入框,可考虑在 SelectInput 内部 wrapper 中先调用外部回调后再进行内部开合逻辑(已基本符合)。无需改动,仅确认预期。
606-612: 全局点击关闭逻辑合理,定制 root 时按customizedTrigger分流该段使用
useSelectTriggerControl管理外部点击关闭且考虑了自定义 root 的接管,符合预期。
| if (RootComponent) { | ||
| if (React.isValidElement<any>(RootComponent)) { | ||
| return React.cloneElement(RootComponent, { | ||
| ...domProps, | ||
| ref: composeRef((RootComponent as any).ref, rootRef), | ||
| }); | ||
| } | ||
|  | ||
| return <RootComponent {...domProps} ref={rootRef} />; | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
components.root 导致结构和行为丢失(未渲染 Provider 与内容、事件未绑定)
当传入 components.root 时,当前实现直接返回自定义元素,未包裹 SelectInputContext.Provider,也未渲染 Prefix/Content/Suffix/Clear/children,更未绑定 onMouseDown/onBlur、className/style 等。这会使自定义 root 完全失去选择器的功能与语义,属于功能性缺陷。
建议:始终渲染 Provider;对自定义 root 仅替换“外层容器”而非整体结构;统一注入 props、ref 与事件,并在其 children 渲染原有内部内容。
可按以下方式重构该分支与默认分支合并为一套渲染路径:
@@
-  if (RootComponent) {
-    if (React.isValidElement<any>(RootComponent)) {
-      return React.cloneElement(RootComponent, {
-        ...domProps,
-        ref: composeRef((RootComponent as any).ref, rootRef),
-      });
-    }
-
-    return <RootComponent {...domProps} ref={rootRef} />;
-  }
-
-  return (
-    <SelectInputContext.Provider value={contextValue}>
-      <div
-        {...domProps}
-        // Style
-        ref={rootRef}
-        className={className}
-        style={style}
-        // Mouse Events
-        onMouseDown={onInternalMouseDown}
-        onBlur={onInternalBlur}
-      >
-        {/* Prefix */}
-        <Affix className={clsx(`${prefixCls}-prefix`, classNames?.prefix)} style={styles?.prefix}>
-          {prefix}
-        </Affix>
-
-        {/* Content */}
-        <SelectContent ref={inputRef} />
-
-        {/* Suffix */}
-        <Affix
-          className={clsx(
-            `${prefixCls}-suffix`,
-            {
-              [`${prefixCls}-suffix-loading`]: loading,
-            },
-            classNames?.suffix,
-          )}
-          style={styles?.suffix}
-        >
-          {suffix}
-        </Affix>
-        {/* Clear Icon */}
-        {clearIcon && (
-          <Affix
-            className={clsx(`${prefixCls}-clear`, classNames?.clear)}
-            style={styles?.clear}
-            onMouseDown={(e) => {
-              // Mark to tell not trigger open or focus
-              (e.nativeEvent as any)._select_lazy = true;
-              onClearMouseDown?.(e);
-            }}
-          >
-            {clearIcon}
-          </Affix>
-        )}
-        {children}
-      </div>
-    </SelectInputContext.Provider>
-  );
+  // Build root props used by both default div and custom RootComponent
+  const rootProps = {
+    ...domProps,
+    ref: rootRef,
+    className,
+    style,
+    onMouseDown: onInternalMouseDown,
+    onBlur: onInternalBlur,
+  };
+
+  const inner = (
+    <>
+      {/* Prefix */}
+      <Affix className={clsx(`${prefixCls}-prefix`, classNames?.prefix)} style={styles?.prefix}>
+        {prefix}
+      </Affix>
+      {/* Content */}
+      <SelectContent ref={inputRef} />
+      {/* Suffix */}
+      <Affix
+        className={clsx(
+          `${prefixCls}-suffix`,
+          { [`${prefixCls}-suffix-loading`]: loading },
+          classNames?.suffix,
+        )}
+        style={styles?.suffix}
+      >
+        {suffix}
+      </Affix>
+      {/* Clear Icon */}
+      {clearIcon && (
+        <Affix
+          className={clsx(`${prefixCls}-clear`, classNames?.clear)}
+          style={styles?.clear}
+          onMouseDown={(e) => {
+            (e.nativeEvent as any)._select_lazy = true;
+            onClearMouseDown?.(e);
+          }}
+        >
+          {clearIcon}
+        </Affix>
+      )}
+      {children}
+    </>
+  );
+
+  const rootElement = RootComponent
+    ? React.isValidElement<any>(RootComponent)
+      ? React.cloneElement(RootComponent, {
+          ...rootProps,
+          ref: composeRef((RootComponent as any).ref, rootRef),
+        }, inner)
+      : React.createElement(RootComponent as any, rootProps, inner)
+    : React.createElement('div', rootProps, inner);
+
+  return (
+    <SelectInputContext.Provider value={contextValue}>
+      {rootElement}
+    </SelectInputContext.Provider>
+  );此改动恢复语义结构与交互,确保自定义 root 仅替换容器实现,且仍获得上下文和功能。
Also applies to: 226-276
🤖 Prompt for AI Agents
In src/SelectInput/index.tsx around lines 215-224 (and similarly lines 226-276),
returning a user-supplied components.root directly causes loss of Provider,
internal content (Prefix/Content/Suffix/Clear/children), and event bindings; fix
by always rendering the SelectInputContext.Provider and the internal structure,
using the supplied RootComponent only as the outer container: render
<RootComponent {...injectedProps} ref={composeRef((RootComponent as any).ref,
rootRef)}> and ensure injectedProps include all domProps, className, style,
onMouseDown, onBlur, other event handlers, aria attributes and merged refs, then
render the original internal elements (Prefix/Content/Suffix/Clear/children)
inside that container so the custom root replaces only the outer wrapper while
preserving context, behavior and events.
        
          
                src/SelectInput/index.tsx
              
                Outdated
          
        
      | import { omit, useEvent } from '@rc-component/util'; | ||
| import KeyCode from '@rc-component/util/lib/KeyCode'; | ||
| import { isValidateOpenKey } from '../utils/keyUtil'; | ||
| import clsx from 'clsx'; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里我统一用具名导入的:
| import clsx from 'clsx'; | |
| import { clsx } from 'clsx'; | 
| Codecov Report❌ Patch coverage is  
 Additional details and impacted files@@            Coverage Diff             @@
##           master    #1166      +/-   ##
==========================================
+ Coverage   98.11%   99.49%   +1.37%     
==========================================
  Files          39       31       -8     
  Lines        1489     1191     -298     
  Branches      452      399      -53     
==========================================
- Hits         1461     1185     -276     
+ Misses         28        6      -22     ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
 | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/SelectInput/Input.tsx (1)
181-185: 修复无效 ARIA 关联:id 缺失时不要输出undefined_list当未提供 id 时,
aria-owns/aria-controls会变成undefined_list,应按条件设置以避免无效属性。- 'aria-owns': `${id}_list`, + 'aria-owns': id ? `${id}_list` : undefined, @@ - 'aria-controls': `${id}_list`, + 'aria-controls': id ? `${id}_list` : undefined,src/SelectInput/index.tsx (1)
215-224: 修复自定义 Root 渲染丢失 Provider/结构/事件的问题:始终渲染 Provider 与内部结构当前传入
components.root时直接返回自定义元素,导致未渲染SelectInputContext.Provider、Prefix/Content/Suffix/Clear/children,也未绑定必要事件与类名,功能缺失。- if (RootComponent) { - if (React.isValidElement<any>(RootComponent)) { - return React.cloneElement(RootComponent, { - ...domProps, - ref: composeRef((RootComponent as any).ref, rootRef), - }); - } - - return <RootComponent {...domProps} ref={rootRef} />; - } - - return ( - <SelectInputContext.Provider value={contextValue}> - <div - {...domProps} - // Style - ref={rootRef} - className={className} - style={style} - // Mouse Events - onMouseDown={onInternalMouseDown} - onBlur={onInternalBlur} - > - {/* Prefix */} - <Affix className={clsx(`${prefixCls}-prefix`, classNames?.prefix)} style={styles?.prefix}> - {prefix} - </Affix> - - {/* Content */} - <SelectContent ref={inputRef} /> - - {/* Suffix */} - <Affix - className={clsx( - `${prefixCls}-suffix`, - { - [`${prefixCls}-suffix-loading`]: loading, - }, - classNames?.suffix, - )} - style={styles?.suffix} - > - {suffix} - </Affix> - {/* Clear Icon */} - {clearIcon && ( - <Affix - className={clsx(`${prefixCls}-clear`, classNames?.clear)} - style={styles?.clear} - onMouseDown={(e) => { - // Mark to tell not trigger open or focus - (e.nativeEvent as any)._select_lazy = true; - onClearMouseDown?.(e); - }} - > - {clearIcon} - </Affix> - )} - {children} - </div> - </SelectInputContext.Provider> - ); + // 统一构建外层容器 props + const rootProps = { + ...domProps, + ref: rootRef, + className, + style, + onMouseDown: onInternalMouseDown, + onBlur: onInternalBlur, + }; + + const inner = ( + <> + {/* Prefix */} + <Affix className={clsx(`${prefixCls}-prefix`, classNames?.prefix)} style={styles?.prefix}> + {prefix} + </Affix> + {/* Content */} + <SelectContent ref={inputRef} /> + {/* Suffix */} + <Affix + className={clsx( + `${prefixCls}-suffix`, + { [`${prefixCls}-suffix-loading`]: loading }, + classNames?.suffix, + )} + style={styles?.suffix} + > + {suffix} + </Affix> + {/* Clear Icon */} + {clearIcon && ( + <Affix + className={clsx(`${prefixCls}-clear`, classNames?.clear)} + style={styles?.clear} + onMouseDown={(e) => { + (e.nativeEvent as any)._select_lazy = true; + onClearMouseDown?.(e); + }} + > + {clearIcon} + </Affix> + )} + {children} + </> + ); + + const rootElement = RootComponent + ? React.isValidElement<any>(RootComponent) + ? React.cloneElement(RootComponent, { + ...rootProps, + ref: composeRef((RootComponent as any).ref, rootRef), + }, inner) + : React.createElement(RootComponent as any, rootProps, inner) + : React.createElement('div', rootProps, inner); + + return ( + <SelectInputContext.Provider value={contextValue}> + {rootElement} + </SelectInputContext.Provider> + );Also applies to: 226-276
🧹 Nitpick comments (3)
src/SelectInput/Input.tsx (2)
187-213: 合并自定义 Input 元素的 className 与 style,避免覆盖当前
{ ...sharedInputProps, ...existingProps }会用自定义元素的 className/style 覆盖共享样式与类名,应显式合并。- const mergedProps = { ...sharedInputProps, ...existingProps }; + const mergedProps = { ...sharedInputProps, ...existingProps }; + // 合并样式与类名(existingProps 优先覆盖冲突样式) + mergedProps.className = clsx(sharedInputProps.className, existingProps.className); + mergedProps.style = { ...(sharedInputProps.style || {}), ...(existingProps.style || {}) };
4-4: 可选:使用项目内的 useLayoutEffect 包装以统一 SSR 行为项目已有
src/hooks/useLayoutEffect封装,建议统一使用以减少包分散与行为差异。-import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect'; +import useLayoutEffect from '../hooks/useLayoutEffect';Based on learnings
src/BaseSelect/index.tsx (1)
616-626: 避免上下文中triggerOpen命名歧义(布尔 vs. 函数)
BaseSelectContext同时暴露open与triggerOpen(布尔),以及toggleOpen(函数)。triggerOpen既作为布尔又在其他处作为函数使用,易引发理解与误用。
- 保留
open: mergedOpen与toggleOpen: triggerOpen;- 移除或废弃
triggerOpen: mergedOpen,统一改为只读open;- 若需兼容,可暂时同时提供
triggerOpen(别名指向mergedOpen),但标记为内部/待移除。Also applies to: 631-641
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
- src/BaseSelect/index.tsx(23 hunks)
- src/SelectInput/Content/SingleContent.tsx(1 hunks)
- src/SelectInput/Input.tsx(1 hunks)
- src/SelectInput/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/SelectInput/Content/SingleContent.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/SelectInput/index.tsx (4)
src/BaseSelect/index.tsx (3)
RenderNode(47-47)
DisplayValueType(43-43)
Mode(44-44)src/hooks/useComponents.ts (1)
ComponentsConfig(5-8)src/utils/keyUtil.ts (1)
isValidateOpenKey(4-45)src/SelectInput/Affix.tsx (1)
Affix(8-16)
src/SelectInput/Input.tsx (3)
src/SelectInput/context.ts (1)
useSelectInputContext(8-10)src/hooks/useBaseProps.ts (1)
useBaseProps(18-20)src/hooks/useLayoutEffect.ts (1)
useLayoutEffect(8-16)
src/BaseSelect/index.tsx (8)
src/interface.ts (2)
RenderNode(22-22)
DisplayValueType(13-20)src/hooks/useComponents.ts (2)
ComponentsConfig(5-8)
useComponents(19-42)src/SelectInput/index.tsx (1)
SelectInputRef(15-19)src/SelectTrigger.tsx (1)
RefTriggerProps(52-54)src/OptionList.tsx (1)
RefOptionListProps(22-26)src/hooks/useOpen.ts (1)
useOpen(36-93)src/hooks/useSelectTriggerControl.ts (1)
useSelectTriggerControl(4-37)src/BaseSelect/Polite.tsx (1)
Polite(9-32)
| useBaseProps(); | ||
|  | ||
| const rootRef = React.useRef<HTMLDivElement>(null); | ||
| const inputRef = React.useRef<HTMLInputElement>(null); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
扩展 inputRef 类型以匹配 TextArea 判定
inputRef 仅声明为 HTMLInputElement,但后续使用了 instanceof HTMLTextAreaElement 判断;应扩大为联合类型以匹配真实节点。
-  const inputRef = React.useRef<HTMLInputElement>(null);
+  const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>(null);
@@
-      const isTextAreaElement = inputRef.current instanceof HTMLTextAreaElement;
+      const isTextAreaElement = inputRef.current instanceof HTMLTextAreaElement;Also applies to: 129-129
🤖 Prompt for AI Agents
In src/SelectInput/index.tsx around lines 121 and 129, inputRef is declared as
React.useRef<HTMLInputElement>(null) but the code later checks for instanceof
HTMLTextAreaElement; change the ref generic to React.useRef<HTMLInputElement |
HTMLTextAreaElement | null>(null) (or the equivalent union) and update any
usages/casts to handle the nullable union (narrow with instanceof or null
checks) so the TypeScript types match the runtime checks.
| const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => { | ||
| const { | ||
| onChange, | ||
| onKeyDown, | ||
| onBlur, | ||
| style, | ||
| syncWidth, | ||
| value, | ||
| className, | ||
| autoComplete, | ||
| ...restProps | ||
| } = props; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
转发缺失的 onFocus 事件
InputProps 定义了 onFocus,但未传递到实际 input,导致外部无法监听 focus。
 const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
   const {
     onChange,
     onKeyDown,
     onBlur,
+    onFocus,
     style,
     syncWidth,
     value,
     className,
     autoComplete,
     ...restProps
   } = props;
@@
     onChange: handleChange,
     onKeyDown: handleKeyDown,
     onBlur: handleBlur,
+    onFocus,
     onPaste: handlePaste,Also applies to: 156-185
🤖 Prompt for AI Agents
In src/SelectInput/Input.tsx around lines 26 to 37 (and also apply the same fix
at lines 156 to 185), the onFocus prop declared in InputProps is being omitted
from the forwarded props so consumers cannot listen for focus; include onFocus
in the component's destructuring (or ensure it is forwarded via ...restProps)
and pass it down to the underlying <input> element (and to any other input-like
element in the later block) so the external onFocus handler is invoked.
| const [widthCssVar, setWidthCssVar] = React.useState<number | undefined>(undefined); | ||
|  | ||
| // When syncWidth is enabled, adjust input width based on content | ||
| useLayoutEffect(() => { | ||
| const input = inputRef.current; | ||
|  | ||
| if (syncWidth && input) { | ||
| input.style.width = '0px'; | ||
| const scrollWidth = input.scrollWidth; | ||
| setWidthCssVar(scrollWidth); | ||
|  | ||
| // Reset input style | ||
| input.style.width = ''; | ||
| } | ||
| }, [syncWidth, value]); | ||
|  | ||
| // ============================= Render ============================= | ||
| // Extract shared input props | ||
| const sharedInputProps = { | ||
| id, | ||
| type: mode === 'combobox' ? 'text' : 'search', | ||
| ...restProps, | ||
| ref: inputRef as React.Ref<HTMLInputElement>, | ||
| style: { | ||
| ...styles?.input, | ||
| ...style, | ||
| '--select-input-width': widthCssVar, | ||
| } as React.CSSProperties, | ||
| autoFocus, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修复宽度同步的单位问题并在关闭时重置
CSS 变量需要带单位的字符串;同时当 syncWidth 关闭或无输入时应清理状态,避免遗留值。
-  const [widthCssVar, setWidthCssVar] = React.useState<number | undefined>(undefined);
+  const [widthCssVar, setWidthCssVar] = React.useState<string | undefined>(undefined);
@@
-  useLayoutEffect(() => {
+  useLayoutEffect(() => {
     const input = inputRef.current;
 
-    if (syncWidth && input) {
+    if (syncWidth && input) {
       input.style.width = '0px';
       const scrollWidth = input.scrollWidth;
-      setWidthCssVar(scrollWidth);
+      setWidthCssVar(`${scrollWidth}px`);
 
       // Reset input style
       input.style.width = '';
-    }
+    } else {
+      setWidthCssVar(undefined);
+    }
   }, [syncWidth, value]);
@@
-      '--select-input-width': widthCssVar,
+      '--select-input-width': widthCssVar,Also applies to: 144-152
🤖 Prompt for AI Agents
In src/SelectInput/Input.tsx around lines 138 to 166 (also applies to lines
144-152), the width CSS variable is currently stored as a number and not cleared
when syncWidth is disabled; change the state to hold a string including units
(e.g., "123px") and set that string when measuring input.scrollWidth, and ensure
you reset/clear the state to undefined when syncWidth is false or when there is
no content so the CSS var is removed; update the style assignment to pass the
string CSS variable and keep the existing input.style.width reset logic.
There was a problem hiding this 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)
jest.config.js (1)
3-3: 建议将覆盖率收集设为可配置。
collectCoverage: true强制在每次测试运行时收集覆盖率,这会显著降低开发时的测试速度。建议改为根据环境变量控制,仅在 CI 或显式请求时启用。应用此差异使覆盖率收集可配置:
- collectCoverage: true, + collectCoverage: process.env.CI === 'true' || process.env.COVERAGE === 'true',
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
- jest.config.js(1 hunks)
- src/hooks/useDelayReset.ts(0 hunks)
- src/hooks/useLayoutEffect.ts(0 hunks)
💤 Files with no reviewable changes (2)
- src/hooks/useDelayReset.ts
- src/hooks/useLayoutEffect.ts
⏰ 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 (1)
jest.config.js (1)
1-5: 确认 Jest 环境与转换配置
当前jest.config.js仅包含setupFilesAfterEnv与覆盖收集,请确认rc-test预设是否已包含 React+TypeScript 所需的:
- testEnvironment: 'jsdom'
- TypeScript/JSX 转换(如 babel-jest 或 ts-jest)
如未覆盖,可在
jest.config.js中显式添加:testEnvironment: 'jsdom', transform: { '^.+\\.(ts|tsx)$': 'babel-jest', },执行
npm test验证测试正常运行。
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (1)
src/BaseSelect/index.tsx (1)
606-620: 修复:BaseSelectContext 中的 triggerOpen 被错误地赋值为布尔值当前把
triggerOpen设成了mergedOpen(boolean)。下游如按上下文约定调用triggerOpen(...)将报错/失效。应传递触发函数本身。建议修改如下:
const baseSelectContext = React.useMemo<BaseSelectContextProps>( () => ({ ...props, notFoundContent, open: mergedOpen, - triggerOpen: mergedOpen, + triggerOpen: triggerOpen, id, showSearch, multiple, toggleOpen: triggerOpen, showScrollBar, styles, classNames, }), [ props, notFoundContent, - triggerOpen, + triggerOpen, id, showSearch, multiple, - mergedOpen, + mergedOpen, showScrollBar, styles, classNames, ], );Also applies to: 623-631
🧹 Nitpick comments (8)
tests/BaseSelect.test.tsx (2)
12-19: 全局计时器启用/还原已在 describe 级别统一处理,建议移除用例内重复设置已经在 beforeEach/afterEach 中统一 useFakeTimers/useRealTimers。单测内(本文件其他用例也有)再次调用会增加心智负担,可能导致计时器状态错乱。可依赖外层统一设置,去掉用例内重复 useFakeTimers/useRealTimers。
20-37: 避免在 JSDOM 中使用带 href 的 触发器在某些环境下可能产生导航/警告。这里仅需交互触发,建议改为 button 或移除 href,降低测试偶发性。
可以如下调整:
- getRawInputElement={() => ( - <a className="trigger" href="#"> - trigger - </a> - )} + getRawInputElement={() => ( + <button type="button" className="trigger">trigger</button> + )}Also applies to: 40-51
tests/Select.test.tsx (2)
845-874: 此处无需再次 useFakeTimers/useRealTimers文件已在 beforeEach/afterEach 统一控制计时器,建议移除本用例内的重复设置,避免潜在的计时器状态干扰。
1860-1885: 点击窗口外关闭的用例:计时器 flush 足够,但可简化已通过 runAllTimers flush 宏任务,建议依赖外层假定时器设置,移除本用例内的 useFakeTimers/useRealTimers 重复调用,代码更简洁。
Also applies to: 1878-1881
src/BaseSelect/index.tsx (4)
321-327: 避免 findDOMNode:直接使用 SelectInputRef.nativeElement既然
SelectInputRef已提供nativeElement,可直接返回以避免额外依赖和潜在 SSR/严格模式警告。可改为:
React.useImperativeHandle(ref, () => ({ focus: containerRef.current?.focus, blur: containerRef.current?.blur, scrollTo: (arg) => listRef.current?.scrollTo(arg), - nativeElement: getDOM(containerRef.current) as HTMLElement, + nativeElement: containerRef.current?.nativeElement as unknown as HTMLElement, }));
597-603: useSelectTriggerControl 的元素获取同样可直接用 nativeElement减少一次 getDOM 调用,更直接也更可靠。
- () => [getDOM(containerRef.current), triggerRef.current?.getPopupElement()], + () => [containerRef.current?.nativeElement, triggerRef.current?.getPopupElement()],
573-577: 条件判断冗余
&& triggerOpen恒为真(始终存在函数),可移除以简化逻辑。- if (popupElement?.contains(target as HTMLElement) && triggerOpen) { + if (popupElement?.contains(target as HTMLElement)) {
33-39: 注释与 API 不一致:声明“移除 getInputElement/getRawInputElement”,但类型仍对外暴露并在内部使用建议:
- 立即为
getInputElement/getRawInputElement标注@deprecated,文档对齐迁移路径(统一到components.input/components.root)。- 明确两者的优先级(当前
useComponents中这两者会覆盖components),并在下个大版本移除。Also applies to: 160-165, 330-331, 344-347
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
- src/BaseSelect/index.tsx(22 hunks)
- tests/BaseSelect.test.tsx(3 hunks)
- tests/Select.test.tsx(25 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tests/Select.test.tsx (2)
tests/shared/allowClearTest.tsx (1)
allowClearTest(5-51)tests/utils/common.ts (4)
waitFakeTimer(138-148)
toggleOpen(22-31)
expectOpen(4-20)
keyDown(113-122)
tests/BaseSelect.test.tsx (1)
tests/utils/common.ts (1)
waitFakeTimer(138-148)
src/BaseSelect/index.tsx (8)
src/hooks/useComponents.ts (2)
ComponentsConfig(5-8)
useComponents(19-42)src/SelectInput/index.tsx (1)
SelectInputRef(15-19)src/SelectTrigger.tsx (1)
RefTriggerProps(52-54)src/OptionList.tsx (1)
RefOptionListProps(22-26)src/hooks/useOpen.ts (1)
useOpen(36-93)src/hooks/useSelectTriggerControl.ts (1)
useSelectTriggerControl(4-37)src/hooks/useBaseProps.ts (1)
BaseSelectContext(16-16)src/BaseSelect/Polite.tsx (1)
Polite(9-32)
⏰ 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)
tests/BaseSelect.test.tsx (2)
142-165: 自定义 RootComponent 覆盖默认根节点的用例设计合理校验了自定义 root 渲染且默认 .rc-select 不再出现,覆盖路径正确,能有效防回归。
167-192: 使用 React 元素作为 RootComponent 的用例也覆盖到位同时验证了元素形态的 root,保证 clone/渲染路径正确。赞。
tests/Select.test.tsx (4)
38-45: 全局假定时器的设置/还原 OK在 describe 中统一 useFakeTimers + clearAllTimers/useRealTimers,有助于稳定处理异步 UI。保持此模式。
54-67: 点击清除触发关闭事件的断言更贴近语义从 clear 按钮交互验证 onPopupVisibleChange(false),与新版 DOM 结构保持一致。
1614-1620: suffix 与 loading 场景的断言覆盖充分校验了单选与多选在 loading 时的后缀类名变化,匹配 PR 中从 suffixIcon → suffix 的迁移。
Also applies to: 1623-1635
2254-2255: 根容器 onClick 透传测试合理改为点击 .rc-select 验证 onClick 透传,符合语义化结构调整后的事件委托。
src/BaseSelect/index.tsx (2)
681-693: className 组合符合新结构(focused/allow-clear/show-arrow/show-search 等)命名与测试断言一致,行为明确。LGTM。
638-653: suffix 合并逻辑合理(支持函数/节点,兼容 suffixIcon)对外支持函数签名
{ searchValue, open, focused, showSearch, loading },满足自定义后缀的复杂场景。LGTM。
Summary by CodeRabbit