diff --git a/packages/components/select-input/SelectInput.tsx b/packages/components/select-input/SelectInput.tsx index ee08f47da3..b6140b6b39 100644 --- a/packages/components/select-input/SelectInput.tsx +++ b/packages/components/select-input/SelectInput.tsx @@ -19,7 +19,9 @@ export interface SelectInputProps extends TdSelectInputProps, StyledProps { options?: any[]; // 参数穿透options, 给SelectInput/SelectInput 自定义选中项呈现的内容和多选状态下设置折叠项内容 } -const SelectInput = React.forwardRef, SelectInputProps>((originalProps, ref) => { +export type SelectInputRef = Partial; + +const SelectInput = React.forwardRef((originalProps, ref) => { const { classPrefix: prefix } = useConfig(); const props = useDefaultProps(originalProps, selectInputDefaultProps); diff --git a/packages/components/select-input/index.ts b/packages/components/select-input/index.ts index 1e269e3dd5..2a264bf40a 100644 --- a/packages/components/select-input/index.ts +++ b/packages/components/select-input/index.ts @@ -2,7 +2,7 @@ import _SelectInput from './SelectInput'; import './style/index.js'; -export type { SelectInputProps } from './SelectInput'; +export type { SelectInputProps, SelectInputRef } from './SelectInput'; export * from './type'; export const SelectInput = _SelectInput; diff --git a/packages/components/select-input/useSingle.tsx b/packages/components/select-input/useSingle.tsx index 9e92324767..ba13b49269 100644 --- a/packages/components/select-input/useSingle.tsx +++ b/packages/components/select-input/useSingle.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import classNames from 'classnames'; import { isObject, pick } from 'lodash-es'; @@ -50,6 +50,8 @@ export default function useSingle(props: TdSelectInputProps) { const inputRef = useRef(null); const blurTimeoutRef = useRef(null); + const [isInputFocused, setIsInputFocused] = useState(false); + const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange); const commonInputProps: SelectInputCommonProperties = { @@ -84,6 +86,7 @@ export default function useSingle(props: TdSelectInputProps) { // 强制把 popupVisible 设置为 false 时,点击 input,会出现 blur -> focus 的情况,因此忽略前面短暂的 blur 事件 blurTimeoutRef.current = setTimeout(() => { if (blurTimeoutRef.current) { + setIsInputFocused(false); if (!popupVisible) { onInnerBlur(ctx); } else if (!props.panel) { @@ -99,6 +102,7 @@ export default function useSingle(props: TdSelectInputProps) { clearTimeout(blurTimeoutRef.current); blurTimeoutRef.current = null; } + setIsInputFocused(true); props.onFocus?.(value, { ...context, inputValue: val }); // focus might not need to change input value. it will caught some curious errors in tree-select // !popupVisible && setInputValue(getInputValue(value, keys), { ...context, trigger: 'input' }); @@ -131,8 +135,8 @@ export default function useSingle(props: TdSelectInputProps) { onBlur={handleBlur} {...props.inputProps} inputClass={classNames(props.inputProps?.className, { - [`${classPrefix}-input--focused`]: popupVisible, - [`${classPrefix}-is-focused`]: popupVisible, + [`${classPrefix}-input--focused`]: popupVisible || isInputFocused, + [`${classPrefix}-is-focused`]: popupVisible || isInputFocused, })} /> ); diff --git a/packages/components/select/base/Select.tsx b/packages/components/select/base/Select.tsx index 8d7e702d9e..7511796ed6 100644 --- a/packages/components/select/base/Select.tsx +++ b/packages/components/select/base/Select.tsx @@ -22,7 +22,8 @@ import useDefaultProps from '../../hooks/useDefaultProps'; import Loading from '../../loading'; import { useLocaleReceiver } from '../../locale/LocalReceiver'; import SelectInput, { - SelectInputChangeContext, + type SelectInputChangeContext, + type SelectInputRef, type SelectInputValue, type SelectInputValueChangeContext, } from '../../select-input'; @@ -49,7 +50,7 @@ export interface SelectProps extends TdSelectProps, StyledP type OptionsType = TdOptionProps[]; const Select = forwardRefWithStatics( - (originalProps: SelectProps, ref: React.Ref) => { + (originalProps: SelectProps, ref: React.Ref) => { const props = useDefaultProps(originalProps, selectDefaultProps); // 国际化文本初始化 const [local, t] = useLocaleReceiver('select'); @@ -107,7 +108,7 @@ const Select = forwardRefWithStatics( const { valueKey, labelKey, disabledKey } = useMemo(() => getKeyMapping(keys), [keys]); - const selectInputRef = useRef(null); + const selectInputRef = useRef(null); const { classPrefix } = useConfig(); const { overlayClassName, onScroll, onScrollToBottom, ...restPopupProps } = popupProps || {}; const [isScrolling, toggleIsScrolling] = useState(false); @@ -291,6 +292,7 @@ const Select = forwardRefWithStatics( const { hoverIndex, handleKeyDown } = useKeyboardControl({ displayOptions: flattenedOptions as TdOptionProps[], + filterable, keys, innerPopupVisible, max, diff --git a/packages/components/select/hooks/useKeyboardControl.ts b/packages/components/select/hooks/useKeyboardControl.ts index 7699cbb027..4799d7e84b 100644 --- a/packages/components/select/hooks/useKeyboardControl.ts +++ b/packages/components/select/hooks/useKeyboardControl.ts @@ -3,11 +3,13 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import useConfig from '../../hooks/useConfig'; import { getKeyMapping, getSelectValueArr } from '../util/helper'; +import type { SelectInputRef } from '../../select-input/SelectInput'; import type { SelectOption, SelectValue, SelectValueChangeTrigger, TdOptionProps, TdSelectProps } from '../type'; export type useKeyboardControlType = { max: number; multiple: boolean; + filterable: TdSelectProps['filterable']; keys: TdSelectProps['keys']; value: SelectValue; valueType: TdSelectProps['valueType']; @@ -22,13 +24,14 @@ export type useKeyboardControlType = { handlePopupVisibleChange: (visible: boolean, ctx: { e: React.KeyboardEvent }) => void; displayOptions: TdOptionProps[]; onCheckAllChange: (checkAll: boolean, e?: React.KeyboardEvent) => void; - selectInputRef: any; + selectInputRef: React.MutableRefObject; toggleIsScrolling: (isScrolling: boolean) => void; }; export default function useKeyboardControl({ max, multiple, + filterable, keys, value, valueType, @@ -93,7 +96,7 @@ export default function useKeyboardControl({ let newIndex = hoverIndex; switch (e.code) { - case 'ArrowUp': + case 'ArrowUp': { e.preventDefault(); if (hoverIndex === -1) newIndex = 0; else if (hoverIndex === 0 || hoverIndex > optionsListLength - 1) newIndex = optionsListLength - 1; @@ -104,7 +107,8 @@ export default function useKeyboardControl({ changeHoverIndex(newIndex); handleKeyboardScroll(newIndex); break; - case 'ArrowDown': + } + case 'ArrowDown': { e.preventDefault(); if (hoverIndex === -1 || hoverIndex >= optionsListLength - 1) newIndex = 0; else newIndex += 1; @@ -114,6 +118,7 @@ export default function useKeyboardControl({ changeHoverIndex(newIndex); handleKeyboardScroll(newIndex); break; + } case 'Enter': { if (!innerPopupVisible) { handlePopupVisibleChange(true, { e }); @@ -152,10 +157,27 @@ export default function useKeyboardControl({ } break; } - case 'Escape': + case 'Escape': { handlePopupVisibleChange(false, { e }); changeHoverIndex(-1); handleKeyboardScroll(0); + break; + } + case 'Backspace': + case 'Delete': { + // 单选模式的删除无意义 + if (!multiple && !innerPopupVisible) { + e.preventDefault(); + } + break; + } + default: { + // filterable 模式,按下其它字符,打开弹窗进入输入态 + if (filterable && !innerPopupVisible && e.key.length === 1) { + handlePopupVisibleChange(true, { e }); + } + break; + } } };