Skip to content

Commit

Permalink
Merge pull request #1838 from dxc-technology/Mil4n0r/fix-NaN-textinput
Browse files Browse the repository at this point in the history
Fixed bug in TextInputs when scrolling
  • Loading branch information
GomezIvann committed Feb 26, 2024
2 parents ecb3627 + 12d707c commit 51152f3
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 77 deletions.
90 changes: 88 additions & 2 deletions lib/src/number-input/NumberInput.test.js
Expand Up @@ -49,7 +49,7 @@ describe("Number input component tests", () => {
await userEvent.click(increment);
expect(number.value).toBe("");
});

test("Number input is read only and cannot be incremented or decremented using the arrow keys", () => {
const { getByLabelText } = render(<DxcNumberInput label="Number label" readOnly />);
const number = getByLabelText("Number label");
Expand All @@ -68,7 +68,9 @@ describe("Number input component tests", () => {
test("Number input is not optional: required field, displays error if not filled in", () => {
const onBlur = jest.fn();
const onChange = jest.fn();
const { getByLabelText } = render(<DxcNumberInput label="Number input label" onBlur={onBlur} onChange={onChange} />);
const { getByLabelText } = render(
<DxcNumberInput label="Number input label" onBlur={onBlur} onChange={onChange} />
);
const number = getByLabelText("Number input label");
userEvent.type(number, "1");
userEvent.clear(number);
Expand Down Expand Up @@ -381,6 +383,90 @@ describe("Number input component tests", () => {
expect(number.value).toBe("5");
});

test("Value is unchanged when using the scroll wheel in mouse in a disabled input", () => {
const { getByLabelText } = render(
<DxcNumberInput disabled label="Number input label" min={5} max={20} step={5} defaultValue={10} />
);
const number = getByLabelText("Number input label");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("10");
});

test("Value is unchanged when using the arrows in keyboard in a disabled input", () => {
const { getByLabelText } = render(
<DxcNumberInput disabled label="Number input label" min={5} max={20} step={5} defaultValue={10} />
);
const number = getByLabelText("Number input label");
fireEvent.keyDown(number, { keyCode: 38 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 40 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 38 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 40 });
expect(number.value).toBe("10");
});

test("Value is unchanged when using the scroll wheel in mouse in a read-only input", () => {
const { getByLabelText } = render(
<DxcNumberInput readOnly label="Number input label" min={5} max={20} step={5} defaultValue={10} />
);
const number = getByLabelText("Number input label");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("10");
});

test("Value is unchanged when using the arrows in keyboard in a read-only input", () => {
const { getByLabelText } = render(
<DxcNumberInput readOnly label="Number input label" min={5} max={20} step={5} defaultValue={10} />
);
const number = getByLabelText("Number input label");
fireEvent.keyDown(number, { keyCode: 38 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 40 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 38 });
expect(number.value).toBe("10");
fireEvent.keyDown(number, { keyCode: 40 });
expect(number.value).toBe("10");
});

test("Increment and decrement the value with min, max and step using the scroll wheel in mouse", () => {
const { getByLabelText } = render(<DxcNumberInput label="Number input label" min={5} max={20} step={5} />);
const number = getByLabelText("Number input label");
userEvent.type(number, "1");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("5");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("15");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("20");
fireEvent.wheel(number, { deltaY: -100 });
expect(number.value).toBe("20");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("15");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("10");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("5");
fireEvent.wheel(number, { deltaY: 100 });
expect(number.value).toBe("5");
});

test("Number has correct accessibility attributes", () => {
const { getByLabelText, getAllByRole } = render(<DxcNumberInput label="Number input label" />);
const number = getByLabelText("Number input label");
Expand Down
72 changes: 44 additions & 28 deletions lib/src/number-input/NumberInput.tsx
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import styled from "styled-components";
import DxcTextInput from "../text-input/TextInput";
import NumberInputPropsType, { RefType } from "./types";
Expand Down Expand Up @@ -38,33 +38,49 @@ const DxcNumberInput = React.forwardRef<RefType, NumberInputPropsType>(
tabIndex,
},
ref
) => (
<NumberInputContext.Provider value={{ typeNumber: "number", minNumber: min, maxNumber: max, stepNumber: step }}>
<NumberInputContainer>
<DxcTextInput
label={label}
name={name}
defaultValue={defaultValue}
value={value}
helperText={helperText}
placeholder={placeholder}
disabled={disabled}
optional={optional}
readOnly={readOnly}
prefix={prefix}
suffix={suffix}
error={error}
onChange={onChange}
onBlur={onBlur}
autocomplete={autocomplete}
margin={margin}
size={size}
tabIndex={tabIndex}
ref={ref}
/>
</NumberInputContainer>
</NumberInputContext.Provider>
)
) => {
const numberInputRef = React.useRef<HTMLInputElement>(null);

useEffect(() => {
const input = numberInputRef.current?.getElementsByTagName("input")[0] as HTMLInputElement;
const preventDefault = (event: WheelEvent) => {
event.preventDefault();
};

input?.addEventListener("wheel", preventDefault, { passive: false });
return () => {
input?.removeEventListener("wheel", preventDefault);
};
}, []);

return (
<NumberInputContext.Provider value={{ typeNumber: "number", minNumber: min, maxNumber: max, stepNumber: step }}>
<NumberInputContainer ref={numberInputRef}>
<DxcTextInput
label={label}
name={name}
defaultValue={defaultValue}
value={value}
helperText={helperText}
placeholder={placeholder}
disabled={disabled}
optional={optional}
readOnly={readOnly}
prefix={prefix}
suffix={suffix}
error={error}
onChange={onChange}
onBlur={onBlur}
autocomplete={autocomplete}
margin={margin}
size={size}
tabIndex={tabIndex}
ref={ref}
/>
</NumberInputContainer>
</NumberInputContext.Provider>
);
}
);

const NumberInputContainer = styled.div`
Expand Down
11 changes: 11 additions & 0 deletions lib/src/text-input/TextInput.test.js
Expand Up @@ -462,6 +462,17 @@ describe("TextInput component tests", () => {
const options = getAllByRole("option");
expect(options[0].getAttribute("aria-selected")).toBeNull();
});

test("Mouse wheel interaction does not affect the text value", () => {
const { getByRole } = render(
<DxcTextInput label="Default label" placeholder="Placeholder" defaultValue="Example text" />
);
const input = getByRole("textbox");
fireEvent.wheel(input, { deltaY: -100 });
expect(input.value).toBe("Example text");
fireEvent.wheel(input, { deltaY: 100 });
expect(input.value).toBe("Example text");
});
});

describe("TextInput component synchronous autosuggest tests", () => {
Expand Down
88 changes: 41 additions & 47 deletions lib/src/text-input/TextInput.tsx
Expand Up @@ -168,6 +168,43 @@ const DxcTextInput = React.forwardRef<RefType, TextInputPropsType>(
else onChange?.({ value: formattedValue });
};

const decrementNumber = (currentValue = value ?? innerValue) => {
if (!disabled && !readOnly) {
const numberValue = Number(currentValue);
const steppedValue = Math.round((numberValue - numberInputContext?.stepNumber + Number.EPSILON) * 100) / 100;

if (currentValue !== "") {
if (numberValue < numberInputContext?.minNumber || steppedValue < numberInputContext?.minNumber)
changeValue(numberValue);
else if (numberValue > numberInputContext?.maxNumber) changeValue(numberInputContext?.maxNumber);
else if (numberValue === numberInputContext?.minNumber) changeValue(numberInputContext?.minNumber);
else changeValue(steppedValue);
} else {
if (numberInputContext?.minNumber >= 0) changeValue(numberInputContext?.minNumber);
else if (numberInputContext?.maxNumber < 0) changeValue(numberInputContext?.maxNumber);
else changeValue(-numberInputContext.stepNumber);
}
}
};
const incrementNumber = (currentValue = value ?? innerValue) => {
if (!disabled && !readOnly) {
const numberValue = Number(currentValue);
const steppedValue = Math.round((numberValue + numberInputContext?.stepNumber + Number.EPSILON) * 100) / 100;

if (currentValue !== "") {
if (numberValue > numberInputContext?.maxNumber || steppedValue > numberInputContext?.maxNumber)
changeValue(numberValue);
else if (numberValue < numberInputContext?.minNumber) changeValue(numberInputContext?.minNumber);
else if (numberValue === numberInputContext?.maxNumber) changeValue(numberInputContext?.maxNumber);
else changeValue(steppedValue);
} else {
if (numberInputContext?.minNumber > 0) changeValue(numberInputContext?.minNumber);
else if (numberInputContext?.maxNumber <= 0) changeValue(numberInputContext?.maxNumber);
else changeValue(numberInputContext.stepNumber);
}
}
};

const handleInputContainerOnClick = () => {
document.activeElement !== actionRef.current && inputRef.current.focus();
};
Expand Down Expand Up @@ -248,12 +285,10 @@ const DxcTextInput = React.forwardRef<RefType, TextInputPropsType>(
break;
}
};
const handleWheel = useCallback((event: WheelEvent) => {
if (document.activeElement === inputRef.current) {
event.preventDefault();
const handleNumberInputWheel = (event: React.WheelEvent<HTMLInputElement>) => {
if (document.activeElement === inputRef.current)
event.deltaY < 0 ? incrementNumber(inputRef.current.value) : decrementNumber(inputRef.current.value);
}
}, []);
};

const handleClearActionOnClick = () => {
changeValue("");
Expand All @@ -276,38 +311,6 @@ const DxcTextInput = React.forwardRef<RefType, TextInputPropsType>(
inputRef?.current?.setAttribute("step", step);
inputRef?.current?.setAttribute("type", type);
};
const decrementNumber = (currentValue = value ?? innerValue) => {
const numberValue = Number(currentValue);
const steppedValue = Math.round((numberValue - numberInputContext?.stepNumber + Number.EPSILON) * 100) / 100;

if (currentValue !== "") {
if (numberValue < numberInputContext?.minNumber || steppedValue < numberInputContext?.minNumber)
changeValue(numberValue);
else if (numberValue > numberInputContext?.maxNumber) changeValue(numberInputContext?.maxNumber);
else if (numberValue === numberInputContext?.minNumber) changeValue(numberInputContext?.minNumber);
else changeValue(steppedValue);
} else {
if (numberInputContext?.minNumber >= 0) changeValue(numberInputContext?.minNumber);
else if (numberInputContext?.maxNumber < 0) changeValue(numberInputContext?.maxNumber);
else changeValue(-numberInputContext.stepNumber);
}
};
const incrementNumber = (currentValue = value ?? innerValue) => {
const numberValue = Number(currentValue);
const steppedValue = Math.round((numberValue + numberInputContext?.stepNumber + Number.EPSILON) * 100) / 100;

if (currentValue !== "") {
if (numberValue > numberInputContext?.maxNumber || steppedValue > numberInputContext?.maxNumber)
changeValue(numberValue);
else if (numberValue < numberInputContext?.minNumber) changeValue(numberInputContext?.minNumber);
else if (numberValue === numberInputContext?.maxNumber) changeValue(numberInputContext?.maxNumber);
else changeValue(steppedValue);
} else {
if (numberInputContext?.minNumber > 0) changeValue(numberInputContext?.minNumber);
else if (numberInputContext?.maxNumber <= 0) changeValue(numberInputContext?.maxNumber);
else changeValue(numberInputContext.stepNumber);
}
};

useEffect(() => {
if (typeof suggestions === "function") {
Expand Down Expand Up @@ -348,16 +351,6 @@ const DxcTextInput = React.forwardRef<RefType, TextInputPropsType>(
);
}, [value, innerValue, suggestions, numberInputContext]);

useEffect(() => {
const input = inputRef.current;

input.addEventListener('wheel', handleWheel, { passive: false });

return () => {
input.removeEventListener('wheel', handleWheel);
};
}, [handleWheel]);

return (
<ThemeProvider theme={colorsTheme.textInput}>
<TextInputContainer margin={margin} size={size} ref={ref}>
Expand Down Expand Up @@ -428,6 +421,7 @@ const DxcTextInput = React.forwardRef<RefType, TextInputPropsType>(
onMouseDown={(event) => {
event.stopPropagation();
}}
onWheel={numberInputContext?.typeNumber === "number" ? handleNumberInputWheel : undefined}
disabled={disabled}
readOnly={readOnly}
ref={inputRef}
Expand Down

0 comments on commit 51152f3

Please sign in to comment.