ngx-mask 22.2.2 → 22.2.4
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 +85 -19
- package/fesm2022/ngx-mask.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-mask.d.ts +16 -1
package/fesm2022/ngx-mask.mjs
CHANGED
|
@@ -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;
|
|
@@ -1427,6 +1447,10 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1427
1447
|
if (!this.writingValue) {
|
|
1428
1448
|
this.formControlResult(this.prefix);
|
|
1429
1449
|
}
|
|
1450
|
+
// The field is now empty: record it, or retyping the previous value in one
|
|
1451
|
+
// input event (autofill, undo) would match the stale currentValue and never emit.
|
|
1452
|
+
this.previousValue = this.currentValue;
|
|
1453
|
+
this.currentValue = MaskExpression.EMPTY_STRING;
|
|
1430
1454
|
return `${this.prefix}${this.maskIsShown}${this.suffix}`;
|
|
1431
1455
|
}
|
|
1432
1456
|
const getSymbol = !!inputValue && typeof this.selStart === 'number'
|
|
@@ -1607,9 +1631,7 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1607
1631
|
this.formControlResult(result);
|
|
1608
1632
|
}
|
|
1609
1633
|
// Reset maskChanged flag even if we didn't emit
|
|
1610
|
-
|
|
1611
|
-
this.maskChanged = false;
|
|
1612
|
-
}
|
|
1634
|
+
this._clearMaskChangedUnlessTriggered();
|
|
1613
1635
|
// Handle hidden input and showMaskTyped
|
|
1614
1636
|
if (!this.showMaskTyped || (this.showMaskTyped && this.hiddenInput)) {
|
|
1615
1637
|
if (this.hiddenInput) {
|
|
@@ -1820,6 +1842,16 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1820
1842
|
.filter((item) => this._findDropSpecialChar(item));
|
|
1821
1843
|
return chars.length;
|
|
1822
1844
|
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Consumes a pending `maskChanged` once a render has handled the reconfiguration, unless
|
|
1847
|
+
* `triggerOnMaskChange` still needs it to emit the re-masked value (formControlResult clears
|
|
1848
|
+
* it then).
|
|
1849
|
+
*/
|
|
1850
|
+
_clearMaskChangedUnlessTriggered() {
|
|
1851
|
+
if (!this.triggerOnMaskChange) {
|
|
1852
|
+
this.maskChanged = false;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1823
1855
|
removeMask(inputValue) {
|
|
1824
1856
|
return this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.specialCharacters.concat('_').concat(this.placeHolderCharacter));
|
|
1825
1857
|
}
|
|
@@ -1973,11 +2005,15 @@ class NgxMaskService extends NgxMaskApplierService {
|
|
|
1973
2005
|
.split(this.placeHolderCharacter)
|
|
1974
2006
|
.join(MaskExpression.EMPTY_STRING);
|
|
1975
2007
|
}
|
|
1976
|
-
|
|
2008
|
+
// #1668: an email address is meaningless without its '@' and '.', so the built-in
|
|
2009
|
+
// email mask always reports the full address, whatever dropSpecialCharacters says.
|
|
2010
|
+
const keepSpecialCharacters = this.maskExpression === MaskExpression.EMAIL_MASK;
|
|
2011
|
+
if (Array.isArray(this.dropSpecialCharacters) && !keepSpecialCharacters) {
|
|
1977
2012
|
this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeMask(this._removeSuffix(this._removePrefix(inputValue)), this.dropSpecialCharacters)))));
|
|
1978
2013
|
}
|
|
1979
|
-
else if (
|
|
1980
|
-
(
|
|
2014
|
+
else if (!keepSpecialCharacters &&
|
|
2015
|
+
(this.dropSpecialCharacters ||
|
|
2016
|
+
(!this.dropSpecialCharacters && this.prefix === inputValue))) {
|
|
1981
2017
|
this.onChange(outputTransformFn(this._toNumber(this._checkSymbols(this._removeSuffix(this._removePrefix(inputValue))))));
|
|
1982
2018
|
}
|
|
1983
2019
|
else {
|
|
@@ -2351,7 +2387,8 @@ class NgxMaskDirective {
|
|
|
2351
2387
|
* originated from the view. Re-masking that unmasked echo is at best a redundant re-render
|
|
2352
2388
|
* and at worst destructive for masks whose unmasked form is ambiguous (e.g. IP:
|
|
2353
2389
|
* '192168178' cannot reconstruct the typed dots of '192.168.1.78'). `writeValue()` consumes
|
|
2354
|
-
* this marker to skip exactly that echo
|
|
2390
|
+
* this marker to skip exactly that echo; ngOnChanges reads it to recognise a `[value]`
|
|
2391
|
+
* binding echo of it (#1671). `null` = no pending propagation.
|
|
2355
2392
|
*/
|
|
2356
2393
|
_lastPropagatedValue = null;
|
|
2357
2394
|
/**
|
|
@@ -2423,15 +2460,11 @@ class NgxMaskDirective {
|
|
|
2423
2460
|
effect(() => {
|
|
2424
2461
|
const signalValue = this.value();
|
|
2425
2462
|
untracked(() => {
|
|
2426
|
-
if (this.
|
|
2427
|
-
|
|
2463
|
+
if (this._valueEffectWillWrite(signalValue)) {
|
|
2464
|
+
this.writeValue(signalValue);
|
|
2428
2465
|
}
|
|
2429
|
-
|
|
2466
|
+
else {
|
|
2430
2467
|
this._skipNextValueEffect.set(false);
|
|
2431
|
-
return;
|
|
2432
|
-
}
|
|
2433
|
-
if (String(signalValue) !== String(this._inputValue())) {
|
|
2434
|
-
this.writeValue(signalValue);
|
|
2435
2468
|
}
|
|
2436
2469
|
});
|
|
2437
2470
|
});
|
|
@@ -2591,7 +2624,20 @@ class NgxMaskDirective {
|
|
|
2591
2624
|
if (keepCharacterPositions) {
|
|
2592
2625
|
this._maskService.keepCharacterPositions = keepCharacterPositions.currentValue;
|
|
2593
2626
|
}
|
|
2594
|
-
|
|
2627
|
+
// #1671: a standalone `[(value)]` change reaches ngOnChanges BEFORE the value effect
|
|
2628
|
+
// writes it, so `_inputValue` still holds the PREVIOUS value here. Re-rendering it would
|
|
2629
|
+
// run applyMask on stale input, and whatever that emits (the showMaskTyped skeleton's
|
|
2630
|
+
// '', a hiddenInput / keepCharacterPositions reconstruction) flows back through onChange,
|
|
2631
|
+
// overwrites the freshly bound model and makes the effect skip the real write. When the
|
|
2632
|
+
// effect is about to render the new value via writeValue() anyway, only apply the mask
|
|
2633
|
+
// configuration here. The effect only runs when the `value` SIGNAL changes: a parent
|
|
2634
|
+
// re-binding the value we last propagated (an echo, possibly applied in a later task)
|
|
2635
|
+
// changes the binding but not the signal, which already holds it — that pass renders.
|
|
2636
|
+
const valueChange = changes['value'];
|
|
2637
|
+
const valueEffectWillRender = !!valueChange &&
|
|
2638
|
+
String(valueChange.currentValue) !== this._lastPropagatedValue &&
|
|
2639
|
+
this._valueEffectWillWrite(valueChange.currentValue);
|
|
2640
|
+
this._applyMask(!valueEffectWillRender);
|
|
2595
2641
|
if (!this._configApplied) {
|
|
2596
2642
|
this._configApplied = true;
|
|
2597
2643
|
if (this._hasPendingInitialValue) {
|
|
@@ -3713,6 +3759,18 @@ class NgxMaskDirective {
|
|
|
3713
3759
|
}
|
|
3714
3760
|
});
|
|
3715
3761
|
}
|
|
3762
|
+
/**
|
|
3763
|
+
* Whether the `value`-model effect will render `value` itself via writeValue(): only in
|
|
3764
|
+
* standalone `[(value)]` / Signal Forms mode (Angular's writeValue owns rendering in CVA mode), not for
|
|
3765
|
+
* the model echo of our own onChange propagation (`_skipNextValueEffect`), and only when it
|
|
3766
|
+
* differs from what is already rendered. Shared with ngOnChanges (#1671), which must leave
|
|
3767
|
+
* exactly that render to the effect.
|
|
3768
|
+
*/
|
|
3769
|
+
_valueEffectWillWrite(value) {
|
|
3770
|
+
return (!this._isCvaMode() &&
|
|
3771
|
+
!this._skipNextValueEffect() &&
|
|
3772
|
+
String(value) !== String(this._inputValue()));
|
|
3773
|
+
}
|
|
3716
3774
|
/**
|
|
3717
3775
|
* Focus the input element.
|
|
3718
3776
|
* Required by FormValueControl interface for Signal Forms.
|
|
@@ -3755,8 +3813,16 @@ class NgxMaskDirective {
|
|
|
3755
3813
|
this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
|
3756
3814
|
this._maskService.formElementProperty = ['disabled', isDisabled];
|
|
3757
3815
|
}
|
|
3758
|
-
_applyMask() {
|
|
3816
|
+
_applyMask(renderInputValue) {
|
|
3759
3817
|
this._maskService.maskExpression = this._maskService._repeatPatternSymbols(this._maskValue() || '');
|
|
3818
|
+
if (!renderInputValue) {
|
|
3819
|
+
// Consume the one-shot flags ngOnChanges set for THIS render, as applyMask would:
|
|
3820
|
+
// writingValue (thousandSeparator change) and maskChanged (mask change). Nothing
|
|
3821
|
+
// else clears them when the following writeValue() takes its no-applyMask branch.
|
|
3822
|
+
this._maskService.writingValue = false;
|
|
3823
|
+
this._maskService._clearMaskChangedUnlessTriggered();
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3760
3826
|
this._maskService.formElementProperty = [
|
|
3761
3827
|
'value',
|
|
3762
3828
|
this._maskedOrVerbatim(this._inputValue()),
|