diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 6a88579028..3b58c7af34 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -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", diff --git a/app/client/src/widgets/BaseInputWidget/component/index.tsx b/app/client/src/widgets/BaseInputWidget/component/index.tsx index c7e807f99d..f8f996648a 100644 --- a/app/client/src/widgets/BaseInputWidget/component/index.tsx +++ b/app/client/src/widgets/BaseInputWidget/component/index.tsx @@ -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 = {}; @@ -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; diff --git a/app/client/src/widgets/CurrencyInputWidget/component/index.tsx b/app/client/src/widgets/CurrencyInputWidget/component/index.tsx index 4509df6de0..85caa29b05 100644 --- a/app/client/src/widgets/CurrencyInputWidget/component/index.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/component/index.tsx @@ -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} diff --git a/app/client/src/widgets/CurrencyInputWidget/component/utilities.test.ts b/app/client/src/widgets/CurrencyInputWidget/component/utilities.test.ts index 50b2f6fe2c..6171739525 100644 --- a/app/client/src/widgets/CurrencyInputWidget/component/utilities.test.ts +++ b/app/client/src/widgets/CurrencyInputWidget/component/utilities.test.ts @@ -1,8 +1,10 @@ +import { + getLocaleDecimalSeperator, + getLocaleThousandSeparator, +} from "widgets/WidgetUtils"; import { countryToFlag, formatCurrencyNumber, - getLocaleThousandSeparator, - getLocaleDecimalSeperator, limitDecimalValue, parseLocaleFormattedStringToNumber, } from "./utilities"; diff --git a/app/client/src/widgets/CurrencyInputWidget/component/utilities.ts b/app/client/src/widgets/CurrencyInputWidget/component/utilities.ts index 6b5690daec..b2c5f40fe7 100644 --- a/app/client/src/widgets/CurrencyInputWidget/component/utilities.ts +++ b/app/client/src/widgets/CurrencyInputWidget/component/utilities.ts @@ -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, ""); -} diff --git a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx index d8208edeb1..9f1ebd4b74 100644 --- a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx @@ -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); } diff --git a/app/client/src/widgets/JSONFormWidget/fields/CurrencyInputField.tsx b/app/client/src/widgets/JSONFormWidget/fields/CurrencyInputField.tsx index 74e6b95e0b..71c1e9f5cd 100644 --- a/app/client/src/widgets/JSONFormWidget/fields/CurrencyInputField.tsx +++ b/app/client/src/widgets/JSONFormWidget/fields/CurrencyInputField.tsx @@ -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; diff --git a/app/client/src/widgets/WidgetUtils.ts b/app/client/src/widgets/WidgetUtils.ts index 57e66fbaf6..fa8cede47e 100644 --- a/app/client/src/widgets/WidgetUtils.ts +++ b/app/client/src/widgets/WidgetUtils.ts @@ -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;