diff --git a/packages/core/src/components/TextField/Mask.jsx b/packages/core/src/components/TextField/Mask.jsx index 714168c312..c668857626 100644 --- a/packages/core/src/components/TextField/Mask.jsx +++ b/packages/core/src/components/TextField/Mask.jsx @@ -61,7 +61,7 @@ function stringWithFixedDigits(value, digits = 2) { } /** - * Remove all non-digits + * Remove everything that isn't a digit or asterisk * @param {String} value * @returns {String} */ @@ -69,6 +69,15 @@ function toDigitsAndAsterisks(value) { return value.replace(/[^\d*]/g, ''); } +/** + * Remove all non-digits + * @param {String} value + * @returns {String} + */ +function toDigits(value) { + return value.replace(/[^\d]/g, ''); +} + /** * Convert string into a number (positive or negative float or integer) * @param {String} value @@ -76,14 +85,21 @@ function toDigitsAndAsterisks(value) { */ function toNumber(value) { if (typeof value !== 'string') return value; + if (!value.match(/\d/)) return undefined; - // 0 = number, 1 = decimals + const sign = value.charAt(0) === '-' ? -1 : 1; const parts = value.split('.'); - const digitsRegex = /^-|\d/g; // include a check for a beginning "-" for negative numbers - const a = parts[0].match(digitsRegex).join(''); - const b = parts.length >= 2 && parts[1].match(digitsRegex).join(''); - - return b ? parseFloat(`${a}.${b}`) : parseInt(a); + // This assumes if the user adds a "." it should be a float. If we want it to + // evaluate as an integer if there are no digits beyond the decimal, then we + // can change it. + const hasDecimal = parts[1] !== undefined; + if (hasDecimal) { + const a = toDigits(parts[0]); + const b = toDigits(parts[1]); + return sign * parseFloat(`${a}.${b}`); + } else { + return sign * parseInt(toDigits(parts[0])); + } } /** @@ -98,7 +114,12 @@ function maskValue(value = '', mask) { if (mask === 'currency') { // Format number with commas. If the number includes a decimal, // ensure it includes two decimal points - value = stringWithFixedDigits(toNumber(value).toLocaleString('en-US')); + const number = toNumber(value); + if (number === undefined) { + value = ''; + } else { + value = stringWithFixedDigits(number.toLocaleString('en-US')); + } } else if (Object.keys(deliminatedMaskRegex).includes(mask)) { value = deliminateRegexGroups(value, deliminatedMaskRegex[mask]); } @@ -248,7 +269,12 @@ export function unmask(value, mask) { if (mask === 'currency') { // Preserve only digits, decimal point, or negative symbol - value = value.match(/^-|[\d.]/g).join(''); + const matches = value.match(/^-|[\d.]/g); + if (matches) { + value = matches.join(''); + } else { + value = ''; + } } else if (Object.keys(deliminatedMaskRegex).includes(mask)) { // Remove the deliminators and revert to single ungrouped string value = toDigitsAndAsterisks(value); diff --git a/packages/core/src/components/TextField/Mask.test.jsx b/packages/core/src/components/TextField/Mask.test.jsx index 9f48175a38..ca4406c139 100644 --- a/packages/core/src/components/TextField/Mask.test.jsx +++ b/packages/core/src/components/TextField/Mask.test.jsx @@ -334,6 +334,26 @@ describe('unmask', () => { expect(unmask(null)).toBeNull(); }); + it('returns empty string when there are no numeric characters in the value', () => { + expect(unmask('banana', 'currency')).toBe(''); + expect(unmask('banana', 'zip')).toBe(''); + expect(unmask('banana', 'ssn')).toBe(''); + expect(unmask('banana', 'phone')).toBe(''); + }); + + it('returns just the numbers when there is other garbage mixed in', () => { + expect(unmask('b4n4n4', 'currency')).toBe('444'); + expect(unmask('b4n4n4', 'zip')).toBe('444'); + expect(unmask('b4n4n4', 'ssn')).toBe('444'); + expect(unmask('b4n4n4', 'phone')).toBe('444'); + + expect(unmask('a1.b2c3', 'currency')).toBe('1.23'); + expect(unmask('1,,00.b', 'currency')).toBe('100.'); + expect(unmask('1-1-1-2-3-4', 'zip')).toBe('111234'); + expect(unmask('4---31', 'ssn')).toBe('431'); + expect(unmask('--2-3444', 'phone')).toBe('23444'); + }); + it('removes mask from currency value', () => { const name = 'currency';