Skip to content

Commit 1dff3dd

Browse files
committed
feat(Select): support keyboard control
1 parent c08ac57 commit 1dff3dd

File tree

5 files changed

+38
-23
lines changed

5 files changed

+38
-23
lines changed

packages/components/select/base/PopupContent.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ interface SelectPopupProps
5757
children?: React.ReactNode;
5858
onCheckAllChange?: (checkAll: boolean, e: React.MouseEvent<HTMLLIElement>) => void;
5959
getPopupInstance?: () => HTMLDivElement;
60-
hoverIndex: number;
60+
hoverOption: TdOptionProps;
6161
}
6262

6363
const PopupContent = React.forwardRef<HTMLDivElement, SelectPopupProps>((props, ref) => {
@@ -81,7 +81,7 @@ const PopupContent = React.forwardRef<HTMLDivElement, SelectPopupProps>((props,
8181
getPopupInstance,
8282
options: propsOptions,
8383
scroll: propsScroll,
84-
hoverIndex,
84+
hoverOption,
8585
} = props;
8686

8787
// 国际化文本初始化
@@ -179,7 +179,8 @@ const PopupContent = React.forwardRef<HTMLDivElement, SelectPopupProps>((props,
179179
// 当 keys 属性配置 content 作为 value 或 label 时,确保 restData 中也包含它, 不参与渲染计算
180180
const { content } = item as TdOptionProps;
181181
const shouldOmitContent = Object.values(keys || {}).includes('content');
182-
const isKeyboardHovered = hoverIndex === index;
182+
183+
const isKeyboardHovered = hoverOption?.value === optionValue;
183184
return (
184185
<Option
185186
key={index}

packages/components/select/base/Select.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,13 @@ const Select = forwardRefWithStatics(
110110
const [showPopup, setShowPopup] = useControlled(props, 'popupVisible', onPopupVisibleChange);
111111
const [inputValue, onInputChange] = useControlled(props, 'inputValue', props.onInputChange);
112112

113-
const { currentOptions, setCurrentOptions, tmpPropOptions, valueToOption, selectedOptions } = useOptions(
114-
keys,
115-
options,
116-
children,
117-
valueType,
118-
value,
119-
reserveKeyword,
120-
);
113+
const { currentOptions, setCurrentOptions, tmpPropOptions, valueToOption, selectedOptions, flattenedOptions } =
114+
useOptions(keys, options, children, valueType, value, reserveKeyword);
121115

122116
const onCheckAllChange = useCallback(
123117
(checkAll: boolean, e: React.MouseEvent<HTMLLIElement>) => {
124118
const isDisabledCheckAll = (opt: TdOptionProps) => opt.checkAll && opt.disabled;
125-
if (!multiple || currentOptions.some((opt) => !isSelectOptionGroup(opt) && isDisabledCheckAll(opt))) {
126-
return;
127-
}
119+
if (!multiple || currentOptions.some((opt) => !isSelectOptionGroup(opt) && isDisabledCheckAll(opt))) return;
128120

129121
const { valueKey } = getKeyMapping(keys);
130122
const isObjectType = valueType === 'object';
@@ -189,8 +181,8 @@ const Select = forwardRefWithStatics(
189181
[currentOptions, keys, multiple, onChange, value, valueToOption, valueType],
190182
);
191183

192-
const { handleKeyDown, hoverIndex } = useKeyboardControl({
193-
displayOptions: currentOptions as TdOptionProps[],
184+
const { handleKeyDown, hoverOption, hoverIndex } = useKeyboardControl({
185+
displayOptions: flattenedOptions as TdOptionProps[],
194186
max,
195187
multiple,
196188
setInnerPopupVisible: setShowPopup,
@@ -430,7 +422,7 @@ const Select = forwardRefWithStatics(
430422
onCheckAllChange,
431423
getPopupInstance,
432424
scroll,
433-
hoverIndex,
425+
hoverOption,
434426
};
435427
return <PopupContent {...popupContentProps}>{childrenWithProps}</PopupContent>;
436428
};

packages/components/select/hooks/useKeyboardControl.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type useKeyboardControlType = {
1616
max: number;
1717
selectInputRef: any;
1818
};
19+
1920
export default function useKeyboardControl({
2021
displayOptions,
2122
innerPopupVisible,
@@ -28,8 +29,8 @@ export default function useKeyboardControl({
2829
selectInputRef,
2930
}: useKeyboardControlType) {
3031
const [hoverIndex, changeHoverIndex] = useState(-1);
31-
const filteredOptions = useState([]); // 处理普通场景选项过滤键盘选中的问题
32-
const virtualFilteredOptions = useState([]); // 处理虚拟滚动下选项过滤通过键盘选择的问题
32+
const [hoverOption, changeHoverOption] = useState<TdOptionProps>(undefined);
33+
3334
const { classPrefix } = useConfig();
3435
// 全选判断
3536
const isCheckAll = useRef(false);
@@ -74,6 +75,8 @@ export default function useKeyboardControl({
7475
newIndex -= 1;
7576
}
7677
changeHoverIndex(newIndex);
78+
79+
changeHoverOption(displayOptions[newIndex]);
7780
handleKeyboardScroll(newIndex);
7881
break;
7982
case 'ArrowDown':
@@ -88,6 +91,8 @@ export default function useKeyboardControl({
8891
newIndex += 1;
8992
}
9093
changeHoverIndex(newIndex);
94+
changeHoverOption(displayOptions[newIndex]);
95+
9196
handleKeyboardScroll(newIndex);
9297
break;
9398
case 'Enter':
@@ -139,8 +144,7 @@ export default function useKeyboardControl({
139144

140145
return {
141146
handleKeyDown,
147+
hoverOption,
142148
hoverIndex,
143-
filteredOptions,
144-
virtualFilteredOptions,
145149
};
146150
}

packages/components/select/hooks/useOptions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,25 @@ import { getKeyMapping, getValueToOption, type ValueToOption } from '../util/hel
66

77
import type { SelectKeysType, SelectOption, SelectOptionGroup, SelectValue, TdOptionProps } from '../type';
88

9+
// 针对分组的相关判断和扁平处理
910
export function isSelectOptionGroup(option: SelectOption): option is SelectOptionGroup {
1011
return !!option && 'group' in option && 'children' in option;
1112
}
1213

14+
export const flattenOptions = (options: SelectOption[]) => {
15+
const flattened = [];
16+
options.forEach((option) => {
17+
if (isSelectOptionGroup(option)) {
18+
if (option.children) {
19+
flattened.push(...option.children);
20+
}
21+
} else {
22+
flattened.push(option);
23+
}
24+
});
25+
return flattened;
26+
};
27+
1328
type OptionValueType = SelectValue<SelectOption>;
1429

1530
// 处理 options 的逻辑
@@ -23,6 +38,7 @@ function useOptions(
2338
) {
2439
const [valueToOption, setValueToOption] = useState<ValueToOption>({});
2540
const [currentOptions, setCurrentOptions] = useState<SelectOption[]>([]);
41+
const [flattenedOptions, setFlattenedOptions] = useState<SelectOption[]>([]);
2642
const [tmpPropOptions, setTmpPropOptions] = useState<SelectOption[]>([]);
2743
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
2844

@@ -65,6 +81,7 @@ function useOptions(
6581
}
6682
setCurrentOptions(transformedOptions);
6783
setTmpPropOptions(transformedOptions);
84+
setFlattenedOptions(flattenOptions(transformedOptions));
6885

6986
setValueToOption(getValueToOption(children as ReactElement, options as TdOptionProps[], keys) || {});
7087
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -115,6 +132,7 @@ function useOptions(
115132
setValueToOption,
116133
selectedOptions,
117134
setSelectedOptions,
135+
flattenedOptions,
118136
};
119137
}
120138

packages/components/tag-input/TagInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ const TagInput = forwardRef<InputRef, TagInputProps>((originalProps, ref) => {
123123

124124
const onKeydown = (value: string, context: { e: React.KeyboardEvent<HTMLInputElement> }) => {
125125
onInputBackspaceKeyDown(value, context);
126-
inputProps.onKeydown?.(value, context);
126+
inputProps?.onKeydown?.(value, context);
127127
};
128128

129129
const onKeyup = (value: string, context: { e: React.KeyboardEvent<HTMLInputElement> }) => {
130130
onInputBackspaceKeyUp(value);
131-
inputProps.onKeyup?.(value, context);
131+
inputProps?.onKeyup?.(value, context);
132132
};
133133

134134
const suffixIconNode = showClearIcon ? (

0 commit comments

Comments
 (0)