Skip to content

Commit

Permalink
fix NumericInput onKeyPress and onInput handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
Sharqiewicz committed Jun 26, 2024
1 parent 29e7071 commit 3b7d9b8
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 44 deletions.
21 changes: 20 additions & 1 deletion src/components/Form/From/NumericInput/NumericInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@ describe('NumericInput Component', () => {
const inputElement = getByPlaceholderText('0.0') as HTMLInputElement;

await userEvent.type(inputElement, '123.4567890123456789abcgdehyu0123456.2746472.93.2.7.3.5.3');
expect(inputElement.value).toBe('123.456789012343');
expect(inputElement.value).toBe('123.456789012345');
});

it('should allow replace any digit user wants', async () => {
const { getByPlaceholderText } = render(<NumericInput register={mockRegister} maxDecimals={3} />);
const inputElement = getByPlaceholderText('0.0') as HTMLInputElement;

await userEvent.type(inputElement, '123.421');
await userEvent.keyboard('{arrowleft}{arrowleft}{arrowleft}{arrowleft}{arrowleft}{backspace}');
// The keyboard is being reset to the end of the input, so we need to move it back to the left
await userEvent.keyboard('{arrowleft}{arrowleft}{arrowleft}{arrowleft}{arrowleft}4');
expect(inputElement.value).toBe('143.421');

await userEvent.keyboard('{arrowleft}{arrowleft}{backspace}');
await userEvent.keyboard('{arrowleft}{arrowleft}7');
expect(inputElement.value).toBe('143.721');

await userEvent.keyboard('{arrowleft}{arrowleft}{arrowleft}{backspace}');
await userEvent.keyboard('{arrowleft}{arrowleft}{arrowleft}9');
expect(inputElement.value).toBe('1439721');
});
});
79 changes: 48 additions & 31 deletions src/components/Form/From/NumericInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Input } from 'react-daisyui';
import { UseFormRegisterReturn } from 'react-hook-form';
import { handleOnKeyPressExceedsMaxDecimals, USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals';

import {
alreadyHasDecimal,
handleOnInputExceedsMaxDecimals,
USER_INPUT_MAX_DECIMALS,
} from '../../../../shared/parseNumbers/maxDecimals';

interface NumericInputProps {
register: UseFormRegisterReturn;
Expand All @@ -11,9 +16,12 @@ interface NumericInputProps {
autoFocus?: boolean;
}

function handleOnInput(e: KeyboardEvent): void {
const target = e.target as HTMLInputElement;
target.value = target.value.replace(/,/g, '.');
const isValidNumericInput = (value: string): boolean => /^[0-9.,]*$/.test(value);

function handleOnKeyPressNumericInput(e: KeyboardEvent): void {
if (!isValidNumericInput(e.key) || alreadyHasDecimal(e)) {
e.preventDefault();
}
}

export const NumericInput = ({
Expand All @@ -23,31 +31,40 @@ export const NumericInput = ({
maxDecimals = USER_INPUT_MAX_DECIMALS.PENDULUM,
defaultValue,
autoFocus,
}: NumericInputProps) => (
<div className="flex justify-between w-full">
<div className="flex-grow text-4xl text-black font-outfit">
<Input
autocomplete="off"
autocorrect="off"
autocapitalize="none"
className={
'input-ghost w-full text-4xl font-outfit pl-0 focus:outline-none focus:text-accent-content text-accent-content ' +
additionalStyle
}
minlength="1"
onKeyPress={(e: KeyboardEvent) => handleOnKeyPressExceedsMaxDecimals(e, maxDecimals)}
onInput={handleOnInput}
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder="0.0"
readOnly={readOnly}
spellcheck="false"
step="any"
type="text"
inputmode="decimal"
value={defaultValue}
autoFocus={autoFocus}
{...register}
/>
}: NumericInputProps) => {
function handleOnInput(e: KeyboardEvent): void {
const target = e.target as HTMLInputElement;
target.value = target.value.replace(/,/g, '.');

handleOnInputExceedsMaxDecimals(e, maxDecimals);
}

return (
<div className="flex justify-between w-full">
<div className="flex-grow text-4xl text-black font-outfit">
<Input
autocomplete="off"
autocorrect="off"
autocapitalize="none"
className={
'input-ghost w-full text-4xl font-outfit pl-0 focus:outline-none focus:text-accent-content text-accent-content ' +
additionalStyle
}
onKeyPress={handleOnKeyPressNumericInput}
minlength="1"
onInput={handleOnInput}
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder="0.0"
readOnly={readOnly}
spellcheck="false"
step="any"
type="text"
inputmode="decimal"
value={defaultValue}
autoFocus={autoFocus}
{...register}
/>
</div>
</div>
</div>
);
);
};
16 changes: 4 additions & 12 deletions src/shared/parseNumbers/maxDecimals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ export enum USER_INPUT_MAX_DECIMALS {
STELLAR = 7,
}

function isValidNumericInput(value: string): boolean {
return /^[0-9.,]*$/.test(value);
}

function alreadyHasDecimal(e: KeyboardEvent) {
export function alreadyHasDecimal(e: KeyboardEvent) {
const decimalChars = ['.', ','];

// In the onInput event, "," is replaced by ".", so we check if the e.target.value already contains a "."
Expand All @@ -18,6 +14,7 @@ export function trimMaxDecimals(value: string, maxDecimals: number): string {
const [integer, decimal] = value.split('.');
return decimal ? `${integer}.${decimal.slice(0, maxDecimals)}` : value;
}

export function exceedsMaxDecimals(value: unknown, maxDecimals: number) {
if (value === undefined || value === null) return true;
const decimalPlaces = value.toString().split('.')[1];
Expand All @@ -31,13 +28,8 @@ function truncateIfExceedsMaxDecimals(value: string, maxDecimals: number): strin
return value;
}

export function handleOnKeyPressExceedsMaxDecimals(e: KeyboardEvent, maxDecimals: number): void {
if (!isValidNumericInput(e.key) || alreadyHasDecimal(e)) {
e.preventDefault();
}
export function handleOnInputExceedsMaxDecimals(e: KeyboardEvent, maxDecimals: number): void {
const target = e.target as HTMLInputElement;

// We subtract 1 from maxDecimals because the onKeyPress event is triggered before the new character is added to the target.value
const onKeyPressMaxDecimals = maxDecimals - 1;
target.value = truncateIfExceedsMaxDecimals(target.value, onKeyPressMaxDecimals);
target.value = truncateIfExceedsMaxDecimals(target.value, maxDecimals);
}

0 comments on commit 3b7d9b8

Please sign in to comment.