You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
4.2 KiB

const dollarSign = '$'
const emptyString = ''
const comma = ','
const period = '.'
const minus = '-'
const minusRegExp = /-/
const nonDigitsRegExp = /\D+/g
const number = 'number'
const digitRegExp = /\d/
const caretTrap = '[]'
export default function createNumberMask({
prefix = dollarSign,
suffix = emptyString,
includeThousandsSeparator = true,
thousandsSeparatorSymbol = comma,
allowDecimal = false,
decimalSymbol = period,
decimalLimit = 2,
requireDecimal = false,
allowNegative = false,
allowLeadingZeroes = false,
integerLimit = null
} = {}) {
const prefixLength = prefix && prefix.length || 0
const suffixLength = suffix && suffix.length || 0
const thousandsSeparatorSymbolLength = thousandsSeparatorSymbol && thousandsSeparatorSymbol.length || 0
function numberMask(rawValue = emptyString) {
const rawValueLength = rawValue.length
if (
rawValue === emptyString ||
(rawValue[0] === prefix[0] && rawValueLength === 1)
) {
return prefix.split(emptyString).concat([digitRegExp]).concat(suffix.split(emptyString))
} else if(
rawValue === decimalSymbol &&
allowDecimal
) {
return prefix.split(emptyString).concat(['0', decimalSymbol, digitRegExp]).concat(suffix.split(emptyString))
}
const isNegative = (rawValue[0] === minus) && allowNegative
//If negative remove "-" sign
if(isNegative) {
rawValue = rawValue.toString().substr(1)
}
const indexOfLastDecimal = rawValue.lastIndexOf(decimalSymbol)
const hasDecimal = indexOfLastDecimal !== -1
let integer
let fraction
let mask
// remove the suffix
if (rawValue.slice(suffixLength * -1) === suffix) {
rawValue = rawValue.slice(0, suffixLength * -1)
}
if (hasDecimal && (allowDecimal || requireDecimal)) {
integer = rawValue.slice(rawValue.slice(0, prefixLength) === prefix ? prefixLength : 0, indexOfLastDecimal)
fraction = rawValue.slice(indexOfLastDecimal + 1, rawValueLength)
fraction = convertToMask(fraction.replace(nonDigitsRegExp, emptyString))
} else {
if (rawValue.slice(0, prefixLength) === prefix) {
integer = rawValue.slice(prefixLength)
} else {
integer = rawValue
}
}
if (integerLimit && typeof integerLimit === number) {
const thousandsSeparatorRegex = thousandsSeparatorSymbol === '.' ? '[.]' : `${thousandsSeparatorSymbol}`
const numberOfThousandSeparators = (integer.match(new RegExp(thousandsSeparatorRegex, 'g')) || []).length
integer = integer.slice(0, integerLimit + (numberOfThousandSeparators * thousandsSeparatorSymbolLength))
}
integer = integer.replace(nonDigitsRegExp, emptyString)
if (!allowLeadingZeroes) {
integer = integer.replace(/^0+(0$|[^0])/, '$1')
}
integer = (includeThousandsSeparator) ? addThousandsSeparator(integer, thousandsSeparatorSymbol) : integer
mask = convertToMask(integer)
if ((hasDecimal && allowDecimal) || requireDecimal === true) {
if (rawValue[indexOfLastDecimal - 1] !== decimalSymbol) {
mask.push(caretTrap)
}
mask.push(decimalSymbol, caretTrap)
if (fraction) {
if (typeof decimalLimit === number) {
fraction = fraction.slice(0, decimalLimit)
}
mask = mask.concat(fraction)
}
if (requireDecimal === true && rawValue[indexOfLastDecimal - 1] === decimalSymbol) {
mask.push(digitRegExp)
}
}
if (prefixLength > 0) {
mask = prefix.split(emptyString).concat(mask)
}
if (isNegative) {
// If user is entering a negative number, add a mask placeholder spot to attract the caret to it.
if (mask.length === prefixLength) {
mask.push(digitRegExp)
}
mask = [minusRegExp].concat(mask)
}
if (suffix.length > 0) {
mask = mask.concat(suffix.split(emptyString))
}
return mask
}
numberMask.instanceOf = 'createNumberMask'
return numberMask
}
function convertToMask(strNumber) {
return strNumber
.split(emptyString)
.map((char) => digitRegExp.test(char) ? digitRegExp : char)
}
// http://stackoverflow.com/a/10899795/604296
function addThousandsSeparator(n, thousandsSeparatorSymbol) {
return n.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparatorSymbol)
}