Input field
attach puts a mask on an <input>. The hard part of masks isn't formatting but editing behaviour, and attach takes care of it.
ts
import { attach } from 'tinyval-mask';
import { phone } from 'tinyval-mask/presets';
const input = document.querySelector<HTMLInputElement>('#phone')!;
const field = attach(input, phone(), {
onChange: ({ value, raw, complete }) => console.log(value, raw, complete),
});
field.raw; // '79991234567'
field.complete; // true
field.destroy(); // remove the maskWhat happens while typing
| Action | Behaviour |
|---|---|
| Typing at the end | Literals appear by themselves: 9 → +7 (9 |
| Typing in the middle | Characters shift, the caret stays right after the typed one |
Backspace after ) or - | The nearest digit is removed; the caret doesn't get stuck on the literal |
| Delete before a literal | The same in the other direction |
| Paste | Any format is accepted: 8 999 123-45-67, +79991234567, (999) 1234567 |
| Unsuitable characters | Ignored: letters won't appear in a phone number |
The caret is tracked by typed characters, not by position in the string. So it doesn't jump even when the mask adds or removes separators — e.g. thousands spaces in numberMask.
Framework compatibility
attach formats the value before framework handlers (an input listener in the capture phase), so v-model, React's onChange and similar get the formatted value. After a programmatic edit (Backspace through a literal) attach dispatches an input event itself.
If the field value is changed from code (input.value = …), call field.update(mask) — it reformats the value.