Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions packages/components/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ const Input = forwardRefWithStatics(
onChange: onChangeFromProps,
...restProps
} = props;
const readOnlyProp = readOnly || readonly;

const [value, onChange] = useControlled(props, 'value', onChangeFromProps);
const { limitNumber, getValueByLimitNumber, tStatus } = useLengthLimit({
Expand All @@ -118,17 +117,19 @@ const Input = forwardRefWithStatics(
});

const { classPrefix, input: inputConfig } = useConfig();

const composingRef = useRef(false);
const inputRef: React.RefObject<HTMLInputElement> = useRef(null);
// inputPreRef 用于预存输入框宽度,应用在 auto width 模式中
const inputPreRef: React.RefObject<HTMLInputElement> = useRef(null);
const wrapperRef: React.RefObject<HTMLDivElement> = useRef(null);

const [isHover, toggleIsHover] = useState(false);
const [isFocused, toggleIsFocused] = useState(false);
const [renderType, setRenderType] = useState(type);

const [composingValue, setComposingValue] = useState<string>('');

const readOnlyProp = readOnly || readonly;
// 组件内部 input 原生控件是否处于 readonly 状态,当整个组件 readonly 时,或者处于不可输入时
const isInnerInputReadonly = readOnlyProp || !allowInput;
const isValueEnabled = value && !disabled;
Expand All @@ -140,14 +141,23 @@ const Input = forwardRefWithStatics(
let suffixIconNew = suffixIcon;

if (isShowClearIcon)
suffixIconNew = <CloseCircleFilledIcon className={`${classPrefix}-input__suffix-clear`} onClick={handleClear} />;
suffixIconNew = (
<CloseCircleFilledIcon
className={`${classPrefix}-input__suffix-clear`}
onMouseDown={handleIconMouseDown}
onClick={handleClear}
/>
);
if (type === 'password' && typeof suffixIcon === 'undefined') {
if (renderType === 'password') {
const PASSWORD_ICON_MAP = {
password: BrowseOffIcon,
text: BrowseIcon,
};
const PasswordIcon = PASSWORD_ICON_MAP[renderType];
if (PasswordIcon) {
suffixIconNew = (
<BrowseOffIcon className={`${classPrefix}-input__suffix-clear`} onClick={togglePasswordVisible} />
<PasswordIcon className={`${classPrefix}-input__suffix-clear`} onClick={togglePasswordVisible} />
);
} else if (renderType === 'text') {
suffixIconNew = <BrowseIcon className={`${classPrefix}-input__suffix-clear`} onClick={togglePasswordVisible} />;
}
}

Expand Down Expand Up @@ -260,6 +270,7 @@ const Input = forwardRefWithStatics(
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseDown={handleMouseDown}
onWheel={(e) => onWheel?.({ e })}
onClick={(e) => {
inputRef.current?.focus();
Expand Down Expand Up @@ -314,6 +325,19 @@ const Input = forwardRefWithStatics(
onChange(newStr, { e, trigger });
}
}
function handleIconMouseDown(e: React.MouseEvent<SVGSVGElement>) {
e.preventDefault();
// 阻止冒泡,防止点击 icon 会导致弹窗闪烁一下
// https://github.com/Tencent/tdesign-react/issues/2320
e.stopPropagation();
// 兼容 React 16
e.nativeEvent.stopImmediatePropagation();
}
function handleMouseDown(e: React.MouseEvent<HTMLDivElement>) {
if (e.target !== inputRef.current) {
e.preventDefault(); // 避免焦点转移
}
}
function handleClear(e: React.MouseEvent<SVGSVGElement>) {
onChange?.('', { e, trigger: 'clear' });
onClear?.({ e });
Expand Down Expand Up @@ -381,12 +405,12 @@ const Input = forwardRefWithStatics(
}

function handleMouseEnter(e: React.MouseEvent<HTMLDivElement>) {
!readOnly && toggleIsHover(true);
!readOnlyProp && toggleIsHover(true);
onMouseenter?.({ e });
}

function handleMouseLeave(e: React.MouseEvent<HTMLDivElement>) {
!readOnly && toggleIsHover(false);
!readOnlyProp && toggleIsHover(false);
onMouseleave?.({ e });
}

Expand Down
22 changes: 4 additions & 18 deletions packages/components/select-input/useMultiple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export default function useMultiple(props: SelectInputProps) {
const [tInputValue, setTInputValue] = useControlled(props, 'inputValue', props.onInputChange);

const tagInputRef = useRef<InputRef>(null);
const blurTimeoutRef = useRef(null);

const iKeys: SelectInputKeys = { ...DEFAULT_KEYS, ...props.keys };

Expand All @@ -63,30 +62,17 @@ export default function useMultiple(props: SelectInputProps) {

const renderSelectMultiple = (p: RenderSelectMultipleParams) => {
const handleBlur = (value: SelectInputValue, context: { e: React.FocusEvent<HTMLInputElement> }) => {
if (blurTimeoutRef.current) {
clearTimeout(blurTimeoutRef.current);
if (!p.popupVisible) {
p.onInnerBlur(context);
} else if (!props.panel) {
props.onBlur?.(value, { e: context.e, inputValue: tInputValue, tagInputValue: tags });
}
// 强制把 popupVisible 设置为 false 时,点击 input,会出现 blur -> focus 的情况,因此忽略前面短暂的 blur 事件
blurTimeoutRef.current = setTimeout(() => {
if (blurTimeoutRef.current) {
if (!p.popupVisible) {
p.onInnerBlur(context);
} else if (!props.panel) {
props.onBlur?.(value, { e: context.e, inputValue: tInputValue, tagInputValue: tags });
}
}
blurTimeoutRef.current = null;
}, 150);
};

const handleFocus = (
val: TagInputValue,
context: { e: React.FocusEvent<HTMLInputElement>; inputValue: string },
) => {
if (blurTimeoutRef.current) {
clearTimeout(blurTimeoutRef.current);
blurTimeoutRef.current = null;
}
props.onFocus?.(props.value, { ...context, tagInputValue: val });
};

Expand Down
22 changes: 4 additions & 18 deletions packages/components/select-input/useSingle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export default function useSingle(props: TdSelectInputProps) {
const { classPrefix } = useConfig();

const inputRef = useRef<InputRef>(null);
const blurTimeoutRef = useRef(null);

const [inputValue, setInputValue] = useControlled(props, 'inputValue', props.onInputChange);

Expand Down Expand Up @@ -78,27 +77,14 @@ export default function useSingle(props: TdSelectInputProps) {
const displayedValue = popupVisible && props.allowInput ? inputValue : getInputValue(value, keys);

const handleBlur = (value, ctx) => {
if (blurTimeoutRef.current) {
clearTimeout(blurTimeoutRef.current);
if (!popupVisible) {
onInnerBlur(ctx);
} else if (!props.panel) {
props.onBlur?.(value, { e: ctx.e, inputValue: value });
}
// 强制把 popupVisible 设置为 false 时,点击 input,会出现 blur -> focus 的情况,因此忽略前面短暂的 blur 事件
blurTimeoutRef.current = setTimeout(() => {
if (blurTimeoutRef.current) {
if (!popupVisible) {
onInnerBlur(ctx);
} else if (!props.panel) {
props.onBlur?.(value, { e: ctx.e, inputValue: value });
}
}
blurTimeoutRef.current = null;
}, 150);
};

const handleFocus = (val, context) => {
if (blurTimeoutRef.current) {
clearTimeout(blurTimeoutRef.current);
blurTimeoutRef.current = null;
}
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' });
Expand Down
Loading