ngx-mask 22.2.1 → 22.2.3
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 +95 -23
- package/fesm2022/ngx-mask.mjs.map +1 -1
- package/package.json +2 -2
- package/types/ngx-mask.d.ts +18 -2
package/fesm2022/ngx-mask.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, EventEmitter, inject, Injectable, signal, ElementRef, Renderer2, makeEnvironmentProviders, input, model, booleanAttribute, output, ChangeDetectorRef, Injector, effect, untracked, HostListener, Directive, Pipe } from '@angular/core';
|
|
2
|
+
import { InjectionToken, EventEmitter, inject, Injectable, signal, ElementRef, Renderer2, makeEnvironmentProviders, input, model, booleanAttribute, output, ChangeDetectorRef, Injector, effect, untracked, HostListener, Directive, EnvironmentInjector, Pipe } from '@angular/core';
|
|
3
3
|
import { DOCUMENT } from '@angular/common';
|
|
4
4
|
import { NgControl, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
5
5
|
|
|
@@ -204,6 +204,22 @@ const cpfCnpjHandler = function (state, params) {
|
|
|
204
204
|
return state;
|
|
205
205
|
};
|
|
206
206
|
|
|
207
|
+
/** Punctuation the built-in email mask accepts besides `[a-zA-Z0-9]` (#1668). */
|
|
208
|
+
const EMAIL_AT = '@';
|
|
209
|
+
const EMAIL_LOCAL_PUNCTUATION = '._+-';
|
|
210
|
+
const EMAIL_DOMAIN_PUNCTUATION = '.-';
|
|
211
|
+
/**
|
|
212
|
+
* Whether `inputSymbol` is allowed email punctuation after `result` (the text accepted so far).
|
|
213
|
+
* Punctuation cannot be the first character of the address, and '..' is blocked.
|
|
214
|
+
*/
|
|
215
|
+
function isEmailPunctuation(inputSymbol, result) {
|
|
216
|
+
const allowed = result.includes(EMAIL_AT) ? EMAIL_DOMAIN_PUNCTUATION : EMAIL_LOCAL_PUNCTUATION;
|
|
217
|
+
const last = result.slice(-1);
|
|
218
|
+
return (allowed.includes(inputSymbol) &&
|
|
219
|
+
last !== MaskExpression.EMPTY_STRING &&
|
|
220
|
+
last !== EMAIL_AT &&
|
|
221
|
+
!(inputSymbol === MaskExpression.DOT && last === MaskExpression.DOT));
|
|
222
|
+
}
|
|
207
223
|
/**
|
|
208
224
|
* Generic character-by-character pattern loop (moved verbatim from applyMask,
|
|
209
225
|
* original lines 581-968). This is the default/fallback handler — always runs when
|
|
@@ -506,6 +522,12 @@ const genericPatternHandler = function (state, params) {
|
|
|
506
522
|
cursor += 3;
|
|
507
523
|
result += inputSymbol;
|
|
508
524
|
}
|
|
525
|
+
else if (maskExpression === MaskExpression.EMAIL_MASK &&
|
|
526
|
+
isEmailPunctuation(inputSymbol, result)) {
|
|
527
|
+
// #1668: 'A' only matches [a-zA-Z0-9], so the punctuation real addresses carry
|
|
528
|
+
// would be dropped. Keep it inside the current 'A*' run (cursor is unchanged).
|
|
529
|
+
result += inputSymbol;
|
|
530
|
+
}
|
|
509
531
|
else if (this.showMaskTyped &&
|
|
510
532
|
this.specialCharacters.indexOf(inputSymbol) < 0 &&
|
|
511
533
|
inputSymbol !== this.placeHolderCharacter &&
|
|
@@ -1377,9 +1399,7 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1377
1399
|
if (this._emitValue && this.triggerOnMaskChange) {
|
|
1378
1400
|
this.formControlResult(rawValue);
|
|
1379
1401
|
}
|
|
1380
|
-
|
|
1381
|
-
this.maskChanged = false;
|
|
1382
|
-
}
|
|
1402
|
+
this._clearMaskChangedUnlessTriggered();
|
|
1383
1403
|
return rawValue;
|
|
1384
1404
|
}
|
|
1385
1405
|
return inputValue !== this.actualValue ? this.actualValue : inputValue;
|
|
@@ -1607,9 +1627,7 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1607
1627
|
this.formControlResult(result);
|
|
1608
1628
|
}
|
|
1609
1629
|
// Reset maskChanged flag even if we didn't emit
|
|
1610
|
-
|
|
1611
|
-
this.maskChanged = false;
|
|
1612
|
-
}
|
|
1630
|
+
this._clearMaskChangedUnlessTriggered();
|
|
1613
1631
|
// Handle hidden input and showMaskTyped
|
|
1614
1632
|
if (!this.showMaskTyped || (this.showMaskTyped && this.hiddenInput)) {
|
|
1615
1633
|
if (this.hiddenInput) {
|
|
@@ -1820,6 +1838,16 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1820
1838
|
.filter((item) => this._findDropSpecialChar(item));
|
|
1821
1839
|
return chars.length;
|
|
1822
1840
|
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Consumes a pending `maskChanged` once a render has handled the reconfiguration, unless
|
|
1843
|
+
* `triggerOnMaskChange` still needs it to emit the re-masked value (formControlResult clears
|
|
1844
|
+
* it then).
|
|
1845
|
+
*/
|
|
1846
|
+
_clearMaskChangedUnlessTriggered() {
|
|
1847
|
+
if (!this.triggerOnMaskChange) {
|
|
1848
|
+
this.maskChanged = false;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1823
1851
|
removeMask(inputValue) {
|
|
1824
1852
|
return this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.specialCharacters.concat('_').concat(this.placeHolderCharacter));
|
|
1825
1853
|
}
|
|
@@ -1973,11 +2001,15 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1973
2001
|
.split(this.placeHolderCharacter)
|
|
1974
2002
|
.join(MaskExpression.EMPTY_STRING);
|
|
1975
2003
|
}
|
|
1976
|
-
|
|
2004
|
+
// #1668: an email address is meaningless without its '@' and '.', so the built-in
|
|
2005
|
+
// email mask always reports the full address, whatever dropSpecialCharacters says.
|
|
2006
|
+
const keepSpecialCharacters = this.maskExpression === MaskExpression.EMAIL_MASK;
|
|
2007
|
+
if (Array.isArray(this.dropSpecialCharacters) && !keepSpecialCharacters) {
|
|
1977
2008
|
this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.dropSpecialCharacters)))));
|
|
1978
2009
|
}
|
|
1979
|
-
else if (
|
|
1980
|
-
(
|
|
2010
|
+
else if (!keepSpecialCharacters &&
|
|
2011
|
+
(this.dropSpecialCharacters ||
|
|
2012
|
+
(!this.dropSpecialCharacters && this.prefix === inputValue))) {
|
|
1981
2013
|
this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeSuffix(this._removePrefix(inputValue))))));
|
|
1982
2014
|
}
|
|
1983
2015
|
else {
|
|
@@ -2351,7 +2383,8 @@ class NgxMaskDirective {
|
|
|
2351
2383
|
* originated from the view. Re-masking that unmasked echo is at best a redundant re-render
|
|
2352
2384
|
* and at worst destructive for masks whose unmasked form is ambiguous (e.g. IP:
|
|
2353
2385
|
* '192168178' cannot reconstruct the typed dots of '192.168.1.78'). `writeValue()` consumes
|
|
2354
|
-
* this marker to skip exactly that echo
|
|
2386
|
+
* this marker to skip exactly that echo; ngOnChanges reads it to recognise a `[value]`
|
|
2387
|
+
* binding echo of it (#1671). `null` = no pending propagation.
|
|
2355
2388
|
*/
|
|
2356
2389
|
_lastPropagatedValue = null;
|
|
2357
2390
|
/**
|
|
@@ -2423,15 +2456,11 @@ class NgxMaskDirective {
|
|
|
2423
2456
|
effect(() => {
|
|
2424
2457
|
const signalValue = this.value();
|
|
2425
2458
|
untracked(() => {
|
|
2426
|
-
if (this.
|
|
2427
|
-
|
|
2459
|
+
if (this._valueEffectWillWrite(signalValue)) {
|
|
2460
|
+
this.writeValue(signalValue);
|
|
2428
2461
|
}
|
|
2429
|
-
|
|
2462
|
+
else {
|
|
2430
2463
|
this._skipNextValueEffect.set(false);
|
|
2431
|
-
return;
|
|
2432
|
-
}
|
|
2433
|
-
if (String(signalValue) !== String(this._inputValue())) {
|
|
2434
|
-
this.writeValue(signalValue);
|
|
2435
2464
|
}
|
|
2436
2465
|
});
|
|
2437
2466
|
});
|
|
@@ -2591,7 +2620,20 @@ class NgxMaskDirective {
|
|
|
2591
2620
|
if (keepCharacterPositions) {
|
|
2592
2621
|
this._maskService.keepCharacterPositions = keepCharacterPositions.currentValue;
|
|
2593
2622
|
}
|
|
2594
|
-
|
|
2623
|
+
// #1671: a standalone `[(value)]` change reaches ngOnChanges BEFORE the value effect
|
|
2624
|
+
// writes it, so `_inputValue` still holds the PREVIOUS value here. Re-rendering it would
|
|
2625
|
+
// run applyMask on stale input, and whatever that emits (the showMaskTyped skeleton's
|
|
2626
|
+
// '', a hiddenInput / keepCharacterPositions reconstruction) flows back through onChange,
|
|
2627
|
+
// overwrites the freshly bound model and makes the effect skip the real write. When the
|
|
2628
|
+
// effect is about to render the new value via writeValue() anyway, only apply the mask
|
|
2629
|
+
// configuration here. The effect only runs when the `value` SIGNAL changes: a parent
|
|
2630
|
+
// re-binding the value we last propagated (an echo, possibly applied in a later task)
|
|
2631
|
+
// changes the binding but not the signal, which already holds it — that pass renders.
|
|
2632
|
+
const valueChange = changes['value'];
|
|
2633
|
+
const valueEffectWillRender = !!valueChange &&
|
|
2634
|
+
String(valueChange.currentValue) !== this._lastPropagatedValue &&
|
|
2635
|
+
this._valueEffectWillWrite(valueChange.currentValue);
|
|
2636
|
+
this._applyMask(!valueEffectWillRender);
|
|
2595
2637
|
if (!this._configApplied) {
|
|
2596
2638
|
this._configApplied = true;
|
|
2597
2639
|
if (this._hasPendingInitialValue) {
|
|
@@ -3713,6 +3755,18 @@ class NgxMaskDirective {
|
|
|
3713
3755
|
}
|
|
3714
3756
|
});
|
|
3715
3757
|
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Whether the `value`-model effect will render `value` itself via writeValue(): only in
|
|
3760
|
+
* standalone `[(value)]` / Signal Forms mode (Angular's writeValue owns rendering in CVA mode), not for
|
|
3761
|
+
* the model echo of our own onChange propagation (`_skipNextValueEffect`), and only when it
|
|
3762
|
+
* differs from what is already rendered. Shared with ngOnChanges (#1671), which must leave
|
|
3763
|
+
* exactly that render to the effect.
|
|
3764
|
+
*/
|
|
3765
|
+
_valueEffectWillWrite(value) {
|
|
3766
|
+
return (!this._isCvaMode() &&
|
|
3767
|
+
!this._skipNextValueEffect() &&
|
|
3768
|
+
String(value) !== String(this._inputValue()));
|
|
3769
|
+
}
|
|
3716
3770
|
/**
|
|
3717
3771
|
* Focus the input element.
|
|
3718
3772
|
* Required by FormValueControl interface for Signal Forms.
|
|
@@ -3755,8 +3809,16 @@ class NgxMaskDirective {
|
|
|
3755
3809
|
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
|
3756
3810
|
this._maskService.formElementProperty = ['disabled', isDisabled];
|
|
3757
3811
|
}
|
|
3758
|
-
_applyMask() {
|
|
3812
|
+
_applyMask(renderInputValue) {
|
|
3759
3813
|
this._maskService.maskExpression = this._maskService._repeatPatternSymbols(this._maskValue() || '');
|
|
3814
|
+
if (!renderInputValue) {
|
|
3815
|
+
// Consume the one-shot flags ngOnChanges set for THIS render, as applyMask would:
|
|
3816
|
+
// writingValue (thousandSeparator change) and maskChanged (mask change). Nothing
|
|
3817
|
+
// else clears them when the following writeValue() takes its no-applyMask branch.
|
|
3818
|
+
this._maskService.writingValue = false;
|
|
3819
|
+
this._maskService._clearMaskChangedUnlessTriggered();
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3760
3822
|
this._maskService.formElementProperty = [
|
|
3761
3823
|
'value',
|
|
3762
3824
|
this._maskedOrVerbatim(this._inputValue()),
|
|
@@ -4033,11 +4095,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
4033
4095
|
|
|
4034
4096
|
class NgxMaskPipe {
|
|
4035
4097
|
defaultOptions = inject(NGX_MASK_CONFIG);
|
|
4036
|
-
|
|
4098
|
+
_environmentInjector = inject(EnvironmentInjector);
|
|
4099
|
+
_maskService;
|
|
4037
4100
|
_maskExpressionArray = [];
|
|
4038
4101
|
mask = '';
|
|
4039
4102
|
transform(value, mask, { patterns, maskAliases, ...config } = {}) {
|
|
4040
4103
|
let processedValue = value;
|
|
4104
|
+
// NgxMaskService is stateful (built for the directive, which keeps value history across
|
|
4105
|
+
// keystrokes), so each transform gets a fresh instance: nothing from a previous call can
|
|
4106
|
+
// leak into this one (#1669).
|
|
4107
|
+
this._maskService = Injector.create({
|
|
4108
|
+
providers: [
|
|
4109
|
+
NgxMaskService,
|
|
4110
|
+
{ provide: NGX_MASK_CONFIG, useValue: this.defaultOptions },
|
|
4111
|
+
],
|
|
4112
|
+
parent: this._environmentInjector,
|
|
4113
|
+
}).get(NgxMaskService);
|
|
4041
4114
|
// User-defined aliases expand BEFORE any other mask processing (incl. `||` handling).
|
|
4042
4115
|
const resolvedMask = resolveMaskAlias(mask, {
|
|
4043
4116
|
...this.defaultOptions.maskAliases,
|
|
@@ -4048,7 +4121,7 @@ class NgxMaskPipe {
|
|
|
4048
4121
|
...this.defaultOptions,
|
|
4049
4122
|
...config,
|
|
4050
4123
|
patterns: {
|
|
4051
|
-
...this.
|
|
4124
|
+
...this.defaultOptions.patterns,
|
|
4052
4125
|
...patterns,
|
|
4053
4126
|
},
|
|
4054
4127
|
};
|
|
@@ -4096,7 +4169,6 @@ class NgxMaskPipe {
|
|
|
4096
4169
|
if (this._maskService.decimalMarker === MaskExpression.COMMA) {
|
|
4097
4170
|
processedValue = processedValue.replace(MaskExpression.DOT, MaskExpression.COMMA);
|
|
4098
4171
|
}
|
|
4099
|
-
this._maskService.isNumberValue = true;
|
|
4100
4172
|
}
|
|
4101
4173
|
if (processedValue === null || typeof processedValue === 'undefined') {
|
|
4102
4174
|
return this._maskService.applyMask('', resolvedMask);
|