Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions lib/src/number-input/NumberInput.test.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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