Files
tools/vue/toolkit/src/composables/forms/mask/presets/template.ts
T

58 lines
1.9 KiB
TypeScript

/**
* Default {@link maskFromTemplate} tokens: `#` → digit, `A` → letter,
* `*` → letter or digit. Every other template character is a fixed literal.
*/
export const DEFAULT_MASK_TOKENS: Readonly<Record<string, RegExp>> = {
'#': /\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<string, ReadonlyArray<RegExp | string>>();
function compileTemplate(template: string, tokens: Readonly<Record<string, RegExp>>): Array<RegExp | string> {
const mask: Array<RegExp | string> = [];
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<string, RegExp>} [tokens=DEFAULT_MASK_TOKENS] Token → matcher map
* @returns {ReadonlyArray<RegExp | string>} 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<Record<string, RegExp>> = DEFAULT_MASK_TOKENS,
): ReadonlyArray<RegExp | string> {
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;
}