Skip to content

Commit 977a293

Browse files
authored
fix: User typing should not block (#328)
* chore: Add typing check * fix: toString logic * fix: safe type * fix: Input percien issue * fix: controlled format * fix: controlled logi * docs: back of demo * test: More test case * test: Coverage
1 parent 33e8577 commit 977a293

6 files changed

Lines changed: 71 additions & 33 deletions

File tree

docs/examples/simple.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export default () => {
77
const [disabled, setDisabled] = React.useState(false);
88
const [readOnly, setReadOnly] = React.useState(false);
99
const [keyboard, setKeyboard] = React.useState(true);
10-
const [stringMode, setStringMode] = React.useState(true);
11-
const [value, setValue] = React.useState<string | number>(5);
10+
const [stringMode, setStringMode] = React.useState(false);
11+
const [value, setValue] = React.useState<string | number>(93);
1212

1313
const onChange = (val: number) => {
1414
console.warn('onChange:', val, typeof val);
@@ -18,7 +18,7 @@ export default () => {
1818
return (
1919
<div style={{ margin: 10 }}>
2020
<h3>Controlled</h3>
21-
{/* <InputNumber
21+
<InputNumber
2222
aria-label="Simple number input example"
2323
min={-8}
2424
max={10}
@@ -29,7 +29,7 @@ export default () => {
2929
disabled={disabled}
3030
keyboard={keyboard}
3131
stringMode={stringMode}
32-
/> */}
32+
/>
3333
<p>
3434
<button type="button" onClick={() => setDisabled(!disabled)}>
3535
toggle Disabled ({String(disabled)})
@@ -47,7 +47,13 @@ export default () => {
4747

4848
<hr />
4949
<h3>Uncontrolled</h3>
50-
<InputNumber style={{ width: 100 }} onChange={onChange} min={1} max={99} defaultValue={33} />
50+
<InputNumber
51+
style={{ width: 100 }}
52+
onChange={onChange}
53+
min={-99}
54+
max={99}
55+
defaultValue={33}
56+
/>
5157
</div>
5258
);
5359
};

src/InputNumber.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const InputNumber = React.forwardRef(
126126
/**
127127
* `precision` is used for formatter & onChange.
128128
* It will auto generate by `value` & `step`.
129-
* But it will not block user typing when auto generated.
129+
* But it will not block user typing.
130130
*
131131
* Note: Auto generate `precision` is used for legacy logic.
132132
* We should remove this since we already support high precision with BigInt.
@@ -136,14 +136,14 @@ const InputNumber = React.forwardRef(
136136
*/
137137
const getPrecision = React.useCallback(
138138
(numStr: string, userTyping: boolean) => {
139-
if (precision >= 0) {
140-
return precision;
141-
}
142-
143139
if (userTyping) {
144140
return undefined;
145141
}
146142

143+
if (precision >= 0) {
144+
return precision;
145+
}
146+
147147
return Math.max(getNumberPrecision(numStr), getNumberPrecision(step));
148148
},
149149
[precision, step],
@@ -215,7 +215,15 @@ const InputNumber = React.forwardRef(
215215

216216
// Should always be string
217217
function setInputValue(newValue: DecimalClass, userTyping: boolean) {
218-
setInternalInputValue(mergedFormatter(newValue.toString(false), userTyping));
218+
setInternalInputValue(
219+
mergedFormatter(
220+
// Invalidate number is sometime passed by external control, we should let it go
221+
// Otherwise is controlled by internal interactive logic which check by userTyping
222+
// You can ref 'show limited value when input is not focused' test for more info.
223+
newValue.isInvalidate() ? newValue.toString(false) : newValue.toString(!userTyping),
224+
userTyping,
225+
),
226+
);
219227
}
220228

221229
// >>> Max & Min limit
@@ -336,7 +344,7 @@ const InputNumber = React.forwardRef(
336344
};
337345

338346
// >>> Input
339-
const onInternalInput: React.ChangeEventHandler<HTMLInputElement> = e => {
347+
const onInternalInput: React.ChangeEventHandler<HTMLInputElement> = (e) => {
340348
let inputStr = e.target.value;
341349

342350
// optimize for chinese input experience
@@ -383,14 +391,14 @@ const InputNumber = React.forwardRef(
383391
/**
384392
* Flush current input content to trigger value change & re-formatter input if needed
385393
*/
386-
const flushInputValue = () => {
394+
const flushInputValue = (userTyping: boolean) => {
387395
const parsedValue = getMiniDecimal(mergedParser(inputValue));
388396
let formatValue: DecimalClass = parsedValue;
389397

390398
if (!parsedValue.isNaN()) {
391399
// Only validate value or empty value can be re-fill to inputValue
392400
// Reassign the formatValue within ranged of trigger control
393-
formatValue = triggerValueUpdate(parsedValue, true);
401+
formatValue = triggerValueUpdate(parsedValue, userTyping);
394402
} else {
395403
formatValue = decimalValue;
396404
}
@@ -404,15 +412,15 @@ const InputNumber = React.forwardRef(
404412
}
405413
};
406414

407-
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = event => {
415+
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
408416
const { which } = event;
409417
userTypingRef.current = true;
410418

411419
if (which === KeyCode.ENTER) {
412420
if (!compositionRef.current) {
413421
userTypingRef.current = false;
414422
}
415-
flushInputValue();
423+
flushInputValue(true);
416424
onPressEnter?.(event);
417425
}
418426

@@ -433,7 +441,7 @@ const InputNumber = React.forwardRef(
433441

434442
// >>> Focus & Blur
435443
const onBlur = () => {
436-
flushInputValue();
444+
flushInputValue(false);
437445

438446
setFocus(false);
439447

src/utils/MiniDecimal.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,13 @@ export class NumberDecimal implements DecimalClass {
101101
return this.number;
102102
}
103103

104-
toString(safe = true) {
104+
toString(safe: boolean = true) {
105+
if (!safe) {
106+
return this.origin;
107+
}
108+
105109
if (this.isInvalidate()) {
106-
return safe ? '' : this.origin;
110+
return '';
107111
}
108112

109113
return num2str(this.number);
@@ -238,10 +242,15 @@ export class BigIntDecimal implements DecimalClass {
238242
return Number(this.toString());
239243
}
240244

241-
toString(safe = true): string {
245+
toString(safe: boolean = true) {
246+
if (!safe) {
247+
return this.origin;
248+
}
249+
242250
if (this.isInvalidate()) {
243-
return safe ? '' : this.origin;
251+
return '';
244252
}
253+
245254
return trimNumber(`${this.getMark()}${this.getIntegerStr()}.${this.getDecimalStr()}`).fullStr;
246255
}
247256
}

tests/decimal.test.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,15 @@ describe('InputNumber.Decimal', () => {
115115
expect(wrapper.getInputValue()).toEqual('1.00');
116116
});
117117

118-
it('zero precision should work',()=>{
118+
it('zero precision should work', () => {
119119
const onChange = jest.fn();
120120
const wrapper = mount(<InputNumber onChange={onChange} precision={0} />);
121121

122122
wrapper.changeValue('1.44');
123123
wrapper.blurInput();
124124
expect(onChange).toHaveBeenCalledWith(1);
125125
expect(wrapper.getInputValue()).toEqual('1');
126-
})
126+
});
127127

128128
it('should not trigger onChange when blur InputNumber with precision', () => {
129129
const onChange = jest.fn();
@@ -159,7 +159,7 @@ describe('InputNumber.Decimal', () => {
159159
expect(wrapper.getInputValue()).toEqual('');
160160
});
161161

162-
it('should trigger onChange when removing value',()=>{
162+
it('should trigger onChange when removing value', () => {
163163
const onChange = jest.fn();
164164
const wrapper = mount(<InputNumber onChange={onChange} />);
165165

@@ -168,18 +168,18 @@ describe('InputNumber.Decimal', () => {
168168
expect(wrapper.getInputValue()).toEqual('1');
169169
expect(onChange).toHaveBeenCalledWith(1);
170170

171-
wrapper.changeValue('')
171+
wrapper.changeValue('');
172172
expect(wrapper.getInputValue()).toEqual('');
173173
expect(onChange).toHaveBeenCalledWith(null);
174174

175-
wrapper.setProps({min:0,max:10})
176-
wrapper.changeValue('2')
177-
expect(wrapper.getInputValue()).toEqual('2')
178-
expect(onChange).toHaveBeenCalledWith(2)
175+
wrapper.setProps({ min: 0, max: 10 });
176+
wrapper.changeValue('2');
177+
expect(wrapper.getInputValue()).toEqual('2');
178+
expect(onChange).toHaveBeenCalledWith(2);
179179

180-
wrapper.changeValue('')
181-
expect(wrapper.getInputValue()).toEqual('')
182-
expect(onChange).toHaveBeenCalledWith(null)
183-
})
180+
wrapper.changeValue('');
181+
expect(wrapper.getInputValue()).toEqual('');
182+
expect(onChange).toHaveBeenCalledWith(null);
183+
});
184184
});
185185
});

tests/github.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,4 +430,13 @@ describe('InputNumber.Github', () => {
430430

431431
expect(wrapper.find('input').last().props().value).toEqual('1');
432432
});
433+
434+
// https://github.com/ant-design/ant-design/issues/30478
435+
it('-0 should input able', () => {
436+
const wrapper = mount(<InputNumber />);
437+
wrapper.changeValue('-');
438+
wrapper.changeValue('-0');
439+
440+
expect(wrapper.getInputValue()).toEqual('-0');
441+
});
433442
});

tests/util.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ describe('InputNumber.Util', () => {
8686
// mini value
8787
expect(getDecimal(0).add(-0.000000001).toString()).toEqual('-0.000000001');
8888
});
89+
90+
it('toString !safe', () => {
91+
const invalidate = getDecimal('Invalidate');
92+
expect(invalidate.toString()).toEqual('');
93+
expect(invalidate.toString(false)).toEqual('Invalidate');
94+
});
8995
});
9096
});
9197

0 commit comments

Comments
 (0)