diff --git a/packages/calcite-components/src/components/input-number/input-number.e2e.ts b/packages/calcite-components/src/components/input-number/input-number.e2e.ts
index b098fb6b80b..834372447af 100644
--- a/packages/calcite-components/src/components/input-number/input-number.e2e.ts
+++ b/packages/calcite-components/src/components/input-number/input-number.e2e.ts
@@ -899,6 +899,35 @@ describe("calcite-input-number", () => {
expect(Number(await element.getProperty("value"))).toBe(195);
});
+ it("allows deleting exponentail number from decimal and adding trailing zeros", async () => {
+ const page = await newE2EPage();
+ await page.setContent(html``);
+
+ const calciteInput = await page.find("calcite-input-number");
+ const input = await page.find("calcite-input-number >>> input");
+ await calciteInput.callMethod("setFocus");
+ await page.waitForChanges();
+ await typeNumberValue(page, "2.100e10");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1e10");
+ expect(await input.getProperty("value")).toBe("2.1e10");
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1e1");
+ expect(await input.getProperty("value")).toBe("2.1e1");
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1");
+ expect(await input.getProperty("value")).toBe("2.1");
+
+ await page.keyboard.type("000");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1000");
+ expect(await input.getProperty("value")).toBe("2.1000");
+ });
+
it("disallows typing non-numeric characters with shift modifier key down", async () => {
const page = await newE2EPage();
await page.setContent(html``);
@@ -1114,6 +1143,80 @@ describe("calcite-input-number", () => {
expect(await calciteInput.getProperty("value")).toBe(assertedValue);
expect(await internalLocaleInput.getProperty("value")).toBe(numberStringFormatter.localize(assertedValue));
});
+
+ it(`should be able to append values after Backspace for ${locale} locale`, async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+
+ `);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const decimalSeparator = numberStringFormatter.decimal;
+ const calciteInput = await page.find("calcite-input-number");
+ const input = await page.find("calcite-input-number >>> input");
+ await calciteInput.callMethod("setFocus");
+ await typeNumberValue(page, `0${decimalSeparator}0000`);
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0000`);
+
+ await page.keyboard.press("Backspace");
+ await typeNumberValue(page, "1");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0001`);
+
+ await typeNumberValue(page, "01");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}000101`);
+ });
+
+ it(`should keep leading decimal separator while input is focused on Backspace ${locale} locale `, async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+
+ `);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const decimalSeparator = numberStringFormatter.decimal;
+ const calciteInput = await page.find("calcite-input-number");
+ const input = await page.find("calcite-input-number >>> input");
+ await calciteInput.callMethod("setFocus");
+ await typeNumberValue(page, `0${decimalSeparator}01`);
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}01`);
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0`);
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}`);
+
+ await typeNumberValue(page, "01");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}01`);
+ });
+
+ it(`should sanitize leading decimal zeros on initial render ${locale} locale`, async () => {
+ const page = await newE2EPage();
+ await page.setContent(html``);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const input = await page.find("calcite-input-number >>> input");
+ expect(await input.getProperty("value")).toBe("0");
+ });
});
});
@@ -1373,7 +1476,7 @@ describe("calcite-input-number", () => {
await page.keyboard.press("Backspace");
await page.waitForChanges();
- expect(await element.getProperty("value")).toBe("1");
+ expect(await element.getProperty("value")).toBe("1.");
expect(calciteInputNumberInput).toHaveReceivedEventTimes(1);
});
diff --git a/packages/calcite-components/src/components/input-number/input-number.tsx b/packages/calcite-components/src/components/input-number/input-number.tsx
index 9893db14c39..356b325c3c4 100644
--- a/packages/calcite-components/src/components/input-number/input-number.tsx
+++ b/packages/calcite-components/src/components/input-number/input-number.tsx
@@ -42,13 +42,13 @@ import {
} from "../../utils/loadable";
import {
connectLocalized,
- defaultNumberingSystem,
disconnectLocalized,
LocalizedComponent,
NumberingSystem,
numberStringFormatter
} from "../../utils/locale";
import {
+ addLocalizedTrailingDecimalZeros,
BigDecimal,
isValidNumber,
parseNumberString,
@@ -840,12 +840,11 @@ export class InputNumber
useGrouping: this.groupSeparator
};
- const sanitizedValue = sanitizeNumberString(
- // no need to delocalize a string that ia already in latn numerals
- (this.numberingSystem && this.numberingSystem !== "latn") || defaultNumberingSystem !== "latn"
- ? numberStringFormatter.delocalize(value)
- : value
- );
+ const isValueDeleted =
+ this.previousValue?.length > value.length || this.value?.length > value.length;
+ const hasTrailingDecimalSeparator = value.charAt(value.length - 1) === ".";
+ const sanitizedValue =
+ hasTrailingDecimalSeparator && isValueDeleted ? value : sanitizeNumberString(value);
const newValue =
value && !sanitizedValue
@@ -854,8 +853,21 @@ export class InputNumber
: ""
: sanitizedValue;
- const newLocalizedValue = numberStringFormatter.localize(newValue);
- this.localizedValue = newLocalizedValue;
+ let newLocalizedValue = numberStringFormatter.localize(newValue);
+
+ if (origin !== "connected" && !hasTrailingDecimalSeparator) {
+ newLocalizedValue = addLocalizedTrailingDecimalZeros(
+ newLocalizedValue,
+ newValue,
+ numberStringFormatter
+ );
+ }
+
+ // adds localized trailing decimal separator
+ this.localizedValue =
+ hasTrailingDecimalSeparator && isValueDeleted
+ ? `${newLocalizedValue}${numberStringFormatter.decimal}`
+ : newLocalizedValue;
this.setPreviousNumberValue(previousValue ?? this.value);
this.previousValueOrigin = origin;
diff --git a/packages/calcite-components/src/components/input/input.e2e.ts b/packages/calcite-components/src/components/input/input.e2e.ts
index b87f8c4e805..7ca4e0a3302 100644
--- a/packages/calcite-components/src/components/input/input.e2e.ts
+++ b/packages/calcite-components/src/components/input/input.e2e.ts
@@ -1055,6 +1055,35 @@ describe("calcite-input", () => {
expect(Number(await element.getProperty("value"))).toBe(195);
});
+ it("allows deleting exponentail number from decimal and adding trailing zeros", async () => {
+ const page = await newE2EPage();
+ await page.setContent(html``);
+
+ const calciteInput = await page.find("calcite-input");
+ const input = await page.find("calcite-input >>> input");
+ await calciteInput.callMethod("setFocus");
+ await page.waitForChanges();
+ await typeNumberValue(page, "2.100e10");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1e10");
+ expect(await input.getProperty("value")).toBe("2.1e10");
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1e1");
+ expect(await input.getProperty("value")).toBe("2.1e1");
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1");
+ expect(await input.getProperty("value")).toBe("2.1");
+
+ await page.keyboard.type("000");
+ await page.waitForChanges();
+ expect(await calciteInput.getProperty("value")).toBe("2.1000");
+ expect(await input.getProperty("value")).toBe("2.1000");
+ });
+
it("disallows typing any non-numeric characters with shift modifier key down", async () => {
const page = await newE2EPage();
await page.setContent(html``);
@@ -1291,6 +1320,80 @@ describe("calcite-input", () => {
expect(await calciteInput.getProperty("value")).toBe(assertedValue);
expect(await internalLocaleInput.getProperty("value")).toBe(localizedValue);
});
+
+ it(`should be able to append values after Backspace for ${locale} locale`, async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+
+ `);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const decimalSeparator = numberStringFormatter.decimal;
+ const calciteInput = await page.find("calcite-input");
+ const input = await page.find("calcite-input >>> input");
+ await calciteInput.callMethod("setFocus");
+ await typeNumberValue(page, `0${decimalSeparator}0000`);
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0000`);
+
+ await page.keyboard.press("Backspace");
+ await typeNumberValue(page, "1");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0001`);
+
+ await typeNumberValue(page, "01");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}000101`);
+ });
+
+ it(`should keep leading decimal separator while input is focused on Backspace ${locale} locale `, async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+
+ `);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const decimalSeparator = numberStringFormatter.decimal;
+ const calciteInput = await page.find("calcite-input");
+ const input = await page.find("calcite-input >>> input");
+ await calciteInput.callMethod("setFocus");
+ await typeNumberValue(page, `0${decimalSeparator}01`);
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}01`);
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}0`);
+
+ await page.keyboard.press("Backspace");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}`);
+
+ await typeNumberValue(page, "01");
+ await page.waitForChanges();
+ expect(await input.getProperty("value")).toBe(`0${decimalSeparator}01`);
+ });
+
+ it(`should sanitize leading decimal zeros on initial render ${locale} locale`, async () => {
+ const page = await newE2EPage();
+ await page.setContent(html``);
+
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ numberingSystem: "latn",
+ useGrouping: false
+ };
+ const input = await page.find("calcite-input >>> input");
+ expect(await input.getProperty("value")).toBe("0");
+ });
});
});
@@ -1551,7 +1654,7 @@ describe("calcite-input", () => {
await page.keyboard.press("Backspace");
await page.waitForChanges();
- expect(await element.getProperty("value")).toBe("1");
+ expect(await element.getProperty("value")).toBe("1.");
expect(calciteInputInput).toHaveReceivedEventTimes(1);
});
diff --git a/packages/calcite-components/src/components/input/input.tsx b/packages/calcite-components/src/components/input/input.tsx
index c89165e1c19..3e63f670163 100644
--- a/packages/calcite-components/src/components/input/input.tsx
+++ b/packages/calcite-components/src/components/input/input.tsx
@@ -42,7 +42,6 @@ import {
} from "../../utils/loadable";
import {
connectLocalized,
- defaultNumberingSystem,
disconnectLocalized,
LocalizedComponent,
NumberingSystem,
@@ -50,6 +49,7 @@ import {
} from "../../utils/locale";
import {
+ addLocalizedTrailingDecimalZeros,
BigDecimal,
isValidNumber,
parseNumberString,
@@ -975,13 +975,11 @@ export class Input
signDisplay: "never"
};
- const sanitizedValue = sanitizeNumberString(
- // no need to delocalize a string that ia already in latn numerals
- (this.numberingSystem && this.numberingSystem !== "latn") ||
- defaultNumberingSystem !== "latn"
- ? numberStringFormatter.delocalize(value)
- : value
- );
+ const isValueDeleted =
+ this.previousValue?.length > value.length || this.value?.length > value.length;
+ const hasTrailingDecimalSeparator = value.charAt(value.length - 1) === ".";
+ const sanitizedValue =
+ hasTrailingDecimalSeparator && isValueDeleted ? value : sanitizeNumberString(value);
const newValue =
value && !sanitizedValue
@@ -990,8 +988,21 @@ export class Input
: ""
: sanitizedValue;
- const newLocalizedValue = numberStringFormatter.localize(newValue);
- this.localizedValue = newLocalizedValue;
+ let newLocalizedValue = numberStringFormatter.localize(newValue);
+
+ if (origin !== "connected" && !hasTrailingDecimalSeparator) {
+ newLocalizedValue = addLocalizedTrailingDecimalZeros(
+ newLocalizedValue,
+ newValue,
+ numberStringFormatter
+ );
+ }
+
+ // adds localized trailing decimal separator
+ this.localizedValue =
+ hasTrailingDecimalSeparator && isValueDeleted
+ ? `${newLocalizedValue}${numberStringFormatter.decimal}`
+ : newLocalizedValue;
this.userChangedValue = origin === "user" && this.value !== newValue;
// don't sanitize the start of negative/decimal numbers, but
diff --git a/packages/calcite-components/src/utils/number.spec.ts b/packages/calcite-components/src/utils/number.spec.ts
index 85d10c9cdaa..bfd82119f6b 100644
--- a/packages/calcite-components/src/utils/number.spec.ts
+++ b/packages/calcite-components/src/utils/number.spec.ts
@@ -1,6 +1,7 @@
import { locales, numberStringFormatter } from "./locale";
import {
BigDecimal,
+ addLocalizedTrailingDecimalZeros,
expandExponentialNumberString,
isValidNumber,
parseNumberString,
@@ -82,6 +83,7 @@ describe("sanitizeNumberString", () => {
const nonLeadingZeroExponentialString = "500000e00600";
const multiDecimalExponentialString = "1.2e2.1";
const crazyExponentialString = "-2-.-1ee.5-3e.1..e--09";
+ const trailingDecimalZeros = "0.110000";
expect(sanitizeNumberString(stringWithMultipleDashes)).toBe("1234");
expect(sanitizeNumberString(negativeStringWithMultipleDashes)).toBe("-1234");
@@ -102,6 +104,7 @@ describe("sanitizeNumberString", () => {
expect(sanitizeNumberString(nonLeadingZeroExponentialString)).toBe("500000e600");
expect(sanitizeNumberString(multiDecimalExponentialString)).toBe("1.2e21");
expect(sanitizeNumberString(crazyExponentialString)).toBe("-2.1e53109");
+ expect(sanitizeNumberString(trailingDecimalZeros)).toBe("0.110000");
});
});
@@ -169,3 +172,60 @@ describe("expandExponentialNumberString", () => {
expect(expandExponentialNumberString("")).toBe("");
});
});
+
+describe("addLocalizedTrailingDecimalZeros", () => {
+ function getLocalizedDeimalValue(value: string, trailingZeros: number): String {
+ const localizedValue = numberStringFormatter.localize(value);
+ const localizedZeroValue = numberStringFormatter.localize("0");
+ return `${localizedValue}`.padEnd(localizedValue.length + trailingZeros, localizedZeroValue);
+ }
+
+ locales.forEach((locale) => {
+ it(`add back sanitized trailing decimal zero values - ${locale}`, () => {
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ // the group separator is different in arabic depending on the numberingSystem
+ numberingSystem: locale === "ar" ? "arab" : "latn",
+ useGrouping: true
+ };
+
+ const stringWithTrailingZeros = "123456.1000";
+ const bigDecimalWithTrailingZeros =
+ "1230000000000000000000000000000.00000000000000000000045000000000000000000000000";
+ const negativeExponentialString = "-10.021e10000";
+
+ expect(
+ addLocalizedTrailingDecimalZeros(
+ numberStringFormatter.localize(stringWithTrailingZeros),
+ stringWithTrailingZeros,
+ numberStringFormatter
+ )
+ ).toBe(getLocalizedDeimalValue(stringWithTrailingZeros, 3));
+ expect(
+ addLocalizedTrailingDecimalZeros(
+ numberStringFormatter.localize(bigDecimalWithTrailingZeros),
+ bigDecimalWithTrailingZeros,
+ numberStringFormatter
+ )
+ ).toBe(getLocalizedDeimalValue(bigDecimalWithTrailingZeros, 24));
+ expect(
+ addLocalizedTrailingDecimalZeros(
+ numberStringFormatter.localize(negativeExponentialString),
+ negativeExponentialString,
+ numberStringFormatter
+ )
+ ).toBe(numberStringFormatter.localize(negativeExponentialString));
+ });
+
+ it(`returns same value if no trailing decimal zero value is removed - ${locale}`, () => {
+ numberStringFormatter.numberFormatOptions = {
+ locale,
+ // the group separator is different in arabic depending on the numberingSystem
+ numberingSystem: locale === "ar" ? "arab" : "latn",
+ useGrouping: true
+ };
+ const localizedValue = numberStringFormatter.localize("0.001");
+ expect(addLocalizedTrailingDecimalZeros(localizedValue, "0.001", numberStringFormatter)).toBe(localizedValue);
+ });
+ });
+});
diff --git a/packages/calcite-components/src/utils/number.ts b/packages/calcite-components/src/utils/number.ts
index 7628b0fd408..e4ab0a4d6d5 100644
--- a/packages/calcite-components/src/utils/number.ts
+++ b/packages/calcite-components/src/utils/number.ts
@@ -130,6 +130,7 @@ const allLeadingZerosOptionallyNegative = /^([-0])0+(?=\d)/;
const decimalOnlyAtEndOfString = /(?!^\.)\.$/;
const allHyphensExceptTheStart = /(?!^-)-/g;
const isNegativeDecimalOnlyZeros = /^-\b0\b\.?0*$/;
+const hasTrailingDecimalZeros = /0*$/;
export const sanitizeNumberString = (numberString: string): string =>
sanitizeExponentialNumberString(numberString, (nonExpoNumString) => {
@@ -137,14 +138,23 @@ export const sanitizeNumberString = (numberString: string): string =>
.replace(allHyphensExceptTheStart, "")
.replace(decimalOnlyAtEndOfString, "")
.replace(allLeadingZerosOptionallyNegative, "$1");
-
return isValidNumber(sanitizedValue)
? isNegativeDecimalOnlyZeros.test(sanitizedValue)
? sanitizedValue
- : new BigDecimal(sanitizedValue).toString()
+ : getBigDecimalAsString(sanitizedValue)
: nonExpoNumString;
});
+export function getBigDecimalAsString(sanitizedValue: string): string {
+ const sanitizedValueDecimals = sanitizedValue.split(".")[1];
+ const value = new BigDecimal(sanitizedValue).toString();
+ const [bigDecimalValueInteger, bigDecimalValueDecimals] = value.split(".");
+
+ return sanitizedValueDecimals && bigDecimalValueDecimals !== sanitizedValueDecimals
+ ? `${bigDecimalValueInteger}.${sanitizedValueDecimals}`
+ : value;
+}
+
export function sanitizeExponentialNumberString(numberString: string, func: (s: string) => string): string {
if (!numberString) {
return numberString;
@@ -217,3 +227,35 @@ export function expandExponentialNumberString(numberString: string): string {
function stringContainsNumbers(string: string): boolean {
return numberKeys.some((number) => string.includes(number));
}
+
+/**
+ * Adds localized trailing decimals zero values to the number string.
+ * BigInt conversion to string removes the trailing decimal zero values (Ex: 1.000 is returned as 1). This method helps adding them back.
+ *
+ * @param {string} localizedValue - localized number string value
+ * @param {string} value - current value in the input field
+ * @param {NumberStringFormat} numberStringFormatter - numberStringFormatter instance to localize the number value
+ * @returns {string} localized number string value
+ */
+export function addLocalizedTrailingDecimalZeros(
+ localizedValue: string,
+ value: string,
+ formatter: NumberStringFormat
+): string {
+ const decimals = value.split(".")[1];
+ if (decimals) {
+ const trailingDecimalZeros = decimals.match(hasTrailingDecimalZeros)[0];
+ if (
+ trailingDecimalZeros &&
+ formatter.delocalize(localizedValue).length !== value.length &&
+ decimals.indexOf("e") === -1
+ ) {
+ const decimalSeparator = formatter.decimal;
+ localizedValue = !localizedValue.includes(decimalSeparator)
+ ? `${localizedValue}${decimalSeparator}`
+ : localizedValue;
+ return localizedValue.padEnd(localizedValue.length + trailingDecimalZeros.length, formatter.localize("0"));
+ }
+ }
+ return localizedValue;
+}