Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(number-input): Turn off formatting for Android and iOS devices #161

Merged
merged 3 commits into from
Oct 27, 2021
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
8 changes: 8 additions & 0 deletions src/core/utils/device/deviceUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function isMobileDevice() {
const userAgent = navigator.userAgent || navigator.vendor;
const devices = [/Android/i, /iPhone/i, /iPad/i];

return devices.some((device) => userAgent.match(device));
}

export {isMobileDevice};
25 changes: 9 additions & 16 deletions src/core/utils/number/numberUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,17 @@ function formatNumber(formatNumberOptions: FormatNumberOptions) {
}

/**
* Coerces a number scientific notation. {@link https://observablehq.com/@mbostock/localized-number-parsing | Reference}
* Coerces a number to scientific notation. {@link https://observablehq.com/@mbostock/localized-number-parsing | Reference}
* @param {object} options - An object includes parse number options
* @param {string} options.locale - Default locale used is browser locale
* @param {number} options.maximumFractionDigits - The maximum digit a decimal can have
* @param {number} value - A number to convert to string
* @returns {string} The value after coercing the given value to a scientific notation.
*/
function parseNumber(
options: ParseNumberOptions = {locale: navigator.language, maximumFractionDigits: 0},
value: string
) {
const {THOUSANDTHS_SEPARATOR, DECIMAL_NUMBER_SEPARATOR} = getNumberSeparators(
options.locale
);
const numerals = getLocaleNumerals(options.locale);
function parseNumber(options: ParseNumberOptions, value: string) {
const {locale = navigator.language, maximumFractionDigits = 0} = options;
const {THOUSANDTHS_SEPARATOR, DECIMAL_NUMBER_SEPARATOR} = getNumberSeparators(locale);
const numerals = getLocaleNumerals(locale);
const numeral = new RegExp(`[${numerals.join("")}]`, "g");
const digitMapper = getDigit(new Map(numerals.map((d, i) => [d, i])));
let parsedNumber = value
Expand All @@ -59,12 +55,12 @@ function parseNumber(
.replace(new RegExp(`[${DECIMAL_NUMBER_SEPARATOR}]`), ".")
.replace(numeral, digitMapper);

if (typeof options.maximumFractionDigits === "number") {
if (typeof maximumFractionDigits === "number") {
const [integerPart, decimalPart] = parsedNumber.split(".");

if (options.maximumFractionDigits === 0) {
if (maximumFractionDigits === 0) {
parsedNumber = integerPart;
} else if (decimalPart && decimalPart.length === options.maximumFractionDigits + 1) {
} else if (decimalPart && decimalPart.length === maximumFractionDigits + 1) {
return parsedNumber.slice(0, parsedNumber.length - 1);
}
} else {
Expand All @@ -86,10 +82,7 @@ function mapDigitsToLocalVersion(
{locale = navigator.language}: {locale?: string},
digits: string
) {
return digits
.split("")
.map(mapDigitToLocalVersion({locale}))
.join("");
return digits.split("").map(mapDigitToLocalVersion({locale})).join("");
}

function mapDigitToLocalVersion({locale = navigator.language}: {locale?: string}) {
Expand Down
50 changes: 4 additions & 46 deletions src/form/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,8 @@ import {
getNegativeZero,
getThousandthSeparatorCount
} from "../../core/utils/number/numberUtils";

export type InputTypes =
| "checkbox"
| "button"
| "color"
| "date"
| "datetime-local"
| "email"
| "file"
| "hidden"
| "image"
| "month"
| "number"
| "password"
| "radio"
| "range"
| "reset"
| "search"
| "submit"
| "tel"
| "text"
| "time"
| "url"
| "week";

export type InputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
"disabled" | "name" | "className"
> & {
name: string;
type?: InputTypes;
testid?: string;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
isDisabled?: boolean;
hasError?: boolean;
customClassName?: string;
onChange: React.ReactEventHandler<HTMLInputElement>;
localizationOptions?: {
shouldFormatToLocaleString?: boolean;
locale?: string;
maximumFractionDigits?: number;
};
};
import {getInputLocalizationOptions, getInputParseNumberOptions} from "./util/inputUtils";
import {InputProps} from "./util/inputTypes";

const Input = React.forwardRef<HTMLInputElement, InputProps>(
/* eslint-disable complexity */
Expand All @@ -80,7 +38,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
shouldFormatToLocaleString = false,
locale,
maximumFractionDigits = 0
} = localizationOptions;
} = getInputLocalizationOptions(localizationOptions);
const [
{
DECIMAL_NUMBER_SEPARATOR: decimalSeparatorForLocale,
Expand Down Expand Up @@ -181,7 +139,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(

if (newValue) {
const formattedNewValue = parseNumber(
{locale, maximumFractionDigits},
getInputParseNumberOptions({locale, maximumFractionDigits}),
newValue
);
// Number("-") returns NaN. Should allow minus sign as first character.
Expand Down
3 changes: 2 additions & 1 deletion src/form/input/input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {render, screen} from "@testing-library/react";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";

import Input, {InputProps} from "./Input";
import Input from "./Input";
import {InputProps} from "./util/inputTypes";
import {testA11y} from "../../core/utils/test/testUtils";

describe("<Input />", () => {
Expand Down
3 changes: 2 additions & 1 deletion src/form/input/typeahead/TypeaheadInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, {useEffect} from "react";
import classNames from "classnames";

import Input, {InputProps, InputTypes} from "../Input";
import Input from "../Input";
import {InputProps, InputTypes} from "../util/inputTypes";
import useDebounce from "../../../core/utils/hooks/useDebounce";

export type TypeaheadInputProps = Omit<InputProps, "onChange" | "type"> & {
Expand Down
45 changes: 45 additions & 0 deletions src/form/input/util/inputTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export type InputTypes =
| "checkbox"
| "button"
| "color"
| "date"
| "datetime-local"
| "email"
| "file"
| "hidden"
| "image"
| "month"
| "number"
| "password"
| "radio"
| "range"
| "reset"
| "search"
| "submit"
| "tel"
| "text"
| "time"
| "url"
| "week";

export type InputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
"disabled" | "name" | "className"
> & {
name: string;
type?: InputTypes;
testid?: string;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
isDisabled?: boolean;
hasError?: boolean;
customClassName?: string;
onChange: React.ReactEventHandler<HTMLInputElement>;
localizationOptions?: InputLocalizationOptions;
};

export type InputLocalizationOptions = {
shouldFormatToLocaleString?: boolean;
locale?: string;
maximumFractionDigits?: number;
};
26 changes: 26 additions & 0 deletions src/form/input/util/inputUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {isMobileDevice} from "../../../core/utils/device/deviceUtils";
import {ParseNumberOptions} from "../../../core/utils/number/numberTypes";
import {InputLocalizationOptions} from "./inputTypes";

function getInputLocalizationOptions(
localizationOptions: InputLocalizationOptions
): InputLocalizationOptions {
return isMobileDevice()
? {
shouldFormatToLocaleString: false,
locale: undefined,
maximumFractionDigits: localizationOptions.maximumFractionDigits
}
: localizationOptions;
}

function getInputParseNumberOptions(options: ParseNumberOptions): ParseNumberOptions {
return isMobileDevice()
? {
...options,
locale: "en-US"
}
: options;
}

export {getInputLocalizationOptions, getInputParseNumberOptions};
3 changes: 2 additions & 1 deletion src/form/password-input/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import "./_password-input.scss";
import React, {useState} from "react";
import classNames from "classnames";

import Input, {InputProps} from "../input/Input";
import Input from "../input/Input";
import {InputProps} from "../input/util/inputTypes";
import Button from "../../button/Button";

export interface PasswordInputProps extends Omit<InputProps, "leftIcon" | "rightIcon"> {
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import "./ui/reference/_measurement.scss";
import FormField, {
FormFieldProps as FormFieldComponentProps
} from "./form/field/FormField";
import Input, {InputProps as InputComponentProps} from "./form/input/Input";
import Input from "./form/input/Input";
import {InputProps as InputComponentProps} from "./form/input/util/inputTypes";
import PasswordInput, {
PasswordInputProps as PasswordInputComponentProps
} from "./form/password-input/PasswordInput";
Expand Down