import { attr, css, element, html, listen, queryAll } from '@joist/element';
import { PATTERN_CHARS, type PatternChar, REG_EXPS, format } from './format.js';
import type { MaskableElement } from './maskable.element.js';
declare global {
interface HTMLElementTagNameMap {
'usa-input-mask': USAInputMaskElement;
}
}
@element({
tagName: 'usa-input-mask',
shadowDom: [
css`
:host {
display: contents;
}
`,
html``,
],
})
export class USAInputMaskElement extends HTMLElement {
@attr()
accessor mask = '';
#maskables = queryAll('[mask]', this);
connectedCallback() {
for (const input of this.#maskables()) {
const { formatted } = format(input.value || input.getAttribute('value') || '', this.#getMaskFor(input));
if (formatted) {
input.value = formatted;
}
}
}
@listen('input')
async onInput(e: Event) {
const input = e.target as MaskableElement;
const selectionStart = input.selectionStart || 0;
const prev = input.value;
const mask = this.#getMaskFor(input);
const { formatted } = format(input.value, mask);
input.value = formatted;
const offset = input.value.length - prev.length;
const maskChar = mask[selectionStart - 1] as PatternChar | undefined;
// check if the current value is not a space for characters and has an offset greater then 0
if (maskChar && !PATTERN_CHARS.includes(maskChar) && offset > 0) {
input.selectionStart = selectionStart + offset;
input.selectionEnd = selectionStart + offset;
} else {
input.selectionStart = selectionStart;
input.selectionEnd = selectionStart;
}
}
@listen('keydown')
onKeyDown(e: KeyboardEvent) {
const input = e.target as MaskableElement;
const mask = this.#getMaskFor(input);
const patternChar = mask[input.selectionStart || 0] as PatternChar;
if (e.key.length === 1 && /^[a-z0-9]/i.test(e.key)) {
// check that the key is a single character and that it is a letter or number
if (input.value.length >= mask.length) {
// prevent default once value is the same as the mask length
e.preventDefault();
} else if (patternChar === '9') {
if (!REG_EXPS.Numbers.test(e.key)) {
// if pattern char specifies number and is not
e.preventDefault();
}
} else if (patternChar === 'A') {
if (!REG_EXPS.Letters.test(e.key)) {
// if pattern char specifies letter and is not
e.preventDefault();
}
}
}
}
#getMaskFor(input: MaskableElement) {
return this.mask || input.getAttribute('mask') || '';
}
}