/** * Default {@link maskFromTemplate} tokens: `#` → digit, `A` → letter, * `*` → letter or digit. Every other template character is a fixed literal. */ export const DEFAULT_MASK_TOKENS: Readonly> = { '#': /\d/, A: /[a-z]/i, '*': /[a-z0-9]/i, }; // Compiled-mask cache for the default-token path. Function masks (phone/card) // re-resolve the same template several times per keystroke; caching collapses // each repeat to a Map hit instead of rebuilding a 12-17 element array. const TEMPLATE_CACHE = new Map>(); function compileTemplate(template: string, tokens: Readonly>): Array { const mask: Array = []; for (const char of template) mask.push(tokens[char] ?? char); return mask; } /** * @name maskFromTemplate * @category Forms * @description Compile a human-readable template into a mask array. Token * characters become matcher slots; everything else becomes a fixed literal. With * the default tokens the compiled array is cached and shared (frozen) per * template string; pass a custom `tokens` map to opt out. * * @param {string} template e.g. `'+1 (###) ###-####'` * @param {Record} [tokens=DEFAULT_MASK_TOKENS] Token → matcher map * @returns {ReadonlyArray} The compiled mask expression * * @example * maskFromTemplate('##/##/####'); // [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/] * * @since 0.0.17 */ export function maskFromTemplate( template: string, tokens: Readonly> = DEFAULT_MASK_TOKENS, ): ReadonlyArray { if (tokens !== DEFAULT_MASK_TOKENS) return compileTemplate(template, tokens); const cached = TEMPLATE_CACHE.get(template); if (cached) return cached; const compiled = Object.freeze(compileTemplate(template, tokens)); TEMPLATE_CACHE.set(template, compiled); return compiled; }