-
-
Notifications
You must be signed in to change notification settings - Fork 478
fix: Select dropdown on search not scroll to the correct options #1161
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -15,6 +15,7 @@ import useBaseProps from './hooks/useBaseProps'; | |||||||||||||||||||||||||||||
import type { FlattenOptionData } from './interface'; | ||||||||||||||||||||||||||||||
import { isPlatformMac } from './utils/platformUtil'; | ||||||||||||||||||||||||||||||
import { isValidCount } from './utils/valueUtil'; | ||||||||||||||||||||||||||||||
import { toArray } from './utils/commonUtil'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// export interface OptionListProps<OptionsType extends object[]> { | ||||||||||||||||||||||||||||||
export type OptionListProps = Record<string, never>; | ||||||||||||||||||||||||||||||
|
@@ -29,6 +30,10 @@ function isTitleType(content: any) { | |||||||||||||||||||||||||||||
return typeof content === 'string' || typeof content === 'number'; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
function includes(test: React.ReactNode, search: string) { | ||||||||||||||||||||||||||||||
return toArray(test).join('').toUpperCase().includes(search.toUpperCase()); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+33
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion includes 对 ReactNode 的文本提取不可靠,建议递归提取纯文本以对齐 useFilterOptions 行为 当前 join 可能丢失元素内的文本(如 America),从而错过匹配,导致未能滚到期望位置。可改为递归提取 children 文本: -function includes(test: React.ReactNode, search: string) {
- return toArray(test).join('').toUpperCase().includes(search.toUpperCase());
-}
+function nodeText(n: React.ReactNode): string {
+ if (n == null || n === false) return '';
+ if (typeof n === 'string' || typeof n === 'number') return String(n);
+ if (Array.isArray(n)) return n.map(nodeText).join('');
+ if (React.isValidElement(n)) return nodeText((n as any).props?.children);
+ return '';
+}
+function includes(test: React.ReactNode, search: string) {
+ return nodeText(test).toUpperCase().includes(search.toUpperCase());
+} 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||
* Using virtual list of option display. | ||||||||||||||||||||||||||||||
* Will fallback to dom if use customize render. | ||||||||||||||||||||||||||||||
|
@@ -60,6 +65,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r | |||||||||||||||||||||||||||||
listHeight, | ||||||||||||||||||||||||||||||
listItemHeight, | ||||||||||||||||||||||||||||||
optionRender, | ||||||||||||||||||||||||||||||
optionFilterProp, | ||||||||||||||||||||||||||||||
classNames: contextClassNames, | ||||||||||||||||||||||||||||||
styles: contextStyles, | ||||||||||||||||||||||||||||||
} = React.useContext(SelectContext); | ||||||||||||||||||||||||||||||
|
@@ -159,9 +165,13 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r | |||||||||||||||||||||||||||||
if (!multiple && open && rawValues.size === 1) { | ||||||||||||||||||||||||||||||
const value: RawValueType = Array.from(rawValues)[0]; | ||||||||||||||||||||||||||||||
// Scroll to the option closest to the searchValue if searching. | ||||||||||||||||||||||||||||||
const index = memoFlattenOptions.findIndex(({ data }) => | ||||||||||||||||||||||||||||||
searchValue ? String(data.value).startsWith(searchValue) : data.value === value, | ||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||
const index = memoFlattenOptions.findIndex(({ data }) => { | ||||||||||||||||||||||||||||||
if (searchValue) { | ||||||||||||||||||||||||||||||
const matchValue = optionFilterProp ? data[optionFilterProp] : data.value; | ||||||||||||||||||||||||||||||
return includes(matchValue, searchValue); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return data.value === value; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+168
to
175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 定位所选项/匹配项时不应使用 data.value;应使用扁平项的 item.value,且当 optionFilterProp 未命中时需兜底 fieldNames.value 当自定义 fieldNames(例如 value 映射为 id)时,data.value 可能为 undefined,导致无法在未搜索时滚动到已选项;另外,搜索时未提供 optionFilterProp 时,也应优先按 fieldNames.value 查找,否则会偏离 useFilterOptions 的字段选择。 - const index = memoFlattenOptions.findIndex(({ data }) => {
- if (searchValue) {
- const matchValue = optionFilterProp ? data[optionFilterProp] : data.value;
- return includes(matchValue, searchValue);
- }
- return data.value === value;
- });
+ const index = memoFlattenOptions.findIndex(({ data, value: itemValue, group }) => {
+ if (group) return false;
+ if (searchValue) {
+ const field = optionFilterProp || fieldNames?.value || 'value';
+ const matchValue = data?.[field];
+ return includes(matchValue, searchValue);
+ }
+ // 未搜索时按已选值精确命中,应使用扁平后的 itemValue
+ return itemValue === value;
+ });
+ // 兜底:如未找到匹配项但处于搜索态,则滚到第一个可用选项(即“置顶”期望)
+ const finalIndex = index !== -1 ? index : (searchValue ? getEnabledActiveIndex(0) : -1); 并同步使用 finalIndex: - if (index !== -1) {
- setActive(index);
+ if (finalIndex !== -1) {
+ setActive(finalIndex);
timeoutId = setTimeout(() => {
- scrollIntoView(index);
+ scrollIntoView(finalIndex);
});
} 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
if (index !== -1) { | ||||||||||||||||||||||||||||||
setActive(index); | ||||||||||||||||||||||||||||||
|
@@ -177,7 +187,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r | |||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return () => clearTimeout(timeoutId); | ||||||||||||||||||||||||||||||
}, [open, searchValue]); | ||||||||||||||||||||||||||||||
}, [open, searchValue, optionFilterProp]); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// ========================== Values ========================== | ||||||||||||||||||||||||||||||
const onSelectValue = (value: RawValueType) => { | ||||||||||||||||||||||||||||||
|
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.
为了提高代码的可维护性和重用性,建议将此
includes
函数移动到共享的工具文件(例如src/utils/commonUtil.ts
)中。在src/hooks/useFilterOptions.ts
中也存在一个功能非常相似的函数。通过将此逻辑提取到公共位置,可以消除代码重复,并确保在整个组件中进行筛选时行为一致。例如,您可以在
src/utils/commonUtil.ts
中创建一个名为caseInsensitiveIncludes
的函数,然后在此处和useFilterOptions.ts
中导入和使用它。