diff --git a/docs/demo/search.md b/docs/demo/search.md
new file mode 100644
index 00000000..d6a5e18f
--- /dev/null
+++ b/docs/demo/search.md
@@ -0,0 +1,8 @@
+---
+title: search
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/examples/search.tsx b/examples/search.tsx
new file mode 100644
index 00000000..8cfe19c0
--- /dev/null
+++ b/examples/search.tsx
@@ -0,0 +1,75 @@
+/* eslint-disable no-console */
+import React from 'react';
+import '../assets/index.less';
+import Cascader from '../src';
+
+const addressOptions = [
+ {
+ label: '福建',
+ value: 'fj',
+ children: [
+ {
+ label: '福州',
+ value: 'fuzhou',
+ children: [
+ {
+ label: '马尾',
+ value: 'mawei',
+ },
+ ],
+ },
+ {
+ label: '泉州',
+ value: 'quanzhou',
+ },
+ ],
+ },
+ {
+ label: '浙江',
+ value: 'zj',
+ children: [
+ {
+ label: '杭州',
+ value: 'hangzhou',
+ children: [
+ {
+ label: '余杭',
+ value: 'yuhang',
+ },
+ {
+ label: '福州',
+ value: 'fuzhou',
+ children: [
+ {
+ label: '马尾',
+ value: 'mawei',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ label: '北京',
+ value: 'bj',
+ children: [
+ {
+ label: '朝阳区',
+ value: 'chaoyang',
+ },
+ {
+ label: '海淀区',
+ value: 'haidian',
+ },
+ ],
+ },
+];
+
+class Demo extends React.Component {
+ render() {
+ return ;
+ }
+}
+
+export default Demo;
diff --git a/src/OptionList/Column.tsx b/src/OptionList/Column.tsx
index a96a767e..39c46604 100644
--- a/src/OptionList/Column.tsx
+++ b/src/OptionList/Column.tsx
@@ -138,7 +138,8 @@ export default function Column({
key={fullPathKey}
className={classNames(menuItemPrefixCls, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
- [`${menuItemPrefixCls}-active`]: activeValue === value,
+ [`${menuItemPrefixCls}-active`]:
+ activeValue === value || activeValue === fullPathKey,
[`${menuItemPrefixCls}-disabled`]: disabled,
[`${menuItemPrefixCls}-loading`]: isLoading,
})}
diff --git a/src/OptionList/index.tsx b/src/OptionList/index.tsx
index 05f24860..769834cc 100644
--- a/src/OptionList/index.tsx
+++ b/src/OptionList/index.tsx
@@ -6,6 +6,7 @@ import * as React from 'react';
import type { DefaultOptionType, SingleValueType } from '../Cascader';
import CascaderContext from '../context';
import {
+ getFullPathKeys,
isLeaf,
scrollIntoParentView,
toPathKey,
@@ -122,10 +123,14 @@ const RefOptionList = React.forwardRef((props, ref) => {
const optionList = [{ options: mergedOptions }];
let currentList = mergedOptions;
+ const fullPathKeys = getFullPathKeys(currentList, fieldNames);
+
for (let i = 0; i < activeValueCells.length; i += 1) {
const activeValueCell = activeValueCells[i];
const currentOption = currentList.find(
- option => option[fieldNames.value] === activeValueCell,
+ (option, index) =>
+ (fullPathKeys[index] ? toPathKey(fullPathKeys[index]) : option[fieldNames.value]) ===
+ activeValueCell,
);
const subOptions = currentOption?.[fieldNames.children];
diff --git a/src/OptionList/useKeyboard.ts b/src/OptionList/useKeyboard.ts
index 19070baf..6131a862 100644
--- a/src/OptionList/useKeyboard.ts
+++ b/src/OptionList/useKeyboard.ts
@@ -1,9 +1,10 @@
-import * as React from 'react';
-import type { RefOptionListProps } from 'rc-select/lib/OptionList';
import { useBaseProps } from 'rc-select';
+import type { RefOptionListProps } from 'rc-select/lib/OptionList';
import KeyCode from 'rc-util/lib/KeyCode';
+import * as React from 'react';
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
import { SEARCH_MARK } from '../hooks/useSearchOptions';
+import { getFullPathKeys, toPathKey } from '../utils/commonUtil';
export default (
ref: React.Ref,
@@ -16,41 +17,46 @@ export default (
const { direction, searchValue, toggleOpen, open } = useBaseProps();
const rtl = direction === 'rtl';
- const [validActiveValueCells, lastActiveIndex, lastActiveOptions] = React.useMemo(() => {
- let activeIndex = -1;
- let currentOptions = options;
+ const [validActiveValueCells, lastActiveIndex, lastActiveOptions, fullPathKeys] =
+ React.useMemo(() => {
+ let activeIndex = -1;
+ let currentOptions = options;
- const mergedActiveIndexes: number[] = [];
- const mergedActiveValueCells: React.Key[] = [];
+ const mergedActiveIndexes: number[] = [];
+ const mergedActiveValueCells: React.Key[] = [];
- const len = activeValueCells.length;
+ const len = activeValueCells.length;
- // Fill validate active value cells and index
- for (let i = 0; i < len && currentOptions; i += 1) {
- // Mark the active index for current options
- const nextActiveIndex = currentOptions.findIndex(
- option => option[fieldNames.value] === activeValueCells[i],
- );
+ const pathKeys = getFullPathKeys(options, fieldNames);
- if (nextActiveIndex === -1) {
- break;
- }
+ // Fill validate active value cells and index
+ for (let i = 0; i < len && currentOptions; i += 1) {
+ // Mark the active index for current options
+ const nextActiveIndex = currentOptions.findIndex(
+ (option, index) =>
+ (pathKeys[index] ? toPathKey(pathKeys[index]) : option[fieldNames.value]) ===
+ activeValueCells[i],
+ );
- activeIndex = nextActiveIndex;
- mergedActiveIndexes.push(activeIndex);
- mergedActiveValueCells.push(activeValueCells[i]);
+ if (nextActiveIndex === -1) {
+ break;
+ }
- currentOptions = currentOptions[activeIndex][fieldNames.children];
- }
+ activeIndex = nextActiveIndex;
+ mergedActiveIndexes.push(activeIndex);
+ mergedActiveValueCells.push(activeValueCells[i]);
- // Fill last active options
- let activeOptions = options;
- for (let i = 0; i < mergedActiveIndexes.length - 1; i += 1) {
- activeOptions = activeOptions[mergedActiveIndexes[i]][fieldNames.children];
- }
+ currentOptions = currentOptions[activeIndex][fieldNames.children];
+ }
- return [mergedActiveValueCells, activeIndex, activeOptions];
- }, [activeValueCells, fieldNames, options]);
+ // Fill last active options
+ let activeOptions = options;
+ for (let i = 0; i < mergedActiveIndexes.length - 1; i += 1) {
+ activeOptions = activeOptions[mergedActiveIndexes[i]][fieldNames.children];
+ }
+
+ return [mergedActiveValueCells, activeIndex, activeOptions, pathKeys];
+ }, [activeValueCells, fieldNames, options]);
// Update active value cells and scroll to target element
const internalSetActiveValueCells = (next: React.Key[]) => {
@@ -69,10 +75,14 @@ export default (
for (let i = 0; i < len; i += 1) {
currentIndex = (currentIndex + offset + len) % len;
const option = lastActiveOptions[currentIndex];
-
if (option && !option.disabled) {
- const value = option[fieldNames.value];
- const nextActiveCells = validActiveValueCells.slice(0, -1).concat(value);
+ const nextActiveCells = validActiveValueCells
+ .slice(0, -1)
+ .concat(
+ fullPathKeys[currentIndex]
+ ? toPathKey(fullPathKeys[currentIndex])
+ : option[fieldNames.value],
+ );
internalSetActiveValueCells(nextActiveCells);
return;
}
diff --git a/src/utils/commonUtil.ts b/src/utils/commonUtil.ts
index 59562678..9c5b19d8 100644
--- a/src/utils/commonUtil.ts
+++ b/src/utils/commonUtil.ts
@@ -1,3 +1,4 @@
+import { SEARCH_MARK } from '../hooks/useSearchOptions';
import type {
DefaultOptionType,
FieldNames,
@@ -49,3 +50,7 @@ export function scrollIntoParentView(element: HTMLElement) {
parent.scrollTo({ top: elementToParent + element.offsetHeight - parent.offsetHeight });
}
}
+
+export function getFullPathKeys(options: DefaultOptionType[], fieldNames: FieldNames) {
+ return options.map(item => item[SEARCH_MARK]?.map(opt => opt[fieldNames.value]));
+}
diff --git a/tests/demoOptions.ts b/tests/demoOptions.ts
index e0fa4ffd..9408dfd8 100644
--- a/tests/demoOptions.ts
+++ b/tests/demoOptions.ts
@@ -70,6 +70,16 @@ export const addressOptions: DefaultOptionType[] = [
},
],
},
+ {
+ label: '福州',
+ value: 'fuzhou',
+ children: [
+ {
+ label: '马尾',
+ value: 'mawei',
+ },
+ ],
+ },
],
},
{
diff --git a/tests/keyboard.spec.tsx b/tests/keyboard.spec.tsx
index 08e47ad6..d0230eca 100644
--- a/tests/keyboard.spec.tsx
+++ b/tests/keyboard.spec.tsx
@@ -94,6 +94,24 @@ describe('Cascader.Keyboard', () => {
addressOptions[1].children[0].children[0],
]);
});
+ it('enter on search when has same sub key', () => {
+ wrapper.find('input').simulate('change', { target: { value: '福' } });
+ wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+ expect(wrapper.find('.rc-cascader-menu-item-active').length).toBe(1);
+ expect(
+ wrapper.find('.rc-cascader-menu-item-active .rc-cascader-menu-item-content').last().text(),
+ ).toEqual('福建 / 福州 / 马尾');
+ wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+ expect(wrapper.find('.rc-cascader-menu-item-active').length).toBe(1);
+ expect(
+ wrapper.find('.rc-cascader-menu-item-active .rc-cascader-menu-item-content').last().text(),
+ ).toEqual('福建 / 泉州');
+ wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+ expect(wrapper.find('.rc-cascader-menu-item-active').length).toBe(1);
+ expect(
+ wrapper.find('.rc-cascader-menu-item-active .rc-cascader-menu-item-content').last().text(),
+ ).toEqual('浙江 / 福州 / 马尾');
+ });
it('rtl', () => {
wrapper = mount();