@@ -36,6 +36,10 @@ type MaskReplaceResult = {
3636const MASK_FLAGS = new Set ( 'aACL09#&?' ) ;
3737const MASK_REQUIRED_FLAGS = new Set ( '0#LA&' ) ;
3838
39+ const ESCAPE_CHAR = '\\' ;
40+ const DEFAULT_FORMAT = 'CCCCCCCCCC' ;
41+ const DEFAULT_PROMPT = '_' ;
42+
3943const ASCII_ZERO = 0x0030 ;
4044const DIGIT_ZERO_CODEPOINTS = [
4145 ASCII_ZERO , // ASCII
@@ -97,8 +101,8 @@ function validate(char: string, flag: string): boolean {
97101
98102/** Default mask parser options */
99103const MaskDefaultOptions : MaskOptionsInternal = {
100- format : 'CCCCCCCCCC' ,
101- promptCharacter : '_' ,
104+ format : DEFAULT_FORMAT ,
105+ promptCharacter : DEFAULT_PROMPT ,
102106} ;
103107
104108/**
@@ -124,7 +128,7 @@ export class MaskParser {
124128 * Returns a set of the all the literal positions in the mask.
125129 * These positions are fixed characters that are not part of the input.
126130 */
127- public get literalPositions ( ) : Set < number > {
131+ public get literalPositions ( ) : ReadonlySet < number > {
128132 return this . _literalPositions ;
129133 }
130134
@@ -187,6 +191,10 @@ export class MaskParser {
187191 this . _parseMaskLiterals ( ) ;
188192 }
189193
194+ private _isEscapedFlag ( char : string , nextChar : string ) : boolean {
195+ return char === ESCAPE_CHAR && MASK_FLAGS . has ( nextChar ) ;
196+ }
197+
190198 /**
191199 * Parses the mask format string to identify literal characters and
192200 * create the escaped mask. This method is called whenever the mask format changes.
@@ -203,7 +211,7 @@ export class MaskParser {
203211 for ( let i = 0 ; i < length ; i ++ ) {
204212 const [ current , next ] = [ mask . charAt ( i ) , mask . charAt ( i + 1 ) ] ;
205213
206- if ( current === '\\' && MASK_FLAGS . has ( next ) ) {
214+ if ( this . _isEscapedFlag ( current , next ) ) {
207215 // Escaped character - push next as a literal character and skip processing it
208216 this . _literals . set ( currentPos , next ) ;
209217 escapedMaskChars . push ( next ) ;
@@ -289,6 +297,14 @@ export class MaskParser {
289297 /**
290298 * Replaces a segment of the masked string with new input, simulating typing or pasting.
291299 * It handles clearing the selected range and inserting new characters according to the mask.
300+ *
301+ * @example
302+ * ```ts
303+ * const parser = new MaskParser({ format: '00/00/0000' });
304+ * const current = '__/__/____';
305+ * const result = parser.replace(current, '12', 0, 0);
306+ * // result.value = '12/__/____', result.end = 2
307+ * ```
292308 */
293309 public replace (
294310 maskString : string ,
@@ -298,12 +314,12 @@ export class MaskParser {
298314 ) : MaskReplaceResult {
299315 const literalPositions = this . literalPositions ;
300316 const escapedMask = this . _escapedMask ;
301- const length = this . _escapedMask . length ;
317+ const length = escapedMask . length ;
302318 const prompt = this . prompt ;
303319 const endBoundary = Math . min ( end , length ) ;
304320
305321 // Initialize the array for the masked string or get a fresh mask with prompts and/or literals
306- const maskedChars = Array . from ( maskString || this . apply ( '' ) ) ;
322+ const maskedChars = maskString ? [ ... maskString ] : [ ... this . apply ( '' ) ] ;
307323
308324 const inputChars = Array . from ( replaceUnicodeNumbers ( value ) ) ;
309325 const inputLength = inputChars . length ;
@@ -358,17 +374,16 @@ export class MaskParser {
358374 const literalPositions = this . literalPositions ;
359375 const prompt = this . prompt ;
360376 const length = masked . length ;
361-
362- let result = '' ;
377+ const result : string [ ] = [ ] ;
363378
364379 for ( let i = 0 ; i < length ; i ++ ) {
365380 const char = masked [ i ] ;
366381 if ( ! literalPositions . has ( i ) && char !== prompt ) {
367- result += char ;
382+ result . push ( char ) ;
368383 }
369384 }
370385
371- return result ;
386+ return result . join ( '' ) ;
372387 }
373388
374389 /**
@@ -379,17 +394,21 @@ export class MaskParser {
379394 const prompt = this . prompt ;
380395
381396 return this . _requiredPositions . every ( ( position ) => {
382- const char = input . charAt ( position ) ;
383- return (
384- validate ( char , this . _escapedMask . charAt ( position ) ) && char !== prompt
385- ) ;
397+ const char = input [ position ] ;
398+ return validate ( char , this . _escapedMask [ position ] ) && char !== prompt ;
386399 } ) ;
387400 }
388401
389402 /**
390403 * Applies the mask format to an input string. This attempts to fit the input
391404 * into the mask from left to right, filling valid positions and skipping invalid
392405 * input characters.
406+ *
407+ * @example
408+ * ```ts
409+ * const parser = new MaskParser({ format: '00/00/0000' });
410+ * parser.apply('12252023'); // Returns '12/25/2023'
411+ * ```
393412 */
394413 public apply ( input = '' ) : string {
395414 const literals = this . _literals ;
@@ -398,7 +417,7 @@ export class MaskParser {
398417 const length = escapedMask . length ;
399418
400419 // Initialize the result array with prompt characters
401- const result = new Array ( escapedMask . length ) . fill ( prompt ) ;
420+ const result = new Array ( length ) . fill ( prompt ) ;
402421
403422 // Place all literal characters into the result array
404423 for ( const [ position , literal ] of literals . entries ( ) ) {
@@ -424,10 +443,11 @@ export class MaskParser {
424443 continue ;
425444 }
426445
427- if ( validate ( normalizedInput . charAt ( inputIndex ) , escapedMask . charAt ( i ) ) ) {
428- result [ i ] = normalizedInput . charAt ( inputIndex ) ;
446+ if ( validate ( normalizedInput [ inputIndex ] , escapedMask [ i ] ) ) {
447+ result [ i ] = normalizedInput [ inputIndex ] ;
429448 }
430449
450+ // Always advance - invalid characters are consumed/skipped
431451 inputIndex ++ ;
432452 }
433453
0 commit comments