UNPKG

ngx-mask

Version:
1 lines 231 kB
{"version":3,"file":"ngx-mask.mjs","sources":["../../../projects/ngx-mask-lib/src/lib/ngx-mask.config.ts","../../../projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts","../../../projects/ngx-mask-lib/src/lib/ngx-mask.service.ts","../../../projects/ngx-mask-lib/src/lib/ngx-mask.providers.ts","../../../projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts","../../../projects/ngx-mask-lib/src/lib/ngx-mask.pipe.ts","../../../projects/ngx-mask-lib/src/ngx-mask.ts"],"sourcesContent":["import { EventEmitter, InjectionToken } from '@angular/core';\nimport { MaskExpression } from './ngx-mask-expression.enum';\n\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport interface InputTransformFn {\n (value: unknown): string | number;\n}\n\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport interface OutputTransformFn {\n (value: string | number | undefined | null): unknown;\n}\n\nexport interface IConfig {\n suffix: string;\n prefix: string;\n thousandSeparator: string;\n decimalMarker: '.' | ',' | ['.', ','];\n clearIfNotMatch: boolean;\n showTemplate: boolean;\n showMaskTyped: boolean;\n placeHolderCharacter: string;\n shownMaskExpression: string;\n specialCharacters: string[] | readonly string[];\n dropSpecialCharacters: boolean | string[] | readonly string[];\n hiddenInput: boolean | undefined;\n validation: boolean;\n separatorLimit: string;\n apm: boolean;\n allowNegativeNumbers: boolean;\n leadZeroDateTime: boolean;\n leadZero: boolean;\n triggerOnMaskChange: boolean;\n keepCharacterPositions: boolean;\n inputTransformFn: InputTransformFn;\n outputTransformFn: OutputTransformFn;\n maskFilled: EventEmitter<void>;\n patterns: {\n [character: string]: {\n pattern: RegExp;\n optional?: boolean;\n symbol?: string;\n };\n };\n}\n\nexport type optionsConfig = Partial<IConfig>;\nexport const NGX_MASK_CONFIG: InjectionToken<IConfig> = new InjectionToken('ngx-mask config');\nexport const NEW_CONFIG: InjectionToken<IConfig> = new InjectionToken('new ngx-mask config');\nexport const INITIAL_CONFIG: InjectionToken<IConfig> = new InjectionToken(\n 'initial ngx-mask config'\n);\n\nexport const initialConfig: IConfig = {\n suffix: '',\n prefix: '',\n thousandSeparator: ' ',\n decimalMarker: ['.', ','],\n clearIfNotMatch: false,\n showTemplate: false,\n showMaskTyped: false,\n placeHolderCharacter: '_',\n dropSpecialCharacters: true,\n hiddenInput: undefined,\n shownMaskExpression: '',\n separatorLimit: '',\n allowNegativeNumbers: false,\n validation: true,\n // eslint-disable-next-line @typescript-eslint/quotes\n specialCharacters: ['-', '/', '(', ')', '.', ':', ' ', '+', ',', '@', '[', ']', '\"', \"'\"],\n leadZeroDateTime: false,\n apm: false,\n leadZero: false,\n keepCharacterPositions: false,\n triggerOnMaskChange: false,\n inputTransformFn: (value: unknown) => value as string | number,\n outputTransformFn: (value: string | number | undefined | null) => value,\n maskFilled: new EventEmitter<void>(),\n patterns: {\n '0': {\n pattern: new RegExp('\\\\d'),\n },\n '9': {\n pattern: new RegExp('\\\\d'),\n optional: true,\n },\n X: {\n pattern: new RegExp('\\\\d'),\n symbol: '*',\n },\n A: {\n pattern: new RegExp('[a-zA-Z0-9]'),\n },\n S: {\n pattern: new RegExp('[a-zA-Z]'),\n },\n U: {\n pattern: new RegExp('[A-Z]'),\n },\n L: {\n pattern: new RegExp('[a-z]'),\n },\n d: {\n pattern: new RegExp('\\\\d'),\n },\n m: {\n pattern: new RegExp('\\\\d'),\n },\n M: {\n pattern: new RegExp('\\\\d'),\n },\n H: {\n pattern: new RegExp('\\\\d'),\n },\n h: {\n pattern: new RegExp('\\\\d'),\n },\n s: {\n pattern: new RegExp('\\\\d'),\n },\n },\n};\n\nexport const timeMasks: string[] = [\n MaskExpression.HOURS_MINUTES_SECONDS,\n MaskExpression.HOURS_MINUTES,\n MaskExpression.MINUTES_SECONDS,\n];\n\nexport const withoutValidation: string[] = [\n MaskExpression.PERCENT,\n MaskExpression.HOURS_HOUR,\n MaskExpression.SECONDS,\n MaskExpression.MINUTES,\n MaskExpression.SEPARATOR,\n MaskExpression.DAYS_MONTHS_YEARS,\n MaskExpression.DAYS_MONTHS,\n MaskExpression.DAYS,\n MaskExpression.MONTHS,\n];\n","import { inject, Injectable } from '@angular/core';\nimport { NGX_MASK_CONFIG, IConfig } from './ngx-mask.config';\nimport { MaskExpression } from './ngx-mask-expression.enum';\n\n@Injectable()\nexport class NgxMaskApplierService {\n protected _config = inject<IConfig>(NGX_MASK_CONFIG);\n\n public dropSpecialCharacters: IConfig['dropSpecialCharacters'] =\n this._config.dropSpecialCharacters;\n\n public hiddenInput: IConfig['hiddenInput'] = this._config.hiddenInput;\n\n public showTemplate!: IConfig['showTemplate'];\n\n public clearIfNotMatch: IConfig['clearIfNotMatch'] = this._config.clearIfNotMatch;\n\n public specialCharacters: IConfig['specialCharacters'] = this._config.specialCharacters;\n\n public patterns: IConfig['patterns'] = this._config.patterns;\n\n public prefix: IConfig['prefix'] = this._config.prefix;\n\n public suffix: IConfig['suffix'] = this._config.suffix;\n\n public thousandSeparator: IConfig['thousandSeparator'] = this._config.thousandSeparator;\n\n public decimalMarker: IConfig['decimalMarker'] = this._config.decimalMarker;\n\n public customPattern!: IConfig['patterns'];\n\n public showMaskTyped: IConfig['showMaskTyped'] = this._config.showMaskTyped;\n\n public placeHolderCharacter: IConfig['placeHolderCharacter'] =\n this._config.placeHolderCharacter;\n\n public validation: IConfig['validation'] = this._config.validation;\n\n public separatorLimit: IConfig['separatorLimit'] = this._config.separatorLimit;\n\n public allowNegativeNumbers: IConfig['allowNegativeNumbers'] =\n this._config.allowNegativeNumbers;\n\n public leadZeroDateTime: IConfig['leadZeroDateTime'] = this._config.leadZeroDateTime;\n\n public leadZero: IConfig['leadZero'] = this._config.leadZero;\n\n public apm: IConfig['apm'] = this._config.apm;\n\n public inputTransformFn: IConfig['inputTransformFn'] = this._config.inputTransformFn;\n\n public outputTransformFn: IConfig['outputTransformFn'] = this._config.outputTransformFn;\n\n public keepCharacterPositions: IConfig['keepCharacterPositions'] =\n this._config.keepCharacterPositions;\n\n private _shift: Set<number> = new Set();\n\n public plusOnePosition: boolean = false;\n\n public maskExpression = '';\n\n public actualValue = '';\n\n public showKeepCharacterExp = '';\n\n public shownMaskExpression = '';\n\n public deletedSpecialCharacter = false;\n\n public ipError?: boolean;\n\n public cpfCnpjError?: boolean;\n\n public applyMaskWithPattern(\n inputValue: string,\n maskAndPattern: [string, IConfig['patterns']]\n ): string {\n const [mask, customPattern] = maskAndPattern;\n this.customPattern = customPattern;\n return this.applyMask(inputValue, mask);\n }\n\n public applyMask(\n inputValue: string | object | boolean | null | undefined,\n maskExpression: string,\n position = 0,\n justPasted = false,\n backspaced = false,\n // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-explicit-any\n cb: (...args: any[]) => any = () => {}\n ): string {\n if (!maskExpression || typeof inputValue !== 'string') {\n return MaskExpression.EMPTY_STRING;\n }\n let cursor = 0;\n let result = '';\n let multi = false;\n let backspaceShift = false;\n let shift = 1;\n let stepBack = false;\n if (inputValue.slice(0, this.prefix.length) === this.prefix) {\n // eslint-disable-next-line no-param-reassign\n inputValue = inputValue.slice(this.prefix.length, inputValue.length);\n }\n if (!!this.suffix && inputValue?.length > 0) {\n // eslint-disable-next-line no-param-reassign\n inputValue = this.checkAndRemoveSuffix(inputValue);\n }\n if (inputValue === '(' && this.prefix) {\n // eslint-disable-next-line no-param-reassign\n inputValue = '';\n }\n const inputArray: string[] = inputValue.toString().split(MaskExpression.EMPTY_STRING);\n if (\n this.allowNegativeNumbers &&\n inputValue.slice(cursor, cursor + 1) === MaskExpression.MINUS\n ) {\n // eslint-disable-next-line no-param-reassign\n result += inputValue.slice(cursor, cursor + 1);\n }\n if (maskExpression === MaskExpression.IP) {\n const valuesIP = inputValue.split(MaskExpression.DOT);\n this.ipError = this._validIP(valuesIP);\n // eslint-disable-next-line no-param-reassign\n maskExpression = '099.099.099.099';\n }\n const arr: string[] = [];\n for (let i = 0; i < inputValue.length; i++) {\n if (inputValue[i]?.match('\\\\d')) {\n arr.push(inputValue[i] ?? MaskExpression.EMPTY_STRING);\n }\n }\n if (maskExpression === MaskExpression.CPF_CNPJ) {\n this.cpfCnpjError = arr.length !== 11 && arr.length !== 14;\n if (arr.length > 11) {\n // eslint-disable-next-line no-param-reassign\n maskExpression = '00.000.000/0000-00';\n } else {\n // eslint-disable-next-line no-param-reassign\n maskExpression = '000.000.000-00';\n }\n }\n if (maskExpression.startsWith(MaskExpression.PERCENT)) {\n if (\n inputValue.match('[a-z]|[A-Z]') ||\n // eslint-disable-next-line no-useless-escape\n (inputValue.match(/[-!$%^&*()_+|~=`{}\\[\\]:\";'<>?,\\/.]/) && !backspaced)\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue = this._stripToDecimal(inputValue);\n const precision: number = this.getPrecision(maskExpression);\n // eslint-disable-next-line no-param-reassign\n inputValue = this.checkInputPrecision(inputValue, precision, this.decimalMarker);\n }\n const decimalMarker =\n typeof this.decimalMarker === 'string' ? this.decimalMarker : MaskExpression.DOT;\n if (\n inputValue.indexOf(decimalMarker) > 0 &&\n !this.percentage(inputValue.substring(0, inputValue.indexOf(decimalMarker)))\n ) {\n let base: string = inputValue.substring(0, inputValue.indexOf(decimalMarker) - 1);\n if (\n this.allowNegativeNumbers &&\n inputValue.slice(cursor, cursor + 1) === MaskExpression.MINUS &&\n !backspaced\n ) {\n base = inputValue.substring(0, inputValue.indexOf(decimalMarker));\n }\n // eslint-disable-next-line no-param-reassign\n inputValue = `${base}${inputValue.substring(\n inputValue.indexOf(decimalMarker),\n inputValue.length\n )}`;\n }\n let value = '';\n this.allowNegativeNumbers &&\n inputValue.slice(cursor, cursor + 1) === MaskExpression.MINUS\n ? (value = `${MaskExpression.MINUS}${inputValue.slice(cursor + 1, cursor + inputValue.length)}`)\n : (value = inputValue);\n if (this.percentage(value)) {\n result = this._splitPercentZero(inputValue);\n } else {\n result = this._splitPercentZero(inputValue.substring(0, inputValue.length - 1));\n }\n } else if (maskExpression.startsWith(MaskExpression.SEPARATOR)) {\n if (\n inputValue.match('[wа-яА-Я]') ||\n inputValue.match('[ЁёА-я]') ||\n inputValue.match('[a-z]|[A-Z]') ||\n inputValue.match(/[-@#!$%\\\\^&*()_£¬'+|~=`{}\\]:\";<>.?/]/) ||\n inputValue.match('[^A-Za-z0-9,]')\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue = this._stripToDecimal(inputValue);\n }\n const precision: number = this.getPrecision(maskExpression);\n const decimalMarker = Array.isArray(this.decimalMarker)\n ? MaskExpression.DOT\n : this.decimalMarker;\n if (precision === 0) {\n // eslint-disable-next-line no-param-reassign\n inputValue = this.allowNegativeNumbers\n ? inputValue.length > 2 &&\n inputValue[0] === MaskExpression.MINUS &&\n inputValue[1] === MaskExpression.NUMBER_ZERO &&\n inputValue[2] !== this.thousandSeparator &&\n inputValue[2] !== MaskExpression.COMMA &&\n inputValue[2] !== MaskExpression.DOT\n ? '-' + inputValue.slice(2, inputValue.length)\n : inputValue[0] === MaskExpression.NUMBER_ZERO &&\n inputValue.length > 1 &&\n inputValue[1] !== this.thousandSeparator &&\n inputValue[1] !== MaskExpression.COMMA &&\n inputValue[1] !== MaskExpression.DOT\n ? inputValue.slice(1, inputValue.length)\n : inputValue\n : inputValue.length > 1 &&\n inputValue[0] === MaskExpression.NUMBER_ZERO &&\n inputValue[1] !== this.thousandSeparator &&\n inputValue[1] !== MaskExpression.COMMA &&\n inputValue[1] !== MaskExpression.DOT\n ? inputValue.slice(1, inputValue.length)\n : inputValue;\n } else {\n // eslint-disable-next-line no-param-reassign\n if (inputValue[0] === decimalMarker && inputValue.length > 1) {\n // eslint-disable-next-line no-param-reassign\n inputValue =\n MaskExpression.NUMBER_ZERO + inputValue.slice(0, inputValue.length + 1);\n this.plusOnePosition = true;\n }\n if (\n inputValue[0] === MaskExpression.NUMBER_ZERO &&\n inputValue[1] !== decimalMarker &&\n inputValue[1] !== this.thousandSeparator\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue =\n inputValue.length > 1\n ? inputValue.slice(0, 1) +\n decimalMarker +\n inputValue.slice(1, inputValue.length + 1)\n : inputValue;\n this.plusOnePosition = true;\n }\n if (\n this.allowNegativeNumbers &&\n inputValue[0] === MaskExpression.MINUS &&\n (inputValue[1] === decimalMarker ||\n inputValue[1] === MaskExpression.NUMBER_ZERO)\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue =\n inputValue[1] === decimalMarker && inputValue.length > 2\n ? inputValue.slice(0, 1) +\n MaskExpression.NUMBER_ZERO +\n inputValue.slice(1, inputValue.length)\n : inputValue[1] === MaskExpression.NUMBER_ZERO &&\n inputValue.length > 2 &&\n inputValue[2] !== decimalMarker\n ? inputValue.slice(0, 2) +\n decimalMarker +\n inputValue.slice(2, inputValue.length)\n : inputValue;\n this.plusOnePosition = true;\n }\n }\n\n if (backspaced) {\n if (\n inputValue[0] === MaskExpression.NUMBER_ZERO &&\n inputValue[1] === this.decimalMarker &&\n (inputValue[position] === MaskExpression.NUMBER_ZERO ||\n inputValue[position] === this.decimalMarker)\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue = inputValue.slice(2, inputValue.length);\n }\n if (\n inputValue[0] === MaskExpression.MINUS &&\n inputValue[1] === MaskExpression.NUMBER_ZERO &&\n inputValue[2] === this.decimalMarker &&\n (inputValue[position] === MaskExpression.NUMBER_ZERO ||\n inputValue[position] === this.decimalMarker)\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue = MaskExpression.MINUS + inputValue.slice(3, inputValue.length);\n }\n // eslint-disable-next-line no-param-reassign\n inputValue = this._compareOrIncludes(\n inputValue[inputValue.length - 1],\n this.decimalMarker,\n this.thousandSeparator\n )\n ? inputValue.slice(0, inputValue.length - 1)\n : inputValue;\n }\n // TODO: we had different rexexps here for the different cases... but tests dont seam to bother - check this\n // separator: no COMMA, dot-sep: no SPACE, COMMA OK, comma-sep: no SPACE, COMMA OK\n\n const thousandSeparatorCharEscaped: string = this._charToRegExpExpression(\n this.thousandSeparator\n );\n let invalidChars: string = '@#!$%^&*()_+|~=`{}\\\\[\\\\]:\\\\s,\\\\.\";<>?\\\\/'.replace(\n thousandSeparatorCharEscaped,\n ''\n );\n //.replace(decimalMarkerEscaped, '');\n if (Array.isArray(this.decimalMarker)) {\n for (const marker of this.decimalMarker) {\n invalidChars = invalidChars.replace(\n this._charToRegExpExpression(marker),\n MaskExpression.EMPTY_STRING\n );\n }\n } else {\n invalidChars = invalidChars.replace(\n this._charToRegExpExpression(this.decimalMarker),\n ''\n );\n }\n\n const invalidCharRegexp = new RegExp('[' + invalidChars + ']');\n if (inputValue.match(invalidCharRegexp)) {\n // eslint-disable-next-line no-param-reassign\n inputValue = inputValue.substring(0, inputValue.length - 1);\n }\n\n // eslint-disable-next-line no-param-reassign\n inputValue = this.checkInputPrecision(inputValue, precision, this.decimalMarker);\n const strForSep: string = inputValue.replace(\n new RegExp(thousandSeparatorCharEscaped, 'g'),\n ''\n );\n\n result = this._formatWithSeparators(\n strForSep,\n this.thousandSeparator,\n this.decimalMarker,\n precision\n );\n\n const commaShift: number =\n result.indexOf(MaskExpression.COMMA) - inputValue.indexOf(MaskExpression.COMMA);\n const shiftStep: number = result.length - inputValue.length;\n\n if (result[position - 1] === this.thousandSeparator && this.prefix && backspaced) {\n // eslint-disable-next-line no-param-reassign\n position = position - 1;\n } else if (shiftStep > 0 && result[position] !== this.thousandSeparator) {\n backspaceShift = true;\n let _shift = 0;\n do {\n this._shift.add(position + _shift);\n _shift++;\n } while (_shift < shiftStep);\n } else if (\n result[position - 1] === this.decimalMarker ||\n shiftStep === -4 ||\n shiftStep === -3 ||\n result[position] === MaskExpression.COMMA\n ) {\n this._shift.clear();\n this._shift.add(position - 1);\n } else if (\n (commaShift !== 0 &&\n position > 0 &&\n !(result.indexOf(MaskExpression.COMMA) >= position && position > 3)) ||\n (!(result.indexOf(MaskExpression.DOT) >= position && position > 3) &&\n shiftStep <= 0)\n ) {\n this._shift.clear();\n backspaceShift = true;\n shift = shiftStep;\n // eslint-disable-next-line no-param-reassign\n position += shiftStep;\n this._shift.add(position);\n } else {\n this._shift.clear();\n }\n } else {\n for (\n // eslint-disable-next-line\n let i: number = 0, inputSymbol: string = inputArray[0]!;\n i < inputArray.length;\n i++, inputSymbol = inputArray[i] ?? MaskExpression.EMPTY_STRING\n ) {\n if (cursor === maskExpression.length) {\n break;\n }\n\n const symbolStarInPattern: boolean = MaskExpression.SYMBOL_STAR in this.patterns;\n if (\n this._checkSymbolMask(\n inputSymbol,\n maskExpression[cursor] ?? MaskExpression.EMPTY_STRING\n ) &&\n maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION\n ) {\n result += inputSymbol;\n cursor += 2;\n } else if (\n maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR &&\n multi &&\n this._checkSymbolMask(\n inputSymbol,\n maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING\n )\n ) {\n result += inputSymbol;\n cursor += 3;\n multi = false;\n } else if (\n this._checkSymbolMask(\n inputSymbol,\n maskExpression[cursor] ?? MaskExpression.EMPTY_STRING\n ) &&\n maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR &&\n !symbolStarInPattern\n ) {\n result += inputSymbol;\n multi = true;\n } else if (\n maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION &&\n this._checkSymbolMask(\n inputSymbol,\n maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING\n )\n ) {\n result += inputSymbol;\n cursor += 3;\n } else if (\n this._checkSymbolMask(\n inputSymbol,\n maskExpression[cursor] ?? MaskExpression.EMPTY_STRING\n )\n ) {\n if (maskExpression[cursor] === MaskExpression.HOURS) {\n if (this.apm ? Number(inputSymbol) > 9 : Number(inputSymbol) > 2) {\n // eslint-disable-next-line no-param-reassign\n position = !this.leadZeroDateTime ? position + 1 : position;\n cursor += 1;\n this._shiftStep(maskExpression, cursor, inputArray.length);\n i--;\n if (this.leadZeroDateTime) {\n result += '0';\n }\n continue;\n }\n }\n if (maskExpression[cursor] === MaskExpression.HOUR) {\n if (\n this.apm\n ? (result.length === 1 && Number(result) > 1) ||\n (result === '1' && Number(inputSymbol) > 2) ||\n (inputValue.slice(cursor - 1, cursor).length === 1 &&\n Number(inputValue.slice(cursor - 1, cursor)) > 2) ||\n (inputValue.slice(cursor - 1, cursor) === '1' &&\n Number(inputSymbol) > 2)\n : (result === '2' && Number(inputSymbol) > 3) ||\n ((result.slice(cursor - 2, cursor) === '2' ||\n result.slice(cursor - 3, cursor) === '2' ||\n result.slice(cursor - 4, cursor) === '2' ||\n result.slice(cursor - 1, cursor) === '2') &&\n Number(inputSymbol) > 3 &&\n cursor > 10)\n ) {\n // eslint-disable-next-line no-param-reassign\n position = position + 1;\n cursor += 1;\n i--;\n continue;\n }\n }\n if (\n maskExpression[cursor] === MaskExpression.MINUTE ||\n maskExpression[cursor] === MaskExpression.SECOND\n ) {\n if (Number(inputSymbol) > 5) {\n // eslint-disable-next-line no-param-reassign\n position = !this.leadZeroDateTime ? position + 1 : position;\n cursor += 1;\n this._shiftStep(maskExpression, cursor, inputArray.length);\n i--;\n if (this.leadZeroDateTime) {\n result += '0';\n }\n continue;\n }\n }\n const daysCount = 31;\n const inputValueCursor = inputValue[cursor] as string;\n const inputValueCursorPlusOne = inputValue[cursor + 1] as string;\n const inputValueCursorPlusTwo = inputValue[cursor + 2] as string;\n const inputValueCursorMinusOne = inputValue[cursor - 1] as string;\n const inputValueCursorMinusTwo = inputValue[cursor - 2] as string;\n const inputValueCursorMinusThree = inputValue[cursor - 3] as string;\n const inputValueSliceMinusThreeMinusOne = inputValue.slice(\n cursor - 3,\n cursor - 1\n );\n const inputValueSliceMinusOnePlusOne = inputValue.slice(cursor - 1, cursor + 1);\n const inputValueSliceCursorPlusTwo = inputValue.slice(cursor, cursor + 2);\n const inputValueSliceMinusTwoCursor = inputValue.slice(cursor - 2, cursor);\n if (maskExpression[cursor] === MaskExpression.DAY) {\n const maskStartWithMonth =\n maskExpression.slice(0, 2) === MaskExpression.MONTHS;\n const startWithMonthInput: boolean =\n maskExpression.slice(0, 2) === MaskExpression.MONTHS &&\n this.specialCharacters.includes(inputValueCursorMinusTwo);\n if (\n (Number(inputSymbol) > 3 && this.leadZeroDateTime) ||\n (!maskStartWithMonth &&\n (Number(inputValueSliceCursorPlusTwo) > daysCount ||\n Number(inputValueSliceMinusOnePlusOne) > daysCount ||\n (this.specialCharacters.includes(inputValueCursorPlusOne) &&\n !backspaced))) ||\n (startWithMonthInput\n ? Number(inputValueSliceMinusOnePlusOne) > daysCount ||\n (!this.specialCharacters.includes(inputValueCursor) &&\n this.specialCharacters.includes(inputValueCursorPlusTwo)) ||\n this.specialCharacters.includes(inputValueCursor)\n : Number(inputValueSliceCursorPlusTwo) > daysCount ||\n (this.specialCharacters.includes(inputValueCursorPlusOne) &&\n !backspaced))\n ) {\n // eslint-disable-next-line no-param-reassign\n position = !this.leadZeroDateTime ? position + 1 : position;\n cursor += 1;\n this._shiftStep(maskExpression, cursor, inputArray.length);\n i--;\n if (this.leadZeroDateTime) {\n result += '0';\n }\n continue;\n }\n }\n if (maskExpression[cursor] === MaskExpression.MONTH) {\n const monthsCount = 12;\n // mask without day\n const withoutDays: boolean =\n cursor === 0 &&\n (Number(inputSymbol) > 2 ||\n Number(inputValueSliceCursorPlusTwo) > monthsCount ||\n (this.specialCharacters.includes(inputValueCursorPlusOne) &&\n !backspaced));\n // day<10 && month<12 for input\n const specialChart = maskExpression.slice(cursor + 2, cursor + 3);\n const day1monthInput: boolean =\n inputValueSliceMinusThreeMinusOne.includes(specialChart) &&\n maskExpression.includes('d0') &&\n ((this.specialCharacters.includes(inputValueCursorMinusTwo) &&\n Number(inputValueSliceMinusOnePlusOne) > monthsCount &&\n !this.specialCharacters.includes(inputValueCursor)) ||\n this.specialCharacters.includes(inputValueCursor) ||\n (this.specialCharacters.includes(inputValueCursorMinusThree) &&\n Number(inputValueSliceMinusTwoCursor) > monthsCount &&\n !this.specialCharacters.includes(inputValueCursorMinusOne)) ||\n this.specialCharacters.includes(inputValueCursorMinusOne));\n // month<12 && day<10 for input\n const day2monthInput: boolean =\n Number(inputValueSliceMinusThreeMinusOne) <= daysCount &&\n !this.specialCharacters.includes(\n inputValueSliceMinusThreeMinusOne as string\n ) &&\n this.specialCharacters.includes(inputValueCursorMinusOne) &&\n (Number(inputValueSliceCursorPlusTwo) > monthsCount ||\n (this.specialCharacters.includes(inputValueCursorPlusOne) &&\n !backspaced));\n // cursor === 5 && without days\n const day2monthInputDot: boolean =\n (Number(inputValueSliceCursorPlusTwo) > monthsCount && cursor === 5) ||\n (this.specialCharacters.includes(inputValueCursorPlusOne) &&\n cursor === 5);\n // // day<10 && month<12 for paste whole data\n const day1monthPaste: boolean =\n Number(inputValueSliceMinusThreeMinusOne) > daysCount &&\n !this.specialCharacters.includes(\n inputValueSliceMinusThreeMinusOne as string\n ) &&\n !this.specialCharacters.includes(\n inputValueSliceMinusTwoCursor as string\n ) &&\n Number(inputValueSliceMinusTwoCursor) > monthsCount &&\n maskExpression.includes('d0');\n // 10<day<31 && month<12 for paste whole data\n const day2monthPaste: boolean =\n Number(inputValueSliceMinusThreeMinusOne) <= daysCount &&\n !this.specialCharacters.includes(\n inputValueSliceMinusThreeMinusOne as string\n ) &&\n !this.specialCharacters.includes(inputValueCursorMinusOne) &&\n Number(inputValueSliceMinusOnePlusOne) > monthsCount;\n if (\n (Number(inputSymbol) > 1 && this.leadZeroDateTime) ||\n withoutDays ||\n day1monthInput ||\n day2monthPaste ||\n day1monthPaste ||\n day2monthInput ||\n (day2monthInputDot && !this.leadZeroDateTime)\n ) {\n // eslint-disable-next-line no-param-reassign\n position = !this.leadZeroDateTime ? position + 1 : position;\n cursor += 1;\n this._shiftStep(maskExpression, cursor, inputArray.length);\n i--;\n if (this.leadZeroDateTime) {\n result += '0';\n }\n continue;\n }\n }\n result += inputSymbol;\n cursor++;\n } else if (\n (inputSymbol === MaskExpression.WHITE_SPACE &&\n maskExpression[cursor] === MaskExpression.WHITE_SPACE) ||\n (inputSymbol === MaskExpression.SLASH &&\n maskExpression[cursor] === MaskExpression.SLASH)\n ) {\n result += inputSymbol;\n cursor++;\n } else if (\n this.specialCharacters.indexOf(\n maskExpression[cursor] ?? MaskExpression.EMPTY_STRING\n ) !== -1\n ) {\n result += maskExpression[cursor];\n cursor++;\n this._shiftStep(maskExpression, cursor, inputArray.length);\n i--;\n } else if (\n maskExpression[cursor] === MaskExpression.NUMBER_NINE &&\n this.showMaskTyped\n ) {\n this._shiftStep(maskExpression, cursor, inputArray.length);\n } else if (\n this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING] &&\n this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING]?.optional\n ) {\n if (\n !!inputArray[cursor] &&\n maskExpression !== '099.099.099.099' &&\n maskExpression !== '000.000.000-00' &&\n maskExpression !== '00.000.000/0000-00' &&\n !maskExpression.match(/^9+\\.0+$/) &&\n !this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING]\n ?.optional\n ) {\n result += inputArray[cursor];\n }\n if (\n maskExpression.includes(\n MaskExpression.NUMBER_NINE + MaskExpression.SYMBOL_STAR\n ) &&\n maskExpression.includes(\n MaskExpression.NUMBER_ZERO + MaskExpression.SYMBOL_STAR\n )\n ) {\n cursor++;\n }\n cursor++;\n i--;\n } else if (\n this.maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR &&\n this._findSpecialChar(\n this.maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING\n ) &&\n this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] &&\n multi\n ) {\n cursor += 3;\n result += inputSymbol;\n } else if (\n this.maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION &&\n this._findSpecialChar(\n this.maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING\n ) &&\n this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] &&\n multi\n ) {\n cursor += 3;\n result += inputSymbol;\n } else if (\n this.showMaskTyped &&\n this.specialCharacters.indexOf(inputSymbol) < 0 &&\n inputSymbol !== this.placeHolderCharacter &&\n this.placeHolderCharacter.length === 1\n ) {\n stepBack = true;\n }\n }\n }\n if (\n result.length + 1 === maskExpression.length &&\n this.specialCharacters.indexOf(\n maskExpression[maskExpression.length - 1] ?? MaskExpression.EMPTY_STRING\n ) !== -1\n ) {\n result += maskExpression[maskExpression.length - 1];\n }\n let newPosition: number = position + 1;\n\n while (this._shift.has(newPosition)) {\n shift++;\n newPosition++;\n }\n\n let actualShift: number =\n justPasted && !maskExpression.startsWith(MaskExpression.SEPARATOR)\n ? cursor\n : this._shift.has(position)\n ? shift\n : 0;\n if (stepBack) {\n actualShift--;\n }\n\n cb(actualShift, backspaceShift);\n if (shift < 0) {\n this._shift.clear();\n }\n let onlySpecial = false;\n if (backspaced) {\n onlySpecial = inputArray.every((char) => this.specialCharacters.includes(char));\n }\n\n let res = `${this.prefix}${onlySpecial ? MaskExpression.EMPTY_STRING : result}${\n this.showMaskTyped ? '' : this.suffix\n }`;\n\n if (result.length === 0) {\n res = !this.dropSpecialCharacters ? `${this.prefix}${result}` : `${result}`;\n }\n if (result.includes(MaskExpression.MINUS) && this.prefix && this.allowNegativeNumbers) {\n if (backspaced && result === MaskExpression.MINUS) {\n return '';\n }\n res = `${MaskExpression.MINUS}${this.prefix}${result\n .split(MaskExpression.MINUS)\n .join(MaskExpression.EMPTY_STRING)}${this.suffix}`;\n }\n return res;\n }\n\n public _findDropSpecialChar(inputSymbol: string): undefined | string {\n if (Array.isArray(this.dropSpecialCharacters)) {\n return this.dropSpecialCharacters.find((val: string) => val === inputSymbol);\n }\n return this._findSpecialChar(inputSymbol);\n }\n\n public _findSpecialChar(inputSymbol: string): undefined | string {\n return this.specialCharacters.find((val: string) => val === inputSymbol);\n }\n\n public _checkSymbolMask(inputSymbol: string, maskSymbol: string): boolean {\n this.patterns = this.customPattern ? this.customPattern : this.patterns;\n return (\n (this.patterns[maskSymbol]?.pattern &&\n this.patterns[maskSymbol]?.pattern.test(inputSymbol)) ??\n false\n );\n }\n\n private _formatWithSeparators = (\n str: string,\n thousandSeparatorChar: string,\n decimalChars: string | string[],\n precision: number\n ) => {\n let x: string[] = [];\n let decimalChar = '';\n if (Array.isArray(decimalChars)) {\n const regExp = new RegExp(\n decimalChars.map((v) => ('[\\\\^$.|?*+()'.indexOf(v) >= 0 ? `\\\\${v}` : v)).join('|')\n );\n x = str.split(regExp);\n decimalChar = str.match(regExp)?.[0] ?? MaskExpression.EMPTY_STRING;\n } else {\n x = str.split(decimalChars);\n decimalChar = decimalChars;\n }\n const decimals: string =\n x.length > 1 ? `${decimalChar}${x[1]}` : MaskExpression.EMPTY_STRING;\n let res: string = x[0] ?? MaskExpression.EMPTY_STRING;\n const separatorLimit: string = this.separatorLimit.replace(\n /\\s/g,\n MaskExpression.EMPTY_STRING\n );\n if (separatorLimit && +separatorLimit) {\n if (res[0] === MaskExpression.MINUS) {\n res = `-${res.slice(1, res.length).slice(0, separatorLimit.length)}`;\n } else {\n res = res.slice(0, separatorLimit.length);\n }\n }\n const rgx = /(\\d+)(\\d{3})/;\n\n while (thousandSeparatorChar && rgx.test(res)) {\n res = res.replace(rgx, '$1' + thousandSeparatorChar + '$2');\n }\n\n if (precision === undefined) {\n return res + decimals;\n } else if (precision === 0) {\n return res;\n }\n return res + decimals.substring(0, precision + 1);\n };\n\n private percentage = (str: string): boolean => {\n const sanitizedStr = str.replace(',', '.');\n const value = Number(\n this.allowNegativeNumbers && str.includes(MaskExpression.MINUS)\n ? sanitizedStr.slice(1, str.length)\n : sanitizedStr\n );\n\n return !isNaN(value) && value >= 0 && value <= 100;\n };\n\n private getPrecision = (maskExpression: string): number => {\n const x: string[] = maskExpression.split(MaskExpression.DOT);\n if (x.length > 1) {\n return Number(x[x.length - 1]);\n }\n\n return Infinity;\n };\n\n private checkAndRemoveSuffix = (inputValue: string): string => {\n for (let i = this.suffix?.length - 1; i >= 0; i--) {\n const substr = this.suffix.substring(i, this.suffix?.length);\n if (\n inputValue.includes(substr) &&\n i !== this.suffix?.length - 1 &&\n (i - 1 < 0 ||\n !inputValue.includes(this.suffix.substring(i - 1, this.suffix?.length)))\n ) {\n return inputValue.replace(substr, MaskExpression.EMPTY_STRING);\n }\n }\n return inputValue;\n };\n\n private checkInputPrecision = (\n inputValue: string,\n precision: number,\n decimalMarker: IConfig['decimalMarker']\n ): string => {\n if (precision < Infinity) {\n // TODO need think about decimalMarker\n if (Array.isArray(decimalMarker)) {\n const marker = decimalMarker.find((dm) => dm !== this.thousandSeparator);\n // eslint-disable-next-line no-param-reassign\n decimalMarker = marker ? marker : decimalMarker[0];\n }\n const precisionRegEx = new RegExp(\n this._charToRegExpExpression(decimalMarker) + `\\\\d{${precision}}.*$`\n );\n const precisionMatch: RegExpMatchArray | null = inputValue.match(precisionRegEx);\n const precisionMatchLength: number = (precisionMatch && precisionMatch[0]?.length) ?? 0;\n if (precisionMatchLength - 1 > precision) {\n const diff = precisionMatchLength - 1 - precision;\n // eslint-disable-next-line no-param-reassign\n inputValue = inputValue.substring(0, inputValue.length - diff);\n }\n if (\n precision === 0 &&\n this._compareOrIncludes(\n inputValue[inputValue.length - 1],\n decimalMarker,\n this.thousandSeparator\n )\n ) {\n // eslint-disable-next-line no-param-reassign\n inputValue = inputValue.substring(0, inputValue.length - 1);\n }\n }\n return inputValue;\n };\n\n private _stripToDecimal(str: string): string {\n return str\n .split(MaskExpression.EMPTY_STRING)\n .filter((i: string, idx: number) => {\n const isDecimalMarker =\n typeof this.decimalMarker === 'string'\n ? i === this.decimalMarker\n : // TODO (inepipenko) use utility type\n this.decimalMarker.includes(\n i as MaskExpression.COMMA | MaskExpression.DOT\n );\n return (\n i.match('^-?\\\\d') ||\n i === this.thousandSeparator ||\n isDecimalMarker ||\n (i === MaskExpression.MINUS && idx === 0 && this.allowNegativeNumbers)\n );\n })\n .join(MaskExpression.EMPTY_STRING);\n }\n\n private _charToRegExpExpression(char: string): string {\n // if (Array.isArray(char)) {\n // \treturn char.map((v) => ('[\\\\^$.|?*+()'.indexOf(v) >= 0 ? `\\\\${v}` : v)).join('|');\n // }\n if (char) {\n const charsToEscape = '[\\\\^$.|?*+()';\n return char === ' ' ? '\\\\s' : charsToEscape.indexOf(char) >= 0 ? `\\\\${char}` : char;\n }\n return char;\n }\n\n private _shiftStep(maskExpression: string, cursor: number, inputLength: number) {\n const shiftStep: number = /[*?]/g.test(maskExpression.slice(0, cursor))\n ? inputLength\n : cursor;\n this._shift.add(shiftStep + this.prefix.length || 0);\n }\n\n protected _compareOrIncludes<T>(value: T, comparedValue: T | T[], excludedValue: T): boolean {\n return Array.isArray(comparedValue)\n ? comparedValue.filter((v) => v !== excludedValue).includes(value)\n : value === comparedValue;\n }\n\n private _validIP(valuesIP: string[]): boolean {\n return !(\n valuesIP.length === 4 &&\n !valuesIP.some((value: string, index: number) => {\n if (valuesIP.length !== index + 1) {\n return value === MaskExpression.EMPTY_STRING || Number(value) > 255;\n }\n return value === MaskExpression.EMPTY_STRING || Number(value.substring(0, 3)) > 255;\n })\n );\n }\n\n private _splitPercentZero(value: string): string {\n if (value === MaskExpression.MINUS && this.allowNegativeNumbers) {\n return value;\n }\n const decimalIndex =\n typeof this.decimalMarker === 'string'\n ? value.indexOf(this.decimalMarker)\n : value.indexOf(MaskExpression.DOT);\n const emptyOrMinus =\n this.allowNegativeNumbers && value.includes(MaskExpression.MINUS) ? '-' : '';\n if (decimalIndex === -1) {\n const parsedValue = parseInt(emptyOrMinus ? value.slice(1, value.length) : value, 10);\n return isNaN(parsedValue)\n ? MaskExpression.EMPTY_STRING\n : `${emptyOrMinus}${parsedValue}`;\n } else {\n const integerPart = parseInt(value.replace('-', '').substring(0, decimalIndex), 10);\n const decimalPart = value.substring(decimalIndex + 1);\n const integerString = isNaN(integerPart) ? '' : integerPart.toStrin