Skip to content

Commit b9577c8

Browse files
authored
feat: decuple steps with shift key (#438)
1 parent d3de147 commit b9577c8

File tree

5 files changed

+154
-29
lines changed

5 files changed

+154
-29
lines changed

src/InputNumber.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ import getMiniDecimal, {
77
roundDownUnsignedDecimal,
88
roundUpUnsignedDecimal,
99
toFixed,
10-
ValueType
10+
ValueType,
1111
} from './utils/MiniDecimal';
1212
import StepHandler from './StepHandler';
13-
import { getNumberPrecision, num2str, trimNumber, validateNumber } from './utils/numberUtil';
13+
import {
14+
getNumberPrecision,
15+
num2str,
16+
getDecupleSteps,
17+
trimNumber,
18+
validateNumber,
19+
} from './utils/numberUtil';
1420
import useCursor from './hooks/useCursor';
1521
import useUpdateEffect from './hooks/useUpdateEffect';
1622
import useFrame from './hooks/useFrame';
@@ -44,13 +50,13 @@ const getDecimalIfValidate = (value: ValueType, precision: number | undefined, i
4450
return decimal;
4551
}
4652

47-
const {negative, integerStr, decimalStr, negativeStr} = trimNumber(decimal.toString());
48-
const unSignedNumberStr = integerStr +'.' + decimalStr;
53+
const { negative, integerStr, decimalStr, negativeStr } = trimNumber(decimal.toString());
54+
const unSignedNumberStr = integerStr + '.' + decimalStr;
4955

5056
if ((isMax && !negative) || (!isMax && negative)) {
51-
return getMiniDecimal(negativeStr + roundDownUnsignedDecimal(unSignedNumberStr, precision));
57+
return getMiniDecimal(negativeStr + roundDownUnsignedDecimal(unSignedNumberStr, precision));
5258
} else {
53-
return getMiniDecimal(negativeStr + roundUpUnsignedDecimal(unSignedNumberStr, precision));
59+
return getMiniDecimal(negativeStr + roundUpUnsignedDecimal(unSignedNumberStr, precision));
5460
}
5561
};
5662

@@ -141,6 +147,7 @@ const InputNumber = React.forwardRef(
141147

142148
const userTypingRef = React.useRef(false);
143149
const compositionRef = React.useRef(false);
150+
const shiftKeyRef = React.useRef(false);
144151

145152
// ============================ Value =============================
146153
// Real value control
@@ -261,8 +268,14 @@ const InputNumber = React.forwardRef(
261268
}
262269

263270
// >>> Max & Min limit
264-
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max, precision, true), [max, precision]);
265-
const minDecimal = React.useMemo(() => getDecimalIfValidate(min, precision, false), [min, precision]);
271+
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max, precision, true), [
272+
max,
273+
precision,
274+
]);
275+
const minDecimal = React.useMemo(() => getDecimalIfValidate(min, precision, false), [
276+
min,
277+
precision,
278+
]);
266279

267280
const upDisabled = React.useMemo(() => {
268281
if (!maxDecimal || !decimalValue || decimalValue.isInvalidate()) {
@@ -400,7 +413,7 @@ const InputNumber = React.forwardRef(
400413
};
401414

402415
// >>> Input
403-
const onInternalInput: React.ChangeEventHandler<HTMLInputElement> = (e) => {
416+
const onInternalInput: React.ChangeEventHandler<HTMLInputElement> = e => {
404417
collectInputValue(e.target.value);
405418
};
406419

@@ -415,7 +428,7 @@ const InputNumber = React.forwardRef(
415428
// We should sync with input value.
416429
userTypingRef.current = false;
417430

418-
let stepDecimal = getMiniDecimal(step);
431+
let stepDecimal = getMiniDecimal(shiftKeyRef.current ? getDecupleSteps(step) : step);
419432
if (!up) {
420433
stepDecimal = stepDecimal.negate();
421434
}
@@ -425,7 +438,7 @@ const InputNumber = React.forwardRef(
425438
const updatedValue = triggerValueUpdate(target, false);
426439

427440
onStep?.(getDecimalValue(stringMode, updatedValue), {
428-
offset: step,
441+
offset: shiftKeyRef.current ? getDecupleSteps(step) : step,
429442
type: up ? 'up' : 'down',
430443
});
431444

@@ -457,10 +470,16 @@ const InputNumber = React.forwardRef(
457470
}
458471
};
459472

460-
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
461-
const { which } = event;
473+
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = event => {
474+
const { which, shiftKey } = event;
462475
userTypingRef.current = true;
463476

477+
if (shiftKey) {
478+
shiftKeyRef.current = true;
479+
} else {
480+
shiftKeyRef.current = false;
481+
}
482+
464483
if (which === KeyCode.ENTER) {
465484
if (!compositionRef.current) {
466485
userTypingRef.current = false;
@@ -482,6 +501,7 @@ const InputNumber = React.forwardRef(
482501

483502
const onKeyUp = () => {
484503
userTypingRef.current = false;
504+
shiftKeyRef.current = false;
485505
};
486506

487507
// >>> Focus & Blur

src/utils/numberUtil.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,12 @@ export function validateNumber(num: string | number) {
113113
/^\s*-?\.\d+\s*$/.test(num)
114114
);
115115
}
116+
117+
export function getDecupleSteps(step: string | number) {
118+
const stepStr = typeof step === 'number' ? num2str(step) : trimNumber(step).fullStr;
119+
const hasPoint = stepStr.includes('.');
120+
if (!hasPoint) {
121+
return step + '0';
122+
}
123+
return trimNumber(stepStr.replace(/(\d)\.(\d)/g, '$1$2.')).fullStr;
124+
}

tests/click.test.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ describe('InputNumber.Click', () => {
3737
testInputNumber(
3838
'up button',
3939
{ defaultValue: 10 },
40-
(wrapper) => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
40+
wrapper => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
4141
11,
4242
'up',
4343
);
4444

4545
testInputNumber(
4646
'down button',
4747
{ value: 10 },
48-
(wrapper) => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
48+
wrapper => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
4949
9,
5050
'down',
5151
);
@@ -55,15 +55,15 @@ describe('InputNumber.Click', () => {
5555
testInputNumber(
5656
'up button',
5757
{},
58-
(wrapper) => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
58+
wrapper => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
5959
1,
6060
'up',
6161
);
6262

6363
testInputNumber(
6464
'down button',
6565
{},
66-
(wrapper) => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
66+
wrapper => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
6767
-1,
6868
'down',
6969
);
@@ -73,15 +73,15 @@ describe('InputNumber.Click', () => {
7373
testInputNumber(
7474
'up button',
7575
{ min: 6, max: 10 },
76-
(wrapper) => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
76+
wrapper => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
7777
6,
7878
'up',
7979
);
8080

8181
testInputNumber(
8282
'down button',
8383
{ min: 6, max: 10 },
84-
(wrapper) => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
84+
wrapper => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
8585
6,
8686
'down',
8787
);
@@ -91,15 +91,15 @@ describe('InputNumber.Click', () => {
9191
testInputNumber(
9292
'up button',
9393
{ value: null, min: 6, max: 10 },
94-
(wrapper) => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
94+
wrapper => wrapper.find('.rc-input-number-handler-up').simulate('mouseDown'),
9595
6,
9696
'up',
9797
);
9898

9999
testInputNumber(
100100
'down button',
101101
{ value: null, min: 6, max: 10 },
102-
(wrapper) => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
102+
wrapper => wrapper.find('.rc-input-number-handler-down').simulate('mouseDown'),
103103
6,
104104
'down',
105105
);
@@ -182,4 +182,36 @@ describe('InputNumber.Click', () => {
182182
expect(onBlur).toHaveBeenCalled();
183183
expect(wrapper.exists('.rc-input-number-focused')).toBeFalsy();
184184
});
185+
186+
it('click down button with pressing shift key', () => {
187+
const onChange = jest.fn();
188+
const onStep = jest.fn();
189+
const wrapper = mount(
190+
<InputNumber onChange={onChange} onStep={onStep} step={0.01} value={1.2} />,
191+
);
192+
193+
wrapper
194+
.find('.rc-input-number-handler-down')
195+
.simulate('keyDown', { shiftKey: true })
196+
.simulate('mouseDown');
197+
198+
expect(onChange).toHaveBeenCalledWith(1.1);
199+
expect(onStep).toHaveBeenCalledWith(1.1, { offset: '0.1', type: 'down' });
200+
});
201+
202+
it('click up button with pressing shift key', () => {
203+
const onChange = jest.fn();
204+
const onStep = jest.fn();
205+
const wrapper = mount(
206+
<InputNumber onChange={onChange} onStep={onStep} step={0.01} value={1.2} />,
207+
);
208+
209+
wrapper
210+
.find('.rc-input-number-handler-up')
211+
.simulate('keyDown', { shiftKey: true })
212+
.simulate('mouseDown');
213+
214+
expect(onChange).toHaveBeenCalledWith(1.3);
215+
expect(onStep).toHaveBeenCalledWith(1.3, { offset: '0.1', type: 'up' });
216+
});
185217
});

tests/keyboard.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,27 @@ describe('InputNumber.Keyboard', () => {
1111
expect(onChange).toHaveBeenCalledWith(1);
1212
});
1313

14+
it('up with pressing shift key', () => {
15+
const onChange = jest.fn();
16+
const wrapper = mount(<InputNumber onChange={onChange} step={0.01} value={1.2} />);
17+
wrapper.find('input').simulate('keyDown', { which: KeyCode.UP, shiftKey: true });
18+
expect(onChange).toHaveBeenCalledWith(1.3);
19+
});
20+
1421
it('down', () => {
1522
const onChange = jest.fn();
1623
const wrapper = mount(<InputNumber onChange={onChange} />);
1724
wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
1825
expect(onChange).toHaveBeenCalledWith(-1);
1926
});
2027

28+
it('down with pressing shift key', () => {
29+
const onChange = jest.fn();
30+
const wrapper = mount(<InputNumber onChange={onChange} step={0.01} value={1.2} />);
31+
wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN, shiftKey: true });
32+
expect(onChange).toHaveBeenCalledWith(1.1);
33+
});
34+
2135
// shift + 10, ctrl + 0.1 test case removed
2236

2337
it('disabled keyboard', () => {

tests/props.test.tsx

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ describe('InputNumber.Props', () => {
1212
}
1313

1414
expect(onChange.mock.calls[onChange.mock.calls.length - 1][0]).toEqual(10);
15-
expect(wrapper.find('input').props()).toEqual(expect.objectContaining({
16-
'aria-valuemax': 10,
17-
'aria-valuenow': '10',
18-
}));
15+
expect(wrapper.find('input').props()).toEqual(
16+
expect.objectContaining({
17+
'aria-valuemax': 10,
18+
'aria-valuenow': '10',
19+
}),
20+
);
1921
});
2022

2123
it('min', () => {
@@ -26,10 +28,12 @@ describe('InputNumber.Props', () => {
2628
}
2729

2830
expect(onChange.mock.calls[onChange.mock.calls.length - 1][0]).toEqual(-10);
29-
expect(wrapper.find('input').props()).toEqual(expect.objectContaining({
30-
'aria-valuemin': -10,
31-
'aria-valuenow': '-10',
32-
}));
31+
expect(wrapper.find('input').props()).toEqual(
32+
expect.objectContaining({
33+
'aria-valuemin': -10,
34+
'aria-valuenow': '-10',
35+
}),
36+
);
3337
});
3438

3539
it('disabled', () => {
@@ -70,6 +74,19 @@ describe('InputNumber.Props', () => {
7074
expect(wrapper.find('input').props().step).toEqual(5);
7175
});
7276

77+
it('basic with pressing shift key', () => {
78+
const onChange = jest.fn();
79+
const wrapper = mount(<InputNumber onChange={onChange} step={5} />);
80+
81+
for (let i = 0; i < 3; i += 1) {
82+
wrapper
83+
.find('.rc-input-number-handler-down')
84+
.simulate('keyDown', { shiftKey: true })
85+
.simulate('mouseDown');
86+
expect(onChange).toHaveBeenCalledWith(-5 * (i + 1) * 10);
87+
}
88+
});
89+
7390
it('stringMode', () => {
7491
const onChange = jest.fn();
7592
const wrapper = mount(
@@ -88,6 +105,27 @@ describe('InputNumber.Props', () => {
88105
expect(onChange).toHaveBeenCalledWith('-0.00000001');
89106
});
90107

108+
it('stringMode with pressing shift key', () => {
109+
const onChange = jest.fn();
110+
const wrapper = mount(
111+
<InputNumber
112+
stringMode
113+
onChange={onChange}
114+
step="0.0000000001" // 1e-10
115+
defaultValue="0.000000001" // 1e-9
116+
/>,
117+
);
118+
119+
for (let i = 0; i < 11; i += 1) {
120+
wrapper
121+
.find('.rc-input-number-handler-down')
122+
.simulate('keyDown', { shiftKey: true })
123+
.simulate('mouseDown');
124+
}
125+
126+
expect(onChange).toHaveBeenCalledWith('-0.00000001'); // -1e-8
127+
});
128+
91129
it('decimal', () => {
92130
const onChange = jest.fn();
93131
const wrapper = mount(<InputNumber onChange={onChange} step={0.1} defaultValue={0.9} />);
@@ -96,6 +134,18 @@ describe('InputNumber.Props', () => {
96134
}
97135
expect(onChange).toHaveBeenCalledWith(1.2);
98136
});
137+
138+
it('decimal with pressing shift key', () => {
139+
const onChange = jest.fn();
140+
const wrapper = mount(<InputNumber onChange={onChange} step={0.1} defaultValue={0.9} />);
141+
for (let i = 0; i < 3; i += 1) {
142+
wrapper
143+
.find('.rc-input-number-handler-up')
144+
.simulate('keyDown', { shiftKey: true })
145+
.simulate('mouseDown');
146+
}
147+
expect(onChange).toHaveBeenCalledWith(3.9);
148+
});
99149
});
100150

101151
describe('controlled', () => {

0 commit comments

Comments
 (0)