feat:allow local decimal sep. curr. & input widget (#16380)

* feat:allow local decimal sep. curr. & input widget

* test: fix the test for input widget

* refactor: code refinement

* refactor: rename the suggestion key

* fix: changed the regex to replaceAll function

* fix: address review callouts

* refactor: new default and min max validation

* fix: directly converting default value to current locale

* fix: converting value to locale directly inside onStep

* fix: add fraction during formatting

* revert: input widget locale changes

* fix: restrict getting of current locale for currency widget

* fix: introduced shouldUseLocale prop

Co-authored-by: balajisoundar <balaji@appsmith.com>
Co-authored-by: keyurparalkar <keyur@appsmith.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
Bhavin K 2022-11-02 16:02:45 +05:30 committed by GitHub
parent 6ea44271c3
commit e8219284ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 30 deletions

View File

@ -560,6 +560,11 @@ export const entityDefinitions = {
"!doc": "The text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/input",
},
inputText: {
"!type": "string",
"!doc": "The unformatted text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/input",
},
isValid: "bool",
isVisible: isVisible,
isDisabled: "bool",

View File

@ -34,6 +34,7 @@ import { InputType } from "widgets/InputWidget/constants";
import { getBaseWidgetClassName } from "constants/componentClassNameConstants";
import { LabelPosition } from "components/constants";
import { lightenColor } from "widgets/WidgetUtils";
import { getLocale } from "utils/helpers";
/**
* All design system component specific logic goes here.
@ -499,6 +500,8 @@ class BaseInputComponent extends React.Component<
};
private numericInputComponent = () => {
// Get current locale only for the currency widget.
const locale = this.props.shouldUseLocale ? getLocale() : undefined;
const leftIcon = this.getLeftIcon();
const conditionalProps: Record<string, number> = {};
@ -523,6 +526,7 @@ class BaseInputComponent extends React.Component<
}}
intent={this.props.intent}
leftIcon={leftIcon}
locale={locale}
majorStepSize={null}
minorStepSize={null}
onBlur={() => this.setFocusState(false)}
@ -760,6 +764,7 @@ export interface BaseInputComponentProps extends ComponentProps {
boxShadow?: string;
accentColor?: string;
errorTooltipBoundary?: string;
shouldUseLocale?: boolean;
}
export default BaseInputComponent;

View File

@ -81,6 +81,7 @@ class CurrencyInputComponent extends React.Component<
onStep={this.props.onStep}
onValueChange={this.props.onValueChange}
placeholder={this.props.placeholder}
shouldUseLocale
showError={this.props.showError}
stepSize={1}
tooltip={this.props.tooltip}

View File

@ -1,8 +1,10 @@
import {
getLocaleDecimalSeperator,
getLocaleThousandSeparator,
} from "widgets/WidgetUtils";
import {
countryToFlag,
formatCurrencyNumber,
getLocaleThousandSeparator,
getLocaleDecimalSeperator,
limitDecimalValue,
parseLocaleFormattedStringToNumber,
} from "./utilities";

View File

@ -1,4 +1,8 @@
import { getLocale } from "utils/helpers";
import {
getLocaleDecimalSeperator,
getLocaleThousandSeparator,
} from "widgets/WidgetUtils";
export const countryToFlag = (isoCode: string) => {
return typeof String.fromCodePoint !== "undefined"
@ -66,15 +70,3 @@ export function parseLocaleFormattedStringToNumber(currencyString = "") {
.replace(new RegExp("\\" + getLocaleDecimalSeperator()), "."),
);
}
export function getLocaleDecimalSeperator() {
return Intl.NumberFormat(getLocale())
.format(1.1)
.replace(/\p{Number}/gu, "");
}
export function getLocaleThousandSeparator() {
return Intl.NumberFormat(getLocale())
.format(11111)
.replace(/\p{Number}/gu, "");
}

View File

@ -27,12 +27,14 @@ import * as Sentry from "@sentry/react";
import log from "loglevel";
import {
formatCurrencyNumber,
getLocaleDecimalSeperator,
getLocaleThousandSeparator,
limitDecimalValue,
} from "../component/utilities";
import { mergeWidgetConfig } from "utils/helpers";
import { getLocale, mergeWidgetConfig } from "utils/helpers";
import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
import {
getLocaleDecimalSeperator,
getLocaleThousandSeparator,
} from "widgets/WidgetUtils";
export function defaultValueValidation(
value: any,
@ -40,7 +42,18 @@ export function defaultValueValidation(
_?: any,
): ValidationResponse {
const NUMBER_ERROR_MESSAGE = "This value must be number";
const DECIMAL_SEPARATOR_ERROR_MESSAGE =
"Please use . as the decimal separator for default values.";
const EMPTY_ERROR_MESSAGE = "";
const localeLang = navigator.languages?.[0] || "en-US";
function getLocaleDecimalSeperator() {
return Intl.NumberFormat(localeLang)
.format(1.1)
.replace(/\p{Number}/gu, "");
}
const decimalSeperator = getLocaleDecimalSeperator();
const defaultDecimalSeperator = ".";
if (_.isObject(value)) {
return {
isValid: false,
@ -64,8 +77,20 @@ export function defaultValueValidation(
* When parsed value is not a finite numer
*/
isValid = false;
messages = [NUMBER_ERROR_MESSAGE];
parsed = undefined;
/**
* Check whether value contains the locale decimal separator apart from "."
* We only allow "." as a decimal separator inside default value
*/
if (
String(value).indexOf(defaultDecimalSeperator) === -1 &&
String(value).indexOf(decimalSeperator) > 0
) {
messages = [DECIMAL_SEPARATOR_ERROR_MESSAGE];
} else {
messages = [NUMBER_ERROR_MESSAGE];
}
} else {
/*
* When parsed value is a Number
@ -262,10 +287,17 @@ class CurrencyInputWidget extends BaseInputWidget<
formatText() {
if (!!this.props.text) {
try {
const formattedValue = formatCurrencyNumber(
this.props.decimals,
this.props.text,
);
/**
* Since we are restricting default value to only have "." decimal separator,
* hence we directly convert it to the current locale
*/
const floatVal = parseFloat(this.props.text);
const formattedValue = Intl.NumberFormat(getLocale(), {
style: "decimal",
minimumFractionDigits: this.props.decimals,
maximumFractionDigits: this.props.decimals,
}).format(floatVal);
this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) {
log.error(e);
@ -347,10 +379,9 @@ class CurrencyInputWidget extends BaseInputWidget<
onStep = (direction: number) => {
const value = Number(this.props.value) + direction;
const formattedValue = formatCurrencyNumber(
this.props.decimals,
String(value),
);
// Since value is always going to be a number therefore, directly converting it to the current locale
const formattedValue = Intl.NumberFormat(getLocale()).format(value);
if (!this.props.isDirty) {
this.props.updateWidgetMetaProperty("isDirty", true);
}

View File

@ -14,13 +14,11 @@ import CurrencyTypeDropdown, {
import FormContext from "../FormContext";
import { BaseFieldComponentProps } from "../constants";
import { RenderModes } from "constants/WidgetConstants";
import {
getLocaleDecimalSeperator,
limitDecimalValue,
} from "widgets/CurrencyInputWidget/component/utilities";
import { limitDecimalValue } from "widgets/CurrencyInputWidget/component/utilities";
import derived from "widgets/CurrencyInputWidget/widget/derived";
import { isEmpty } from "../helper";
import { BASE_LABEL_TEXT_SIZE } from "../component/FieldLabel";
import { getLocaleDecimalSeperator } from "widgets/WidgetUtils";
type CurrencyInputComponentProps = BaseInputComponentProps & {
currencyCountryCode: string;

View File

@ -32,6 +32,7 @@ import { rgbaMigrationConstantV56 } from "./constants";
import { DynamicPath } from "utils/DynamicBindingUtils";
import { isArray } from "lodash";
import { PropertyHookUpdates } from "constants/PropertyControlConstants";
import { getLocale } from "utils/helpers";
const punycode = require("punycode/");
@ -692,6 +693,18 @@ export function composePropertyUpdateHook(
};
}
export function getLocaleDecimalSeperator() {
return Intl.NumberFormat(getLocale())
.format(1.1)
.replace(/\p{Number}/gu, "");
}
export function getLocaleThousandSeparator() {
return Intl.NumberFormat(getLocale())
.format(11111)
.replace(/\p{Number}/gu, "");
}
interface DropdownOption {
label: string;
value: string | number;