ngx-mask 22.0.2 → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/ngx-mask.mjs +221 -53
- package/fesm2022/ngx-mask.mjs.map +1 -0
- package/package.json +2 -1
- package/types/ngx-mask.d.ts +60 -5
package/fesm2022/ngx-mask.mjs
CHANGED
|
@@ -29,6 +29,8 @@ var MaskExpression;
|
|
|
29
29
|
MaskExpression["SECOND"] = "s";
|
|
30
30
|
MaskExpression["LETTER_S"] = "S";
|
|
31
31
|
MaskExpression["DOT"] = ".";
|
|
32
|
+
MaskExpression["NUMPAD_DECIMAL"] = "Decimal";
|
|
33
|
+
MaskExpression["NUMPAD_DECIMAL_CODE"] = "NumpadDecimal";
|
|
32
34
|
MaskExpression["COMMA"] = ",";
|
|
33
35
|
MaskExpression["CURLY_BRACKETS_LEFT"] = "{";
|
|
34
36
|
MaskExpression["CURLY_BRACKETS_RIGHT"] = "}";
|
|
@@ -105,12 +107,14 @@ const initialConfig = {
|
|
|
105
107
|
},
|
|
106
108
|
d: {
|
|
107
109
|
pattern: new RegExp('\\d'),
|
|
110
|
+
symbol: '*',
|
|
108
111
|
},
|
|
109
112
|
m: {
|
|
110
113
|
pattern: new RegExp('\\d'),
|
|
111
114
|
},
|
|
112
115
|
M: {
|
|
113
116
|
pattern: new RegExp('\\d'),
|
|
117
|
+
symbol: '*',
|
|
114
118
|
},
|
|
115
119
|
H: {
|
|
116
120
|
pattern: new RegExp('\\d'),
|
|
@@ -537,9 +541,9 @@ const percentHandler = function (state, params) {
|
|
|
537
541
|
(processedValue.match(/[-!$%^&*()_+|~=`{}\[\]:";'<>?,\/.]/) && !backspaced)) {
|
|
538
542
|
processedValue = this._stripToDecimal(processedValue);
|
|
539
543
|
const precision = this.getPrecision(state.maskExpression);
|
|
540
|
-
processedValue = this.checkInputPrecision(processedValue, precision
|
|
544
|
+
processedValue = this.checkInputPrecision(processedValue, precision);
|
|
541
545
|
}
|
|
542
|
-
const decimalMarker =
|
|
546
|
+
const decimalMarker = this._activeDecimalMarker(processedValue);
|
|
543
547
|
if (processedValue.indexOf(decimalMarker) > 0 &&
|
|
544
548
|
!this.percentage(processedValue.substring(0, processedValue.indexOf(decimalMarker)))) {
|
|
545
549
|
let base = processedValue.substring(0, processedValue.indexOf(decimalMarker) - 1);
|
|
@@ -592,15 +596,7 @@ const separatorHandler = function (state, params) {
|
|
|
592
596
|
const precision = this.getPrecision(state.maskExpression);
|
|
593
597
|
let decimalMarker = this.decimalMarker;
|
|
594
598
|
if (Array.isArray(this.decimalMarker)) {
|
|
595
|
-
|
|
596
|
-
this.actualValue.includes(this.decimalMarker[1])) {
|
|
597
|
-
decimalMarker = this.actualValue.includes(this.decimalMarker[0])
|
|
598
|
-
? this.decimalMarker[0]
|
|
599
|
-
: this.decimalMarker[1];
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
decimalMarker = this.decimalMarker.find((dm) => dm !== this.thousandSeparator);
|
|
603
|
-
}
|
|
599
|
+
decimalMarker = this._activeDecimalMarker(this.actualValue);
|
|
604
600
|
}
|
|
605
601
|
// Issues #733/#1414/#1315: opt-in "banking" typing mode — typed digits fill
|
|
606
602
|
// the value from the decimal end, ATM/calculator style (5 -> 0.05 -> 0.57
|
|
@@ -802,7 +798,7 @@ const separatorHandler = function (state, params) {
|
|
|
802
798
|
if (processedValue.match(invalidCharRegexp)) {
|
|
803
799
|
processedValue = processedValue.substring(0, processedValue.length - 1);
|
|
804
800
|
}
|
|
805
|
-
processedValue = this.checkInputPrecision(processedValue, precision
|
|
801
|
+
processedValue = this.checkInputPrecision(processedValue, precision);
|
|
806
802
|
const strForSep = processedValue.replace(new RegExp(thousandSeparatorCharEscaped, 'g'), '');
|
|
807
803
|
result = this._formatWithSeparators(strForSep, this.thousandSeparator, this.decimalMarker, precision);
|
|
808
804
|
const commaShift = result.indexOf(MaskExpression.COMMA) - processedValue.indexOf(MaskExpression.COMMA);
|
|
@@ -1191,18 +1187,11 @@ class NgxMaskApplierService {
|
|
|
1191
1187
|
}
|
|
1192
1188
|
return inputValue;
|
|
1193
1189
|
};
|
|
1194
|
-
checkInputPrecision = (inputValue, precision
|
|
1190
|
+
checkInputPrecision = (inputValue, precision) => {
|
|
1195
1191
|
let processedInputValue = inputValue;
|
|
1196
|
-
let processedDecimalMarker = decimalMarker;
|
|
1197
1192
|
if (precision < Infinity) {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
// resolution used in separator.handler.ts (no marker typed yet in this value).
|
|
1201
|
-
if (Array.isArray(processedDecimalMarker)) {
|
|
1202
|
-
const marker = processedDecimalMarker.find((dm) => dm !== this.thousandSeparator);
|
|
1203
|
-
processedDecimalMarker = marker ? marker : processedDecimalMarker[0];
|
|
1204
|
-
}
|
|
1205
|
-
const precisionRegEx = new RegExp(this._charToRegExpExpression(processedDecimalMarker) + `\\d{${precision}}.*$`);
|
|
1193
|
+
const decimalMarker = this._activeDecimalMarker(inputValue);
|
|
1194
|
+
const precisionRegEx = new RegExp(this._charToRegExpExpression(decimalMarker) + `\\d{${precision}}.*$`);
|
|
1206
1195
|
const precisionMatch = processedInputValue.match(precisionRegEx);
|
|
1207
1196
|
const precisionMatchLength = (precisionMatch && precisionMatch[0]?.length) ?? 0;
|
|
1208
1197
|
if (precisionMatchLength - 1 > precision) {
|
|
@@ -1210,7 +1199,7 @@ class NgxMaskApplierService {
|
|
|
1210
1199
|
processedInputValue = processedInputValue.substring(0, processedInputValue.length - diff);
|
|
1211
1200
|
}
|
|
1212
1201
|
if (precision === 0 &&
|
|
1213
|
-
this._compareOrIncludes(processedInputValue[processedInputValue.length - 1],
|
|
1202
|
+
this._compareOrIncludes(processedInputValue[processedInputValue.length - 1], decimalMarker, this.thousandSeparator)) {
|
|
1214
1203
|
processedInputValue = processedInputValue.substring(0, processedInputValue.length - 1);
|
|
1215
1204
|
}
|
|
1216
1205
|
}
|
|
@@ -1258,13 +1247,24 @@ class NgxMaskApplierService {
|
|
|
1258
1247
|
return value === MaskExpression.EMPTY_STRING || Number(value.substring(0, 3)) > 255;
|
|
1259
1248
|
}));
|
|
1260
1249
|
}
|
|
1250
|
+
/**
|
|
1251
|
+
* The single rule for "which decimal marker is active": the configured marker, or — for an
|
|
1252
|
+
* array — the first configured marker present in `value` that is not the thousandSeparator,
|
|
1253
|
+
* else the first configured marker that is not the thousandSeparator (#1653).
|
|
1254
|
+
*/
|
|
1255
|
+
_activeDecimalMarker(value) {
|
|
1256
|
+
if (!Array.isArray(this.decimalMarker)) {
|
|
1257
|
+
return this.decimalMarker;
|
|
1258
|
+
}
|
|
1259
|
+
const candidates = this.decimalMarker.filter((dm) => dm !== this.thousandSeparator);
|
|
1260
|
+
return (candidates.find((dm) => value.includes(dm)) ?? candidates[0] ?? this.decimalMarker[0]);
|
|
1261
|
+
}
|
|
1261
1262
|
_splitPercentZero(value) {
|
|
1262
1263
|
if (value === MaskExpression.MINUS && this.allowNegativeNumbers) {
|
|
1263
1264
|
return value;
|
|
1264
1265
|
}
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
: value.indexOf(MaskExpression.DOT);
|
|
1266
|
+
const decimal = this._activeDecimalMarker(value);
|
|
1267
|
+
const decimalIndex = value.indexOf(decimal);
|
|
1268
1268
|
const emptyOrMinus = this.allowNegativeNumbers && value.includes(MaskExpression.MINUS) ? '-' : '';
|
|
1269
1269
|
if (decimalIndex === -1) {
|
|
1270
1270
|
const parsedValue = parseInt(emptyOrMinus ? value.slice(1, value.length) : value, 10);
|
|
@@ -1276,7 +1276,6 @@ class NgxMaskApplierService {
|
|
|
1276
1276
|
const integerPart = parseInt(value.replace('-', '').substring(0, decimalIndex), 10);
|
|
1277
1277
|
const decimalPart = value.substring(decimalIndex + 1);
|
|
1278
1278
|
const integerString = isNaN(integerPart) ? '' : integerPart.toString();
|
|
1279
|
-
const decimal = typeof this.decimalMarker === 'string' ? this.decimalMarker : MaskExpression.DOT;
|
|
1280
1279
|
return integerString === MaskExpression.EMPTY_STRING
|
|
1281
1280
|
? MaskExpression.EMPTY_STRING
|
|
1282
1281
|
: `${emptyOrMinus}${integerString}${decimal}${decimalPart}`;
|
|
@@ -1302,10 +1301,10 @@ class NgxMaskApplierService {
|
|
|
1302
1301
|
nonZeroIndex,
|
|
1303
1302
|
};
|
|
1304
1303
|
}
|
|
1305
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
1306
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
1304
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskApplierService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1305
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskApplierService });
|
|
1307
1306
|
}
|
|
1308
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
1307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskApplierService, decorators: [{
|
|
1309
1308
|
type: Injectable
|
|
1310
1309
|
}] });
|
|
1311
1310
|
|
|
@@ -1664,11 +1663,24 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1664
1663
|
return inputValue
|
|
1665
1664
|
.split(MaskExpression.EMPTY_STRING)
|
|
1666
1665
|
.map((curr, index) => {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
return
|
|
1671
|
-
|
|
1666
|
+
const maskChar = maskExpression[index] ?? MaskExpression.EMPTY_STRING;
|
|
1667
|
+
const pattern = this.patterns?.[maskChar];
|
|
1668
|
+
if (pattern?.symbol) {
|
|
1669
|
+
return pattern.symbol;
|
|
1670
|
+
}
|
|
1671
|
+
// #1574: a '0' immediately following a concealed DAY/MONTH token
|
|
1672
|
+
// is that field's second digit (d0/M0) — conceal it too so the
|
|
1673
|
+
// whole 2-digit field is hidden, not just its first character.
|
|
1674
|
+
// Display-only: does not touch maskExpression/processedValue, so
|
|
1675
|
+
// date validation/leadZero clamping in generic-pattern.handler.ts
|
|
1676
|
+
// is unaffected.
|
|
1677
|
+
const prevMaskChar = maskExpression[index - 1];
|
|
1678
|
+
const prevPattern = prevMaskChar ? this.patterns?.[prevMaskChar] : null;
|
|
1679
|
+
if (maskChar === MaskExpression.NUMBER_ZERO &&
|
|
1680
|
+
(prevMaskChar === MaskExpression.DAY ||
|
|
1681
|
+
prevMaskChar === MaskExpression.MONTH) &&
|
|
1682
|
+
prevPattern?.symbol) {
|
|
1683
|
+
return prevPattern.symbol;
|
|
1672
1684
|
}
|
|
1673
1685
|
return curr;
|
|
1674
1686
|
})
|
|
@@ -1992,7 +2004,10 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1992
2004
|
}
|
|
1993
2005
|
_removeMask(value, specialCharactersForRemove) {
|
|
1994
2006
|
if (this.maskExpression.startsWith(MaskExpression.PERCENT) &&
|
|
1995
|
-
value.includes(MaskExpression.DOT)
|
|
2007
|
+
(value.includes(MaskExpression.DOT) ||
|
|
2008
|
+
(Array.isArray(this.decimalMarker) &&
|
|
2009
|
+
value.includes(MaskExpression.COMMA) &&
|
|
2010
|
+
this._activeDecimalMarker(value) === MaskExpression.COMMA))) {
|
|
1996
2011
|
return value;
|
|
1997
2012
|
}
|
|
1998
2013
|
return value
|
|
@@ -2048,7 +2063,7 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
2048
2063
|
return processedResult;
|
|
2049
2064
|
}
|
|
2050
2065
|
if (this.maskExpression.startsWith(MaskExpression.PERCENT) &&
|
|
2051
|
-
this.
|
|
2066
|
+
this._activeDecimalMarker(processedResult) === MaskExpression.COMMA) {
|
|
2052
2067
|
processedResult = processedResult.replace(MaskExpression.COMMA, MaskExpression.DOT);
|
|
2053
2068
|
}
|
|
2054
2069
|
const separatorPrecision = this._retrieveSeparatorPrecision(this.maskExpression);
|
|
@@ -2186,10 +2201,10 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
2186
2201
|
currentLocaleDecimalMarker() {
|
|
2187
2202
|
return MaskExpression.DOT;
|
|
2188
2203
|
}
|
|
2189
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
2190
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.
|
|
2204
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
2205
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskService });
|
|
2191
2206
|
}
|
|
2192
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
2207
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskService, decorators: [{
|
|
2193
2208
|
type: Injectable
|
|
2194
2209
|
}] });
|
|
2195
2210
|
|
|
@@ -2280,6 +2295,18 @@ class NgxMaskDirective {
|
|
|
2280
2295
|
// config, so no ngOnChanges mirroring into the service is needed (#1435).
|
|
2281
2296
|
defaultValueOnBlur = input(null, /* @ts-ignore */
|
|
2282
2297
|
...(ngDevMode ? [{ debugName: "defaultValueOnBlur" }] : /* istanbul ignore next */ []));
|
|
2298
|
+
/**
|
|
2299
|
+
* Signal Forms `FormValueControl` surface. At runtime `FormField` never drives this model
|
|
2300
|
+
* when the directive is bound (it prefers the `NG_VALUE_ACCESSOR` provided above, see
|
|
2301
|
+
* `_isCvaMode`), but the template type-checker treats any directive owning a `value` model
|
|
2302
|
+
* as the field's custom control and checks the bound field's value type against it (#1640).
|
|
2303
|
+
* The model therefore accepts exactly what `writeValue()` accepts — `string | number | null |
|
|
2304
|
+
* undefined` — so numeric and nullable fields (`signal({ amount: 0 })`,
|
|
2305
|
+
* `signal<number | null>(null)`, `signal<string | null>(null)`) compile under
|
|
2306
|
+
* `strictTemplates`. Values pushed back into the model are always the unmasked string (see
|
|
2307
|
+
* `_propagateToValueModel`), i.e. `value()` / `valueChange` are declared wider than what is
|
|
2308
|
+
* emitted.
|
|
2309
|
+
*/
|
|
2283
2310
|
value = model('', /* @ts-ignore */
|
|
2284
2311
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
2285
2312
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
@@ -2519,7 +2546,10 @@ class NgxMaskDirective {
|
|
|
2519
2546
|
}
|
|
2520
2547
|
}
|
|
2521
2548
|
if (placeHolderCharacter) {
|
|
2522
|
-
|
|
2549
|
+
// `null`/`undefined` means "no custom placeholder" (#1643): fall back to the default
|
|
2550
|
+
// instead of storing null, which broke every `.length` read on the service.
|
|
2551
|
+
this._maskService.placeHolderCharacter =
|
|
2552
|
+
placeHolderCharacter.currentValue ?? this._config.placeHolderCharacter;
|
|
2523
2553
|
if (typeof placeHolderCharacter.currentValue === 'string' &&
|
|
2524
2554
|
placeHolderCharacter.currentValue.length > 1 &&
|
|
2525
2555
|
!this._warnedAboutMultiCharPlaceholder) {
|
|
@@ -2973,10 +3003,17 @@ class NgxMaskDirective {
|
|
|
2973
3003
|
inputSymbol +
|
|
2974
3004
|
this._inputValue().slice(position + 1);
|
|
2975
3005
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3006
|
+
// #1488: generalizes the single-mask (d0/M0/0000) caret-shift-past-separator
|
|
3007
|
+
// fix to any date/time field. Locates the date/time field containing
|
|
3008
|
+
// `position` by scanning maskExpression for a DAY/MONTH/HOURS/HOUR/MINUTE/
|
|
3009
|
+
// SECOND token followed by its digit run, then checks overflow against the
|
|
3010
|
+
// same per-field thresholds generic-pattern.handler.ts already establishes
|
|
3011
|
+
// (day 31, month 12, hour 23/12 w/ apm, minute/second 59). Read-only against
|
|
3012
|
+
// generic-pattern.handler.ts — mirrors its thresholds, does not import from it.
|
|
3013
|
+
if (this.leadZeroDateTime()) {
|
|
3014
|
+
const field = this._findDateTimeFieldAt(this._maskService.maskExpression, position);
|
|
3015
|
+
if (field &&
|
|
3016
|
+
this._isFieldOverflowing(field, position, el.value, this._inputValue(), !!this.apm())) {
|
|
2980
3017
|
position = position + 2;
|
|
2981
3018
|
}
|
|
2982
3019
|
}
|
|
@@ -3078,6 +3115,86 @@ class NgxMaskDirective {
|
|
|
3078
3115
|
this._maskService.applyValueChanges(el.value.length, this._justPasted(), this._code() === MaskExpression.BACKSPACE || this._code() === MaskExpression.DELETE);
|
|
3079
3116
|
}
|
|
3080
3117
|
}
|
|
3118
|
+
/**
|
|
3119
|
+
* #1488: locates the date/time field (DAY/MONTH/HOURS/HOUR/MINUTE/SECOND) that
|
|
3120
|
+
* `position` falls within, scanning `maskExpression` left-to-right. A field spans
|
|
3121
|
+
* its token plus its digit-pair companion — `0` for DAY/MONTH/MINUTE/SECOND (e.g.
|
|
3122
|
+
* `d0`, `M0`, `m0`, `s0`), or `HOUR` for the combined `Hh` hour field. Matches any
|
|
3123
|
+
* position from the field's first char up to and including its end (separator index),
|
|
3124
|
+
* so both a still-mid-typing caret and the just-completed field are covered — the
|
|
3125
|
+
* precise overflow decision (single first digit vs. completed 2-digit value) happens
|
|
3126
|
+
* in `_isFieldOverflowing`. Returns null for non-date/time masks, a no-op for the caller.
|
|
3127
|
+
*/
|
|
3128
|
+
_findDateTimeFieldAt(maskExpression, position) {
|
|
3129
|
+
const dateTimeTokens = [
|
|
3130
|
+
MaskExpression.DAY,
|
|
3131
|
+
MaskExpression.MONTH,
|
|
3132
|
+
MaskExpression.HOURS,
|
|
3133
|
+
MaskExpression.HOUR,
|
|
3134
|
+
MaskExpression.MINUTE,
|
|
3135
|
+
MaskExpression.SECOND,
|
|
3136
|
+
];
|
|
3137
|
+
let index = 0;
|
|
3138
|
+
while (index < maskExpression.length) {
|
|
3139
|
+
const token = maskExpression[index] ?? MaskExpression.EMPTY_STRING;
|
|
3140
|
+
if (dateTimeTokens.includes(token)) {
|
|
3141
|
+
const next = maskExpression[index + 1];
|
|
3142
|
+
const isCombinedHour = token === MaskExpression.HOURS && next === MaskExpression.HOUR;
|
|
3143
|
+
const end = next === MaskExpression.NUMBER_ZERO || isCombinedHour ? index + 2 : index + 1;
|
|
3144
|
+
if (position >= index && position <= end) {
|
|
3145
|
+
return {
|
|
3146
|
+
token: token,
|
|
3147
|
+
start: index,
|
|
3148
|
+
end,
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
index = end;
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
index++;
|
|
3155
|
+
}
|
|
3156
|
+
return null;
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Per-token overflow thresholds, mirroring the ones `generic-pattern.handler.ts`
|
|
3160
|
+
* already establishes (day 31, month 12, hour 23/12 w/ apm, minute/second 59) — kept
|
|
3161
|
+
* as a small local table rather than importing from the handler (see design's
|
|
3162
|
+
* Rejected Alternatives: the directive and the handler stay on separate sides of the
|
|
3163
|
+
* display/parse boundary).
|
|
3164
|
+
*
|
|
3165
|
+
* Two overflow shapes are checked, mirroring how the handler itself decides mid-type:
|
|
3166
|
+
* - `firstDigitValue`: when `position` is exactly one char past the field's start (the
|
|
3167
|
+
* just-typed char is the field's first digit), an out-of-range first digit alone
|
|
3168
|
+
* (e.g. month digit `3`, day digit `4`) already triggers the handler's own leadZero
|
|
3169
|
+
* pad within this same keystroke — `el.value` hasn't caught up yet at this point in
|
|
3170
|
+
* the pipeline, so the raw typed digit (`inputValue`) must be read instead.
|
|
3171
|
+
* - `fieldValue`: once both digits of the field are present in `el.value`, an
|
|
3172
|
+
* out-of-range 2-digit value (e.g. day `33`, month `13`) triggers the shift directly.
|
|
3173
|
+
*/
|
|
3174
|
+
_isFieldOverflowing(field, position, elValue, inputValue, apm) {
|
|
3175
|
+
const fieldValue = Number(elValue.slice(field.start, field.end));
|
|
3176
|
+
const isFirstDigitOfField = position === field.start + 1;
|
|
3177
|
+
const firstDigitValue = isFirstDigitOfField
|
|
3178
|
+
? Number(inputValue.slice(position - 1, position))
|
|
3179
|
+
: NaN;
|
|
3180
|
+
switch (field.token) {
|
|
3181
|
+
case MaskExpression.DAY:
|
|
3182
|
+
return (fieldValue > 31 && fieldValue < 40) || firstDigitValue > 3;
|
|
3183
|
+
case MaskExpression.MONTH:
|
|
3184
|
+
return fieldValue > 12 || firstDigitValue > 1;
|
|
3185
|
+
case MaskExpression.HOURS:
|
|
3186
|
+
return apm
|
|
3187
|
+
? fieldValue > 9 || firstDigitValue > 9
|
|
3188
|
+
: fieldValue > 23 || firstDigitValue > 2;
|
|
3189
|
+
case MaskExpression.HOUR:
|
|
3190
|
+
return apm ? fieldValue > 12 : fieldValue > 23;
|
|
3191
|
+
case MaskExpression.MINUTE:
|
|
3192
|
+
case MaskExpression.SECOND:
|
|
3193
|
+
return fieldValue > 59 || firstDigitValue > 5;
|
|
3194
|
+
default:
|
|
3195
|
+
return false;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3081
3198
|
/**
|
|
3082
3199
|
* Whether any pattern slot of the current mask expression can accept a letter.
|
|
3083
3200
|
* IME composition is only meaningful for such masks; for purely numeric masks
|
|
@@ -3261,6 +3378,9 @@ class NgxMaskDirective {
|
|
|
3261
3378
|
this._setMask();
|
|
3262
3379
|
const isTextarea = el.tagName.toLowerCase() === 'textarea';
|
|
3263
3380
|
if (el.type !== 'number') {
|
|
3381
|
+
if (this._insertNumpadDecimalMarker(e, el)) {
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3264
3384
|
if (e.key === MaskExpression.ARROW_UP && !isTextarea) {
|
|
3265
3385
|
e.preventDefault();
|
|
3266
3386
|
}
|
|
@@ -3310,7 +3430,15 @@ class NgxMaskDirective {
|
|
|
3310
3430
|
// triggers change detection (DOM churn around the input), leaving the
|
|
3311
3431
|
// model empty but the view untouched until a second Backspace (#1350).
|
|
3312
3432
|
e.preventDefault();
|
|
3313
|
-
|
|
3433
|
+
// The default action (and its input event) is suppressed, so the
|
|
3434
|
+
// inputTransformFn would never see the cleared value; run it here (#1651).
|
|
3435
|
+
const transformedEmpty = this._maskService.inputTransformFn
|
|
3436
|
+
? this._maskService.inputTransformFn(MaskExpression.EMPTY_STRING)
|
|
3437
|
+
: MaskExpression.EMPTY_STRING;
|
|
3438
|
+
const clearedValue = typeof transformedEmpty === 'string' || typeof transformedEmpty === 'number'
|
|
3439
|
+
? String(transformedEmpty)
|
|
3440
|
+
: MaskExpression.EMPTY_STRING;
|
|
3441
|
+
const displayValue = this._maskService.applyMask(clearedValue, this._maskService.maskExpression, 0, false, true);
|
|
3314
3442
|
el.value = displayValue;
|
|
3315
3443
|
this._inputValue.set(displayValue);
|
|
3316
3444
|
const caret = Math.min(this._maskService.prefix.length, displayValue.length);
|
|
@@ -3332,6 +3460,44 @@ class NgxMaskDirective {
|
|
|
3332
3460
|
this._maskService.selEnd = el.selectionEnd;
|
|
3333
3461
|
}
|
|
3334
3462
|
}
|
|
3463
|
+
/**
|
|
3464
|
+
* #1641: on layouts whose decimal separator is a comma, the numeric keypad still emits
|
|
3465
|
+
* a period, which a separator mask configured with a comma decimal marker rejects. The
|
|
3466
|
+
* keypad decimal key (`key: 'Decimal'`, or a `.` from `code: 'NumpadDecimal'`) is
|
|
3467
|
+
* therefore turned into the configured marker. Only the keypad key is intercepted: a
|
|
3468
|
+
* period typed on the main keyboard keeps its normal behavior (e.g. as a thousand
|
|
3469
|
+
* separator). Returns true when the key press was handled.
|
|
3470
|
+
*/
|
|
3471
|
+
_insertNumpadDecimalMarker(e, el) {
|
|
3472
|
+
const isNumpadDecimal = e.key === MaskExpression.NUMPAD_DECIMAL ||
|
|
3473
|
+
(e.code === MaskExpression.NUMPAD_DECIMAL_CODE && e.key === MaskExpression.DOT);
|
|
3474
|
+
if (!isNumpadDecimal ||
|
|
3475
|
+
e.ctrlKey ||
|
|
3476
|
+
e.metaKey ||
|
|
3477
|
+
e.altKey ||
|
|
3478
|
+
el.readOnly ||
|
|
3479
|
+
!this._maskValue().startsWith(MaskExpression.SEPARATOR)) {
|
|
3480
|
+
return false;
|
|
3481
|
+
}
|
|
3482
|
+
const configuredMarker = this._maskService.decimalMarker;
|
|
3483
|
+
const marker = Array.isArray(configuredMarker)
|
|
3484
|
+
? configuredMarker.find((item) => item !== this._maskService.thousandSeparator)
|
|
3485
|
+
: configuredMarker;
|
|
3486
|
+
if (marker !== MaskExpression.COMMA) {
|
|
3487
|
+
return false;
|
|
3488
|
+
}
|
|
3489
|
+
const start = el.selectionStart ?? el.value.length;
|
|
3490
|
+
const end = el.selectionEnd ?? start;
|
|
3491
|
+
e.preventDefault();
|
|
3492
|
+
this._code.set(MaskExpression.NUMPAD_DECIMAL_CODE);
|
|
3493
|
+
this._inputValue.set(el.value);
|
|
3494
|
+
this._maskService.selStart = start;
|
|
3495
|
+
this._maskService.selEnd = end;
|
|
3496
|
+
el.value = `${el.value.slice(0, start)}${marker}${el.value.slice(end)}`;
|
|
3497
|
+
el.setSelectionRange(start + 1, start + 1);
|
|
3498
|
+
el.dispatchEvent(new InputEvent('input', { inputType: 'insertText', data: marker, bubbles: true }));
|
|
3499
|
+
return true;
|
|
3500
|
+
}
|
|
3335
3501
|
/**
|
|
3336
3502
|
* Restores the control's pristine/untouched state after a writeValue-driven emission.
|
|
3337
3503
|
*
|
|
@@ -3683,7 +3849,9 @@ class NgxMaskDirective {
|
|
|
3683
3849
|
: this.prefix()
|
|
3684
3850
|
? alternative.length + this.prefix().length
|
|
3685
3851
|
: alternative.length;
|
|
3686
|
-
|
|
3852
|
+
// Exact match, not `>=`: a longer value must not pass on the strength of a
|
|
3853
|
+
// shorter alternative it has already outgrown (#1645, 7 digits vs `00000 9`).
|
|
3854
|
+
return processedValue.length === requiredLength;
|
|
3687
3855
|
});
|
|
3688
3856
|
}
|
|
3689
3857
|
/**
|
|
@@ -3799,8 +3967,8 @@ class NgxMaskDirective {
|
|
|
3799
3967
|
return uniqueCharacters.size === 1;
|
|
3800
3968
|
});
|
|
3801
3969
|
}
|
|
3802
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
3803
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.
|
|
3970
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3971
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", 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: [
|
|
3804
3972
|
{
|
|
3805
3973
|
provide: NG_VALUE_ACCESSOR,
|
|
3806
3974
|
useExisting: NgxMaskDirective,
|
|
@@ -3814,7 +3982,7 @@ class NgxMaskDirective {
|
|
|
3814
3982
|
NgxMaskService,
|
|
3815
3983
|
], exportAs: ["mask", "ngxMask"], usesOnChanges: true, ngImport: i0 });
|
|
3816
3984
|
}
|
|
3817
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
3985
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskDirective, decorators: [{
|
|
3818
3986
|
type: Directive,
|
|
3819
3987
|
args: [{
|
|
3820
3988
|
selector: 'input[mask], textarea[mask]',
|
|
@@ -3952,10 +4120,10 @@ class NgxMaskPipe {
|
|
|
3952
4120
|
});
|
|
3953
4121
|
}
|
|
3954
4122
|
}
|
|
3955
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
3956
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.
|
|
4123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
4124
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskPipe, isStandalone: true, name: "mask" });
|
|
3957
4125
|
}
|
|
3958
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
4126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: NgxMaskPipe, decorators: [{
|
|
3959
4127
|
type: Pipe,
|
|
3960
4128
|
args: [{
|
|
3961
4129
|
name: 'mask',
|