ngx-mask
Version:
Input masking for modern Angular — Reactive, template-driven and Signal Forms, zoneless and SSR ready. Dates, numbers, separators, custom patterns.
2,913 lines • 141 kB
JavaScript
import * as i0 from "@angular/core";
import { ChangeDetectorRef, Directive, ElementRef, EnvironmentInjector, EventEmitter, Injectable, InjectionToken, Injector, NgZone, Pipe, Renderer2, afterNextRender, booleanAttribute, effect, inject, input, makeEnvironmentProviders, model, output, signal, untracked } from "@angular/core";
import { DOCUMENT } from "@angular/common";
import { NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl } from "@angular/forms";
var MaskExpression;
(function(MaskExpression) {
MaskExpression["SEPARATOR"] = "separator";
MaskExpression["PERCENT"] = "percent";
MaskExpression["IP"] = "IP";
MaskExpression["CPF_CNPJ"] = "CPF_CNPJ";
MaskExpression["CPF_CNPJ_ALPHA"] = "CPF_CNPJ_ALPHA";
MaskExpression["MONTH"] = "M";
MaskExpression["MONTHS"] = "M0";
MaskExpression["MINUTE"] = "m";
MaskExpression["HOUR"] = "h";
MaskExpression["HOURS"] = "H";
MaskExpression["MINUTES"] = "m0";
MaskExpression["HOURS_HOUR"] = "Hh";
MaskExpression["SECONDS"] = "s0";
MaskExpression["HOURS_MINUTES_SECONDS"] = "Hh:m0:s0";
MaskExpression["EMAIL_MASK"] = "A*@A*.A*";
MaskExpression["HOURS_MINUTES"] = "Hh:m0";
MaskExpression["MINUTES_SECONDS"] = "m0:s0";
MaskExpression["DAYS_MONTHS_YEARS"] = "d0/M0/0000";
MaskExpression["DAYS_MONTHS"] = "d0/M0";
MaskExpression["DAYS"] = "d0";
MaskExpression["DAY"] = "d";
MaskExpression["SECOND"] = "s";
MaskExpression["LETTER_S"] = "S";
MaskExpression["DOT"] = ".";
MaskExpression["NUMPAD_DECIMAL"] = "Decimal";
MaskExpression["NUMPAD_DECIMAL_CODE"] = "NumpadDecimal";
MaskExpression["COMMA"] = ",";
MaskExpression["CURLY_BRACKETS_LEFT"] = "{";
MaskExpression["CURLY_BRACKETS_RIGHT"] = "}";
MaskExpression["MINUS"] = "-";
MaskExpression["OR"] = "||";
MaskExpression["HASH"] = "#";
MaskExpression["EMPTY_STRING"] = "";
MaskExpression["SYMBOL_STAR"] = "*";
MaskExpression["SYMBOL_QUESTION"] = "?";
MaskExpression["SLASH"] = "/";
MaskExpression["WHITE_SPACE"] = " ";
MaskExpression["NUMBER_ZERO"] = "0";
MaskExpression["NUMBER_NINE"] = "9";
MaskExpression["BACKSPACE"] = "Backspace";
MaskExpression["DELETE"] = "Delete";
MaskExpression["ARROW_LEFT"] = "ArrowLeft";
MaskExpression["ARROW_UP"] = "ArrowUp";
MaskExpression["DOUBLE_ZERO"] = "00";
})(MaskExpression || (MaskExpression = {}));
const NGX_MASK_CONFIG = new InjectionToken("ngx-mask config");
const NEW_CONFIG = new InjectionToken("new ngx-mask config");
const INITIAL_CONFIG = new InjectionToken("initial ngx-mask config");
const initialConfig = {
suffix: "",
prefix: "",
thousandSeparator: " ",
decimalMarker: [".", ","],
clearIfNotMatch: false,
showMaskTyped: false,
instantPrefix: false,
placeHolderCharacter: "_",
dropSpecialCharacters: true,
hiddenInput: false,
shownMaskExpression: "",
separatorLimit: "",
allowNegativeNumbers: false,
validation: true,
specialCharacters: [
"-",
"/",
"(",
")",
".",
":",
" ",
"+",
",",
"@",
"[",
"]",
"\"",
"'"
],
leadZeroDateTime: false,
apm: false,
leadZero: false,
typeFromDecimals: false,
keepCharacterPositions: false,
triggerOnMaskChange: false,
inputTransformFn: (value) => value,
outputTransformFn: (value) => value,
maskFilled: new EventEmitter(),
maskAliases: {},
defaultValueOnBlur: null,
patterns: {
"0": { pattern: /* @__PURE__ */ new RegExp("\\d") },
"9": {
pattern: /* @__PURE__ */ new RegExp("\\d"),
optional: true
},
X: {
pattern: /* @__PURE__ */ new RegExp("\\d"),
symbol: "*"
},
A: { pattern: /* @__PURE__ */ new RegExp("[a-zA-Z0-9]") },
S: { pattern: /* @__PURE__ */ new RegExp("[a-zA-Z]") },
U: { pattern: /* @__PURE__ */ new RegExp("[A-Z]") },
L: { pattern: /* @__PURE__ */ new RegExp("[a-z]") },
d: {
pattern: /* @__PURE__ */ new RegExp("\\d"),
symbol: "*"
},
m: { pattern: /* @__PURE__ */ new RegExp("\\d") },
M: {
pattern: /* @__PURE__ */ new RegExp("\\d"),
symbol: "*"
},
H: { pattern: /* @__PURE__ */ new RegExp("\\d") },
h: { pattern: /* @__PURE__ */ new RegExp("\\d") },
s: { pattern: /* @__PURE__ */ new RegExp("\\d") }
}
};
const RESERVED_MASK_TOKENS = /* @__PURE__ */ new Set([
MaskExpression.IP,
MaskExpression.CPF_CNPJ,
MaskExpression.CPF_CNPJ_ALPHA,
MaskExpression.EMAIL_MASK,
MaskExpression.SEPARATOR,
MaskExpression.PERCENT
]);
const warnedShadowedAliases = /* @__PURE__ */ new Set();
function resolveMaskAlias(maskExpression, maskAliases) {
const expression = maskExpression ?? MaskExpression.EMPTY_STRING;
if (!expression || !maskAliases) return expression;
const aliased = maskAliases[expression];
if (typeof aliased !== "string") return expression;
if (RESERVED_MASK_TOKENS.has(expression)) {
if (!warnedShadowedAliases.has(expression)) {
warnedShadowedAliases.add(expression);
console.warn(`ngx-mask: mask alias "${expression}" shadows a built-in mask token and is ignored. Rename the alias.`);
}
return expression;
}
return aliased;
}
const timeMasks = [
MaskExpression.HOURS_MINUTES_SECONDS,
MaskExpression.HOURS_MINUTES,
MaskExpression.MINUTES_SECONDS
];
const withoutValidation = [
MaskExpression.PERCENT,
MaskExpression.HOURS_HOUR,
MaskExpression.SECONDS,
MaskExpression.MINUTES,
MaskExpression.SEPARATOR,
MaskExpression.DAYS_MONTHS_YEARS,
MaskExpression.DAYS_MONTHS,
MaskExpression.DAYS,
MaskExpression.MONTHS
];
function escapeRegExp(value) {
return (value ?? "").replace(/[.*+?^${}()|[\]\\/-]/g, "\\$&");
}
function nextFillableIndex(maskExpression, specialCharacters, index) {
let maskIdx = index;
while (maskIdx < maskExpression.length && specialCharacters.includes(maskExpression[maskIdx] ?? MaskExpression.EMPTY_STRING)) maskIdx += 1;
return maskIdx;
}
function digitsOf(value) {
return value.match(/\d/g) ?? [];
}
function ignoreDigitSeparator(separator) {
return separator && digitsOf(separator).length ? "" : separator;
}
function activeDecimalMarker(value, decimalMarker, thousandSeparator) {
if (!Array.isArray(decimalMarker)) return decimalMarker;
const candidates = decimalMarker.filter((dm) => dm !== thousandSeparator);
return candidates.find((dm) => value.includes(dm)) ?? candidates[0] ?? decimalMarker[0];
}
function usesCommaDecimal(value, decimalMarker, thousandSeparator) {
return activeDecimalMarker(value, decimalMarker, thousandSeparator) === MaskExpression.COMMA;
}
function displayToPlainDecimal(value, decimalMarker, thousandSeparator) {
const marker = activeDecimalMarker(value, decimalMarker, thousandSeparator);
if (!thousandSeparator || marker === thousandSeparator || !value.includes(thousandSeparator) || !value.includes(marker)) return value;
return value.split(thousandSeparator).join(MaskExpression.EMPTY_STRING).replace(marker, MaskExpression.DOT);
}
function containsStarSymbol(value, maskExpression, thousandSeparator) {
return value.includes(MaskExpression.SYMBOL_STAR) && !(maskExpression.startsWith(MaskExpression.SEPARATOR) && thousandSeparator?.includes(MaskExpression.SYMBOL_STAR));
}
function retargetSeparatorValue(value, previous, current) {
const prefix = previous.prefix && value.startsWith(previous.prefix) ? previous.prefix : "";
const suffix = previous.suffix && value.length >= prefix.length + previous.suffix.length ? value.endsWith(previous.suffix) ? previous.suffix : "" : "";
const number = value.slice(prefix.length, value.length - suffix.length);
const previousMarker = activeDecimalMarker(number, previous.decimalMarker, previous.thousandSeparator ?? MaskExpression.EMPTY_STRING);
const ungrouped = previous.thousandSeparator ? number.split(previous.thousandSeparator).join(MaskExpression.EMPTY_STRING) : number;
const retargeted = ungrouped.replace(previousMarker, activeDecimalMarker(ungrouped, current.decimalMarker, current.thousandSeparator));
return `${prefix ? current.prefix : ""}${retargeted}${suffix ? current.suffix : ""}`;
}
const cpfCnpjHandler = function(state, params) {
const isCpfCnpjAlpha = state.maskExpression === MaskExpression.CPF_CNPJ_ALPHA;
this.cpfCnpjError = params.arr.length !== 11 && params.arr.length !== 14;
if (/[a-zA-Z]/.test(state.processedValue) && isCpfCnpjAlpha) state.maskExpression = "AA.AAA.AAA/AAAA-00";
else if (params.arr.length > 11) state.maskExpression = isCpfCnpjAlpha ? "AA.AAA.AAA/AAAA-00" : "00.000.000/0000-00";
else state.maskExpression = "000.000.000-00";
return state;
};
function findDateTimeFieldAt(maskExpression, position) {
const dateTimeTokens = [
MaskExpression.DAY,
MaskExpression.MONTH,
MaskExpression.HOURS,
MaskExpression.HOUR,
MaskExpression.MINUTE,
MaskExpression.SECOND
];
let index = 0;
while (index < maskExpression.length) {
const token = maskExpression[index] ?? MaskExpression.EMPTY_STRING;
if (dateTimeTokens.includes(token)) {
const next = maskExpression[index + 1];
const isCombinedHour = (token === MaskExpression.HOURS || token === MaskExpression.HOUR) && next === MaskExpression.HOUR;
const end = next === MaskExpression.NUMBER_ZERO || isCombinedHour ? index + 2 : index + 1;
if (position >= index && position <= end) return {
token,
start: index,
end
};
index = end;
continue;
}
index++;
}
return null;
}
function isDateTimeMask(maskExpression) {
for (let position = 0; position < maskExpression.length; position++) if (findDateTimeFieldAt(maskExpression, position)) return true;
return false;
}
function isFieldOverflowing(field, position, elValue, inputValue, apm) {
const fieldValue = Number(elValue.slice(field.start, field.end));
const firstDigitValue = position === field.start + 1 ? Number(inputValue.slice(position - 1, position)) : NaN;
switch (field.token) {
case MaskExpression.DAY: return fieldValue > 31 && fieldValue < 40 || firstDigitValue > 3;
case MaskExpression.MONTH: return fieldValue > 12 || firstDigitValue > 1;
case MaskExpression.HOURS: return apm ? fieldValue > 12 || firstDigitValue > 9 : fieldValue > 23 || firstDigitValue > 2;
case MaskExpression.HOUR: return apm ? fieldValue > 12 : fieldValue > 23;
case MaskExpression.MINUTE:
case MaskExpression.SECOND: return fieldValue > 59 || firstDigitValue > 5;
default: return false;
}
}
function leadZeroDateTimeCaret(maskExpression, position, elValue, inputValue, apm) {
const field = findDateTimeFieldAt(maskExpression, position);
return field && isFieldOverflowing(field, position, elValue, inputValue, apm) ? position + 2 : position;
}
function overwriteFullDateValue(maskExpression, inputValue, position, code) {
if (inputValue.length >= maskExpression.length - 1 && code !== MaskExpression.BACKSPACE && maskExpression === MaskExpression.DAYS_MONTHS_YEARS && position < 10) {
const inputSymbol = inputValue.slice(position - 1, position);
return inputValue.slice(0, position - 1) + inputSymbol + inputValue.slice(position + 1);
}
return null;
}
const FIELD_MAX = {
d: 31,
M: 12,
H: 23,
h: 23,
m: 59,
s: 59
};
function overwriteDateSlot(svc, before, elValue, position) {
const mask = svc.maskExpression;
const start = position - 1;
const display = elValue.length > mask.length ? elValue.slice(0, start) + elValue.slice(position) : before;
const selEnd = display.length - elValue.length + position;
const typed = elValue.charAt(start);
const target = nextFillableIndex(mask, svc.specialCharacters, start);
const slot = findDateTimeFieldAt(mask, target);
const field = slot?.end === target ? findDateTimeFieldAt(mask, target + 1) : slot;
if (!/[dM]/.test(mask) || display.length !== mask.length || !/\d/.test(display.charAt(target)) || elValue !== display.slice(0, start) + typed + display.slice(selEnd) || selEnd > (field?.end ?? target + 1) || selEnd - start > 1 && !svc.leadZeroDateTime) return null;
if (!svc._checkSymbolMask(typed, mask.charAt(target))) return {
value: display,
position: start,
end: -1
};
let value = display.slice(0, target) + typed + display.slice(target + 1);
let caret = target + 1;
if (field) {
const max = svc.apm && /[Hh]/.test(field.token) ? 12 : FIELD_MAX[field.token];
const over = Number(value.slice(field.start, field.end)) > max;
if (svc.leadZeroDateTime && (over || selEnd > caret)) {
const tens = target === field.start && Number(typed) * 10 <= max;
value = display.slice(0, field.start) + (tens ? typed + "0" : "0" + typed) + display.slice(field.end);
caret = tens ? caret : field.end;
} else if (over) return null;
}
return {
value,
position: caret,
end: caret
};
}
const userEdits = /* @__PURE__ */ new WeakMap();
function setUserEdit(service, edit) {
if (edit) userEdits.set(service, edit);
else userEdits.delete(service);
}
function userEditFor(service, inputValue) {
const edit = userEdits.get(service);
return edit?.value === inputValue ? edit : null;
}
const EMAIL_AT = "@";
const EMAIL_LOCAL_PUNCTUATION = "._+-";
const EMAIL_DOMAIN_PUNCTUATION = ".-";
function isEmailPunctuation(inputSymbol, result) {
const allowed = result.includes(EMAIL_AT) ? EMAIL_DOMAIN_PUNCTUATION : EMAIL_LOCAL_PUNCTUATION;
const last = result.slice(-1);
return allowed.includes(inputSymbol) && last !== MaskExpression.EMPTY_STRING && last !== EMAIL_AT && !(inputSymbol === MaskExpression.DOT && last === MaskExpression.DOT);
}
const genericPatternHandler = function(state, params) {
const { inputArray } = params;
const maskExpression = state.maskExpression;
const processedValue = state.processedValue;
let { cursor, result, multi, processedPosition, stepBack } = state;
const userEdit = userEditFor(this, params.inputValue);
const prefixOffset = params.startsWithPrefix && !params.prefixAlreadyRemovedByCaller ? this.prefix.length : 0;
for (let i = 0, inputSymbol = inputArray[0]; i < inputArray.length; i++, inputSymbol = inputArray[i] ?? MaskExpression.EMPTY_STRING) {
if (cursor === maskExpression.length) break;
const symbolStarInPattern = MaskExpression.SYMBOL_STAR in this.patterns;
if (this._checkSymbolMask(inputSymbol, maskExpression[cursor] ?? MaskExpression.EMPTY_STRING) && maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION) {
result += inputSymbol;
cursor += 2;
} else if (maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR && multi && this._checkSymbolMask(inputSymbol, maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING)) {
result += inputSymbol;
cursor += 3;
multi = false;
} else if (this._checkSymbolMask(inputSymbol, maskExpression[cursor] ?? MaskExpression.EMPTY_STRING) && maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR && !symbolStarInPattern) {
result += inputSymbol;
multi = true;
} else if (maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION && this._checkSymbolMask(inputSymbol, maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING)) {
result += inputSymbol;
cursor += 3;
} else if (this._checkSymbolMask(inputSymbol, maskExpression[cursor] ?? MaskExpression.EMPTY_STRING)) {
if (this.leadZeroDateTime && (maskExpression[cursor] === MaskExpression.HOURS || maskExpression[cursor] === MaskExpression.HOUR && maskExpression[cursor + 1] === MaskExpression.HOUR) && Number(inputSymbol + (inputArray[i + 1] ?? MaskExpression.EMPTY_STRING)) > (this.apm ? 12 : 23) || maskExpression[cursor] === MaskExpression.HOURS && (this.apm ? Number(inputSymbol) > 9 : Number(inputSymbol) > 2)) {
processedPosition = !this.leadZeroDateTime ? processedPosition + 1 : processedPosition;
cursor += 1;
this._shiftStep(cursor);
i--;
if (this.leadZeroDateTime) result += "0";
continue;
}
if (maskExpression[cursor] === MaskExpression.HOUR) {
const previousHourDigit = this.leadZeroDateTime ? result.slice(-1) : processedValue.slice(cursor - 1, cursor);
if (this.apm ? result.length === 1 && Number(result) > 1 || result === "1" && Number(inputSymbol) > 2 || previousHourDigit.length === 1 && Number(previousHourDigit) > 2 || previousHourDigit === "1" && Number(inputSymbol) > 2 : result === "2" && Number(inputSymbol) > 3 || (result.slice(cursor - 2, cursor) === "2" || result.slice(cursor - 3, cursor) === "2" || result.slice(cursor - 4, cursor) === "2" || result.slice(cursor - 1, cursor) === "2") && Number(inputSymbol) > 3 && cursor > 10) {
processedPosition = processedPosition + 1;
cursor += 1;
i--;
continue;
}
}
if (maskExpression[cursor] === MaskExpression.MINUTE || maskExpression[cursor] === MaskExpression.SECOND) {
if (Number(inputSymbol) > 5) {
processedPosition = !this.leadZeroDateTime ? processedPosition + 1 : processedPosition;
cursor += 1;
this._shiftStep(cursor);
i--;
if (this.leadZeroDateTime) result += "0";
continue;
}
}
if (userEdit && this.leadZeroDateTime && !this.keepCharacterPositions && !this.writingValue && inputSymbol === MaskExpression.NUMBER_ZERO && result.slice(-1) === MaskExpression.NUMBER_ZERO && i + prefixOffset >= userEdit.start && i + prefixOffset <= userEdit.end && (maskExpression[cursor - 1] === MaskExpression.DAY || maskExpression[cursor - 1] === MaskExpression.MONTH || this.apm && maskExpression[cursor] === MaskExpression.HOUR && (maskExpression[cursor - 1] === MaskExpression.HOURS || maskExpression[cursor - 1] === MaskExpression.HOUR))) inputSymbol = "1";
const daysCount = 31;
const inputValueCursor = processedValue[cursor];
const inputValueCursorPlusOne = processedValue[cursor + 1];
const inputValueCursorPlusTwo = processedValue[cursor + 2];
const inputValueCursorMinusOne = processedValue[cursor - 1];
const inputValueCursorMinusTwo = processedValue[cursor - 2];
const inputValueSliceMinusThreeMinusOne = processedValue.slice(cursor - 3, cursor - 1);
const inputValueSliceMinusOnePlusOne = processedValue.slice(cursor - 1, cursor + 1);
const inputValueSliceCursorPlusTwo = processedValue.slice(cursor, cursor + 2);
const inputValueSliceMinusTwoCursor = processedValue.slice(cursor - 2, cursor);
const windowStart = params.justPasted || this.writingValue ? i : cursor;
const windowSlice = processedValue.slice(windowStart, windowStart + 2);
const windowNext = processedValue[windowStart + 1];
const tokenAbutsDigitField = maskExpression[cursor - 1] === MaskExpression.NUMBER_ZERO;
if (maskExpression[cursor] === MaskExpression.DAY) {
const maskStartWithMonth = maskExpression.slice(0, 2) === MaskExpression.MONTHS;
const startWithMonthInput = maskExpression.slice(0, 2) === MaskExpression.MONTHS && this.specialCharacters.includes(inputValueCursorMinusTwo);
if (Number(inputSymbol) > 3 && this.leadZeroDateTime || !maskStartWithMonth && (Number(inputValueSliceCursorPlusTwo) > daysCount || !tokenAbutsDigitField && Number(inputValueSliceMinusOnePlusOne) > daysCount || this.specialCharacters.includes(inputValueCursorPlusOne)) || (startWithMonthInput ? Number(inputValueSliceMinusOnePlusOne) > daysCount || !this.specialCharacters.includes(inputValueCursor) && this.specialCharacters.includes(inputValueCursorPlusTwo) || this.specialCharacters.includes(inputValueCursor) : Number(windowSlice) > daysCount || this.specialCharacters.includes(windowNext) && !params.backspaced)) {
processedPosition = !this.leadZeroDateTime ? processedPosition + 1 : processedPosition;
cursor += 1;
this._shiftStep(cursor);
i--;
if (this.leadZeroDateTime) result += "0";
continue;
}
}
if (maskExpression[cursor] === MaskExpression.MONTH) {
const monthsCount = 12;
let precedingFieldEnd = cursor - 1;
while (precedingFieldEnd >= 0 && this.specialCharacters.includes(maskExpression[precedingFieldEnd])) precedingFieldEnd--;
let precedingFieldStart = precedingFieldEnd;
while (precedingFieldStart >= 0 && !this.specialCharacters.includes(maskExpression[precedingFieldStart])) precedingFieldStart--;
const precedingField = maskExpression.slice(precedingFieldStart + 1, precedingFieldEnd + 1);
const yearFieldPrecedesMonth = precedingField.length > 2 && precedingField.split(MaskExpression.EMPTY_STRING).every((token) => token === MaskExpression.NUMBER_ZERO);
const withoutDays = cursor === 0 && (Number(inputSymbol) > 2 || Number(inputValueSliceCursorPlusTwo) > monthsCount || this.specialCharacters.includes(inputValueCursorPlusOne) && (!params.backspaced || this.leadZeroDateTime && /\d/.test(inputValueCursorPlusTwo)));
const monthPair = processedValue.slice(i, i + 2);
const yearFirstMonthOverflow = this.leadZeroDateTime && yearFieldPrecedesMonth && (Number(monthPair) > monthsCount || this.specialCharacters.includes(monthPair.charAt(1)) && this.specialCharacters.includes(processedValue.charAt(i - 1)));
const specialChart = maskExpression.slice(cursor + 2, cursor + 3);
const day1monthInput = inputValueSliceMinusThreeMinusOne.includes(specialChart) && maskExpression.includes("d0") && (this.specialCharacters.includes(inputValueCursorMinusTwo) && Number(inputValueSliceMinusOnePlusOne) > monthsCount && !this.specialCharacters.includes(inputValueCursor) || this.specialCharacters.includes(inputValueCursor));
const day2monthInput = !tokenAbutsDigitField && !yearFieldPrecedesMonth && Number(inputValueSliceMinusThreeMinusOne) <= daysCount && !this.specialCharacters.includes(inputValueSliceMinusThreeMinusOne) && this.specialCharacters.includes(inputValueCursorMinusOne) && (Number(inputValueSliceCursorPlusTwo) > monthsCount || this.specialCharacters.includes(inputValueCursorPlusOne));
const day2monthInputDot = Number(windowSlice) > monthsCount && cursor === 5 || this.specialCharacters.includes(windowNext) && cursor === 5;
const day1monthPaste = !tokenAbutsDigitField && !yearFieldPrecedesMonth && Number(inputValueSliceMinusThreeMinusOne) > daysCount && !this.specialCharacters.includes(inputValueSliceMinusThreeMinusOne) && !this.specialCharacters.includes(inputValueSliceMinusTwoCursor) && Number(inputValueSliceMinusTwoCursor) > monthsCount && maskExpression.includes("d0");
const day2monthPaste = !tokenAbutsDigitField && !yearFieldPrecedesMonth && Number(inputValueSliceMinusThreeMinusOne) <= daysCount && !this.specialCharacters.includes(inputValueSliceMinusThreeMinusOne) && !this.specialCharacters.includes(inputValueCursorMinusOne) && Number(inputValueSliceMinusOnePlusOne) > monthsCount;
if (Number(inputSymbol) > 1 && this.leadZeroDateTime || withoutDays || day1monthInput || day2monthPaste || day1monthPaste || day2monthInput || day2monthInputDot && !this.leadZeroDateTime || yearFirstMonthOverflow) {
processedPosition = !this.leadZeroDateTime ? processedPosition + 1 : processedPosition;
cursor += 1;
this._shiftStep(cursor);
i--;
if (this.leadZeroDateTime) result += "0";
continue;
}
}
result += inputSymbol;
cursor++;
} else if (this.specialCharacters.includes(inputSymbol) && maskExpression[cursor] === inputSymbol) {
result += inputSymbol;
cursor++;
} else if (this.specialCharacters.indexOf(maskExpression[cursor] ?? MaskExpression.EMPTY_STRING) !== -1) {
result += maskExpression[cursor];
cursor++;
this._shiftStep(cursor);
i--;
} else if (maskExpression[cursor] === MaskExpression.NUMBER_NINE && this.showMaskTyped) this._shiftStep(cursor);
else if (this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING] && this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING]?.optional) {
if (inputSymbol.trim() === MaskExpression.EMPTY_STRING) continue;
if (!!inputArray[cursor] && maskExpression !== "099.099.099.099" && maskExpression !== "000.000.000-00" && maskExpression !== "00.000.000/0000-00" && !maskExpression.match(/^9+\.0+$/) && !this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING]?.optional) result += inputArray[cursor];
if (maskExpression.includes(MaskExpression.NUMBER_NINE + MaskExpression.SYMBOL_STAR) && maskExpression.includes(MaskExpression.NUMBER_ZERO + MaskExpression.SYMBOL_STAR)) cursor++;
cursor++;
i--;
} else if (this.maskExpression[cursor + 1] === MaskExpression.SYMBOL_STAR && (this._findSpecialChar(this.maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING) ? this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] : inputSymbol === this.maskExpression[cursor + 2]) && multi) {
cursor += 3;
result += inputSymbol;
} else if (this.maskExpression[cursor + 1] === MaskExpression.SYMBOL_QUESTION && (this._findSpecialChar(this.maskExpression[cursor + 2] ?? MaskExpression.EMPTY_STRING) ? this._findSpecialChar(inputSymbol) === this.maskExpression[cursor + 2] : inputSymbol === this.maskExpression[cursor + 2]) && multi) {
cursor += 3;
result += inputSymbol;
} else if (maskExpression === MaskExpression.EMAIL_MASK && isEmailPunctuation(inputSymbol, result)) result += inputSymbol;
else if (this.showMaskTyped && this.specialCharacters.indexOf(inputSymbol) < 0 && inputSymbol !== this.placeHolderCharacter && this.placeHolderCharacter.length === 1) stepBack = true;
}
state.cursor = cursor;
state.result = result;
state.multi = multi;
state.processedPosition = processedPosition;
state.stepBack = stepBack;
return state;
};
const ipHandler = function(state) {
const valuesIP = state.processedValue.split(MaskExpression.DOT);
this.ipError = this._validIP(valuesIP);
state.maskExpression = "099.099.099.099";
return state;
};
const percentHandler = function(state, params) {
const { backspaced } = params;
const { cursor } = state;
let processedValue = state.processedValue;
if (processedValue.match("[a-z]|[A-Z]") || processedValue.match(/[-!$%^&*()_+|~=`{}\[\]:";'<>?,\/.]/) && !backspaced) {
processedValue = this._stripToDecimal(processedValue);
const precision = this.getPrecision(state.maskExpression);
processedValue = this.checkInputPrecision(processedValue, precision);
}
const decimalMarker = this._activeDecimalMarker(processedValue);
if (processedValue.indexOf(decimalMarker) > 0 && !this.percentage(processedValue.substring(0, processedValue.indexOf(decimalMarker)))) {
let base = processedValue.substring(0, processedValue.indexOf(decimalMarker) - 1);
if (this.allowNegativeNumbers && processedValue.slice(cursor, cursor + 1) === MaskExpression.MINUS && !backspaced) base = processedValue.substring(0, processedValue.indexOf(decimalMarker));
processedValue = `${base}${processedValue.substring(processedValue.indexOf(decimalMarker), processedValue.length)}`;
}
let value;
this.allowNegativeNumbers && processedValue.slice(cursor, cursor + 1) === MaskExpression.MINUS ? value = `${MaskExpression.MINUS}${processedValue.slice(cursor + 1, cursor + processedValue.length)}` : value = processedValue;
if (this.percentage(value)) state.result = this._splitPercentZero(processedValue);
else state.result = this._splitPercentZero(processedValue.substring(0, processedValue.length - 1));
state.processedValue = processedValue;
return state;
};
const INVALID_SEPARATOR_CHARS = [..."@#!$%^&*()_+|~=`{}[]: ,.\";<>?/"];
const separatorHandler = function(state, params) {
const { backspaced, justPasted, startsWithPrefix, prefixAlreadyRemovedByCaller, cb, inputValue } = params;
let { processedValue, processedPosition, backspaceShift, shift, stepBack } = state;
let result;
if (processedValue.match("[wа-яА-Я]") || processedValue.match("[ЁёА-я]") || processedValue.match("[a-z]|[A-Z]") || processedValue.match(/[-@#!$%\\^&*()_£¬'+|~=`{}\]:";<>.?/]/) || processedValue.match("[^A-Za-z0-9,]")) processedValue = this._stripToDecimal(processedValue);
const precision = this.getPrecision(state.maskExpression);
let decimalMarker = this.decimalMarker;
if (Array.isArray(this.decimalMarker)) decimalMarker = this._activeDecimalMarker(this.actualValue);
if (this.typeFromDecimals && Number.isFinite(precision) && precision > 0 && !justPasted && !this.writingValue) {
result = this._formatFromDecimals(processedValue, precision, decimalMarker);
this._shift.clear();
const res = result.includes(MaskExpression.MINUS) && this.prefix && this.allowNegativeNumbers ? `${MaskExpression.MINUS}${this.prefix}${result.split(MaskExpression.MINUS).join(MaskExpression.EMPTY_STRING)}${this.suffix}` : result.length ? `${this.prefix}${result}${this.suffix}` : this.instantPrefix ? this.prefix : MaskExpression.EMPTY_STRING;
cb(res.length - this.suffix.length - processedPosition, true);
state.earlyReturn = res;
return state;
}
if (!justPasted && !backspaced && !this.writingValue) {
const isDecimalMarkerChar = (char) => !!char && char !== this.thousandSeparator && (Array.isArray(this.decimalMarker) ? this.decimalMarker.includes(char) : char === this.decimalMarker);
const prefixOffset = startsWithPrefix && !prefixAlreadyRemovedByCaller ? this.prefix.length : 0;
const typedMarkerIndex = processedPosition - prefixOffset - 1;
if (typedMarkerIndex >= 0 && isDecimalMarkerChar(processedValue[typedMarkerIndex])) {
let markerCount = 0;
for (const char of processedValue) if (isDecimalMarkerChar(char)) markerCount++;
if (markerCount > 1) {
processedValue = processedValue.slice(0, typedMarkerIndex) + processedValue.slice(typedMarkerIndex + 1);
stepBack = true;
}
}
}
if (justPasted && Array.isArray(this.decimalMarker)) {
const markerPositions = [];
for (let i = 0; i < processedValue.length; i++) {
const char = processedValue[i];
if (char !== this.thousandSeparator && this.decimalMarker.includes(char)) markerPositions.push(i);
}
if (markerPositions.length > 1) {
const lastMarkerPosition = markerPositions[markerPositions.length - 1];
processedValue = processedValue.split(MaskExpression.EMPTY_STRING).filter((_, index) => index === lastMarkerPosition || !markerPositions.includes(index)).join(MaskExpression.EMPTY_STRING);
}
}
if (backspaced) {
const { decimalMarkerIndex, nonZeroIndex } = this._findFirstNonZeroAndDecimalIndex(processedValue, decimalMarker);
const zeroIndexMinus = processedValue[0] === MaskExpression.MINUS;
const zeroIndexDecimalMarker = processedValue[0] === decimalMarker;
const firstIndexDecimalMarker = processedValue[1] === decimalMarker;
if (zeroIndexDecimalMarker && !nonZeroIndex || zeroIndexMinus && firstIndexDecimalMarker && !nonZeroIndex) processedValue = MaskExpression.NUMBER_ZERO;
if (decimalMarkerIndex && nonZeroIndex && zeroIndexMinus && processedPosition === 1) {
if (decimalMarkerIndex < nonZeroIndex || decimalMarkerIndex > nonZeroIndex) processedValue = MaskExpression.MINUS + processedValue.slice(nonZeroIndex);
}
if (decimalMarkerIndex === null && nonZeroIndex && processedValue.length > nonZeroIndex) processedValue = zeroIndexMinus ? MaskExpression.MINUS + processedValue.slice(nonZeroIndex) : processedValue.slice(nonZeroIndex);
if (decimalMarkerIndex && nonZeroIndex && processedPosition === 0) {
if (decimalMarkerIndex < nonZeroIndex) processedValue = processedValue.slice(decimalMarkerIndex - 1);
if (decimalMarkerIndex > nonZeroIndex) processedValue = processedValue.slice(nonZeroIndex);
}
}
if (precision === 0 && !backspaced) processedValue = this.allowNegativeNumbers ? processedValue.length > 2 && processedValue[0] === MaskExpression.MINUS && processedValue[1] === MaskExpression.NUMBER_ZERO && processedValue[2] !== this.thousandSeparator && processedValue[2] !== MaskExpression.COMMA && processedValue[2] !== MaskExpression.DOT ? "-" + processedValue.slice(2, processedValue.length) : processedValue[0] === MaskExpression.NUMBER_ZERO && processedValue.length > 1 && processedValue[1] !== this.thousandSeparator && processedValue[1] !== MaskExpression.COMMA && processedValue[1] !== MaskExpression.DOT ? processedValue.slice(1, processedValue.length) : processedValue : processedValue.length > 1 && processedValue[0] === MaskExpression.NUMBER_ZERO && processedValue[1] !== this.thousandSeparator && processedValue[1] !== MaskExpression.COMMA && processedValue[1] !== MaskExpression.DOT ? processedValue.slice(1, processedValue.length) : processedValue;
else {
if (processedValue[0] === decimalMarker && processedValue.length > 1 && !backspaced) {
processedValue = MaskExpression.NUMBER_ZERO + processedValue.slice(0, processedValue.length + 1);
this.plusOnePosition = true;
}
if (processedValue[0] === MaskExpression.NUMBER_ZERO && processedValue[1] !== decimalMarker && processedValue[1] !== this.thousandSeparator && !backspaced) {
processedValue = processedValue.length > 1 ? processedValue.slice(0, 1) + decimalMarker + processedValue.slice(1, processedValue.length + 1) : processedValue;
this.plusOnePosition = true;
}
if (this.allowNegativeNumbers && !backspaced && processedValue[0] === MaskExpression.MINUS && (processedValue[1] === decimalMarker || processedValue[1] === MaskExpression.NUMBER_ZERO)) {
processedValue = processedValue[1] === decimalMarker && processedValue.length > 2 ? processedValue.slice(0, 1) + MaskExpression.NUMBER_ZERO + processedValue.slice(1, processedValue.length) : processedValue[1] === MaskExpression.NUMBER_ZERO && processedValue.length > 2 && processedValue[2] !== decimalMarker ? processedValue.slice(0, 2) + decimalMarker + processedValue.slice(2, processedValue.length) : processedValue;
this.plusOnePosition = true;
}
}
const thousandSeparatorCharEscaped = this._charToRegExpExpression(this.thousandSeparator);
const allowedChars = [this.thousandSeparator, ...[this.decimalMarker].flat()];
const invalidChars = INVALID_SEPARATOR_CHARS.filter((char) => !allowedChars.includes(char)).map((char) => char === " " ? "\\s" : escapeRegExp(char)).join("");
const invalidCharRegexp = new RegExp("[" + invalidChars + "]");
if (processedValue.match(invalidCharRegexp)) processedValue = processedValue.substring(0, processedValue.length - 1);
processedValue = this.checkInputPrecision(processedValue, precision);
const strForSep = processedValue.replace(new RegExp(thousandSeparatorCharEscaped, "g"), "");
result = this._formatWithSeparators(strForSep, this.thousandSeparator, this.decimalMarker, precision);
const commaShift = result.indexOf(MaskExpression.COMMA) - processedValue.indexOf(MaskExpression.COMMA);
const shiftStep = result.length - processedValue.length;
const backspacedDecimalMarkerWithSeparatorLimit = backspaced && result.length < inputValue.length - this.suffix.length && this.separatorLimit;
if ((result[processedPosition - 1] === this.thousandSeparator || result[processedPosition - this.prefix.length]) && this.prefix && backspaced) processedPosition = processedPosition - 1;
else if (shiftStep > 0 && result[processedPosition] !== this.thousandSeparator || backspacedDecimalMarkerWithSeparatorLimit) {
backspaceShift = true;
let _shift = 0;
do {
this._shift.add(processedPosition + _shift);
_shift++;
} while (_shift < shiftStep);
} else if (result[processedPosition - 1] === this.thousandSeparator || shiftStep === -4 || shiftStep === -3 || result[processedPosition] === this.thousandSeparator) {
this._shift.clear();
this._shift.add(processedPosition - 1);
} else if (commaShift !== 0 && processedPosition > 0 && !(result.indexOf(MaskExpression.COMMA) >= processedPosition && processedPosition > 3) || !(result.indexOf(MaskExpression.DOT) >= processedPosition && processedPosition > 3) && shiftStep <= 0) {
this._shift.clear();
backspaceShift = true;
shift = shiftStep;
processedPosition += shiftStep;
this._shift.add(processedPosition);
} else this._shift.clear();
state.processedValue = processedValue;
state.processedPosition = processedPosition;
state.result = result;
state.backspaceShift = backspaceShift;
state.shift = shift;
state.stepBack = stepBack;
return state;
};
const MASK_HANDLERS = [
{
id: "ip",
terminal: false,
match: (maskExpression) => maskExpression === MaskExpression.IP,
handle: ipHandler
},
{
id: "cpf-cnpj",
terminal: false,
match: (maskExpression) => maskExpression === MaskExpression.CPF_CNPJ || maskExpression === MaskExpression.CPF_CNPJ_ALPHA,
handle: cpfCnpjHandler
},
{
id: "percent",
terminal: true,
match: (maskExpression) => maskExpression.startsWith(MaskExpression.PERCENT),
handle: percentHandler
},
{
id: "separator",
terminal: true,
match: (maskExpression) => maskExpression.startsWith(MaskExpression.SEPARATOR),
handle: separatorHandler
}
];
function dispatchMaskHandler(self, state, params) {
for (const entry of MASK_HANDLERS) if (entry.match(state.maskExpression)) {
const next = entry.handle.call(self, state, params);
return entry.terminal ? next : genericPatternHandler.call(self, next, params);
}
return genericPatternHandler.call(self, state, params);
}
var NgxMaskApplierService = class NgxMaskApplierService {
_config = inject(NGX_MASK_CONFIG);
dropSpecialCharacters = this._config.dropSpecialCharacters;
hiddenInput = this._config.hiddenInput;
clearIfNotMatch = this._config.clearIfNotMatch;
specialCharacters = this._config.specialCharacters;
patterns = this._config.patterns;
prefix = this._config.prefix;
suffix = this._config.suffix;
_thousandSeparator = ignoreDigitSeparator(this._config.thousandSeparator);
get thousandSeparator() {
return this._thousandSeparator;
}
set thousandSeparator(value) {
this._thousandSeparator = ignoreDigitSeparator(value);
}
decimalMarker = this._config.decimalMarker;
showMaskTyped = this._config.showMaskTyped;
placeHolderCharacter = this._config.placeHolderCharacter;
validation = this._config.validation;
separatorLimit = this._config.separatorLimit;
allowNegativeNumbers = this._config.allowNegativeNumbers;
leadZeroDateTime = this._config.leadZeroDateTime;
leadZero = this._config.leadZero;
typeFromDecimals = this._config.typeFromDecimals;
apm = this._config.apm;
inputTransformFn = this._config.inputTransformFn;
outputTransformFn = this._config.outputTransformFn;
keepCharacterPositions = this._config.keepCharacterPositions;
instantPrefix = this._config.instantPrefix;
triggerOnMaskChange = this._config.triggerOnMaskChange;
_shift = /* @__PURE__ */ new Set();
plusOnePosition = false;
maskExpression = "";
actualValue = "";
shownMaskExpression = this._config.shownMaskExpression;
deletedSpecialCharacter = false;
writingValue = false;
ipError;
cpfCnpjError;
applyMask(inputValue, maskExpression, position = 0, justPasted = false, backspaced = false, cb = () => {}) {
if (!maskExpression || typeof inputValue !== "string") return MaskExpression.EMPTY_STRING;
let cursor = 0;
let result = "";
const multi = false;
let backspaceShift = false;
let shift = 1;
let stepBack = false;
let processedValue = inputValue;
let processedPosition = position;
const startsWithPrefix = processedValue.slice(0, this.prefix.length) === this.prefix;
const prefixAlreadyRemovedByCaller = justPasted && this.showMaskTyped && this.placeHolderCharacter.length === 1 && !this.leadZeroDateTime && processedValue !== this.prefix;
if (startsWithPrefix && !prefixAlreadyRemovedByCaller) processedValue = processedValue.slice(this.prefix.length);
if (!!this.suffix && processedValue.length > 0) processedValue = this.checkAndRemoveSuffix(processedValue);
if (processedValue === "(" && this.prefix) processedValue = "";
const inputArray = processedValue.toString().split(MaskExpression.EMPTY_STRING);
if (this.allowNegativeNumbers && processedValue.slice(cursor, cursor + 1) === MaskExpression.MINUS) result += processedValue.slice(cursor, cursor + 1);
const arr = digitsOf(processedValue);
const state = {
processedValue,
processedPosition,
cursor,
result,
multi,
backspaceShift,
shift,
stepBack,
maskExpression
};
const resolved = dispatchMaskHandler(this, state, {
inputValue,
position,
justPasted,
backspaced,
cb,
inputArray,
arr,
startsWithPrefix,
prefixAlreadyRemovedByCaller
});
if (typeof resolved.earlyReturn === "string") return resolved.earlyReturn;
processedValue = resolved.processedValue;
processedPosition = resolved.processedPosition;
cursor = resolved.cursor;
result = resolved.result;
backspaceShift = resolved.backspaceShift;
shift = resolved.shift;
stepBack = resolved.stepBack;
maskExpression = resolved.maskExpression;
if (result[processedPosition - 1] && result.length + 1 === maskExpression.length && this.specialCharacters.indexOf(maskExpression[maskExpression.length - 1] ?? MaskExpression.EMPTY_STRING) !== -1) result += maskExpression[maskExpression.length - 1];
let newPosition = processedPosition + 1;
while (this._shift.has(newPosition)) {
shift++;
newPosition++;
}
let actualShift = justPasted && !maskExpression.startsWith(MaskExpression.SEPARATOR) ? cursor : this._shift.has(processedPosition) ? shift : 0;
if (stepBack) actualShift--;
cb(actualShift, backspaceShift);
if (shift < 0) this._shift.clear();
let onlySpecial = false;
if (backspaced) onlySpecial = inputArray.every((char) => this.specialCharacters.includes(char));
let res = `${this.prefix}${onlySpecial ? MaskExpression.EMPTY_STRING : result}${this.showMaskTyped ? "" : this.suffix}`;
if (result.length === 0) res = this.instantPrefix ? `${this.prefix}${result}` : `${result}`;
if (processedValue.length === 1 && this.specialCharacters.includes(maskExpression[0]) && processedValue !== maskExpression[0]) {
const firstPatternIndex = nextFillableIndex(maskExpression, this.specialCharacters, 1);
if (!this._checkSymbolMask(processedValue, maskExpression[firstPatternIndex] ?? MaskExpression.EMPTY_STRING)) return "";
}
if (result.includes(MaskExpression.MINUS) && this.prefix && this.allowNegativeNumbers) {
if (backspaced && result === MaskExpression.MINUS) return "";
res = `${MaskExpression.MINUS}${this.prefix}${result.split(MaskExpression.MINUS).join(MaskExpression.EMPTY_STRING)}${this.suffix}`;
}
return res;
}
_findDropSpecialChar(inputSymbol) {
if (Array.isArray(this.dropSpecialCharacters)) return this.dropSpecialCharacters.find((val) => val === inputSymbol);
return this._findSpecialChar(inputSymbol);
}
_findSpecialChar(inputSymbol) {
return this.specialCharacters.find((val) => val === inputSymbol);
}
_checkSymbolMask(inputSymbol, maskSymbol) {
const pattern = this.patterns[maskSymbol]?.pattern;
return pattern ? this._testPattern(pattern, inputSymbol) : false;
}
_testPattern(pattern, value) {
pattern.lastIndex = 0;
return pattern.test(value);
}
_formatWithSeparators = (str, thousandSeparatorChar, decimalChars, precision) => {
let x;
let decimalChar;
if (Array.isArray(decimalChars)) {
const regExp = new RegExp(decimalChars.map((v) => escapeRegExp(v)).join("|"));
x = str.split(regExp);
decimalChar = str.match(regExp)?.[0] ?? MaskExpression.EMPTY_STRING;
} else {
x = str.split(decimalChars);
decimalChar = decimalChars;
}
const decimals = x.length > 1 ? `${decimalChar}${x[1]}` : MaskExpression.EMPTY_STRING;
let res = x[0] ?? MaskExpression.EMPTY_STRING;
const separatorLimit = this.separatorLimit.replace(/\s/g, MaskExpression.EMPTY_STRING);
if (separatorLimit && +separatorLimit) {
if (res[0] === MaskExpression.MINUS) res = `-${res.slice(1, res.length).slice(0, separatorLimit.length)}`;
else res = res.slice(0, separatorLimit.length);
}
res = this._applyThousandGrouping(res, thousandSeparatorChar);
if (typeof precision === "undefined") return res + decimals;
else if (precision === 0) return res;
return res + decimals.substring(0, precision + 1);
};
_formatFromDecimals(value, precision, decimalMarker) {
const negative = this.allowNegativeNumbers && value.startsWith(MaskExpression.MINUS);
let digits = value.replace(/\D+/g, MaskExpression.EMPTY_STRING).replace(/^0+/, "");
if (!digits) return negative ? MaskExpression.MINUS : MaskExpression.EMPTY_STRING;
const separatorLimit = this.separatorLimit.replace(/\s/g, MaskExpression.EMPTY_STRING);
if (separatorLimit && +separatorLimit) digits = digits.slice(0, separatorLimit.length + precision);
digits = digits.padStart(precision + 1, MaskExpression.NUMBER_ZERO);
const integerPart = this._applyThousandGrouping(digits.slice(0, digits.length - precision), this.thousandSeparator);
const decimalPart = digits.slice(digits.length - precision);
return `${negative ? MaskExpression.MINUS : MaskExpression.EMPTY_STRING}${integerPart}${decimalMarker}${decimalPart}`;
}
_applyThousandGrouping(digits, separator) {
const rgx = /(\d+)(\d{3})/;
let grouped = digits;
while (separator && rgx.test(grouped)) grouped = grouped.replace(rgx, (_match, head, tail) => head + separator + tail);
return grouped;
}
percentage = (str) => {
const sanitizedStr = str.replace(",", ".");
const value = Number(this.allowNegativeNumbers && str.includes(MaskExpression.MINUS) ? sanitizedStr.slice(1, str.length) : sanitizedStr);
return !isNaN(value) && value >= 0 && value <= 100;
};
getPrecision = (maskExpression) => {
const x = maskExpression.split(MaskExpression.DOT);
if (x.length > 1) return Number(x[x.length - 1]);
return Infinity;
};
checkAndRemoveSuffix = (inputValue) => {
for (let i = this.suffix?.length - 1; i >= 0; i--) {
const substr = this.suffix.substring(i, this.suffix?.length);
if (inputValue.endsWith(substr) && i !== this.suffix?.length - 1 && (i === 0 || inputValue.length > substr.length || this.actualValue.endsWith(this.suffix) && inputValue.length <= this.actualValue.length - this.suffix.length) && (i - 1 < 0 || !inputValue.endsWith(this.suffix.substring(i - 1, this.suffix?.length)))) return inputValue.slice(0, inputValue.length - substr.length);
}
return inputValue;
};
checkInputPrecision = (inputValue, precision) => {
let processedInputValue = inputValue;
if (precision < Infinity) {
const decimalMarker = this._activeDecimalMarker(inputValue);
const precisionRegEx = new RegExp(this._charToRegExpExpression(decimalMarker) + `\\d{${precision}}.*$`);
const precisionMatch = processedInputValue.match(precisionRegEx);
const precisionMatchLength = (precisionMatch && precisionMatch[0]?.length) ?? 0;
if (precisionMatchLength - 1 > precision) {
const diff = precisionMatchLength - 1 - precision;
processedInputValue = processedInputValue.substring(0, processedInputValue.length - diff);
}
if (precision === 0 && this._compareOrIncludes(processedInputValue[processedInputValue.length - 1], decimalMarker, this.thousandSeparator)) processedInputValue = processedInputValue.substring(0, processedInputValue.length - 1);
}
return processedInputValue;
};
_stripToDecimal(str) {
const isDecimalMarkerChar = (char) => char === MaskExpression.DOT || char === MaskExpression.COMMA;
return str.split(MaskExpression.EMPTY_STRING).filter((i, idx) => {
const isDecimalMarker = typeof this.decimalMarker === "string" ? i === this.decimalMarker : isDecimalMarkerChar(i) && this.decimalMarker.includes(i);
return i.match("^-?\\d") || i === this.thousandSeparator || isDecimalMarker || i === MaskExpression.MINUS && idx === 0 && this.allowNegativeNumbers;
}).join(MaskExpression.EMPTY_STRING);
}
_charToRegExpExpression(char) {
if (!char) return char;
return char === " " ? "\\s" : escapeRegExp(char);
}
_shiftStep(cursor) {
this._shift.add(cursor + this.prefix.length || 0);
}
_compareOrIncludes(value, comparedValue, excludedValue) {
return Array.isArray(comparedValue) ? comparedValue.filter((v) => v !== excludedValue).includes(value) : value === comparedValue;
}
_validIP(valuesIP) {
return !(valuesIP.length === 4 && !valuesIP.some((value, index) => {
if (valuesIP.length !== index + 1) return value === MaskExpression.EMPTY_STRING || Number(value) > 255;
return value === MaskExpression.EMPTY_STRING || Number(value.substring(0, 3)) > 255;
}));
}
_activeDecimalMarker(value) {
return activeDecimalMarker(value, this.decimalMarker, this.thousandSeparator);
}
_splitPercentZero(value) {
if (value === MaskExpression.MINUS && this.allowNegativeNumbers) return value;
const decimal = this._activeDecimalMarker(value);
const decimalIndex = value.indexOf(decimal);
const emptyOrMinus = this.allowNegativeNumbers && value.includes(MaskExpression.MINUS) ? "-" : "";
if (decimalIndex === -1) {
const parsedValue = parseInt(emptyOrMinus ? value.slice(1, value.length) : value, 10);
return isNaN(parsedValue) ? MaskExpression.EMPTY_STRING : `${emptyOrMinus}${parsedValue}`;
} else {
const integerPart = parseInt(value.replace("-", "").substring(0, decimalIndex), 10);
const decimalPart = value.substring(decimalIndex + 1);
const integerString = isNaN(integerPart) ? "" : integerPart.toString();
return integerString === MaskExpression.EMPTY_STRING ? MaskExpression.EMPTY_STRING : `${emptyOrMinus}${integerString}${decimal}${decimalPart}`;
}
}
_findFirstNonZeroAndDecimalIndex(inputString, decimalMarker) {
let decimalMarkerIndex = null;
let nonZeroIndex = null;
for (let i = 0; i < inputString.length; i++) {
const char = inputString[i];
if (char === decimalMarker && decimalMarkerIndex === null) decimalMarkerIndex = i;
if (char && char >= "1" && char <= "9" && nonZeroIndex === null) nonZeroIndex = i;
if (decimalMarkerIndex !== null && nonZeroIndex !== null) break;
}
return {
decimalMarkerIndex,
nonZeroIndex
};
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskApplierService,
deps: [],
target: i0.ɵɵFactoryTarget.Injectable
});
static ɵprov = i0.ɵɵngDeclareInjectable({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskApplierService
});
};
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskApplierService,
decorators: [{ type: Injectable }]
});
var NgxMaskService = class NgxMaskService extends NgxMaskApplierService {
isNumberValue = false;
maskIsShown = "";
selStart = null;
selEnd = null;
maskChanged = false;
previousValue = "";
currentValue = "";
isInitialized = false;
keepCharacterPositionsHandled = null;
_isFocused = signal(false, ...ngDevMode ? [{ debugName: "_isFocused" }] : /* istanbul ignore next */ []);
_emitValue = false;
onChange = (_) => {};
_elementRef = inject(ElementRef, { optional: true });
document = inject(DOCUMENT);
_config = inject(NGX_MASK_CONFIG);
_renderer = inject(Renderer2, { optional: true });
applyMask(inputValue, maskExpression, position = 0, justPasted = false, backspaced = false, cb = () => {}) {
const kcpHandled = this.keepCharacterPositionsHandled;
this.keepCharacterPositionsHandled = null;
if (!maskExpression) {
if (this.maskChanged) {
const rawValue = this.removeMask(inputValue);
this.previousValue = this.currentValue;
this.currentValue = rawValue;
this.actualValue = rawValue;
this._emitValue = this.previousValue !== this.currentValue;
if (this._emitValue && this.triggerOnMaskChange) this.formControlResult(rawValue);
this._clearMaskChangedUnlessTriggered();
return rawValue;
}
return inputValue !== this.actualValue ? this.actualValue : inputValue;
}
if (maskExpression.startsWith(MaskExpression.SEPARATOR) && inputValue && /\d[eE][+-]?\d/.test(inputValue)) {
const expandedNumber = Number(this._replaceDecimalMarkerToDot(inputValue));
if (!Number.isNaN(expandedNumber)) {
inputValue = this._toPlainDecimalString(expandedNumber);
if (this.decimalMarker === MaskExpression.COMMA || Array.isArray(this.decimalMarker) && this.thousandSeparator === MaskExpression.DOT) inputValue = inputValue.replace(MaskExpression.DOT, MaskExpression.COMMA);
}
}
this.maskIsShown = this.showMaskTyped ? this.showMaskInInput() : MaskExpression.EMPTY_STRING;
if (this.maskExpression === MaskExpression.IP && this.showMaskTyped) this.maskIsShown = this.showMaskInInput(inputValue || MaskExpression.HASH);
if ((this.maskExpression === MaskExpression.CPF_CNPJ || this.maskExpression === MaskExpression.CPF_CNPJ_ALPHA) && this.showMaskTyped) this.maskIsShown = this.showMaskInInput(inputValue || MaskExpression.HASH);
if (!inputValue && this.showMaskTyped) {
if (!this.writingValue) this.formControlResult(this.prefix);
this.previousValue = this.currentValue;
this.currentValue = MaskExpression.EMPTY_STRING;
return `${this.prefix}${this.maskIsShown}${this.suffix}`;
}
const getSymbol = !!inputValue && typeof this.selStart === "number" ? inputValue[this.selStart] ?? MaskExpression.EMPTY_STRING : MaskExpression.EMPTY_STRING;
let newInputValue = "";
let newPosition = position;
if ((this.hiddenInput || inputValue && containsStarSymbol(inputValue, this.maskExpression, this.thousandSeparator)) && !this.writingValue) {
let actualResult = inputValue && inputValue.length === 1 && !backspaced ? inputValue.split(MaskExpression.EMPTY_STRING) : this.actualValue.split(MaskExpression.EMPTY_STRING);
if (backspaced) actualResult = actualResult.slice(0, position).concat(actualResult.slice(position + 1));
if (this.showMaskTyped) {
inputValue = this.removeMask(inputValue);
actualResult = this.removeMask(actualResult.join("")).split(MaskExpression.EMPTY_STRING);
}
if (typeof this.selStart === "object" && typeof this.selEnd === "object") {
this.selStart = Number(this.selStart);
this.selEnd = Number(this.selEnd);
} else if (inputValue !== MaskExpression.EMPTY_STRING && actualResult.length) {
if (typeof this.selStart === "number" && typeof this.selEnd === "number") {
if (inputValue.length > actualResult.length) actualResult.splice(this.selStart, 0, getSymbol);
else if (inputValue.length < actualResult.length) {
if (actualResult.length - inputValue.length === 1) {
if (backspaced) actualResult.splice(this.selStart - 1, 1);
else actualResult.splice(inputValue.length - 1, 1);
} else actualResult.splice(this.selStart, this.selEnd - this.selStart);
}
}
} else actualResult = [];
if (this.actualValue.length) {
if (actualResult.length < inputValue.length) newInputValue = this.shiftTypedSymbols(actualResult.join(MaskExpression.EMPTY_STRING));
else if (actualResult.length === inputValue.length) newInputValue = actualResult.join(MaskExpression.EMPTY_STRING);
else newInputValue = inputValue;
} else newInputValue = inputValue;
}
if (justPasted) newInputValue = inputValue;
if (backspaced && this.specialCharacters.indexOf(this.maskExpression[newPosition] ?? MaskExpression.EMPTY_STRING) !== -1 && this.showMaskTyped && !this.prefix) newInputValue = this.currentValue;
if (this.deletedSpecialCharacter && newPosition) {
if (this.specialCharacters.includes(this.actualValue.slice(newPosition, newPosition + 1))) newPosition = newPosition + 1;
else if (maskExpression.slice(newPosition - 1, newPosition + 1) !== MaskExpression.MONTHS) newPosition = newPosition - 2;
this.deletedSpecialCharacter = false;
}
if (this.showMaskTyped && this.placeHolderCharacter.length === 1 && !this.leadZeroDateTime) newInputValue = this.removeMask(newInputValue);
if (this.maskChanged) newInputValue = inputValue;
else newInputValue = Boolean(newInputValue) && newInputValue.length ? newInputValue : inputValue;
if ((kcpHandled ?? this.showMaskTyped) && this.keepCharacterPositions && this.actualValue && !justPasted && !this.writingValue) {
const value = this.dropSpecialCharacters ? this.removeMask(this.actualValue) : this.actualValue;
this.formControlResult(value);
return this.actualValue ? this.actualValue : `${this.prefix}${this.maskIsShown}${this.suffix}`;
}
const result = super.applyMask(newInputValue, maskExpression, newPosition, justPasted, backspaced, cb);
if ((this.writingValue || !this.maskChanged && inputValue === this.currentValue) && inputValue && !result && this.removeMask(inputValue)) {
this.actualValue = inputValue;
this.previousValue = this.currentValue;
this.currentValue = inputValue;
return inputValue;
}
this.actualValue = this.getActualValue(result);
if (this.thousandSeparator === MaskExpression.DOT && this.decimalMarker === MaskExpression.DOT) this.decimalMarker = MaskExpression.COMMA;
if (this.maskExpression.startsWith(MaskExpression.SEPARATOR) && this.dropSpecialCharacters === true) this.specialCharacters = this.specialCharacters.filter((item) => !this._compareOrIncludes(item, this.decimalMarker, this.thousandSeparator));
if (result || result === "") {
this.previousValue = this.currentValue;
this.currentValue = result;
this._emitValue = this.previousValue !== this.currentValue || this.previousValue === this.currentValue && justPasted;
}
if (this._emitValue && (!this.maskChanged || this.triggerOnMaskChange)) this.formControlResult(result);
this._clearMaskChangedUnlessTriggered();
if (!this.showMaskTyped || this.showMaskTyped && this.hiddenInput) {
if (this.hiddenInput) return `${this.hideInput(result, this.maskExpression)}${this.maskIsShown.slice(result.length)}`;
return result;
}
const resLen = result.length;
const prefNmask = `${this.prefix}${this.maskIsShown}${this.suffix}`;
if (this.maskExpression === MaskExpression.IP || this.maskExpression === MaskExpression.CPF_CNPJ || this.maskExpression === MaskExpression.CPF_CNPJ_ALPHA) return `${result}${prefNmask}`;
else if (this.maskExpression.includes(MaskExpression.HOURS)) {
const countSkipedSymbol = this._numberSkipedSymbols(result);
return `${result}${prefNmask.slice(resLen + countSkipedSymbol)}`;
}
return `${result}${prefNmask.slice(resLen)}`;
}
_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, justPasted, backspaced, cb = () => {}) {
const formElement = this._elementRef?.nativeElement;
if (!formElement) return;
const typedValue = formElement.value;
const maskedValue = this.applyMask(typedValue, this.maskExpression, position, justPasted, backspaced, cb);
if (formElement.value === typedValue && formElement.value !== maskedValue) formElement.value = maskedValue;
if (formElement === this._getActiveElement()) return;
this.clearIfNotMatchFn();
}
hideInput(inputValue, maskExpression) {
return inputValue.split(MaskExpression.EMPTY_STRING).map((curr, index) => {
const maskChar = maskExpression[index] ?? MaskExpression.EMPTY_STRING;
const pattern = this.patterns?.[maskChar];
if (pattern?.symbol) return pattern.symbol;
const prevMaskChar = maskExpression[index - 1];
const prevPattern = prevMaskChar ? this.patterns?.[prevMaskChar] : null;
if (maskChar === MaskExpression.NUMBER_ZERO && (prevMaskChar === MaskExpression.DAY || prevMaskChar === MaskExpression.MONTH) && prevPattern?.symbol) return prevPattern.symbol;
return curr;
}).join(MaskExpression.EMPTY_STRING);
}
getActualValue(res) {
const compare = res.split(MaskExpression.EMPTY_STRING).filter((symbol, i) => {
const maskChar = this.maskExpression[i] ?? MaskExpression.EMPTY_STRING;
return this._checkSymbolMask(symbol, maskChar) || this.specialCharacters.includes(maskChar) && symbol === maskChar;
});
if (compare.join(MaskExpression.EMPTY_STRING) === res) return compare.join(MaskExpression.EMPTY_STRING);
return res;
}
shiftTypedSymbols(inputValue) {
let symbolToReplace = "";
return (inputValue && inputValue.split(MaskExpression.EMPTY_STRING).map((currSymbol, index) => {
if (this.specialCharacters.includes(inputValue[index + 1] ?? MaskExpression.EMPTY_STRING) && inputValue[index + 1] !== this.maskExpression[index + 1]) {
symbolToReplace = currSymbol;
return inputValue[index + 1];
}
if (symbolToReplace.length) {
const replaceSymbol = symbolToReplace;
symbolToReplace = MaskExpression.EMPTY_STRING;
return replaceSymbol;
}
return currSymbol;
}) || []).join(MaskExpression.EMPTY_STRING);
}
numberToString(value) {
if (!value && value !== 0 || this.maskExpression.startsWith(MaskExpression.SEPARATOR) && (this.leadZero || !this.dropSpecialCharacters) || this.maskExpression.startsWith(MaskExpression.SEPARATOR) && this.separatorLimit.length > 14 && String(value).length > 14) return String(value);
return this._toPlainDecimalString(Number(value));
}
_toPlainDecimalString(value) {
const stringValue = String(value);
const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(stringValue);
if (!match) return stringValue;
const [, sign, integerPart, fractionPart = "", exponentPart] = match;
const exponent = Number(exponentPart);
const digits = `${integerPart}${fractionPart}`;
const pointIndex = integerPart.length + exponent;
if (pointIndex <= 0) return `${sign}0.${"0".repeat(-pointIndex)}${digits}`;
if (pointIndex >= digits.length) return `${sign}${digits}${"0".repeat(pointIndex - digits.length)}`;
return `${sign}${digits.slice(0, pointIndex)}.${digits.slice(pointIndex)}`;
}
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 === MaskExpression.IP) return this._checkForIp(inputVal);
if (this.maskExpression === MaskExpression.CPF_CNPJ || this.maskExpression === MaskExpression.CPF_CNPJ_ALPHA) return this._checkForCpfCnpj(inputVal);
}
if (this.placeHolderCharacter.length === this.maskExpression.length) return this.placeHolderCharacter;
return this.maskExpression.replace(/\w/g, this.placeHolderCharacter);
}
return "";
}
clearIfNotMatchFn() {
const formElement = this._elementRef?.nativeElement;
if (!formElement) return;
if (this.clearIfNotMatch && this.prefix.length + this.maskExpression.length + this.suffix.length !== formElement.value.replace(this.placeHolderCharacter, MaskExpression.EMPTY_STRING).length) {
this.formElementProperty = ["value", MaskExpression.EMPTY_STRING];
this.applyMask("", this.maskExpression);
}
}
set formElementProperty([name, value]) {
if (!this._renderer || !this._elementRef) return;
queueMicrotask(() => {
const element = this._elementRef?.nativeElement;
if (element && Reflect.get(element, name) !== value) this._renderer?.setProperty(element, name, value);
});
}
checkDropSpecialCharAmount(mask) {
return mask.split(MaskExpression.EMPTY_STRING).filter((item) => this._findDropSpecialChar(item)).length;
}
_clearMaskChangedUnlessTriggered() {
if (!this.triggerOnMaskChange) this.maskChanged = false;
}
removeMask(inputValue) {
return this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.specialCharacters.concat("_").concat(this.placeHolderCharacter));
}
_checkForIp(inputVal) {
if (inputVal === MaskExpression.HASH) return `${this.placeHolderCharacter}.${this.placeHolderCharacter}.${this.placeHolderCharacter}.${this.placeHolderCharacter}`;
const arr = digitsOf(inputVal);
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 === MaskExpression.HASH) return cpf;
const isCpfCnpjAlpha = this.maskExpression === MaskExpression.CPF_CNPJ_ALPHA;
const hasAnyLetter = /[a-zA-Z]/.test(inputVal);
const arr = this._countCpfCnpjTypedChars(inputVal, isCpfCnpjAlpha);
if (isCpfCnpjAlpha && hasAnyLetter) {
if (arr.length <= 2) return cnpj.slice(arr.length, cnpj.length);
if (arr.length > 2 && arr.length <= 5) return cnpj.slice(arr.length + 1, cnpj.length);
if (arr.length > 5 && arr.length <= 8) return cnpj.slice(arr.length + 2, cnpj.length);
if (arr.length > 8 && arr.length <= 12) return cnpj.slice(arr.length + 3, cnpj.length);
return cnpj.slice(arr.length + 4, cnpj.length);
} else {
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 "";
}
_countCpfCnpjTypedChars(inputVal, isCpfCnpjAlpha) {
return isCpfCnpjAlpha ? inputVal.match(/[a-zA-Z0-9]/g) ?? [] : digitsOf(inputVal);
}
_getActiveElement(document = this.document) {
const shadowRootEl = document?.activeElement?.shadowRoot;
if (!shadowRootEl?.activeElement) return document.activeElement;
else return this._getActiveElement(shadowRootEl);
}
formControlResult(inputValue) {
const outputTransformFn = this.outputTransformFn ? this.outputTransformFn : (v) => v;
this.writingValue = false;
this.maskChanged = false;
if (!this.isInitialized && this._emitValue) return;
if (this.showMaskTyped && this.placeHolderCharacter.length === 1 && this.specialCharacters.indexOf(this.placeHolderCharacter) === -1) inputValue = inputValue.split(this.placeHolderCharacter).join(MaskExpression.EMPTY_STRING);
const keepSpecialCharacters = this.maskExpression === MaskExpression.EMAIL_MASK;
if (Array.isArray(this.dropSpecialCharacters) && !keepSpecialCharacters) this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.dropSpecialCharacters)))));
else if (!keepSpecialCharacters && (this.dropSpecialCharacters || !this.dropSpecialCharacters && this.prefix === inputValue)) this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeSuffix(this._removePrefix(inputValue))))));
else this.onChange(outputTransformFn(this._toNumber(inputValue)));
}
_toNumber(value) {
if (!this.isNumberValue || value === MaskExpression.EMPTY_STRING) return value;
if (this.maskExpression.startsWith(MaskExpression.SEPARATOR) && (this.leadZero || !this.dropSpecialCharacters)) return value;
if (String(value).length > 14 && this.maskExpression.startsWith(MaskExpression.SEPARATOR)) return String(value);
const num = Number(value);
if (this.maskExpression.startsWith(MaskExpression.SEPARATOR) && Number.isNaN(num)) {
const val = String(value).replace(",", ".");
return Number(val);
}
return Number.isNaN(num) ? value : num;
}
_removeMask(value, specialCharactersForRemove) {
if (this.maskExpression.startsWith(MaskExpression.PERCENT) && (value.includes(MaskExpression.DOT) || Array.isArray(this.decimalMarker) && value.includes(MaskExpression.COMMA) && this._activeDecimalMarker(value) === MaskExpression.COMMA)) return value;
return value ? value.replace(this._regExpForRemove(specialCharactersForRemove), MaskExpression.EMPTY_STRING) : value;
}
_removePrefix(value) {
if (!this.prefix) return value;
return value ? value.replace(this.prefix, MaskExpression.EMPTY_STRING) : value;
}
_removeSuffix(value) {
if (!this.suffix) return value;
return value ? value.replace(this.suffix, MaskExpression.EMPTY_STRING) : value;
}
_retrieveSeparatorValue(result) {
let specialCharacters = Array.isArray(this.dropSpecialCharacters) ? this.specialCharacters.filter((v) => {
return this.dropSpecialCharacters.includes(v);
}) : this.specialCharacters;
if (!this.deletedSpecialCharacter && this._checkPatternForSpace() && result.includes(MaskExpression.WHITE_SPACE) && this.maskExpression.includes(MaskExpression.SYMBOL_STAR)) specialCharacters = specialCharacters.filter((char) => char !== MaskExpression.WHITE_SPACE);
if (!Array.isArray(this.dropSpecialCharacters) && this.maskExpression.startsWith(MaskExpression.SEPARATOR) && this.thousandSeparator && !specialCharacters.includes(this.thousandSeparator)) specialCharacters = [...specialCharacters, this.thousandSeparator];
return this._removeMask(result, specialCharacters);
}
_regExpForRemove(specialCharactersForRemove) {
return new RegExp(specialCharactersForRemove.map((item) => escapeRegExp(item)).join("|"), "gi");
}
_replaceDecimalMarkerToDot(value) {
const markers = Array.isArray(this.decimalMarker) ? this.decimalMarker : [this.decimalMarker];
return value.replace(this._regExpForRemove(markers), MaskExpression.DOT);
}
_checkSymbols(result) {
let processedResult = result;
if (processedResult === MaskExpression.EMPTY_STRING) return processedResult;
if (this.maskExpression.startsWith(MaskExpression.PERCENT) && this._activeDecimalMarker(processedResult) === MaskExpression.COMMA) processedResult = processedResult.replace(MaskExpression.COMMA, MaskExpression.DOT);
const separatorPrecision = this._retrieveSeparatorPrecision(this.maskExpression);
const separatorValue = this.specialCharacters.length === 0 ? this._retrieveSeparatorValue(processedResult) : this._replaceDecimalMarkerToDot(this._retrieveSeparatorValue(processedResult));
if (!this.isNumberValue) return separatorValue;
if (separatorPrecision) {
if (processedResult === this.decimalMarker) return null;
if (separatorValue.length > 14) return String(separatorValue);
return this._checkPrecision(this.maskExpression, separatorValue);
} else return separatorValue;
}
_checkPatternForSpace() {
for (const key in this.patterns) if (this.patterns[key] && this.patterns[key]?.hasOwnProperty("pattern")) {
const patternString = this.patterns[key]?.pattern.toString();
const pattern = this.patterns[key]?.pattern;
if (patternString?.includes(MaskExpression.WHITE_SPACE) && pattern && this._testPattern(pattern, this.maskExpression)) return true;
}
return false;
}
_retrieveSeparatorPrecision(maskExpression) {
const matcher = maskExpression.match(new RegExp(`^separator\\.([^d]*)`));
return matcher ? Number(matcher[1]) : null;
}
_checkPrecision(separatorExpression, separatorValue) {
const separatorPrecision = this.getPrecision(separatorExpression);
let value = separatorValue;
if (separatorExpression.indexOf("2") > 0 && !this._isFocused() || this.leadZero && !this._isFocused() && Number(separatorPrecision) > 0 && Number.isFinite(separatorPrecision)) {
if (this.leadZero && usesCommaDecimal(value, this.decimalMarker, this.thousandSeparator)) value = value.replace(MaskExpression.COMMA, MaskExpression.DOT);
if (Number.isNaN(Number(value))) return separatorValue;
const precision = this.leadZero ? Number(separatorPrecision) : 2;
if (this._exceedsDoublePrecision(value)) return this._stringToFixed(value, precision);
return Number(value).toFixed(precision);
}
return this.numberToString(value);
}
_exceedsDoublePrecision(value) {
if (!/^-?\d+(\.\d+)?$/.test(value)) return false;
return value.replace(/\D/g, "").replace(/^0+/, "").length > 15;
}
_stringToFixed(value, precision) {
const isNegative = value.startsWith(MaskExpression.MINUS);
const [integerPart = "0", fractionPart = ""] = (isNegative ? value.slice(1) : value).split(MaskExpression.DOT);
const paddedFraction = fractionPart.padEnd(precision + 1, "0");
const keptFraction = paddedFraction.slice(0, precision);
const shouldRoundUp = (paddedFraction.charCodeAt(precision) || 0) >= 53;
let scaled = BigInt(integerPart + keptFraction);
if (shouldRoundUp) scaled += 1n;
const digits = scaled.toString().padStart(precision + 1, "0");
const sign = isNegative && scaled > 0n ? MaskExpression.MINUS : "";
return precision > 0 ? `${sign}${digits.slice(0, -precision)}.${digits.slice(-precision)}` : `${sign}${digits}`;
}
_repeatPatternSymbols(maskExp) {
let start = 0;
return maskExp.match(/{[0-9]+}/) && maskExp.split(MaskExpression.EMPTY_STRING).reduce((accum, currVal, index) => {
start = currVal === MaskExpression.CURLY_BRACKETS_LEFT ? index : start;
if (currVal !== MaskExpression.CURLY_BRACKETS_RIGHT) return this._findSpecialChar(currVal) ? accum + currVal : accum;
const repeatNumber = Number(maskExp.slice(start + 1, index));
const replaceWith = new Array(repeatNumber + 1).join(maskExp[start - 1]);
if (maskExp.slice(0, start).length > 1 && maskExp.includes(MaskExpression.LETTER_S)) {
const symbols = maskExp.slice(0, start - 1);
return symbols.includes(MaskExpression.CURLY_BRACKETS_LEFT) ? accum + replaceWith : symbols + accum + replaceWith;
} else return accum + replaceWith;
}, "") || maskExp;
}
currentLocaleDecimalMarker() {
return MaskExpression.DOT;
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskService,
deps: null,
target: i0.ɵɵFactoryTarget.Injectable
});
static ɵprov = i0.ɵɵngDeclareInjectable({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskService
});
};
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskService,
decorators: [{ type: Injectable }]
});
function _configFactory() {
const initConfig = inject(INITIAL_CONFIG);
const configValue = inject(NEW_CONFIG);
return configValue instanceof Function ? {
...initConfig,
...configValue()
} : {
...initConfig,
...configValue
};
}
function provideNgxMask(configValue) {
return [
{
provide: NEW_CONFIG,
useValue: configValue
},
{
provide: INITIAL_CONFIG,
useValue: initialConfig
},
{
provide: NGX_MASK_CONFIG,
useFactory: _configFactory
},
NgxMaskService
];
}
function provideEnvironmentNgxMask(configValue) {
return makeEnvironmentProviders(provideNgxMask(configValue));
}
function resolveKeepCharacterPositions(svc, edit) {
const { prefix, suffix, showMaskTyped, maskExpression, placeHolderCharacter, specialCharacters, selStart: selStartRaw, selEnd: selEndRaw } = svc;
const { elValue } = edit;
let position = edit.position;
let actualValue = null;
const inputSymbol = elValue.slice(position - 1, position);
const prefixLength = prefix.length;
const maskSkeleton = svc.maskIsShown.length ? svc.maskIsShown : maskExpression.replace(/\w/g, placeHolderCharacter);
const hasSelection = selStartRaw !== selEndRaw;
const selStartAbs = Number(selStartRaw);
const selEndAbs = Number(selEndRaw);
const selStart = selStartAbs - prefixLength;
const selEnd = selEndAbs - prefixLength;
const restoreBefore = (preEditLength) => svc.actualValue && edit.before.length === preEditLength && edit.before.startsWith(elValue.slice(0, selStartAbs)) ? edit.before : null;
const maskEndOf = (preEditLength) => preEditLength - suffix.length;
const blankEndOf = (preEditLength) => Math.max(Math.min(selEndAbs, maskEndOf(preEditLength)) - prefixLength, 0);
const backspaceOrDelete = edit.code === MaskExpression.BACKSPACE || edit.code === MaskExpression.DELETE;
let handled = true;
let trimmedBodyEnd = null;
if (backspaceOrDelete) {
if (hasSelection) {
const preEditLength = elValue.length + (selEndAbs - selStartAbs);
const maskEndAbs = maskEndOf(preEditLength);
const blankEnd = blankEndOf(preEditLength);
const displayBefore = restoreBefore(preEditLength) ?? svc.actualValue;
if (!showMaskTyped && selStartAbs <= prefixLength && selEndAbs >= preEditLength) handled = false;
else if (selStartRaw === prefixLength) {
const rest = selEndAbs > maskEndAbs ? suffix : elValue.split(prefix).join("");
actualValue = `${prefix}${maskSkeleton.slice(0, blankEnd)}${rest}`;
} else if (selStartRaw === maskSkeleton.length + prefixLength) actualValue = `${elValue}${maskSkeleton.slice(selStart, selEnd)}`;
else actualValue = `${prefix}${elValue.split(prefix).join("").slice(0, selStart)}${maskSkeleton.slice(selStart, blankEnd)}${displayBefore.slice(selEndAbs, maskEndAbs)}${suffix}`;
if (!showMaskTyped && actualValue !== null && selEndAbs >= maskEndAbs) {
const bodyEnd = actualValue.endsWith(suffix) ? actualValue.length - suffix.length : actualValue.length;
let end = bodyEnd;
while (end > prefixLength && (actualValue.charAt(end - 1) === placeHolderCharacter || specialCharacters.includes(actualValue.charAt(end - 1)))) end--;
actualValue = `${actualValue.slice(0, end)}${actualValue.slice(bodyEnd)}`;
trimmedBodyEnd = end;
}
} else if (!specialCharacters.includes(maskExpression.slice(position - prefixLength, position + 1 - prefixLength))) {
if (!showMaskTyped && position >= elValue.length) handled = false;
else if (selStart === 1 && prefix) {
actualValue = `${prefix}${placeHolderCharacter}${elValue.split(prefix).join("").split(suffix).join("")}${suffix}`;
position = position - 1;
} else actualValue = `${elValue.substring(0, position)}${placeHolderCharacter}${elValue.substring(position)}`;
}
position = edit.code === MaskExpression.DELETE && !hasSelection ? position + 1 : position;
if (trimmedBodyEnd !== null && suffix) position = Math.min(position, trimmedBodyEnd);
} else if (hasSelection) {
const preEditLength = elValue.length - 1 + (selEndAbs - selStartAbs);
const typedSymbol = elValue.charAt(selStartAbs);
if (!showMaskTyped && selStartAbs <= prefixLength && selEndAbs >= preEditLength) handled = false;
else {
const maskIdx = nextFillableIndex(maskExpression, specialCharacters, Math.max(selStart, 0));
const head = selStartAbs < prefixLength ? prefix : elValue.slice(0, selStartAbs);
const maskEndAbs = maskEndOf(preEditLength);
const prefixLeft = Math.max(prefixLength - selEndAbs, 0);
const tail = selEndAbs > maskEndAbs ? suffix : elValue.slice(selStartAbs + 1 + prefixLeft);
const blankEnd = blankEndOf(preEditLength);
const blanked = `${head}${maskSkeleton.slice(Math.max(selStart, 0), blankEnd)}${tail}`;
const targetAbs = maskIdx + prefixLength;
if (targetAbs < blanked.length - suffix.length && svc._checkSymbolMask(typedSymbol, maskExpression[maskIdx] ?? MaskExpression.EMPTY_STRING)) {
actualValue = `${blanked.slice(0, targetAbs)}${typedSymbol}${blanked.slice(targetAbs + 1)}`;
position = targetAbs + 1;
} else {
actualValue = restoreBefore(preEditLength) ?? blanked;
position = Math.max(selStartAbs, prefixLength);
}
}
} else {
const oldDisplay = `${elValue.slice(0, position - 1)}${elValue.slice(position)}`;
const oldDisplayNoSuffix = suffix ? oldDisplay.split(suffix).join("") : oldDisplay;
if (position - 1 - prefixLength < 0) {
actualValue = restoreBefore(elValue.length - 1);
position = position - 1;
} else {
const maskIdx = nextFillableIndex(maskExpression, specialCharacters, position - 1 - prefixLength);
const targetAbs = maskIdx + prefixLength;
if (targetAbs >= oldDisplayNoSuffix.length) {
if (oldDisplayNoSuffix.length <= prefixLength || !showMaskTyped) handled = false;
else {
actualValue = restoreBefore(elValue.length - 1);
position = position - 1;
}
} else if (svc._checkSymbolMask(inputSymbol, maskExpression[maskIdx] ?? MaskExpression.EMPTY_STRING)) {
actualValue = `${oldDisplayNoSuffix.slice(0, targetAbs)}${inputSymbol}${oldDisplayNoSuffix.slice(targetAbs + 1)}${suffix}`;
position = targetAbs + 1;
} else {
actualValue = restoreBefore(elValue.length - 1);
position = position - 1;
}
}
}
return {
actualValue,
position,
handled
};
}
const notFilled = (errors) => ({
errors,
filled: false
});
const suffixStart = (display, suffix) => suffix && display.endsWith(suffix) ? display.length - suffix.length : Infinity;
const MIRRORED_INPUTS = [
"instantPrefix",
"prefix",
"suffix",
"decimalMarker",
"dropSpecialCharacters",
"shownMaskExpression",
"clearIfNotMatch",
"validation",
"separatorLimit",
"typeFromDecimals",
"leadZeroDateTime",
"leadZero",
"triggerOnMaskChange",
"inputTransformFn",
"outputTransformFn",
"keepCharacterPositions"
];
var NgxMaskDirective = class NgxMaskDirective {
mask = input("", ...ngDevMode ? [{ debugName: "mask" }] : /* istanbul ignore next */ []);
specialCharacters = input([], ...ngDevMode ? [{ debugName: "specialCharacters" }] : /* istanbul ignore next */ []);
patterns = input({}, ...ngDevMode ? [{ debugName: "patterns" }] : /* istanbul ignore next */ []);
prefix = input("", ...ngDevMode ? [{ debugName: "prefix" }] : /* istanbul ignore next */ []);
suffix = input("", ...ngDevMode ? [{ debugName: "suffix" }] : /* istanbul ignore next */ []);
thousandSeparator = input(" ", ...ngDevMode ? [{ debugName: "thousandSeparator" }] : /* istanbul ignore next */ []);
decimalMarker = input(".", ...ngDevMode ? [{ debugName: "decimalMarker" }] : /* istanbul ignore next */ []);
dropSpecialCharacters = input(null, ...ngDevMode ? [{ debugName: "dropSpecialCharacters" }] : /* istanbul ignore next */ []);
hiddenInput = input(null, ...ngDevMode ? [{ debugName: "hiddenInput" }] : /* istanbul ignore next */ []);
showMaskTyped = input(null, ...ngDevMode ? [{ debugName: "showMaskTyped" }] : /* istanbul ignore next */ []);
placeHolderCharacter = input(null, ...ngDevMode ? [{ debugName: "placeHolderCharacter" }] : /* istanbul ignore next */ []);
shownMaskExpression = input(null, ...ngDevMode ? [{ debugName: "shownMaskExpression" }] : /* istanbul ignore next */ []);
clearIfNotMatch = input(null, ...ngDevMode ? [{ debugName: "clearIfNotMatch" }] : /* istanbul ignore next */ []);
validation = input(null, ...ngDevMode ? [{ debugName: "validation" }] : /* istanbul ignore next */ []);
separatorLimit = input("", ...ngDevMode ? [{ debugName: "separatorLimit" }] : /* istanbul ignore next */ []);
typeFromDecimals = input(null, ...ngDevMode ? [{ debugName: "typeFromDecimals" }] : /* istanbul ignore next */ []);
allowNegativeNumbers = input(null, ...ngDevMode ? [{ debugName: "allowNegativeNumbers" }] : /* istanbul ignore next */ []);
leadZeroDateTime = input(null, ...ngDevMode ? [{ debugName: "leadZeroDateTime" }] : /* istanbul ignore next */ []);
leadZero = input(null, ...ngDevMode ? [{ debugName: "leadZero" }] : /* istanbul ignore next */ []);
triggerOnMaskChange = input(null, ...ngDevMode ? [{ debugName: "triggerOnMaskChange" }] : /* istanbul ignore next */ []);
apm = input(null, ...ngDevMode ? [{ debugName: "apm" }] : /* istanbul ignore next */ []);
inputTransformFn = input(null, ...ngDevMode ? [{ debugName: "inputTransformFn" }] : /* istanbul ignore next */ []);
outputTransformFn = input(null, ...ngDevMode ? [{ debugName: "outputTransformFn" }] : /* istanbul ignore next */ []);
keepCharacterPositions = input(null, ...ngDevMode ? [{ debugName: "keepCharacterPositions" }] : /* istanbul ignore next */ []);
instantPrefix = input(null, ...ngDevMode ? [{ debugName: "instantPrefix" }] : /* istanbul ignore next */ []);
defaultValueOnBlur = input(null, ...ngDevMode ? [{ debugName: "defaultValueOnBlur" }] : /* istanbul ignore next */ []);
value = model("", ...ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []);
disabled = input(false, {
...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
transform: booleanAttribute
});
touched = model(false, ...ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []);
maskFilled = output();
_maskValue = signal("", ...ngDevMode ? [{ debugName: "_maskValue" }] : /* istanbul ignore next */ []);
_inputValue = signal("", ...ngDevMode ? [{ debugName: "_inputValue" }] : /* istanbul ignore next */ []);
_code = signal("", ...ngDevMode ? [{ debugName: "_code" }] : /* istanbul ignore next */ []);
_maskExpressionArray = signal([], ...ngDevMode ? [{ debugName: "_maskExpressionArray" }] : /* istanbul ignore next */ []);
_justPasted = signal(false, ...ngDevMode ? [{ debugName: "_justPasted" }] : /* istanbul ignore next */ []);
_isComposing = signal(false, ...ngDevMode ? [{ debugName: "_isComposing" }] : /* istanbul ignore next */ []);
_isCvaMode = signal(false, ...ngDevMode ? [{ debugName: "_isCvaMode" }] : /* istanbul ignore next */ []);
_skipNextValueEffect = signal(false, ...ngDevMode ? [{ debugName: "_skipNextValueEffect" }] : /* istanbul ignore next */ []);
_lastPropagatedValue = null;
_writtenValue = null;
_writeSequence = 0;
_programmaticWrite = false;
_valuePropagatedByEdit = null;
_configApplied = false;
_pendingInitialValue;
_hasPendingInitialValue = false;
_disabledEverSet = false;
_warnedAboutMultiCharPlaceholder = false;
_maskService = inject(NgxMaskService, { self: true });
_config = inject(NGX_MASK_CONFIG);
_boundConfig = {
specialCharacters: this._config.specialCharacters,
decimalMarker: this._config.decimalMarker
};
_changeDetectorRef = inject(ChangeDetectorRef);
_elementRef = inject(ElementRef);
_renderer = inject(Renderer2);
_injector = inject(Injector);
_ngZone = inject(NgZone);
_ngControl;
_resolveNgControl() {
if (typeof this._ngControl === "undefined") this._ngControl = this._injector.get(NgControl, null);
return this._ngControl;
}
onChange = (_) => {};
onTouch = () => {};
_onValidatorChange = () => {};
constructor() {
this._maskService.onChange = this.onChange = (value) => {
this._propagateToValueModel(value);
};
effect(() => {
const signalValue = this.value();
untracked(() => {
if (this._valueEffectWillWrite(signalValue)) this.writeValue(signalValue);
else this._skipNextValueEffect.set(false);
});
});
effect(() => {
const isDisabled = this.disabled();
untracked(() => {
if (!isDisabled && !this._disabledEverSet) return;
this._disabledEverSet = true;
this.setDisabledState(isDisabled);
});
});
}
ngOnChanges(changes) {
const { mask, specialCharacters, patterns, prefix, suffix, thousandSeparator, decimalMarker, dropSpecialCharacters, hiddenInput, showMaskTyped, placeHolderCharacter, clearIfNotMatch, validation, allowNegativeNumbers, apm } = changes;
const previousSeparators = {
thousandSeparator: this._maskService.thousandSeparator,
decimalMarker: this._maskService.decimalMarker,
prefix: this._maskService.prefix,
suffix: this._maskService.suffix
};
if (mask) {
if (mask.currentValue !== mask.previousValue && !mask.firstChange) this._maskService.maskChanged = true;
const resolvedMask = this._resolvedMaskInput();
const maskParts = resolvedMask.split(MaskExpression.OR);
if (maskParts.length > 1) {
this._maskExpressionArray.set(maskParts.sort((a, b) => a.length - b.length));
this._setMask();
} else {
this._maskExpressionArray.set([]);
this._maskValue.set(resolvedMask);
this._maskService.maskExpression = this._maskValue();
}
}
if (specialCharacters) {
if (Array.isArray(specialCharacters.currentValue)) {
this._boundConfig.specialCharacters = specialCharacters.currentValue;
this._maskService.specialCharacters = specialCharacters.currentValue;
}
}
if (allowNegativeNumbers) {
this._maskService.allowNegativeNumbers = allowNegativeNumbers.currentValue;
this._keepMinusOutOfSpecialCharacters();
}
if (patterns && patterns.currentValue) this._maskService.patterns = patterns.currentValue;
if (apm) this._maskService.apm = apm.currentValue ?? this._config.apm;
if (decimalMarker) this._boundConfig.decimalMarker = decimalMarker.currentValue;
if (thousandSeparator) {
this._maskService.thousandSeparator = thousandSeparator.currentValue;
if (thousandSeparator.previousValue && thousandSeparator.currentValue) {
this._maskService.decimalMarker = this._boundConfig.decimalMarker;
if (thousandSeparator.currentValue === this._maskService.decimalMarker) this._maskService.decimalMarker = thousandSeparator.currentValue === MaskExpression.COMMA ? MaskExpression.DOT : MaskExpression.COMMA;
this._maskService.writingValue = true;
}
}
if (hiddenInput) {
this._maskService.hiddenInput = hiddenInput.currentValue;
if (hiddenInput.previousValue === true && hiddenInput.currentValue === false) this._inputValue.set(this._maskService.actualValue);
}
if (showMaskTyped) {
this._maskService.showMaskTyped = showMaskTyped.currentValue;
if (showMaskTyped.previousValue === false && showMaskTyped.currentValue === true && this._maskService._isFocused()) afterNextRender(() => {
queueMicrotask(() => this._ngZone.run(() => {
this._maskService._elementRef?.nativeElement.click();
}));
}, { injector: this._injector });
}
if (placeHolderCharacter) {
this._maskService.placeHolderCharacter = placeHolderCharacter.currentValue ?? this._config.placeHolderCharacter;
if (typeof placeHolderCharacter.currentValue === "string" && placeHolderCharacter.currentValue.length > 1 && !this._warnedAboutMultiCharPlaceholder) {
this._warnedAboutMultiCharPlaceholder = true;
console.warn("Ngx-mask: placeHolderCharacter should be a single character; behavior with multi-character values is undefined (e.g. keepCharacterPositions will not work). Current value:", placeHolderCharacter.currentValue);
}
}
const specialCharactersSources = [
mask,
specialCharacters,
allowNegativeNumbers,
thousandSeparator,
decimalMarker,
dropSpecialCharacters
];
if (this._maskValue() && specialCharactersSources.some((change) => change && !change.firstChange)) {
this._maskService.specialCharacters = this._boundConfig.specialCharacters;
this._keepMinusOutOfSpecialCharacters();
}
for (const key of MIRRORED_INPUTS) {
const change = changes[key];
if (change) this._mirrorInput(key, change.currentValue);
}
const retargetSeparators = (thousandSeparator || decimalMarker) && this._configApplied && this._maskValue().startsWith(MaskExpression.SEPARATOR) && this._inputValue();
if (retargetSeparators) {
this._inputValue.set(retargetSeparatorValue(this._inputValue(), previousSeparators, this._maskService));
this._maskService.actualValue = this._inputValue();
this._maskService.writingValue = true;
this._writeSequence++;
}
const valueChange = changes["value"];
const valueEffectWillRender = !!valueChange && String(valueChange.currentValue) !== this._lastPropagatedValue && this._valueEffectWillWrite(valueChange.currentValue);
if (retargetSeparators && !this._maskService.maskChanged) this._writeProgrammatically(() => this._applyMask(!valueEffectWillRender));
else this._applyMask(!valueEffectWillRender);
if ([
mask,
validation,
patterns,
prefix,
suffix,
dropSpecialCharacters,
specialCharacters,
clearIfNotMatch
].some((change) => change && !change.firstChange)) this._onValidatorChange();
if (!this._configApplied) {
this._configApplied = true;
if (this._hasPendingInitialValue) {
this._hasPendingInitialValue = false;
const pendingValue = this._pendingInitialValue;
this._pendingInitialValue = null;
this.writeValue(pendingValue);
}
}
}
_keepMinusOutOfSpecialCharacters() {
if (this._maskService.allowNegativeNumbers) this._maskService.specialCharacters = this._maskService.specialCharacters.filter((c) => c !== MaskExpression.MINUS);
}
validate({ value }) {
return this._validateValue(value).errors;
}
_validateValue(value) {
const processedValue = typeof value === "number" ? String(value) : value;
const maskValue = this._maskValue();
if (!this._maskService.validation || !maskValue) return notFilled(null);
const isCpfCnpj = maskValue === MaskExpression.CPF_CNPJ || maskValue === MaskExpression.CPF_CNPJ_ALPHA;
if (maskValue === MaskExpression.IP && this._maskService.ipError) return notFilled(this._createValidationError(processedValue));
if (isCpfCnpj && this._maskService.cpfCnpjError) return notFilled(this._createValidationError(processedValue));
if (maskValue.startsWith(MaskExpression.SEPARATOR)) return notFilled(null);
if (withoutValidation.includes(maskValue)) return notFilled(null);
if (this._maskService.clearIfNotMatch) return notFilled(null);
if (timeMasks.includes(maskValue)) return notFilled(this._validateTime(processedValue));
if (maskValue === MaskExpression.EMAIL_MASK) {
if (!/^[^@]+@[^@]+\.[^@]+$/.test(processedValue) && processedValue) return notFilled(this._createValidationError(processedValue));
else return notFilled(null);
}
if (processedValue && processedValue.length >= 1) {
let counterOfOpt = 0;
if (maskValue.includes(MaskExpression.CURLY_BRACKETS_LEFT) && maskValue.includes(MaskExpression.CURLY_BRACKETS_RIGHT)) {
const lengthInsideCurlyBrackets = maskValue.slice(maskValue.indexOf(MaskExpression.CURLY_BRACKETS_LEFT) + 1, maskValue.indexOf(MaskExpression.CURLY_BRACKETS_RIGHT));
return notFilled(lengthInsideCurlyBrackets === String(processedValue.length) ? null : this._createValidationError(processedValue));
}
if (maskValue.startsWith(MaskExpression.PERCENT)) return notFilled(null);
for (const key in this._maskService.patterns) if (this._maskService.patterns[key]?.optional) {
if (maskValue.indexOf(key) !== maskValue.lastIndexOf(key)) {
const opt = maskValue.split(MaskExpression.EMPTY_STRING).filter((i) => i === key).join(MaskExpression.EMPTY_STRING);
counterOfOpt += opt.length;
} else if (maskValue.indexOf(key) !== -1) counterOfOpt++;
const firstOptionalIndex = maskValue.indexOf(key);
if (firstOptionalIndex !== -1 && processedValue.length >= firstOptionalIndex) {
if (!maskValue.slice(firstOptionalIndex).split(MaskExpression.EMPTY_STRING).some((symbol) => !!this._maskService.patterns[symbol] && !this._maskService.patterns[symbol]?.optional)) return notFilled(null);
if (!this._maskService.prefix && !this._maskService.suffix && this._isPlainTokenMask(maskValue)) return notFilled(this._matchesMaskWithOptionalSkip(processedValue, maskValue) ? null : this._createValidationError(processedValue));
}
if (counterOfOpt === maskValue.length) return notFilled(null);
}
if (maskValue.indexOf(MaskExpression.SYMBOL_STAR) > 1 && processedValue.length < maskValue.indexOf(MaskExpression.SYMBOL_STAR) || maskValue.indexOf(MaskExpression.SYMBOL_QUESTION) > 1 && processedValue.length < maskValue.indexOf(MaskExpression.SYMBOL_QUESTION)) return notFilled(this._createValidationError(processedValue));
if (maskValue.indexOf(MaskExpression.SYMBOL_STAR) === -1 || maskValue.indexOf(MaskExpression.SYMBOL_QUESTION) === -1) {
const array = maskValue.split("*");
const length = this._maskService.dropSpecialCharacters ? maskValue.length - this._maskService.checkDropSpecialCharAmount(maskValue) - counterOfOpt : this._maskService.prefix ? maskValue.length + this._maskService.prefix.length - counterOfOpt : maskValue.length - counterOfOpt;
if (array.length === 1) {
if (processedValue.length < length) {
if (this._isCompleteAlternativeBoundary(processedValue)) return notFilled(null);
return notFilled(this._createValidationError(processedValue));
}
}
if (array.length > 1) {
const lastIndexArray = array[array.length - 1];
if (lastIndexArray && this._maskService.specialCharacters.includes(lastIndexArray[0]) && String(processedValue).includes(lastIndexArray[0] ?? "") && !this._maskService.dropSpecialCharacters) {
const special = processedValue.split(lastIndexArray[0]);
return notFilled(special[special.length - 1]?.length === lastIndexArray.length - 1 ? null : this._createValidationError(processedValue));
} else if ((lastIndexArray && !this._maskService.specialCharacters.includes(lastIndexArray[0]) || !lastIndexArray || this._maskService.dropSpecialCharacters) && processedValue.length >= length - 1) return notFilled(null);
else return notFilled(this._createValidationError(processedValue));
}
}
if (maskValue.indexOf(MaskExpression.SYMBOL_STAR) === 1 || maskValue.indexOf(MaskExpression.SYMBOL_QUESTION) === 1) return notFilled(null);
}
return {
errors: null,
filled: !!value
};
}
onPaste() {
this._justPasted.set(true);
}
onFocus() {
this._maskService._isFocused.set(true);
}
onModelChange(value) {
if ((value === MaskExpression.EMPTY_STRING || value === null || typeof value === "undefined") && this._maskService.actualValue) this._maskService.actualValue = this._maskService.getActualValue(MaskExpression.EMPTY_STRING);
}
onInput(e) {
this._writeSequence++;
this._valuePropagatedByEdit = null;
this._processInput(e);
const propagated = this._valuePropagatedByEdit;
this._valuePropagatedByEdit = null;
if (propagated !== null && this._validateValue(propagated).filled) this.maskFilled.emit();
}
_isBulkInsertion(e) {
const { inputType, data } = e;
return typeof inputType === "string" && inputType.startsWith("insert") && inputType !== "insertCompositionText" && typeof data === "string" && data.length > 1;
}
_editEndsValue(el) {
return !this._maskService.removeMask(el.value.slice(el.selectionEnd ?? el.value.length));
}
_processInput(e) {
this._maskService.isInitialized = true;
const preEditValue = this._inputValue();
const inputType = e.inputType;
if (inputType === "deleteContentBackward") this._code.set(MaskExpression.BACKSPACE);
else if (inputType === "deleteContentForward") this._code.set(MaskExpression.DELETE);
else if (inputType && (this._code() === MaskExpression.BACKSPACE || this._code() === MaskExpression.DELETE)) this._code.set(inputType);
if (this._isComposing() && this._maskAcceptsLetterInput()) return;
const el = e.target;
const transformedValue = this._maskService.inputTransformFn ? this._maskService.inputTransformFn(el.value) : el.value;
if (el.type !== "number") {
if (typeof transformedValue === "string" || typeof transformedValue === "number") {
const transformedString = transformedValue.toString();
if (el.value !== transformedString) el.value = transformedString;
this._inputValue.set(el.value);
this._setMask();
if (!this._maskValue()) {
this.onChange(el.value);
return;
}
if (this._isBulkInsertion(e) && this._editEndsValue(el)) this._justPasted.set(true);
const pastedValueWithoutPrefix = this._justPasted() && !!this._maskService.prefix && !el.value.startsWith(this._maskService.prefix);
const caret = el.selectionStart;
let position = el.selectionStart === 1 ? el.selectionStart + this._maskService.prefix.length : el.selectionStart;
if (this._maskService.keepCharacterPositions && this._maskService.placeHolderCharacter.length === 1 && !this._justPasted()) {
const kcp = resolveKeepCharacterPositions(this._maskService, {
elValue: el.value,
position,
code: this._code(),
before: preEditValue
});
if (kcp.actualValue !== null) this._maskService.actualValue = kcp.actualValue;
position = kcp.position;
this._maskService.keepCharacterPositionsHandled = kcp.handled;
}
if (!this._maskService.hiddenInput && !this._maskService.showMaskTyped && !this._maskService.keepCharacterPositions && this._maskService.selStart !== this._maskService.selEnd && containsStarSymbol(el.value, this._maskService.maskExpression, this._maskService.thousandSeparator)) this._maskService.actualValue = MaskExpression.EMPTY_STRING;
let caretShift = 0;
let backspaceShift = false;
const isDeletion = this._code() === MaskExpression.BACKSPACE || this._code() === MaskExpression.DELETE;
if (this._code() === MaskExpression.DELETE) this._maskService.deletedSpecialCharacter = true;
const keepsFullDateOverwrite = this._justPasted() || this._maskService.prefix || this._maskService.suffix || this._maskService.keepCharacterPositions || this._maskService.hiddenInput;
const overwrite = keepsFullDateOverwrite || isDeletion ? null : overwriteDateSlot(this._maskService, preEditValue, el.value, position);
if (overwrite) {
el.value = overwrite.value;
this._inputValue.set(overwrite.value);
position = overwrite.position;
} else if (keepsFullDateOverwrite || !isDeletion) {
const overwrittenDate = overwriteFullDateValue(this._maskService.maskExpression, this._inputValue(), position, this._code());
if (overwrittenDate !== null) el.value = overwrittenDate;
}
if (this._maskService.leadZeroDateTime) {
const removedChar = isDeletion ? preEditValue[caret] ?? MaskExpression.EMPTY_STRING : MaskExpression.EMPTY_STRING;
setUserEdit(this._maskService, this._maskService.specialCharacters.includes(removedChar) ? null : {
value: el.value,
start: this._justPasted() ? 0 : caret - 1,
end: this._justPasted() ? el.value.length : overwrite?.end ?? caret
});
position = leadZeroDateTimeCaret(this._maskService.maskExpression, position, el.value, this._inputValue(), !!this._maskService.apm);
}
this._maskService.applyValueChanges(position, this._justPasted(), isDeletion, (shift, _backspaceShift) => {
this._justPasted.set(false);
caretShift = shift;
backspaceShift = _backspaceShift;
});
setUserEdit(this._maskService, null);
if (this._maskService._getActiveElement() !== el) return;
if (this._maskService.plusOnePosition) {
position = position + 1;
this._maskService.plusOnePosition = false;
}
if (this._maskExpressionArray().length) {
if (this._code() === MaskExpression.BACKSPACE) {
const specialChartMinusOne = this._maskService.specialCharacters.includes(this._maskService.actualValue.slice(position - 1, position));
const allowFewMaskChangeMask = this._maskService.removeMask(this._inputValue())?.length === this._maskService.removeMask(this._maskService.maskExpression)?.length;
const specialChartPlusOne = this._maskService.specialCharacters.includes(this._maskService.actualValue.slice(position, position + 1));
if (allowFewMaskChangeMask && !specialChartPlusOne) position = el.selectionStart + 1;
else position = specialChartMinusOne ? position - 1 : position;
} else position = el.selectionStart === 1 ? el.selectionStart + this._maskService.prefix.length : el.selectionStart;
}
let positionToApply = position + (this._code() === MaskExpression.BACKSPACE && !backspaceShift ? 0 : caretShift);
if (pastedValueWithoutPrefix && this._maskValue().startsWith(MaskExpression.SEPARATOR)) positionToApply += this._maskService.prefix.length;
if (positionToApply > this._getActualInputLength()) {
const decimalMarker = this._maskService.decimalMarker;
const prefix = this._maskService.prefix;
const valueWithoutPrefix = el.value.startsWith(prefix) ? el.value.slice(prefix.length) : el.value;
positionToApply = valueWithoutPrefix.length === 1 && (Array.isArray(decimalMarker) ? decimalMarker.includes(valueWithoutPrefix) : valueWithoutPrefix === decimalMarker) ? this._getActualInputLength() + 1 : this._getActualInputLength();
}
if (positionToApply < 0) positionToApply = 0;
if (el.selectionStart !== positionToApply || el.selectionEnd !== positionToApply) el.setSelectionRange(positionToApply, positionToApply);
} else console.warn("Ngx-mask writeValue work with string | number, your current value:", typeof transformedValue);
} else {
if (!this._maskValue()) {
this.onChange(el.value);
return;
}
this._maskService.applyValueChanges(el.value.length, this._justPasted(), this._code() === MaskExpression.BACKSPACE || this._code() === MaskExpression.DELETE);
}
}
_maskAcceptsLetterInput() {
for (const symbol of this._maskService.maskExpression) if (this._maskService._checkSymbolMask("a", symbol) || this._maskService._checkSymbolMask("A", symbol)) return true;
return false;
}
onCompositionStart() {
this._isComposing.set(true);
}
onCompositionEnd(e) {
this._isComposing.set(false);
if (!this._maskAcceptsLetterInput()) return;
this._justPasted.set(true);
this.onInput(e);
}
onBlur(e) {
if (this._maskValue()) {
const el = e.target;
if (this._applyDefaultValueOnBlur(el)) {
this._writeProgrammatically(() => this._formatOnBlur(el));
this._changeDetectorRef.markForCheck();
} else this._formatOnBlur(el);
}
this._maskService._isFocused.set(false);
this.onTouch();
}
_formatOnBlur(el) {
if (this._maskService.leadZero && el.value.length > 0 && typeof this._maskService.decimalMarker === "string") {
const maskExpression = this._maskService.maskExpression;
const decimalMarker = this._maskService.decimalMarker;
const suffix = this._maskService.suffix;
const precision = Number(this._maskService.maskExpression.slice(maskExpression.length - 1, maskExpression.length));
if (precision > 0) {
const unsuffixedValue = suffix ? el.value.split(suffix).join("") : el.value;
const decimalPart = unsuffixedValue.split(decimalMarker)[1];
const paddedValue = unsuffixedValue.includes(decimalMarker) ? unsuffixedValue + MaskExpression.NUMBER_ZERO.repeat(precision - (decimalPart?.length || 0)) + suffix : unsuffixedValue + decimalMarker + MaskExpression.NUMBER_ZERO.repeat(precision) + suffix;
if (paddedValue !== el.value) {
el.value = paddedValue;
this._maskService.actualValue = el.value;
this._maskService.formControlResult(this._maskService.actualValue);
}
}
}
this._maskService.clearIfNotMatchFn();
}
_applyDefaultValueOnBlur(el) {
const defaultValue = this.defaultValueOnBlur() ?? this._config.defaultValueOnBlur;
if (!defaultValue || this._maskService.removeMask(el.value)) return false;
const displayValue = this._writeProgrammatically(() => this._maskService.applyMask(defaultValue, this._maskService.maskExpression));
el.value = displayValue;
this._inputValue.set(displayValue);
return true;
}
onClick(e) {
if (!this._maskValue()) return;
const el = e.target;
if (el !== null && el.selectionStart !== null && el.selectionStart === el.selectionEnd && el.selectionStart > this._maskService.prefix.length) {
if (this._maskService.showMaskTyped && !this._maskService.keepCharacterPositions) {
this._maskService.maskIsShown = this._maskService.showMaskInInput();
if (el.setSelectionRange && this._maskService.prefix + this._maskService.maskIsShown === el.value) {
el.focus();
el.setSelectionRange(0, 0);
} else if (el.selectionStart > this._maskService.actualValue.length) el.setSelectionRange(this._maskService.actualValue.length, this._maskService.actualValue.length);
}
}
const nextValue = el && (el.value === this._maskService.prefix ? this._maskService.prefix + this._maskService.maskIsShown : el.value);
if (el && el.value !== nextValue) el.value = nextValue;
if (el && el.type !== "number" && (el.selectionStart || el.selectionEnd) <= this._maskService.prefix.length) {
const specialCharactersAtTheStart = this._maskService.maskExpression.match(new RegExp(`^[${this._maskService.specialCharacters.map((c) => escapeRegExp(c)).join("")}]+`))?.[0].length || 0;
el.selectionStart = this._maskService.prefix.length + specialCharactersAtTheStart;
return;
}
if (el && el.selectionEnd > this._getActualInputLength()) el.selectionEnd = this._getActualInputLength();
}
onKeyDown(event) {
const e = event;
if (!this._maskValue()) return;
if (this._isComposing()) {
if (e.key === "Enter") {
this.onCompositionEnd(event);
return;
}
const composingEl = e.target;
this._inputValue.set(composingEl.value);
this._maskService.selStart = composingEl.selectionStart;
this._maskService.selEnd = composingEl.selectionEnd;
return;
}
this._code.set(e.code ? e.code : e.key);
const el = e.target;
this._inputValue.set(el.value);
this._setMask();
const isTextarea = el.tagName.toLowerCase() === "textarea";
if (el.type !== "number") {
if (this._insertNumpadDecimalMarker(e, el)) return;
if (e.key === MaskExpression.ARROW_UP && !isTextarea) e.preventDefault();
if (e.key === MaskExpression.ARROW_LEFT || e.key === MaskExpression.BACKSPACE || e.key === MaskExpression.DELETE) {
if (e.key === MaskExpression.BACKSPACE && el.value.length === 0) el.selectionStart = el.selectionEnd;
if (e.key === MaskExpression.BACKSPACE && el.selectionStart !== 0) {
const prefixLength = this._maskService.prefix.length;
const specialCharacters = this._maskService.specialCharacters;
if (prefixLength > 1 && el.selectionStart <= prefixLength) el.setSelectionRange(prefixLength, el.selectionEnd);
else if (this._inputValue().length !== el.selectionStart && el.selectionStart !== 1) {
const collapsedCaret = el.selectionStart === el.selectionEnd;
const hopFrom = el.selectionStart;
while (specialCharacters.includes((this._inputValue()[el.selectionStart - 1] ?? MaskExpression.EMPTY_STRING).toString()) && (prefixLength >= 1 && el.selectionStart > prefixLength || prefixLength === 0)) el.setSelectionRange(el.selectionStart - 1, el.selectionEnd);
if (this._maskService.keepCharacterPositions && collapsedCaret && el.selectionStart !== hopFrom && el.selectionStart > prefixLength && hopFrom <= suffixStart(this._inputValue(), this._maskService.suffix)) el.setSelectionRange(el.selectionStart - 1, el.selectionEnd);
}
}
const { prefix, suffix, maskExpression, specialCharacters } = this._maskService;
const numberMask = maskExpression.startsWith(MaskExpression.SEPARATOR) || maskExpression.startsWith(MaskExpression.PERCENT);
const deleteAtFirstSlot = e.key === MaskExpression.DELETE && el.selectionStart === el.selectionEnd && el.selectionStart === prefix.length && prefix.length > 1 && el.value.startsWith(prefix) && !numberMask && !isDateTimeMask(maskExpression) && !specialCharacters.includes(maskExpression.charAt(0));
this.checkSelectionOnDeletion(el);
if (prefix.length && !deleteAtFirstSlot && el.selectionStart <= prefix.length && el.selectionEnd <= prefix.length) e.preventDefault();
if (e.key === MaskExpression.DELETE && suffix && !numberMask && el.selectionStart === el.selectionEnd && el.selectionStart >= suffixStart(this._inputValue(), this._maskService.suffix)) e.preventDefault();
const cursorStart = el.selectionStart;
if (e.key === MaskExpression.BACKSPACE && !el.readOnly && cursorStart === 0 && el.selectionEnd === el.value.length && el.value.length !== 0) {
e.preventDefault();
const transformedEmpty = this._maskService.inputTransformFn ? this._maskService.inputTransformFn(MaskExpression.EMPTY_STRING) : MaskExpression.EMPTY_STRING;
const clearedValue = typeof transformedEmpty === "string" || typeof transformedEmpty === "number" ? String(transformedEmpty) : MaskExpression.EMPTY_STRING;
const displayValue = this._maskService.applyMask(clearedValue, this._maskService.maskExpression, 0, false, true);
el.value = displayValue;
this._inputValue.set(displayValue);
const caret = Math.min(this._maskService.prefix.length, displayValue.length);
el.setSelectionRange(caret, caret);
}
}
if (!!this._maskService.suffix && this._maskService.suffix.length > 1 && this._inputValue().length - this._maskService.suffix.length < el.selectionStart) el.setSelectionRange(this._inputValue().length - this._maskService.suffix.length, this._inputValue().length);
else if (e.code === "KeyA" && e.ctrlKey || e.code === "KeyA" && e.metaKey) {
el.setSelectionRange(0, this._getActualInputLength());
e.preventDefault();
}
this._maskService.selStart = el.selectionStart;
this._maskService.selEnd = el.selectionEnd;
}
}
_insertNumpadDecimalMarker(e, el) {
if (!(e.key === MaskExpression.NUMPAD_DECIMAL || e.code === MaskExpression.NUMPAD_DECIMAL_CODE && e.key === MaskExpression.DOT) || e.ctrlKey || e.metaKey || e.altKey || el.readOnly || !this._maskValue().startsWith(MaskExpression.SEPARATOR)) return false;
const configuredMarker = this._maskService.decimalMarker;
const marker = Array.isArray(configuredMarker) ? configuredMarker.find((item) => item !== this._maskService.thousandSeparator) : configuredMarker;
if (marker !== MaskExpression.COMMA) return false;
const start = el.selectionStart ?? el.value.length;
const end = el.selectionEnd ?? start;
e.preventDefault();
this._code.set(MaskExpression.NUMPAD_DECIMAL_CODE);
this._inputValue.set(el.value);
this._maskService.selStart = start;
this._maskService.selEnd = end;
el.value = `${el.value.slice(0, start)}${marker}${el.value.slice(end)}`;
el.setSelectionRange(start + 1, start + 1);
el.dispatchEvent(new InputEvent("input", {
inputType: "insertText",
data: marker,
bubbles: true
}));
return true;
}
_writeProgrammatically(write) {
const enclosingWrite = this._programmaticWrite;
this._programmaticWrite = true;
try {
return write();
} finally {
this._programmaticWrite = enclosingWrite;
}
}
_emitProgrammaticValue(fn, value) {
const ngControl = this._resolveNgControl();
const control = ngControl?.control;
if (ngControl?.valueAccessor === this && control && typeof control.setValue === "function") {
control.setValue(value, { emitModelToViewChange: false });
ngControl.viewToModelUpdate(value);
return;
}
const wasPristine = ngControl ? Boolean(ngControl.pristine) : true;
const wasUntouched = ngControl ? Boolean(ngControl.untouched) : true;
fn(value);
if (wasPristine && ngControl?.dirty && typeof control?.markAsPristine === "function") control.markAsPristine({ onlySelf: true });
if (wasUntouched && ngControl?.touched && typeof control?.markAsUntouched === "function") control.markAsUntouched({ onlySelf: true });
}
writeValue(controlValue) {
if (!this._configApplied && this.mask()) {
this._pendingInitialValue = controlValue;
this._hasPendingInitialValue = true;
return;
}
const lastPropagated = this._lastPropagatedValue;
this._lastPropagatedValue = null;
if (lastPropagated !== null && lastPropagated !== "" && (typeof controlValue === "string" || typeof controlValue === "number") && String(controlValue) === lastPropagated) return;
const writeSequence = ++this._writeSequence;
let value = controlValue;
const inputTransformFn = this._maskService.inputTransformFn;
if (typeof value === "object" && value !== null && "value" in value) {
if ("disable" in value) this.setDisabledState(Boolean(value.disable));
value = value.value;
}
if (value !== null) value = inputTransformFn ? inputTransformFn(value) : value;
if (typeof value === "string" || typeof value === "number" || value === null || typeof value === "undefined") {
if (value === null || typeof value === "undefined" || value === "") {
this._maskService.currentValue = "";
this._maskService.previousValue = "";
}
let inputValue = value;
if (typeof inputValue === "number" || this._maskValue().startsWith(MaskExpression.SEPARATOR)) {
inputValue = displayToPlainDecimal(String(inputValue), this._maskService.decimalMarker, this._maskService.thousandSeparator);
const localeDecimalMarker = this._maskService.currentLocaleDecimalMarker();
if (!Array.isArray(this._maskService.decimalMarker)) inputValue = this._maskService.decimalMarker !== localeDecimalMarker ? inputValue.replace(localeDecimalMarker, this._maskService.decimalMarker) : inputValue;
const commaDecimal = usesCommaDecimal(inputValue, this._maskService.decimalMarker, this._maskService.thousandSeparator);
if (this._maskService.leadZero && inputValue && this.mask() && this._maskService.dropSpecialCharacters !== false) inputValue = this._maskService._checkPrecision(this._maskService.maskExpression, inputValue);
if (commaDecimal) inputValue = inputValue.toString().replace(MaskExpression.DOT, MaskExpression.COMMA);
if (this._resolvedMaskInput().startsWith(MaskExpression.SEPARATOR) && this._maskService.leadZero) {
const isFirstWrite = !this._maskService.isInitialized;
afterNextRender(() => {
if (writeSequence !== this._writeSequence) return;
this._ngZone.run(() => {
if (isFirstWrite) this._maskService.isInitialized = false;
this._writeProgrammatically(() => this._maskService.applyMask(inputValue?.toString() ?? "", this._maskService.maskExpression));
if (isFirstWrite) this._maskService.isInitialized = true;
this._changeDetectorRef.markForCheck();
});
}, { injector: this._injector });
}
this._maskService.isNumberValue = true;
} else if (typeof inputValue === "string" && inputValue !== "") this._maskService.isNumberValue = false;
if (typeof inputValue !== "string" || value === null || typeof value === "undefined") inputValue = "";
this._inputValue.set(inputValue);
this._setMask();
if (inputValue && this._maskService.maskExpression || this._maskService.maskExpression && (this._maskService.prefix || this._maskService.showMaskTyped)) {
this._maskService.writingValue = true;
this._writtenValue = { value: controlValue };
let displayValue;
try {
displayValue = this._writeProgrammatically(() => this._maskedOrVerbatim(inputValue));
} finally {
this._writtenValue = null;
}
if (writeSequence === this._writeSequence) {
this._maskService.formElementProperty = ["value", displayValue];
this._writeElementValueSync(displayValue);
}
this._maskService.writingValue = false;
this._maskService.isInitialized = true;
} else {
this._maskService.formElementProperty = ["value", inputValue];
this._writeElementValueSync(inputValue);
this._maskService.isInitialized = true;
}
this._changeDetectorRef.markForCheck();
} else console.warn("Ngx-mask writeValue work with string | number, your current value:", typeof value);
}
_writeElementValueSync(value) {
if (this._resolvedMaskInput() !== this._maskValue()) return;
this._renderer.setProperty(this._elementRef.nativeElement, "value", value);
}
_resolvedMaskInput() {
return resolveMaskAlias(this.mask(), this._config.maskAliases);
}
registerOnChange(fn) {
this._isCvaMode.set(true);
const originalFn = fn;
this._maskService.onChange = this.onChange = (value) => {
if (!this._writtenValue || !Object.is(this._writtenValue.value, value)) {
if (this._programmaticWrite) this._emitProgrammaticValue(originalFn, value);
else originalFn(value);
}
this._propagateToValueModel(value);
};
}
registerOnValidatorChange(fn) {
this._onValidatorChange = fn;
}
registerOnTouched(fn) {
this.onTouch = () => {
fn();
if (!this.touched()) this.touched.set(true);
};
}
_propagateToValueModel(value) {
const stringValue = value === null || typeof value === "undefined" ? "" : String(value);
this._lastPropagatedValue = stringValue;
this._valuePropagatedByEdit = stringValue;
untracked(() => {
if (String(this.value()) !== stringValue) {
this._skipNextValueEffect.set(true);
this.value.set(stringValue);
}
});
}
_valueEffectWillWrite(value) {
return !this._isCvaMode() && !this._skipNextValueEffect() && String(value) !== String(this._inputValue());
}
focus() {
this._maskService._elementRef?.nativeElement?.focus();
}
checkSelectionOnDeletion(el) {
const prefixLength = this._maskService.prefix.length;
const suffixLength = this._maskService.suffix.length;
const inputValueLength = this._inputValue().length;
el.selectionStart = Math.min(Math.max(prefixLength, el.selectionStart), inputValueLength - suffixLength);
el.selectionEnd = Math.min(Math.max(prefixLength, el.selectionEnd), inputValueLength - suffixLength);
}
setDisabledState(isDisabled) {
this._renderer.setProperty(this._elementRef.nativeElement, "disabled", isDisabled);
this._maskService.formElementProperty = ["disabled", isDisabled];
}
_mirrorInput(key, value) {
this._maskService[key] = value;
}
_applyMask(renderInputValue) {
this._maskService.maskExpression = this._maskService._repeatPatternSymbols(this._maskValue() || "");
if (!renderInputValue) {
this._maskService.writingValue = false;
this._maskService._clearMaskChangedUnlessTriggered();
return;
}
this._maskService.formElementProperty = ["value", this._maskedOrVerbatim(this._inputValue())];
this._maskService.writingValue = false;
this._maskService._clearMaskChangedUnlessTriggered();
}
_maskedOrVerbatim(inputValue) {
const wasMaskChanged = this._maskService.maskChanged;
const maskedResult = this._maskService.applyMask(inputValue, this._maskService.maskExpression);
return !maskedResult && inputValue && !wasMaskChanged && this._maskService.removeMask(inputValue) ? inputValue : maskedResult;
}
_validateTime(value) {
const rowMaskLen = this._maskValue().split(MaskExpression.EMPTY_STRING).filter((s) => s !== ":").length;
if (!value) return null;
if (+(value[value.length - 1] ?? -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;
}
_isCompleteAlternativeBoundary(processedValue) {
const alternatives = this._maskExpressionArray();
if (!alternatives.length) return false;
const maskValue = this._maskValue();
const cleanValue = this._maskService.removeMask(processedValue);
const cleanMask = this._maskService.removeMask(maskValue);
if (!cleanValue.split(MaskExpression.EMPTY_STRING).every((character, index) => this._maskService._checkSymbolMask(character, cleanMask.charAt(index)))) return false;
let patternCount = 0;
let boundaryCharacter = MaskExpression.EMPTY_STRING;
for (const maskCharacter of maskValue) {
if (patternCount === cleanValue.length) {
boundaryCharacter = maskCharacter;
break;
}
if (!this._maskService.specialCharacters.includes(maskCharacter)) patternCount++;
}
if (!boundaryCharacter || !this._maskService.specialCharacters.includes(boundaryCharacter)) return false;
return alternatives.some((alternative) => {
const requiredLength = this._maskService.dropSpecialCharacters ? alternative.length - this._maskService.checkDropSpecialCharAmount(alternative) : this._maskService.prefix ? alternative.length + this._maskService.prefix.length : alternative.length;
return processedValue.length === requiredLength;
});
}
_isPlainTokenMask(mask) {
return mask.split(MaskExpression.EMPTY_STRING).every((symbol) => !!this._maskService.patterns[symbol] || this._maskService.specialCharacters.includes(symbol));
}
_matchesMaskWithOptionalSkip(value, mask) {
const patterns = this._maskService.patterns;
const failed = /* @__PURE__ */ new Set();
const match = (maskIndex, valueIndex) => {
const state = maskIndex * (value.length + 1) + valueIndex;
if (failed.has(state)) return false;
const matched = matchFrom(maskIndex, valueIndex);
if (!matched) failed.add(state);
return matched;
};
const matchFrom = (maskIndex, valueIndex) => {
if (valueIndex === value.length) return mask.slice(maskIndex).split(MaskExpression.EMPTY_STRING).every((symbol) => !patterns[symbol] || !!patterns[symbol]?.optional);
if (maskIndex === mask.length) return false;
const maskSymbol = mask[maskIndex];
const valueSymbol = value[valueIndex];
const pattern = patterns[maskSymbol];
if (pattern) {
if (this._maskService._checkSymbolMask(valueSymbol, maskSymbol) && match(maskIndex + 1, valueIndex + 1)) return true;
return !!pattern.optional && match(maskIndex + 1, valueIndex);
}
if (valueSymbol === maskSymbol && match(maskIndex + 1, valueIndex + 1)) return true;
return match(maskIndex + 1, valueIndex);
};
return match(0, 0);
}
_createValidationError(actualValue) {
return { mask: {
requiredMask: this._maskValue(),
actualValue
} };
}
_setMask() {
this._maskExpressionArray().some((mask) => {
if (mask.split(MaskExpression.EMPTY_STRING).some((char) => this._maskService.specialCharacters.includes(char)) && this._inputValue() && this._areAllCharactersInEachStringSame(this._maskExpressionArray()) || mask.includes(MaskExpression.CURLY_BRACKETS_LEFT)) {
const test = this._maskService.removeMask(this._inputValue())?.length <= this._maskService.removeMask(mask)?.length;
if (test) {
const maskValue = mask.includes(MaskExpression.CURLY_BRACKETS_LEFT) ? this._maskService._repeatPatternSymbols(mask) : mask;
this._maskValue.set(maskValue);
this._maskService.maskExpression = maskValue;
return test;
} else {
const expression = this._maskExpressionArray()[this._maskExpressionArray().length - 1] ?? MaskExpression.EMPTY_STRING;
const maskValue = expression.includes(MaskExpression.CURLY_BRACKETS_LEFT) ? this._maskService._repeatPatternSymbols(expression) : expression;
this._maskValue.set(maskValue);
this._maskService.maskExpression = maskValue;
}
} else {
const cleanMask = this._maskService.removeMask(mask);
const check = this._maskService.removeMask(this._inputValue())?.split(MaskExpression.EMPTY_STRING).every((character, index) => {
const indexMask = cleanMask.charAt(index);
return this._maskService._checkSymbolMask(character, indexMask);
});
if (check || this._justPasted()) {
this._maskValue.set(mask);
this._maskService.maskExpression = mask;
return check;
}
}
});
}
_areAllCharactersInEachStringSame(array) {
const specialCharacters = this._maskService.specialCharacters;
function removeSpecialCharacters(str) {
const regex = new RegExp(`[${specialCharacters.map((ch) => escapeRegExp(ch)).join("")}]`, "g");
return str.replace(regex, "");
}
return array.map(removeSpecialCharacters).every((str) => {
return new Set(str).size === 1;
});
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskDirective,
deps: [],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "17.1.0",
version: "22.2.3",
type: NgxMaskDirective,
isStandalone: true,
selector: "input[mask], textarea[mask]",
inputs: {
mask: {
classPropertyName: "mask",
publicName: "mask",
isSignal: true,
isRequired: false,
transformFunction: null
},
specialCharacters: {
classPropertyName: "specialCharacters",
publicName: "specialCharacters",
isSignal: true,
isRequired: false,
transformFunction: null
},
patterns: {
classPropertyName: "patterns",
publicName: "patterns",
isSignal: true,
isRequired: false,
transformFunction: null
},
prefix: {
classPropertyName: "prefix",
publicName: "prefix",
isSignal: true,
isRequired: false,
transformFunction: null
},
suffix: {
classPropertyName: "suffix",
publicName: "suffix",
isSignal: true,
isRequired: false,
transformFunction: null
},
thousandSeparator: {
classPropertyName: "thousandSeparator",
publicName: "thousandSeparator",
isSignal: true,
isRequired: false,
transformFunction: null
},
decimalMarker: {
classPropertyName: "decimalMarker",
publicName: "decimalMarker",
isSignal: true,
isRequired: false,
transformFunction: null
},
dropSpecialCharacters: {
classPropertyName: "dropSpecialCharacters",
publicName: "dropSpecialCharacters",
isSignal: true,
isRequired: false,
transformFunction: null
},
hiddenInput: {
classPropertyName: "hiddenInput",
publicName: "hiddenInput",
isSignal: true,
isRequired: false,
transformFunction: null
},
showMaskTyped: {
classPropertyName: "showMaskTyped",
publicName: "showMaskTyped",
isSignal: true,
isRequired: false,
transformFunction: null
},
placeHolderCharacter: {
classPropertyName: "placeHolderCharacter",
publicName: "placeHolderCharacter",
isSignal: true,
isRequired: false,
transformFunction: null
},
shownMaskExpression: {
classPropertyName: "shownMaskExpression",
publicName: "shownMaskExpression",
isSignal: true,
isRequired: false,
transformFunction: null
},
clearIfNotMatch: {
classPropertyName: "clearIfNotMatch",
publicName: "clearIfNotMatch",
isSignal: true,
isRequired: false,
transformFunction: null
},
validation: {
classPropertyName: "validation",
publicName: "validation",
isSignal: true,
isRequired: false,
transformFunction: null
},
separatorLimit: {
classPropertyName: "separatorLimit",
publicName: "separatorLimit",
isSignal: true,
isRequired: false,
transformFunction: null
},
typeFromDecimals: {
classPropertyName: "typeFromDecimals",
publicName: "typeFromDecimals",
isSignal: true,
isRequired: false,
transformFunction: null
},
allowNegativeNumbers: {
classPropertyName: "allowNegativeNumbers",
publicName: "allowNegativeNumbers",
isSignal: true,
isRequired: false,
transformFunction: null
},
leadZeroDateTime: {
classPropertyName: "leadZeroDateTime",
publicName: "leadZeroDateTime",
isSignal: true,
isRequired: false,
transformFunction: null
},
leadZero: {
classPropertyName: "leadZero",
publicName: "leadZero",
isSignal: true,
isRequired: false,
transformFunction: null
},
triggerOnMaskChange: {
classPropertyName: "triggerOnMaskChange",
publicName: "triggerOnMaskChange",
isSignal: true,
isRequired: false,
transformFunction: null
},
apm: {
classPropertyName: "apm",
publicName: "apm",
isSignal: true,
isRequired: false,
transformFunction: null
},
inputTransformFn: {
classPropertyName: "inputTransformFn",
publicName: "inputTransformFn",
isSignal: true,
isRequired: false,
transformFunction: null
},
outputTransformFn: {
classPropertyName: "outputTransformFn",
publicName: "outputTransformFn",
isSignal: true,
isRequired: false,
transformFunction: null
},
keepCharacterPositions: {
classPropertyName: "keepCharacterPositions",
publicName: "keepCharacterPositions",
isSignal: true,
isRequired: false,
transformFunction: null
},
instantPrefix: {
classPropertyName: "instantPrefix",
publicName: "instantPrefix",
isSignal: true,
isRequired: false,
transformFunction: null
},
defaultValueOnBlur: {
classPropertyName: "defaultValueOnBlur",
publicName: "defaultValueOnBlur",
isSignal: true,
isRequired: false,
transformFunction: null
},
value: {
classPropertyName: "value",
publicName: "value",
isSignal: true,
isRequired: false,
transformFunction: null
},
disabled: {
classPropertyName: "disabled",
publicName: "disabled",
isSignal: true,
isRequired: false,
transformFunction: null
},
touched: {
classPropertyName: "touched",
publicName: "touched",
isSignal: true,
isRequired: false,
transformFunction: null
}
},
outputs: {
value: "valueChange",
touched: "touchedChange",
maskFilled: "maskFilled"
},
host: { listeners: {
"paste": "onPaste()",
"focus": "onFocus()",
"ngModelChange": "onModelChange($event)",
"input": "onInput($event)",
"compositionstart": "onCompositionStart()",
"compositionend": "onCompositionEnd($event)",
"blur": "onBlur($event)",
"click": "onClick($event)",
"keydown": "onKeyDown($event)"
} },
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: NgxMaskDirective,
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: NgxMaskDirective,
multi: true
},
NgxMaskService
],
exportAs: ["mask", "ngxMask"],
usesOnChanges: true,
ngImport: i0
});
};
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskDirective,
decorators: [{
type: Directive,
args: [{
selector: "input[mask], textarea[mask]",
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: NgxMaskDirective,
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: NgxMaskDirective,
multi: true
},
NgxMaskService
],
exportAs: "mask,ngxMask",
host: {
"(paste)": "onPaste()",
"(focus)": "onFocus()",
"(ngModelChange)": "onModelChange($event)",
"(input)": "onInput($event)",
"(compositionstart)": "onCompositionStart()",
"(compositionend)": "onCompositionEnd($event)",
"(blur)": "onBlur($event)",
"(click)": "onClick($event)",
"(keydown)": "onKeyDown($event)"
}
}]
}],
ctorParameters: () => [],
propDecorators: {
mask: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "mask",
required: false
}]
}],
specialCharacters: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "specialCharacters",
required: false
}]
}],
patterns: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "patterns",
required: false
}]
}],
prefix: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "prefix",
required: false
}]
}],
suffix: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "suffix",
required: false
}]
}],
thousandSeparator: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "thousandSeparator",
required: false
}]
}],
decimalMarker: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "decimalMarker",
required: false
}]
}],
dropSpecialCharacters: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "dropSpecialCharacters",
required: false
}]
}],
hiddenInput: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "hiddenInput",
required: false
}]
}],
showMaskTyped: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "showMaskTyped",
required: false
}]
}],
placeHolderCharacter: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "placeHolderCharacter",
required: false
}]
}],
shownMaskExpression: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "shownMaskExpression",
required: false
}]
}],
clearIfNotMatch: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "clearIfNotMatch",
required: false
}]
}],
validation: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "validation",
required: false
}]
}],
separatorLimit: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "separatorLimit",
required: false
}]
}],
typeFromDecimals: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "typeFromDecimals",
required: false
}]
}],
allowNegativeNumbers: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "allowNegativeNumbers",
required: false
}]
}],
leadZeroDateTime: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "leadZeroDateTime",
required: false
}]
}],
leadZero: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "leadZero",
required: false
}]
}],
triggerOnMaskChange: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "triggerOnMaskChange",
required: false
}]
}],
apm: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "apm",
required: false
}]
}],
inputTransformFn: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "inputTransformFn",
required: false
}]
}],
outputTransformFn: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "outputTransformFn",
required: false
}]
}],
keepCharacterPositions: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "keepCharacterPositions",
required: false
}]
}],
instantPrefix: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "instantPrefix",
required: false
}]
}],
defaultValueOnBlur: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "defaultValueOnBlur",
required: false
}]
}],
value: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "value",
required: false
}]
}, {
type: i0.Output,
args: ["valueChange"]
}],
disabled: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "disabled",
required: false
}]
}],
touched: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "touched",
required: false
}]
}, {
type: i0.Output,
args: ["touchedChange"]
}],
maskFilled: [{
type: i0.Output,
args: ["maskFilled"]
}]
}
});
var NgxMaskPipe = class NgxMaskPipe {
defaultOptions = inject(NGX_MASK_CONFIG);
_environmentInjector = inject(EnvironmentInjector);
_maskService;
_maskExpressionArray = [];
mask = "";
transform(value, mask, { patterns, maskAliases, ...config } = {}) {
let processedValue = value;
this._maskService = Injector.create({
providers: [NgxMaskService, {
provide: NGX_MASK_CONFIG,
useValue: this.defaultOptions
}],
parent: this._environmentInjector
}).get(NgxMaskService);
const resolvedMask = resolveMaskAlias(mask, {
...this.defaultOptions.maskAliases,
...maskAliases
});
const currentConfig = {
maskExpression: resolvedMask,
...this.defaultOptions,
...config,
patterns: {
...this.defaultOptions.patterns,
...patterns
}
};
Object.entries(currentConfig).forEach(([key, val]) => {
this._maskService[key] = val;
});
if (resolvedMask.includes("||")) {
this._maskExpressionArray = resolvedMask.split("||").sort((a, b) => a.length - b.length);
this._setMask(`${processedValue}`);
return this._maskService.applyMask(`${processedValue}`, this.mask);
}
if (resolvedMask.includes(MaskExpression.CURLY_BRACKETS_LEFT)) return this._maskService.applyMask(`${processedValue}`, this._maskService._repeatPatternSymbols(resolvedMask));
if (resolvedMask.startsWith(MaskExpression.SEPARATOR)) {
if (config.decimalMarker) this._maskService.decimalMarker = config.decimalMarker;
if (config.thousandSeparator) this._maskService.thousandSeparator = config.thousandSeparator;
if (config.leadZero) this._maskService.leadZero = config.leadZero;
processedValue = String(processedValue);
const localeDecimalMarker = this._maskService.currentLocaleDecimalMarker();
if (!Array.isArray(this._maskService.decimalMarker)) processedValue = this._maskService.decimalMarker !== localeDecimalMarker ? processedValue.replace(localeDecimalMarker, this._maskService.decimalMarker) : processedValue;
if (this._maskService.leadZero && processedValue && this._maskService.dropSpecialCharacters !== false) processedValue = this._maskService._checkPrecision(resolvedMask, processedValue);
if (this._maskService.decimalMarker === MaskExpression.COMMA) processedValue = processedValue.replace(MaskExpression.DOT, MaskExpression.COMMA);
}
if (processedValue === null || typeof processedValue === "undefined") return this._maskService.applyMask("", resolvedMask);
return this._maskService.applyMask(`${processedValue}`, resolvedMask);
}
_setMask(value) {
if (this._maskExpressionArray.length > 0) this._maskExpressionArray.some((mask) => {
const test = this._maskService.removeMask(value)?.length <= this._maskService.removeMask(mask)?.length;
if (value && test) {
this.mask = mask;
return test;
} else this.mask = this._maskExpressionArray[this._maskExpressionArray.length - 1] ?? MaskExpression.EMPTY_STRING;
});
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskPipe,
deps: [],
target: i0.ɵɵFactoryTarget.Pipe
});
static ɵpipe = i0.ɵɵngDeclarePipe({
minVersion: "14.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskPipe,
isStandalone: true,
name: "mask"
});
};
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "22.2.3",
ngImport: i0,
type: NgxMaskPipe,
decorators: [{
type: Pipe,
args: [{
name: "mask",
pure: true
}]
}]
});
export { INITIAL_CONFIG, NEW_CONFIG, NGX_MASK_CONFIG, NgxMaskDirective, NgxMaskPipe, NgxMaskService, initialConfig, provideEnvironmentNgxMask, provideNgxMask, resolveMaskAlias, timeMasks, withoutValidation };
//# sourceMappingURL=ngx-mask.mjs.map