Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ This is a major release, and it might be not compatible with your current usage
### Fixed

- toggling on/off the `<HandleTools/>` was corrected, they kept displayed after re-entering with the cursor
- `TextField` with type "number" allows only numeric input in all browsers

### Changed

Expand Down
25 changes: 23 additions & 2 deletions src/components/TextField/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,27 @@ export const TextField = ({
handleLabelEscape();
return false;
}

if (otherBlueprintInputGroupProps.type === "number") {
const allowedKeys = ["Backspace", "ArrowLeft", "ArrowRight", "Delete"];
const inputValue = (event.target as HTMLInputElement).value;

if (event.key === "-" && inputValue.length === 0) {
return;
}

if (event.key === "." && !inputValue.includes(".")) {
return;
}

if (!/^\d$/.test(event.key) && !allowedKeys.includes(event.key)) {
event.preventDefault();
}
}

return otherBlueprintInputGroupProps.onKeyDown?.(event);
},
[otherBlueprintInputGroupProps.onKeyDown, escapeToBlur]
[otherBlueprintInputGroupProps.onKeyDown, otherBlueprintInputGroupProps.type, escapeToBlur]
);

let iconIntent;
Expand All @@ -97,6 +115,9 @@ export const TextField = ({
otherBlueprintInputGroupProps["title"] = otherBlueprintInputGroupProps.value;
}

const isKeyDownShouldBeTriggered =
otherBlueprintInputGroupProps.onKeyDown || escapeToBlur || otherBlueprintInputGroupProps.type === "number";

return (
<BlueprintInputGroup
inputRef={inputRef}
Expand Down Expand Up @@ -127,7 +148,7 @@ export const TextField = ({
}
dir={"auto"}
onChange={maybeWrappedOnChange}
onKeyDown={otherBlueprintInputGroupProps.onKeyDown || escapeToBlur ? onKeyDown : undefined}
onKeyDown={isKeyDownShouldBeTriggered ? onKeyDown : undefined}
/>
);
};
Expand Down
15 changes: 14 additions & 1 deletion src/components/TextField/stories/TextField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const invisibleCharacterWarningProps: TextFieldProps = {
const codePointsString = [...Array.from(codePoints)]
.map((n) => {
const info = characters.invisibleZeroWidthCharacters.codePointMap.get(n);
return info.fullLabel;
return info!.fullLabel;
})
.join(", ");
alert("Invisible character detected in input string. Code points: " + codePointsString);
Expand All @@ -60,3 +60,16 @@ const invisibleCharacterWarningProps: TextFieldProps = {
defaultValue: "Invisible character ->​<-",
};
InvisibleCharacterWarning.args = invisibleCharacterWarningProps;

/** Numeric text field should have the same behaviour in all browsers.
* For example, Firefox allows to a user to type characters into input with "number" type,
* that may lead to unexpected result
*/
export const NumericTextField = Template.bind({});

const numericTextFieldProps: TextFieldProps = {
...Default.args,
type: "number",
"data-testid": "text_field",
};
NumericTextField.args = numericTextFieldProps;
57 changes: 57 additions & 0 deletions src/components/TextField/tests/TextField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import "@testing-library/jest-dom";

import { TextField } from "../TextField";

describe("TestField", () => {
it("should render default input", () => {
render(<TextField data-testid="text_field" placeholder="placeholder text" />);
const input = screen.getByTestId("text_field") as HTMLInputElement;

expect(input).toBeInTheDocument();
expect(input.placeholder).toBe("placeholder text");
expect(input.value).toBe("");
expect(input.type).toBe("text");
});

it("should allow all symbols", () => {
render(<TextField data-testid="text_field" />);
const input = screen.getByTestId("text_field") as HTMLInputElement;

expect(input).toBeInTheDocument();
expect(input.value).toBe("");

fireEvent.change(input, { target: { value: "test" } });
expect(input.value).toBe("test");
});

it("should allow only numbers", async () => {
render(<TextField data-testid="numeric_field" type="number" />);
const input = screen.getByTestId("numeric_field") as HTMLInputElement;

expect(input).toBeInTheDocument();
expect(input.value).toBe("");

await userEvent.type(input, "test");
expect(input.value).toBe("");

await userEvent.type(input, "1");
expect(input.value).toBe("1");

await userEvent.type(input, "-");
expect(input.value).toBe("1");

await userEvent.type(input, ".2");
expect(input.value).toBe("1.2");

await userEvent.type(input, ".3");
expect(input.value).toBe("1.23");

await userEvent.clear(input);
await userEvent.type(input, "-1.5");
expect(input.value).toBe("-1.5");
});
});
Loading