Skip to content

Commit 00197c4

Browse files
committed
feat: add input test
1 parent bef2e27 commit 00197c4

File tree

7 files changed

+698
-42
lines changed

7 files changed

+698
-42
lines changed

site/test-coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports = {
2828
image: { statements: '97.72%', branches: '100%', functions: '92.3%', lines: '97.61%' },
2929
imageViewer: { statements: '8.47%', branches: '2.87%', functions: '0%', lines: '8.84%' },
3030
indexes: { statements: '95.65%', branches: '69.81%', functions: '100%', lines: '96.94%' },
31-
input: { statements: '3.57%', branches: '0%', functions: '0%', lines: '3.7%' },
31+
input: { statements: '100%', branches: '98.18%', functions: '100%', lines: '100%' },
3232
layout: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
3333
link: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
3434
list: { statements: '8%', branches: '0%', functions: '0%', lines: '8.69%' },

src/input/Input.tsx

Lines changed: 66 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { CompositionEvent, FocusEvent, FormEvent, TouchEvent } from 'react';
2-
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
1+
import type { ChangeEvent, CompositionEvent, CSSProperties, FocusEvent, FormEvent, TouchEvent } from 'react';
2+
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
33
import classNames from 'classnames';
44
import { isFunction } from 'lodash-es';
55
import { BrowseIcon, BrowseOffIcon, CloseCircleFilledIcon } from 'tdesign-icons-react';
@@ -46,6 +46,8 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
4646
tips,
4747
type,
4848
readonly,
49+
extra,
50+
cursorColor,
4951
onBlur,
5052
onClear,
5153
onFocus,
@@ -54,11 +56,12 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
5456
onChange,
5557
} = useDefaultProps(props, inputDefaultProps);
5658

57-
const [showClear, setShowClear] = useState<boolean>(false);
5859
const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange);
60+
const [composingValue, setComposingValue] = useState<string>('');
5961
const [renderType, setRenderType] = useState(type);
6062
const inputRef = useRef<HTMLInputElement>(null);
61-
const focused = useRef<boolean>(false);
63+
const composingRef = useRef<boolean>(false);
64+
const [focused, setFocused] = useState<boolean>(false);
6265
const status = props.status || 'default';
6366
const { classPrefix } = useConfig();
6467
const rootClassName = `${classPrefix}-input`;
@@ -78,18 +81,15 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
7881
blur,
7982
}));
8083

81-
useEffect(() => {
82-
const computeShowClear = () => {
83-
if (disabled || readonly) {
84-
return false;
85-
}
86-
if (clearable) {
87-
return clearTrigger === 'always' || (clearTrigger === 'focus' && focused.current);
88-
}
84+
const showClear = useMemo<boolean>(() => {
85+
if (disabled || readonly) {
8986
return false;
90-
};
91-
setShowClear(computeShowClear());
92-
}, [clearTrigger, clearable, disabled, readonly]);
87+
}
88+
if (clearable) {
89+
return clearTrigger === 'always' || (clearTrigger === 'focus' && focused);
90+
}
91+
return false;
92+
}, [clearTrigger, clearable, disabled, focused, readonly]);
9393

9494
useEffect(() => {
9595
if (autofocus) {
@@ -102,63 +102,84 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
102102
}, [type]);
103103

104104
function focus() {
105-
focused.current = true;
105+
setFocused(true);
106106
inputRef.current?.focus();
107107
}
108108

109109
function blur() {
110-
focused.current = false;
110+
setFocused(false);
111111
inputRef.current?.blur();
112112
}
113113

114-
const inputValueChangeHandle = (e: FormEvent<HTMLInputElement>) => {
114+
const handleInputValue = (e: FormEvent<HTMLInputElement>) => {
115115
const { value } = e.target as HTMLInputElement;
116116
const { allowInputOverMax, maxcharacter } = props;
117-
if (!allowInputOverMax && maxcharacter && !Number.isNaN(maxcharacter)) {
117+
118+
// 如果允许超出最大输入限制,直接更新值并返回
119+
if (allowInputOverMax) {
120+
return value;
121+
}
122+
123+
// 根据不同的限制条件处理输入值
124+
let finalValue = value;
125+
126+
// 处理maxcharacter限制(优先级高于maxlength)
127+
if (maxcharacter && !Number.isNaN(maxcharacter)) {
118128
const { characters } = getCharacterLength(value, maxcharacter) as {
119129
length: number;
120130
characters: string;
121131
};
122-
setInnerValue(characters);
123-
} else {
124-
setInnerValue(value);
132+
finalValue = characters;
125133
}
134+
// 处理maxlength限制
135+
else if (resultMaxLength > 0 && value.length > resultMaxLength) {
136+
finalValue = value.slice(0, resultMaxLength);
137+
}
138+
139+
return finalValue;
126140
};
127141

128-
const handleInput = (e: FormEvent<HTMLInputElement>) => {
129-
// 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。
130-
if (e instanceof InputEvent) {
131-
const checkInputType = e.inputType && e.inputType === 'insertCompositionText';
132-
if (e.isComposing || checkInputType) return;
142+
const handleInput = (e: ChangeEvent<HTMLInputElement>) => {
143+
const finalValue = handleInputValue(e);
144+
// react 中为合成事件,需要通过 nativeEvent 获取原始的事件
145+
const nativeEvent = e.nativeEvent as InputEvent;
146+
// 中文输入的时候 inputType 是 insertCompositionText 所以中文输入的时候禁止触发。
147+
if (nativeEvent.isComposing || nativeEvent.inputType === 'insertCompositionText') {
148+
composingRef.current = true;
149+
setComposingValue(finalValue);
150+
return;
133151
}
134-
inputValueChangeHandle(e);
152+
153+
setInnerValue(finalValue, { e, trigger: 'input' });
135154
};
136155

137156
const handleClear = (e: TouchEvent<HTMLInputElement>) => {
138157
e.preventDefault();
139-
setInnerValue('');
158+
setInnerValue('', { e, trigger: 'clear' });
140159
focus();
141160
onClear?.({ e });
142161
};
143162

144163
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
145-
focused.current = true;
164+
setFocused(true);
146165
onFocus?.(innerValue, { e });
147166
};
148167

149168
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
150-
focused.current = false;
151-
169+
setFocused(false);
152170
// 失焦时处理 format
153171
if (isFunction(format)) {
154-
setInnerValue(format(innerValue));
172+
setInnerValue(format(innerValue), { e, trigger: 'blur' });
155173
}
156174

157175
onBlur?.(innerValue, { e });
158176
};
159177

160178
const handleCompositionend = (e: CompositionEvent<HTMLInputElement>) => {
161-
inputValueChangeHandle(e);
179+
const finalValue = handleInputValue(e);
180+
composingRef.current = false;
181+
// 更新输入值
182+
setInnerValue(finalValue, { e, trigger: 'input' });
162183
};
163184

164185
const handlePwdIconClick = () => {
@@ -175,6 +196,8 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
175196
</div>
176197
);
177198

199+
const renderExtra = () => parseTNode(extra);
200+
178201
const renderClearable = () =>
179202
showClear ? (
180203
<div className={`${rootClassName}__wrap--clearable-icon`} onTouchEnd={handleClear}>
@@ -200,6 +223,12 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
200223
const renderTips = () =>
201224
tips ? <div className={`${rootClassName}__tips ${rootClassName}--${align}`}>{parseTNode(tips)}</div> : null;
202225

226+
const style: CSSProperties = {
227+
'--td-input-cursor-color': cursorColor,
228+
} as any;
229+
230+
const finalValue = composingRef.current ? composingValue : (innerValue ?? '');
231+
203232
return withNativeProps(
204233
props,
205234
<div className={rootClasses}>
@@ -209,28 +238,29 @@ const Input = forwardRef<InputRefProps, InputProps>((props, ref) => {
209238
<input
210239
ref={inputRef}
211240
autoFocus={autofocus}
212-
value={innerValue}
241+
value={finalValue}
213242
name={name}
214243
className={inputClasses}
215244
type={renderType}
216245
disabled={disabled}
217246
autoComplete={autocomplete}
218247
placeholder={placeholder}
219248
readOnly={readonly}
220-
maxLength={resultMaxLength || -1}
221249
enterKeyHint={enterkeyhint}
222250
spellCheck={spellcheck}
223251
onFocus={handleFocus}
224252
onBlur={handleBlur}
225253
onInput={handleInput}
226254
onCompositionEnd={handleCompositionend}
255+
style={style}
227256
/>
228257
{renderClearable()}
229258
{renderSuffix()}
230259
{renderSuffixIcon()}
231260
</div>
232261
{renderTips()}
233262
</div>
263+
{renderExtra()}
234264
</div>,
235265
);
236266
});

0 commit comments

Comments
 (0)