UNPKG

ngx-mask

Version:
1,451 lines (1,441 loc) 63 kB
import { InjectionToken, Injectable, Inject, ElementRef, Renderer2, Directive, forwardRef, Input, HostListener, Pipe, NgModule } from '@angular/core'; import { __awaiter } from 'tslib'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { DOCUMENT } from '@angular/common'; const config = new InjectionToken('config'); const NEW_CONFIG = new InjectionToken('NEW_CONFIG'); const INITIAL_CONFIG = new InjectionToken('INITIAL_CONFIG'); const initialConfig = { suffix: '', prefix: '', thousandSeparator: ' ', decimalMarker: '.', clearIfNotMatch: false, showTemplate: false, showMaskTyped: false, placeHolderCharacter: '_', dropSpecialCharacters: true, hiddenInput: undefined, shownMaskExpression: '', separatorLimit: '', allowNegativeNumbers: false, validation: true, // tslint:disable-next-line: quotemark specialCharacters: ['-', '/', '(', ')', '.', ':', ' ', '+', ',', '@', '[', ']', '"', "'"], leadZeroDateTime: false, patterns: { '0': { pattern: new RegExp('\\d'), }, '9': { pattern: new RegExp('\\d'), optional: true, }, X: { pattern: new RegExp('\\d'), symbol: '*', }, A: { pattern: new RegExp('[a-zA-Z0-9]'), }, S: { pattern: new RegExp('[a-zA-Z]'), }, d: { pattern: new RegExp('\\d'), }, m: { pattern: new RegExp('\\d'), }, M: { pattern: new RegExp('\\d'), }, H: { pattern: new RegExp('\\d'), }, h: { pattern: new RegExp('\\d'), }, s: { pattern: new RegExp('\\d'), }, }, }; const timeMasks = ['Hh:m0:s0', 'Hh:m0', 'm0:s0']; const withoutValidation = [ 'percent', 'Hh', 's0', 'm0', 'separator', 'd0/M0/0000', 'd0/M0', 'd0', 'M0', ]; class MaskApplierService { constructor(_config) { this._config = _config; this.maskExpression = ''; this.actualValue = ''; this.shownMaskExpression = ''; this._formatWithSeparators = (str, thousandSeparatorChar, decimalChar, precision) => { const x = str.split(decimalChar); const decimals = x.length > 1 ? `${decimalChar}${x[1]}` : ''; let res = x[0]; const separatorLimit = this.separatorLimit.replace(/\s/g, ''); if (separatorLimit && +separatorLimit) { if (res[0] === '-') { res = `-${res.slice(1, res.length).slice(0, separatorLimit.length)}`; } else { res = res.slice(0, separatorLimit.length); } } const rgx = /(\d+)(\d{3})/; while (thousandSeparatorChar && rgx.test(res)) { res = res.replace(rgx, '$1' + thousandSeparatorChar + '$2'); } if (precision === undefined) { return res + decimals; } else if (precision === 0) { return res; } return res + decimals.substr(0, precision + 1); }; this.percentage = (str) => { return Number(str) >= 0 && Number(str) <= 100; }; this.getPrecision = (maskExpression) => { const x = maskExpression.split('.'); if (x.length > 1) { return Number(x[x.length - 1]); } return Infinity; }; this.checkAndRemoveSuffix = (inputValue) => { var _a, _b, _c; for (let i = ((_a = this.suffix) === null || _a === void 0 ? void 0 : _a.length) - 1; i >= 0; i--) { const substr = this.suffix.substr(i, (_b = this.suffix) === null || _b === void 0 ? void 0 : _b.length); if (inputValue.includes(substr) && (i - 1 < 0 || !inputValue.includes(this.suffix.substr(i - 1, (_c = this.suffix) === null || _c === void 0 ? void 0 : _c.length)))) { return inputValue.replace(substr, ''); } } return inputValue; }; this.checkInputPrecision = (inputValue, precision, decimalMarker) => { if (precision < Infinity) { const precisionRegEx = new RegExp(this._charToRegExpExpression(decimalMarker) + `\\d{${precision}}.*$`); const precisionMatch = inputValue.match(precisionRegEx); if (precisionMatch && precisionMatch[0].length - 1 > precision) { const diff = precisionMatch[0].length - 1 - precision; inputValue = inputValue.substring(0, inputValue.length - diff); } if (precision === 0 && inputValue.endsWith(decimalMarker)) { inputValue = inputValue.substring(0, inputValue.length - 1); } } return inputValue; }; this._shift = new Set(); this.clearIfNotMatch = this._config.clearIfNotMatch; this.dropSpecialCharacters = this._config.dropSpecialCharacters; this.maskSpecialCharacters = this._config.specialCharacters; this.maskAvailablePatterns = this._config.patterns; this.prefix = this._config.prefix; this.suffix = this._config.suffix; this.thousandSeparator = this._config.thousandSeparator; this.decimalMarker = this._config.decimalMarker; this.hiddenInput = this._config.hiddenInput; this.showMaskTyped = this._config.showMaskTyped; this.placeHolderCharacter = this._config.placeHolderCharacter; this.validation = this._config.validation; this.separatorLimit = this._config.separatorLimit; this.allowNegativeNumbers = this._config.allowNegativeNumbers; this.leadZeroDateTime = this._config.leadZeroDateTime; } applyMaskWithPattern(inputValue, maskAndPattern) { const [mask, customPattern] = maskAndPattern; this.customPattern = customPattern; return this.applyMask(inputValue, mask); } applyMask(inputValue, maskExpression, position = 0, justPasted = false, backspaced = false, cb = () => { }) { if (inputValue === undefined || inputValue === null || maskExpression === undefined) { return ''; } let cursor = 0; let result = ''; let multi = false; let backspaceShift = false; let shift = 1; let stepBack = false; if (inputValue.slice(0, this.prefix.length) === this.prefix) { inputValue = inputValue.slice(this.prefix.length, inputValue.length); } if (!!this.suffix && (inputValue === null || inputValue === void 0 ? void 0 : inputValue.length) > 0) { inputValue = this.checkAndRemoveSuffix(inputValue); } const inputArray = inputValue.toString().split(''); if (maskExpression === 'IP') { this.ipError = !!(inputArray.filter((i) => i === '.').length < 3 && inputArray.length < 7); maskExpression = '099.099.099.099'; } const arr = []; for (let i = 0; i < inputValue.length; i++) { if (inputValue[i].match('\\d')) { arr.push(inputValue[i]); } } if (maskExpression === 'CPF_CNPJ') { this.cpfCnpjError = !!(arr.length !== 11 && arr.length !== 14); if (arr.length > 11) { maskExpression = '00.000.000/0000-00'; } else { maskExpression = '000.000.000-00'; } } if (maskExpression.startsWith('percent')) { if (inputValue.match('[a-z]|[A-Z]') || inputValue.match(/[-!$%^&*()_+|~=`{}\[\]:";'<>?,\/.]/)) { inputValue = this._stripToDecimal(inputValue); const precision = this.getPrecision(maskExpression); inputValue = this.checkInputPrecision(inputValue, precision, this.decimalMarker); } if (inputValue.indexOf('.') > 0 && !this.percentage(inputValue.substring(0, inputValue.indexOf('.')))) { const base = inputValue.substring(0, inputValue.indexOf('.') - 1); inputValue = `${base}${inputValue.substring(inputValue.indexOf('.'), inputValue.length)}`; } if (this.percentage(inputValue)) { result = inputValue; } else { result = inputValue.substring(0, inputValue.length - 1); } } else if (maskExpression.startsWith('separator')) { if (inputValue.match('[wа-яА-Я]') || inputValue.match('[ЁёА-я]') || inputValue.match('[a-z]|[A-Z]') || inputValue.match(/[-@#!$%\\^&*()_£¬'+|~=`{}\[\]:";<>.?\/]/) || inputValue.match('[^A-Za-z0-9,]')) { inputValue = this._stripToDecimal(inputValue); } inputValue = inputValue.length > 1 && inputValue[0] === '0' && inputValue[1] !== this.decimalMarker ? inputValue.slice(1, inputValue.length) : inputValue; // TODO: we had different rexexps here for the different cases... but tests dont seam to bother - check this // separator: no COMMA, dot-sep: no SPACE, COMMA OK, comma-sep: no SPACE, COMMA OK const thousandSeperatorCharEscaped = this._charToRegExpExpression(this.thousandSeparator); const decimalMarkerEscaped = this._charToRegExpExpression(this.decimalMarker); const invalidChars = '@#!$%^&*()_+|~=`{}\\[\\]:\\s,\\.";<>?\\/' .replace(thousandSeperatorCharEscaped, '') .replace(decimalMarkerEscaped, ''); const invalidCharRegexp = new RegExp('[' + invalidChars + ']'); if (inputValue.match(invalidCharRegexp)) { inputValue = inputValue.substring(0, inputValue.length - 1); } const precision = this.getPrecision(maskExpression); inputValue = this.checkInputPrecision(inputValue, precision, this.decimalMarker); const strForSep = inputValue.replace(new RegExp(thousandSeperatorCharEscaped, 'g'), ''); result = this._formatWithSeparators(strForSep, this.thousandSeparator, this.decimalMarker, precision); const commaShift = result.indexOf(',') - inputValue.indexOf(','); const shiftStep = result.length - inputValue.length; if (shiftStep > 0 && result[position] !== ',') { backspaceShift = true; let _shift = 0; do { this._shift.add(position + _shift); _shift++; } while (_shift < shiftStep); } else if ((commaShift !== 0 && position > 0 && !(result.indexOf(',') >= position && position > 3)) || (!(result.indexOf('.') >= position && position > 3) && shiftStep <= 0)) { this._shift.clear(); backspaceShift = true; shift = shiftStep; position += shiftStep; this._shift.add(position); } else { this._shift.clear(); } } else { for ( // tslint:disable-next-line let i = 0, inputSymbol = inputArray[0]; i < inputArray.length; i++, inputSymbol = inputArray[i]) { if (cursor === maskExpression.length) { break; } if (this._checkSymbolMask(inputSymbol, maskExpression[cursor]) && maskExpression[cursor + 1] === '?') { result += inputSymbol; cursor += 2; } else if (maskExpression[cursor + 1] === '*' && multi && this._checkSymbolMask(inputSymbol, maskExpression[cursor + 2])) { result += inputSymbol; cursor += 3; multi = false; } else if (this._checkSymbolMask(inputSymbol, maskExpression[cursor]) && maskExpression[cursor + 1] === '*') { result += inputSymbol; multi = true; } else if (maskExpression[cursor + 1] === '?' && this._checkSymbolMask(inputSymbol, maskExpression[cursor + 2])) { result += inputSymbol; cursor += 3; } else if (this._checkSymbolMask(inputSymbol, maskExpression[cursor])) { if (maskExpression[cursor] === 'H') { if (Number(inputSymbol) > 2) { cursor += 1; this._shiftStep(maskExpression, cursor, inputArray.length); i--; if (this.leadZeroDateTime) { result += '0'; } continue; } } if (maskExpression[cursor] === 'h') { if (result === '2' && Number(inputSymbol) > 3) { cursor += 1; i--; continue; } } if (maskExpression[cursor] === 'm') { if (Number(inputSymbol) > 5) { cursor += 1; this._shiftStep(maskExpression, cursor, inputArray.length); i--; if (this.leadZeroDateTime) { result += '0'; } continue; } } if (maskExpression[cursor] === 's') { if (Number(inputSymbol) > 5) { cursor += 1; this._shiftStep(maskExpression, cursor, inputArray.length); i--; if (this.leadZeroDateTime) { result += '0'; } continue; } } const daysCount = 31; if (maskExpression[cursor] === 'd') { if ((Number(inputSymbol) > 3 && this.leadZeroDateTime) || Number(inputValue.slice(cursor, cursor + 2)) > daysCount || inputValue[cursor + 1] === '/') { cursor += 1; this._shiftStep(maskExpression, cursor, inputArray.length); i--; if (this.leadZeroDateTime) { result += '0'; } continue; } } if (maskExpression[cursor] === 'M') { const monthsCount = 12; // mask without day const withoutDays = cursor === 0 && (Number(inputSymbol) > 2 || Number(inputValue.slice(cursor, cursor + 2)) > monthsCount || inputValue[cursor + 1] === '/'); // day<10 && month<12 for input const day1monthInput = inputValue.slice(cursor - 3, cursor - 1).includes('/') && ((inputValue[cursor - 2] === '/' && Number(inputValue.slice(cursor - 1, cursor + 1)) > monthsCount && inputValue[cursor] !== '/') || inputValue[cursor] === '/' || (inputValue[cursor - 3] === '/' && Number(inputValue.slice(cursor - 2, cursor)) > monthsCount && inputValue[cursor - 1] !== '/') || inputValue[cursor - 1] === '/'); // 10<day<31 && month<12 for input const day2monthInput = Number(inputValue.slice(cursor - 3, cursor - 1)) <= daysCount && !inputValue.slice(cursor - 3, cursor - 1).includes('/') && inputValue[cursor - 1] === '/' && (Number(inputValue.slice(cursor, cursor + 2)) > monthsCount || inputValue[cursor + 1] === '/'); // day<10 && month<12 for paste whole data const day1monthPaste = Number(inputValue.slice(cursor - 3, cursor - 1)) > daysCount && !inputValue.slice(cursor - 3, cursor - 1).includes('/') && !inputValue.slice(cursor - 2, cursor).includes('/') && Number(inputValue.slice(cursor - 2, cursor)) > monthsCount; // 10<day<31 && month<12 for paste whole data const day2monthPaste = Number(inputValue.slice(cursor - 3, cursor - 1)) <= daysCount && !inputValue.slice(cursor - 3, cursor - 1).includes('/') && inputValue[cursor - 1] !== '/' && Number(inputValue.slice(cursor - 1, cursor + 1)) > monthsCount; if ((Number(inputSymbol) > 1 && this.leadZeroDateTime) || withoutDays || day1monthInput || day2monthInput || day1monthPaste || day2monthPaste) { cursor += 1; this._shiftStep(maskExpression, cursor, inputArray.length); i--; if (this.leadZeroDateTime) { result += '0'; } continue; } } result += inputSymbol; cursor++; } else if (this.maskSpecialCharacters.indexOf(maskExpression[cursor]) !== -1) { result += maskExpression[cursor]; cursor++; this._shiftStep(maskExpression, cursor, inputArray.length); i--; } else if (this.maskSpecialCharacters.indexOf(inputSymbol) > -1 && this.maskAvailablePatterns[maskExpression[cursor]] && this.maskAvailablePatterns[maskExpression[cursor]].optional) { if (!!inputArray[cursor] && maskExpression !== '099.099.099.099' && maskExpression !== '000.000.000-00' && maskExpression !== '00.000.000/0000-00') { result += inputArray[cursor]; } cursor++; i--; } else if (this.maskExpression[cursor + 1] === '*' && this._findSpecialChar(this.maskExpression[cursor + 2]) && this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] && multi) { cursor += 3; result += inputSymbol; } else if (this.maskExpression[cursor + 1] === '?' && this._findSpecialChar(this.maskExpression[cursor + 2]) && this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] && multi) { cursor += 3; result += inputSymbol; } else if (this.showMaskTyped && this.maskSpecialCharacters.indexOf(inputSymbol) < 0 && inputSymbol !== this.placeHolderCharacter) { stepBack = true; } } } if (result.length + 1 === maskExpression.length && this.maskSpecialCharacters.indexOf(maskExpression[maskExpression.length - 1]) !== -1) { result += maskExpression[maskExpression.length - 1]; } let newPosition = position + 1; while (this._shift.has(newPosition)) { shift++; newPosition++; } let actualShift = justPasted ? cursor : this._shift.has(position) ? shift : 0; if (stepBack) { actualShift--; } cb(actualShift, backspaceShift); if (shift < 0) { this._shift.clear(); } let onlySpecial = false; if (backspaced) { onlySpecial = inputArray.every((char) => this.maskSpecialCharacters.includes(char)); } let res = `${this.prefix}${onlySpecial ? '' : result}${this.suffix}`; if (result.length === 0) { res = `${this.prefix}${result}`; } return res; } _findSpecialChar(inputSymbol) { return this.maskSpecialCharacters.find((val) => val === inputSymbol); } _checkSymbolMask(inputSymbol, maskSymbol) { this.maskAvailablePatterns = this.customPattern ? this.customPattern : this.maskAvailablePatterns; return (this.maskAvailablePatterns[maskSymbol] && this.maskAvailablePatterns[maskSymbol].pattern && this.maskAvailablePatterns[maskSymbol].pattern.test(inputSymbol)); } _stripToDecimal(str) { return str .split('') .filter((i, idx) => { return (i.match('^-?\\d') || i.match('\\s') || i === '.' || i === ',' || (i === '-' && idx === 0 && this.allowNegativeNumbers)); }) .join(''); } _charToRegExpExpression(char) { if (char) { const charsToEscape = '[\\^$.|?*+()'; return char === ' ' ? '\\s' : charsToEscape.indexOf(char) >= 0 ? '\\' + char : char; } return char; } _shiftStep(maskExpression, cursor, inputLength) { const shiftStep = /[*?]/g.test(maskExpression.slice(0, cursor)) ? inputLength : cursor; this._shift.add(shiftStep + this.prefix.length || 0); } } MaskApplierService.decorators = [ { type: Injectable } ]; MaskApplierService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [config,] }] } ]; class MaskService extends MaskApplierService { constructor(document, _config, _elementRef, _renderer) { super(_config); this.document = document; this._config = _config; this._elementRef = _elementRef; this._renderer = _renderer; this.maskExpression = ''; this.isNumberValue = false; this.placeHolderCharacter = '_'; this.maskIsShown = ''; this.selStart = null; this.selEnd = null; /** * Whether we are currently in writeValue function, in this case when applying the mask we don't want to trigger onChange function, * since writeValue should be a one way only process of writing the DOM value based on the Angular model value. */ this.writingValue = false; this.onChange = (_) => { }; } // tslint:disable-next-line:cyclomatic-complexity applyMask(inputValue, maskExpression, position = 0, justPasted = false, backspaced = false, cb = () => { }) { if (!maskExpression) { return inputValue; } this.maskIsShown = this.showMaskTyped ? this.showMaskInInput() : ''; if (this.maskExpression === 'IP' && this.showMaskTyped) { this.maskIsShown = this.showMaskInInput(inputValue || '#'); } if (this.maskExpression === 'CPF_CNPJ' && this.showMaskTyped) { this.maskIsShown = this.showMaskInInput(inputValue || '#'); } if (!inputValue && this.showMaskTyped) { this.formControlResult(this.prefix); return this.prefix + this.maskIsShown; } const getSymbol = !!inputValue && typeof this.selStart === 'number' ? inputValue[this.selStart] : ''; let newInputValue = ''; if (this.hiddenInput && !this.writingValue) { let actualResult = this.actualValue.split(''); // tslint:disable no-unused-expression inputValue !== '' && actualResult.length ? typeof this.selStart === 'number' && typeof this.selEnd === 'number' ? inputValue.length > actualResult.length ? actualResult.splice(this.selStart, 0, getSymbol) : inputValue.length < actualResult.length ? actualResult.length - inputValue.length === 1 ? actualResult.splice(this.selStart - 1, 1) : actualResult.splice(this.selStart, this.selEnd - this.selStart) : null : null : (actualResult = []); // tslint:enable no-unused-expression newInputValue = this.actualValue.length && actualResult.length <= inputValue.length ? this.shiftTypedSymbols(actualResult.join('')) : inputValue; } newInputValue = Boolean(newInputValue) && newInputValue.length ? newInputValue : inputValue; const result = super.applyMask(newInputValue, maskExpression, position, justPasted, backspaced, cb); this.actualValue = this.getActualValue(result); // handle some separator implications: // a.) adjust decimalMarker default (. -> ,) if thousandSeparator is a dot if (this.thousandSeparator === '.' && this.decimalMarker === '.') { this.decimalMarker = ','; } // b) remove decimal marker from list of special characters to mask if (this.maskExpression.startsWith('separator') && this.dropSpecialCharacters === true) { this.maskSpecialCharacters = this.maskSpecialCharacters.filter((item) => item !== this.decimalMarker); } this.formControlResult(result); if (!this.showMaskTyped) { if (this.hiddenInput) { return result && result.length ? this.hideInput(result, this.maskExpression) : result; } return result; } const resLen = result.length; const prefNmask = this.prefix + this.maskIsShown; if (this.maskExpression.includes('H')) { const countSkipedSymbol = this._numberSkipedSymbols(result); return result + prefNmask.slice(resLen + countSkipedSymbol); } else if (this.maskExpression === 'IP' || this.maskExpression === 'CPF_CNPJ') { return result + prefNmask; } return result + prefNmask.slice(resLen); } // get the number of characters that were shifted _numberSkipedSymbols(value) { const regex = /(^|\D)(\d\D)/g; let match = regex.exec(value); let countSkipedSymbol = 0; while (match != null) { countSkipedSymbol += 1; match = regex.exec(value); } return countSkipedSymbol; } applyValueChanges(position = 0, justPasted, backspaced, cb = () => { }) { const formElement = this._elementRef.nativeElement; formElement.value = this.applyMask(formElement.value, this.maskExpression, position, justPasted, backspaced, cb); if (formElement === this.document.activeElement) { return; } this.clearIfNotMatchFn(); } hideInput(inputValue, maskExpression) { return inputValue .split('') .map((curr, index) => { if (this.maskAvailablePatterns && this.maskAvailablePatterns[maskExpression[index]] && this.maskAvailablePatterns[maskExpression[index]].symbol) { return this.maskAvailablePatterns[maskExpression[index]].symbol; } return curr; }) .join(''); } // this function is not necessary, it checks result against maskExpression getActualValue(res) { const compare = res .split('') .filter((symbol, i) => this._checkSymbolMask(symbol, this.maskExpression[i]) || (this.maskSpecialCharacters.includes(this.maskExpression[i]) && symbol === this.maskExpression[i])); if (compare.join('') === res) { return compare.join(''); } return res; } shiftTypedSymbols(inputValue) { let symbolToReplace = ''; const newInputValue = (inputValue && inputValue.split('').map((currSymbol, index) => { if (this.maskSpecialCharacters.includes(inputValue[index + 1]) && inputValue[index + 1] !== this.maskExpression[index + 1]) { symbolToReplace = currSymbol; return inputValue[index + 1]; } if (symbolToReplace.length) { const replaceSymbol = symbolToReplace; symbolToReplace = ''; return replaceSymbol; } return currSymbol; })) || []; return newInputValue.join(''); } showMaskInInput(inputVal) { if (this.showMaskTyped && !!this.shownMaskExpression) { if (this.maskExpression.length !== this.shownMaskExpression.length) { throw new Error('Mask expression must match mask placeholder length'); } else { return this.shownMaskExpression; } } else if (this.showMaskTyped) { if (inputVal) { if (this.maskExpression === 'IP') { return this._checkForIp(inputVal); } if (this.maskExpression === 'CPF_CNPJ') { return this._checkForCpfCnpj(inputVal); } } return this.maskExpression.replace(/\w/g, this.placeHolderCharacter); } return ''; } clearIfNotMatchFn() { const formElement = this._elementRef.nativeElement; if (this.clearIfNotMatch && this.prefix.length + this.maskExpression.length + this.suffix.length !== formElement.value.replace(/_/g, '').length) { this.formElementProperty = ['value', '']; this.applyMask(formElement.value, this.maskExpression); } } set formElementProperty([name, value]) { Promise.resolve().then(() => this._renderer.setProperty(this._elementRef.nativeElement, name, value)); } checkSpecialCharAmount(mask) { const chars = mask.split('').filter((item) => this._findSpecialChar(item)); return chars.length; } removeMask(inputValue) { return this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.maskSpecialCharacters.concat('_').concat(this.placeHolderCharacter)); } _checkForIp(inputVal) { if (inputVal === '#') { return `${this.placeHolderCharacter}.${this.placeHolderCharacter}.${this.placeHolderCharacter}.${this.placeHolderCharacter}`; } const arr = []; for (let i = 0; i < inputVal.length; i++) { if (inputVal[i].match('\\d')) { arr.push(inputVal[i]); } } if (arr.length <= 3) { return `${this.placeHolderCharacter}.${this.placeHolderCharacter}.${this.placeHolderCharacter}`; } if (arr.length > 3 && arr.length <= 6) { return `${this.placeHolderCharacter}.${this.placeHolderCharacter}`; } if (arr.length > 6 && arr.length <= 9) { return this.placeHolderCharacter; } if (arr.length > 9 && arr.length <= 12) { return ''; } return ''; } _checkForCpfCnpj(inputVal) { const cpf = `${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `.${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `.${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `-${this.placeHolderCharacter}${this.placeHolderCharacter}`; const cnpj = `${this.placeHolderCharacter}${this.placeHolderCharacter}` + `.${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `.${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `/${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}${this.placeHolderCharacter}` + `-${this.placeHolderCharacter}${this.placeHolderCharacter}`; if (inputVal === '#') { return cpf; } const arr = []; for (let i = 0; i < inputVal.length; i++) { if (inputVal[i].match('\\d')) { arr.push(inputVal[i]); } } if (arr.length <= 3) { return cpf.slice(arr.length, cpf.length); } if (arr.length > 3 && arr.length <= 6) { return cpf.slice(arr.length + 1, cpf.length); } if (arr.length > 6 && arr.length <= 9) { return cpf.slice(arr.length + 2, cpf.length); } if (arr.length > 9 && arr.length < 11) { return cpf.slice(arr.length + 3, cpf.length); } if (arr.length === 11) { return ''; } if (arr.length === 12) { if (inputVal.length === 17) { return cnpj.slice(16, cnpj.length); } return cnpj.slice(15, cnpj.length); } if (arr.length > 12 && arr.length <= 14) { return cnpj.slice(arr.length + 4, cnpj.length); } return ''; } /** * Propogates the input value back to the Angular model by triggering the onChange function. It won't do this if writingValue * is true. If that is true it means we are currently in the writeValue function, which is supposed to only update the actual * DOM element based on the Angular model value. It should be a one way process, i.e. writeValue should not be modifying the Angular * model value too. Therefore, we don't trigger onChange in this scenario. * @param inputValue the current form input value */ formControlResult(inputValue) { if (this.writingValue) { return; } if (Array.isArray(this.dropSpecialCharacters)) { this.onChange(this._toNumber(this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.dropSpecialCharacters))); } else if (this.dropSpecialCharacters) { this.onChange(this._toNumber(this._checkSymbols(inputValue))); } else { this.onChange(this._removeSuffix(inputValue)); } } _toNumber(value) { if (!this.isNumberValue || value === '') { return value; } const num = Number(value); return Number.isNaN(num) ? value : num; } _removeMask(value, specialCharactersForRemove) { return value ? value.replace(this._regExpForRemove(specialCharactersForRemove), '') : value; } _removePrefix(value) { if (!this.prefix) { return value; } return value ? value.replace(this.prefix, '') : value; } _removeSuffix(value) { if (!this.suffix) { return value; } return value ? value.replace(this.suffix, '') : value; } _retrieveSeparatorValue(result) { return this._removeMask(this._removeSuffix(this._removePrefix(result)), this.maskSpecialCharacters); } _regExpForRemove(specialCharactersForRemove) { return new RegExp(specialCharactersForRemove.map((item) => `\\${item}`).join('|'), 'gi'); } _checkSymbols(result) { if (result === '') { return result; } const separatorPrecision = this._retrieveSeparatorPrecision(this.maskExpression); let separatorValue = this._retrieveSeparatorValue(result); if (this.decimalMarker !== '.') { separatorValue = separatorValue.replace(this.decimalMarker, '.'); } if (!this.isNumberValue) { return separatorValue; } if (separatorPrecision) { if (result === this.decimalMarker) { return null; } return this._checkPrecision(this.maskExpression, separatorValue); } else { return Number(separatorValue); } } // TODO should think about helpers or separting decimal precision to own property _retrieveSeparatorPrecision(maskExpretion) { const matcher = maskExpretion.match(new RegExp(`^separator\\.([^d]*)`)); return matcher ? Number(matcher[1]) : null; } _checkPrecision(separatorExpression, separatorValue) { if (separatorExpression.indexOf('2') > 0) { return Number(separatorValue).toFixed(2); } return Number(separatorValue); } } MaskService.decorators = [ { type: Injectable } ]; MaskService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: undefined, decorators: [{ type: Inject, args: [config,] }] }, { type: ElementRef }, { type: Renderer2 } ]; // tslint:disable deprecation // tslint:disable no-input-rename class MaskDirective { constructor(document, _maskService, _config) { this.document = document; this._maskService = _maskService; this._config = _config; this.maskExpression = ''; this.specialCharacters = []; this.patterns = {}; this.prefix = ''; this.suffix = ''; this.thousandSeparator = ' '; this.decimalMarker = '.'; this.dropSpecialCharacters = null; this.hiddenInput = null; this.showMaskTyped = null; this.placeHolderCharacter = null; this.shownMaskExpression = null; this.showTemplate = null; this.clearIfNotMatch = null; this.validation = null; this.separatorLimit = null; this.allowNegativeNumbers = null; this.leadZeroDateTime = null; this._maskValue = ''; this._position = null; this._maskExpressionArray = []; this._justPasted = false; this.onChange = (_) => { }; this.onTouch = () => { }; } ngOnChanges(changes) { const { maskExpression, specialCharacters, patterns, prefix, suffix, thousandSeparator, decimalMarker, dropSpecialCharacters, hiddenInput, showMaskTyped, placeHolderCharacter, shownMaskExpression, showTemplate, clearIfNotMatch, validation, separatorLimit, allowNegativeNumbers, leadZeroDateTime, } = changes; if (maskExpression) { this._maskValue = maskExpression.currentValue || ''; if (maskExpression.currentValue && maskExpression.currentValue.split('||').length > 1) { this._maskExpressionArray = maskExpression.currentValue.split('||').sort((a, b) => { return a.length - b.length; }); this._maskValue = this._maskExpressionArray[0]; this.maskExpression = this._maskExpressionArray[0]; this._maskService.maskExpression = this._maskExpressionArray[0]; } } if (specialCharacters) { if (!specialCharacters.currentValue || !Array.isArray(specialCharacters.currentValue)) { return; } else { this._maskService.maskSpecialCharacters = specialCharacters.currentValue || []; } } // Only overwrite the mask available patterns if a pattern has actually been passed in if (patterns && patterns.currentValue) { this._maskService.maskAvailablePatterns = patterns.currentValue; } if (prefix) { this._maskService.prefix = prefix.currentValue; } if (suffix) { this._maskService.suffix = suffix.currentValue; } if (thousandSeparator) { this._maskService.thousandSeparator = thousandSeparator.currentValue; } if (decimalMarker) { this._maskService.decimalMarker = decimalMarker.currentValue; } if (dropSpecialCharacters) { this._maskService.dropSpecialCharacters = dropSpecialCharacters.currentValue; } if (hiddenInput) { this._maskService.hiddenInput = hiddenInput.currentValue; } if (showMaskTyped) { this._maskService.showMaskTyped = showMaskTyped.currentValue; } if (placeHolderCharacter) { this._maskService.placeHolderCharacter = placeHolderCharacter.currentValue; } if (shownMaskExpression) { this._maskService.shownMaskExpression = shownMaskExpression.currentValue; } if (showTemplate) { this._maskService.showTemplate = showTemplate.currentValue; } if (clearIfNotMatch) { this._maskService.clearIfNotMatch = clearIfNotMatch.currentValue; } if (validation) { this._maskService.validation = validation.currentValue; } if (separatorLimit) { this._maskService.separatorLimit = separatorLimit.currentValue; } if (allowNegativeNumbers) { this._maskService.allowNegativeNumbers = allowNegativeNumbers.currentValue; if (this._maskService.allowNegativeNumbers) { this._maskService.maskSpecialCharacters = this._maskService.maskSpecialCharacters.filter((c) => c !== '-'); } } if (leadZeroDateTime) { this._maskService.leadZeroDateTime = leadZeroDateTime.currentValue; } this._applyMask(); } // tslint:disable-next-line: cyclomatic-complexity validate({ value }) { if (!this._maskService.validation || !this._maskValue) { return null; } if (this._maskService.ipError) { return this._createValidationError(value); } if (this._maskService.cpfCnpjError) { return this._createValidationError(value); } if (this._maskValue.startsWith('separator')) { return null; } if (withoutValidation.includes(this._maskValue)) { return null; } if (this._maskService.clearIfNotMatch) { return null; } if (timeMasks.includes(this._maskValue)) { return this._validateTime(value); } if (value && value.toString().length >= 1) { let counterOfOpt = 0; for (const key in this._maskService.maskAvailablePatterns) { if (this._maskService.maskAvailablePatterns[key].optional && this._maskService.maskAvailablePatterns[key].optional === true) { if (this._maskValue.indexOf(key) !== this._maskValue.lastIndexOf(key)) { const opt = this._maskValue .split('') .filter((i) => i === key) .join(''); counterOfOpt += opt.length; } else if (this._maskValue.indexOf(key) !== -1) { counterOfOpt++; } if (this._maskValue.indexOf(key) !== -1 && value.toString().length >= this._maskValue.indexOf(key)) { return null; } if (counterOfOpt === this._maskValue.length) { return null; } } } if (this._maskValue.indexOf('{') === 1 && value.toString().length === this._maskValue.length + Number(this._maskValue.split('{')[1].split('}')[0]) - 4) { return null; } if (this._maskValue.indexOf('*') === 1 || this._maskValue.indexOf('?') === 1) { return null; } else if ((this._maskValue.indexOf('*') > 1 && value.toString().length < this._maskValue.indexOf('*')) || (this._maskValue.indexOf('?') > 1 && value.toString().length < this._maskValue.indexOf('?')) || this._maskValue.indexOf('{') === 1) { return this._createValidationError(value); } if (this._maskValue.indexOf('*') === -1 || this._maskValue.indexOf('?') === -1) { const length = this._maskService.dropSpecialCharacters ? this._maskValue.length - this._maskService.checkSpecialCharAmount(this._maskValue) - counterOfOpt : this._maskValue.length - counterOfOpt; if (value.toString().length < length) { return this._createValidationError(value); } } } return null; } onPaste() { this._justPasted = true; } onInput(e) { const el = e.target; this._inputValue = el.value; this._setMask(); if (!this._maskValue) { this.onChange(el.value); return; } const position = el.selectionStart === 1 ? el.selectionStart + this._maskService.prefix.length : el.selectionStart; let caretShift = 0; let backspaceShift = false; this._maskService.applyValueChanges(position, this._justPasted, this._code === 'Backspace', (shift, _backspaceShift) => { this._justPasted = false; caretShift = shift; backspaceShift = _backspaceShift; }); // only set the selection if the element is active if (this.document.activeElement !== el) { return; } this._position = this._position === 1 && this._inputValue.length === 1 ? null : this._position; let positionToApply = this._position ? this._inputValue.length + position + caretShift : position + (this._code === 'Backspace' && !backspaceShift ? 0 : caretShift); if (positionToApply > this._getActualInputLength()) { positionToApply = this._getActualInputLength(); } el.setSelectionRange(positionToApply, positionToApply); this._position = null; } onBlur() { if (this._maskValue) { this._maskService.clearIfNotMatchFn(); } this.onTouch(); } onFocus(e) { if (!this._maskValue) { return; } const el = e.target; const posStart = 0; const posEnd = 0; if (el !== null && el.selectionStart !== null && el.selectionStart === el.selectionEnd && el.selectionStart > this._maskService.prefix.length && // tslint:disable-next-line e.keyCode !== 38) { if (this._maskService.showMaskTyped) { // We are showing the mask in the input this._maskService.maskIsShown = this._maskService.showMaskInInput(); if (el.setSelectionRange && this._maskService.prefix + this._maskService.maskIsShown === el.value) { // the input ONLY contains the mask, so position the cursor at the start el.focus(); el.setSelectionRange(posStart, posEnd); } else { // the input contains some characters already if (el.selectionStart > this._maskService.actualValue.length) { // if the user clicked beyond our value's length, position the cursor at the end of our value el.setSelectionRange(this._maskService.actualValue.length, this._maskService.actualValue.length); } } } } const nextValue = !el.value || el.value === this._maskService.prefix ? this._maskService.prefix + this._maskService.maskIsShown : el.value; /** Fix of cursor position jumping to end in most browsers no matter where cursor is inserted onFocus */ if (el.value !== nextValue) { el.value = nextValue; } /** fix of cursor position with prefix when mouse click occur */ if ((el.selectionStart || el.selectionEnd) <= this._maskService.prefix.length) { el.selectionStart = this._maskService.prefix.length; return; } /** select only inserted text */ if (el.selectionEnd > this._getActualInputLength()) { el.selectionEnd = this._getActualInputLength(); } } // tslint:disable-next-line: cyclomatic-complexity onKeyDown(e) { var _a; if (!this._maskValue) { return; } this._code = e.code ? e.code : e.key; const el = e.target; this._inputValue = el.value; this._setMask(); if (e.keyCode === 38) { e.preventDefault(); } if (e.keyCode === 37 || e.keyCode === 8 || e.keyCode === 46) { if (e.keyCode === 8 && el.value.length === 0) { el.selectionStart = el.selectionEnd; } if (e.keyCode === 8 && el.selectionStart !== 0) { // If specialChars is false, (shouldn't ever happen) then set to the defaults this.specialCharacters = ((_a = this.specialCharacters) === null || _a === void 0 ? void 0 : _a.length) ? this.specialCharacters : this._config.specialCharacters; if (this.prefix.length > 1 && el.selectionStart <= this.prefix.length) { el.setSelectionRange(this.prefix.length, this.prefix.length); } else { if (this._inputValue.length !== el.selectionStart && el.selectionStart !== 1) { while (this.specialCharacters.includes(this._inputValue[el.selectionStart - 1].toString()) && ((this.prefix.length >= 1 && el.selectionStart > this.prefix.length) || this.prefix.length === 0)) { el.setSelectionRange(el.selectionStart - 1, el.selectionStart - 1); } } this.suffixCheckOnPressDelete(e.keyCode, el); } } this.suffixCheckOnPressDelete(e.keyCode, el); if (this._maskService.prefix.length && el.selectionStart <= this._maskService.prefix.length && el.selectionEnd <= this._maskService.prefix.length) { e.preventDefault(); } const cursorStart = el.selectionStart; // this.onFocus(e); if (e.keyCode === 8 && !el.readOnly && cursorStart === 0 && el.selectionEnd === el.value.length && el.value.length !== 0) { this._position = this._maskService.prefix ? this._maskService.prefix.length : 0; this._maskService.applyMask(this._maskService.prefix, this._maskService.maskExpression, this._position); } } if (!!this.suffix && this.suffix.length > 1 && this._inputValue.length - this.suffix.length < el.selectionStart) { el.setSelectionRange(this._inputValue.length - this.suffix.length, this._inputValue.length); } else if ((e.keyCode === 65 && e.ctrlKey === true) || // Ctrl+ A (e.keyCode === 65 && e.metaKey === true) // Cmd + A (Mac) ) { el.setSelectionRange(0, this._getActualInputLength()); e.preventDefault(); } this._maskService.selStart = el.selectionStart; this._maskService.selEnd = el.selectionEnd; } /** It writes the value in the input */ writeValue(inputValue) { return __awaiter(this, void 0, void 0, function* () { if (typeof inputValue === 'object' && inputValue !== null && 'value' in inputValue) { if ('disable' in inputValue) { this.setDisabledState(Boolean(inputValue.disable)); } inputValue = inputValue.value; } if (inputValue === undefined) { inputValue = ''; } if (typeof inputValue === 'number') { inputValue = String(inputValue); inputValue = this.decimalMarker !== '.' ? inputValue.replace('.', this.decimalMarker) : inputValue; this._maskService.isNumberValue = true; } this._inputValue = inputValue; this._setMask(); if ((inputValue && this._maskService.maskExpression) || (this._maskService.maskExpression && (this._maskService.prefix || this._maskService.showMaskTyped))) { // Let the service we know we are writing value so that triggering onChange function wont happen during applyMask this._maskService.writingValue = true; this._maskService.formElementProperty = [ 'value', this._maskService.applyMask(inputValue, this._maskService.maskExpression), ]; // Let the service know we've finished writing value this._maskService.writingValue = false; } else { this._maskService.formElementProperty = ['value', inputValue]; } this._inputValue = inputValue; }); } registerOnChange(fn) { this.onChange = fn; this._maskService.onChange = this.onChange; } registerOnTouched(fn) { this.onTouch = fn; } suffixCheckOnPressDelete(keyCode, el) { if (keyCode === 46 && this.suffix.length > 0) { if (this._inputValue.length - this.suffix.length <= el.selectionStart) { el.setSelectionRange(this._inputValue.length - this.suffix.length, this._inputValue.length); } } if (keyCode === 8) { if (this.suffix.length > 1 && this._inputValue.length - this.suffix.length < el.selectionStart) { el.setSelectionRange(this._inputValue.length - this.suffix.length, this._inputValue.length); } if (this.suffix.length === 1 && this._inputValue.length === el.selectionStart) { el.setSelectionRange(el.selectionStart - 1, el.selectionStart - 1); } } } /** It disables the input element */ setDisabledState(isDisabled) { this._maskService.formElementProperty = ['disabled', isDisabled]; } _repeatPatternSymbols(maskExp) { return ((maskExp.match(/{[0-9]+}/) && maskExp.split('').reduce((accum, currval, index) => { this._start = currval === '{' ? index : this._start; if (currval !== '}') { return this._maskService._findSpecialChar(currval) ? accum + currval : accum; } this._end = index; const repeatNumber = Number(maskExp.slice(this._start + 1, this._end)); const replaceWith = new Array(repeatNumber + 1).join(maskExp[this._start - 1]); return accum + replaceWith; }, '')) || maskExp); } // tslint:disable-next-line:no-any _applyMask() { this._maskService.maskExpression = this._repeatPatternSymbols(this._maskValue || ''); this._maskService.formElementProperty = [ 'value', this._maskService.applyMask(this._inputValue, this._maskService.maskExpression), ]; } _validateTime(value) { const rowMaskLen = this._maskValue.split('').filter((s) => s !== ':').length; if (value === null || value.length === 0) { return null; // Don't validate empty values to allow for optional form control } if ((+value[value.length - 1] === 0 && value.length < rowMaskLen) || value.length <= rowMaskLen - 2) { return this._createValidationError(value); } return null; } _getActualInputLength() { return (this._maskService.actualValue.length || this._maskService.actualValue.length + this._maskService.prefix.length); } _createValidationError(actualValue) { return { mask: { requiredMask: this._maskValue, actualValue, }, }; } _setMask() { if (this._maskExpressionArray.length > 0) { this._maskExpressionArray.some((mask) => { const test = this._maskService.removeMask(this._inputValue).length <= this._maskService.removeMask(mask).length; if (this._inputValue && test) { this._maskValue = mask; this.maskExpression = mask; this._maskService.maskExpression = mask; return test; } else { this._maskValue = this._maskExpressionArray[this._maskExpressionArray.length - 1]; this.maskExpression = this._maskExpressionArray[this._maskExpressionArray.length - 1]; this._maskService.maskExpression = this._maskExpressionArray[this._maskExpressionArray.length - 1]; } }); } } } MaskDirective.decorators = [ { type: Directive, args: [{ selector: 'input[mask], textarea[mask]', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MaskDirective), multi: true, }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => MaskDirective), multi: true, }, MaskService, ], },] } ]; MaskDirective.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] }, { type: MaskService }, { type: undefined, decorators: [{ type: Inject, args: [config,] }] } ]; MaskDirective.propDecorators = { maskExpression: [{ type: Input, args: ['mask',] }], specialCharacters: [{ type: Input }], patterns: [{ type: Input }], prefix: [{ type: Input }], suffix: [{ type: Input }], thousandSeparator: [{ type: Input }], decimalMarker: [{ type: Input }], dropSpecialCharacters: [{ type: Input }], hiddenInput: [{ type: Input }], showMaskTyped: [{ type: Input }], placeHolderCharacter: [{ type: Input }], shownMaskExpression: [{ type: Input }], showTemplate: [{ type: Input }], clearIfNotMatch: [{ type: Input }], validation: [{ type: Input }], separatorLimit: [{ type: Input }], allowNegativeNumbers: [{ type: Input }], leadZeroDateTime: [{ type: Input }], onPaste: [{ type: HostListener, args: ['paste',] }], onInput: [{ type: HostListener, args: ['input', ['$event'],] }], onBlur: [{ type: HostListener, args: ['blur',] }], onFocus: [{ type: HostListener, args: ['click', ['$event'],] }], onKeyDown: [{ type: HostListener, args: ['keydown', ['$event'],] }] }; class MaskPipe { constructor(_maskService) { this._maskService = _maskService; } transform(value, mask, thousandSeparator = null) { if (!value && typeof value !== 'number') { return ''; } if (thousandSeparator) { this._maskService.thousandSeparator = thousandSeparator; } if (typeof mask === 'string') { return this._maskService.applyMask(`${value}`, mask); } return this._maskService.applyMaskWithPattern(`${value}`, mask); } } MaskPipe.decorators = [ { type: Pipe, args: [{ name: 'mask', pure: true, },] } ]; MaskPipe.ctorParameters = () => [ { type: MaskApplierService } ]; class NgxMaskModule { static forRoot(configValue) { return { ngModule: NgxMaskModule, providers: [ { provide: NEW_CONFIG, useValue: configValue, }, { provide: INITIAL_CONFIG, useValue: initialConfig, }, { provide: config, useFactory: _configFactory, deps: [INITIAL_CONFIG, NEW_CONFIG], }, MaskApplierService, ], }; } static forChild() { return { ngModule: NgxMaskModule, }; } } NgxMaskModule.decorators = [ { type: NgModule, args: [{ exports: [MaskDirective, MaskPipe], declarations: [MaskDirective, MaskPipe], },] } ]; /** * @internal */ function _configFactory(initConfig, configValue) { return configValue instanceof Function ? Object.assign(Object.assign({}, initConfig), configValue()) : Object.assign(Object.assign({}, initConfig), configValue); } const commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; (function () { if (!commonjsGlobal.KeyboardEvent) { commonjsGlobal.KeyboardEvent = function (_eventType, _init) { }; } })(); /** * Generated bundle index. Do not edit. */ export { INITIAL_CONFIG, MaskApplierService, MaskDirective, MaskPipe, MaskService, NEW_CONFIG, NgxMaskModule, _configFactory, config, initialConfig, timeMasks, withoutValidation }; //# sourceMappingURL=ngx-mask.js.map