Skip to content

Commit

Permalink
feat: support mouse wheel (#597)
Browse files Browse the repository at this point in the history
* feat: support mouse wheel

* Fix scroll, add to simple demo

---------

Co-authored-by: Mikhail Petrov <for.mvpetrov@gmail.com>
  • Loading branch information
mvp-v and Mikhail Petrov authored Oct 29, 2023
1 parent 86a93fe commit c139821
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ online example: https://input-number.vercel.app/
<td></td>
<td>Specifies the inputmode of input</td>
</tr>
<tr>
<td>wheel</td>
<td>Boolean</td>
<td>true</td>
<td>Allows changing value with mouse wheel</td>
</tr>
</tbody>
</table>

Expand All @@ -246,6 +252,10 @@ online example: https://input-number.vercel.app/
* With the <kbd>Shift</kbd> key (<kbd>Shift+⬆</kbd>, <kbd>Shift+⬇</kbd>), the input value will be changed by `10 * step`
* With the <kbd>Ctrl</kbd> or <kbd>⌘</kbd> key (<kbd>Ctrl+⬆</kbd> or <kbd>⌘+⬆</kbd> or <kbd>Ctrl+⬇</kbd> or <kbd>⌘+⬇</kbd> ), the input value will be changed by `0.1 * step`

## Mouse Wheel
* When you scroll up or down, the input value will be increased or decreased by `step`
* Scrolling with the <kbd>Shift</kbd> key, the input value will be changed by `10 * step`

## Test Case

```
Expand Down
5 changes: 5 additions & 0 deletions docs/demo/simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default () => {
const [disabled, setDisabled] = React.useState(false);
const [readOnly, setReadOnly] = React.useState(false);
const [keyboard, setKeyboard] = React.useState(true);
const [wheel, setWheel] = React.useState(true);
const [stringMode, setStringMode] = React.useState(false);
const [value, setValue] = React.useState<string | number>(93);

Expand All @@ -28,6 +29,7 @@ export default () => {
readOnly={readOnly}
disabled={disabled}
keyboard={keyboard}
wheel={wheel}
stringMode={stringMode}
/>
<p>
Expand All @@ -43,6 +45,9 @@ export default () => {
<button type="button" onClick={() => setStringMode(!stringMode)}>
toggle stringMode ({String(stringMode)})
</button>
<button type="button" onClick={() => setWheel(!wheel)}>
toggle wheel ({String(wheel)})
</button>
</p>

<hr />
Expand Down
22 changes: 22 additions & 0 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface InputNumberProps<T extends ValueType = ValueType>
upHandler?: React.ReactNode;
downHandler?: React.ReactNode;
keyboard?: boolean;
wheel?: boolean;

/** Parse display value to validate number */
parser?: (displayValue: string | undefined) => T;
Expand Down Expand Up @@ -127,6 +128,7 @@ const InternalInputNumber = React.forwardRef(
upHandler,
downHandler,
keyboard,
wheel,
controls = true,

classNames,
Expand Down Expand Up @@ -517,6 +519,26 @@ const InternalInputNumber = React.forwardRef(
shiftKeyRef.current = false;
};

React.useEffect(() => {
const onWheel = (event) => {
if (wheel === false) {
return;
};
// moving mouse wheel rises wheel event with deltaY < 0
// scroll value grows from top to bottom, as screen Y coordinate
onInternalStep(event.deltaY < 0);
event.preventDefault();
};
const input = inputRef.current;
if (input) {
// React onWheel is passive and we can't preventDefault() in it.
// That's why we should subscribe with DOM listener
// https://stackoverflow.com/questions/63663025/react-onwheel-handler-cant-preventdefault-because-its-a-passive-event-listenev
input.addEventListener('wheel', onWheel);
return () => input.removeEventListener('wheel', onWheel);
};
}, [onInternalStep]);

// >>> Focus & Blur
const onBlur = () => {
if (changeOnBlur) {
Expand Down
71 changes: 71 additions & 0 deletions tests/wheel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import KeyCode from 'rc-util/lib/KeyCode';
import InputNumber from '../src';
import { fireEvent, render } from './util/wrapper';

describe('InputNumber.Wheel', () => {
it('wheel up', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber onChange={onChange} />);
fireEvent.wheel(container.querySelector('input'), {deltaY: -1});
expect(onChange).toHaveBeenCalledWith(1);
});

it('wheel up with pressing shift key', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber onChange={onChange} step={0.01} value={1.2} />);
fireEvent.keyDown(container.querySelector('input'), {
which: KeyCode.SHIFT,
key: 'Shift',
keyCode: KeyCode.SHIFT,
shiftKey: true,
});
fireEvent.wheel(container.querySelector('input'), {deltaY: -1});
expect(onChange).toHaveBeenCalledWith(1.3);
});

it('wheel down', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber onChange={onChange} />);
fireEvent.wheel(container.querySelector('input'), {deltaY: 1});
expect(onChange).toHaveBeenCalledWith(-1);
});

it('wheel down with pressing shift key', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber onChange={onChange} step={0.01} value={1.2} />);
fireEvent.keyDown(container.querySelector('input'), {
which: KeyCode.SHIFT,
key: 'Shift',
keyCode: KeyCode.SHIFT,
shiftKey: true,
});
fireEvent.wheel(container.querySelector('input'), {deltaY: 1});
expect(onChange).toHaveBeenCalledWith(1.1);
});

it('disabled wheel', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber wheel={false} onChange={onChange} />);

fireEvent.wheel(container.querySelector('input'), {deltaY: -1});
expect(onChange).not.toHaveBeenCalled();

fireEvent.wheel(container.querySelector('input'), {deltaY: 1});
expect(onChange).not.toHaveBeenCalled();
});

it('wheel is limited to range', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber onChange={onChange} min={-3} max={3} />);
fireEvent.keyDown(container.querySelector('input'), {
which: KeyCode.SHIFT,
key: 'Shift',
keyCode: KeyCode.SHIFT,
shiftKey: true,
});
fireEvent.wheel(container.querySelector('input'), {deltaY: -1});
expect(onChange).toHaveBeenCalledWith(3);
fireEvent.wheel(container.querySelector('input'), {deltaY: 1});
expect(onChange).toHaveBeenCalledWith(-3);
});
});

0 comments on commit c139821

Please sign in to comment.