Getting Started
Install from npm:
npm install imaskAnd import or require:
import IMask from 'imask';or use CDN:
<script src="https://unpkg.com/imask"></script>Simple use case:
const element = document.getElementById('selector');
const maskOptions = {
mask: '+{7}(000)000-00-00'
};
const mask = IMask(element, maskOptions);IMask consists of two independent layers: model and view.
Model layer contains all masking facilities which can be used independently without UI.
View layer is a glue between UI element and model, it connects listeners and controls changes in both directions.
Input processing is based on a simple idea of comparing states before and after change. State before change is obtained on keydown and on input actual processing takes place. In order to support older browsers manually call _saveSelection to save state and _onInput to handle changes. Pull requests for the beauty are welcomed.
Currently, view layer contains only one component InputMask which supports HTML input-like API. Instance of InputMask is returned when IMask constructor is called.
To create new mask on element use:
const mask = IMask(element, maskOptions);Get/set value and unmasked value:
mask.value = '+7(999)999-99-99';
console.log(mask.value); // '+7(999)999-99-99'
console.log(mask.unmaskedValue); // '79999999999'
mask.unmaskedValue = '70000000000';
console.log(mask.value); // '+7(000)000-00-00'
console.log(mask.unmaskedValue); // '70000000000'For typed masks like Number or Date it is possible to work with typed values:
mask.updateOptions({mask: Number});
mask.typedValue = 100; // use number
console.log(mask.value); // '100'
console.log(mask.unmaskedValue); // '100'
console.log(mask.typedValue); // 100For untyped masks typedValue and unmaskedValue works identically.
Update options:
mask.updateOptions({
mask: Number,
min: 0,
max: 100
}); // also updates UIClean and destroy:
mask.destroy();Listen to events:
const log = () => console.log(mask.value);
// 'accept' event fired on input when mask value has changed
mask.on('accept', log);
// 'complete' event fired when the value is completely filled
// Note: this makes sense only for Pattern-based masks
mask.on('complete', () => console.log(mask.value));Stop listening to events:
mask.off('accept', log);
// omit handler argument to unbind all handlers
mask.off('complete');Get Masked model:
const masked = mask.masked;
masked.reset(); // UI will NOT be updatedIn the above example all changes are proxied to the model layer first and then UI is updated. The core of masking on model layer is IMask.Masked base class.
There are also several other model classes for the different mask property types that provide additional functionality:
mask prop |
Model class |
|---|---|
IMask.Masked descendant or instance |
IMask.Masked |
RegExp |
IMask.MaskedRegExp |
Function |
IMask.MaskedFunction |
String |
IMask.MaskedPattern |
Number |
IMask.MaskedNumber |
Date |
IMask.MaskedDate |
Array of masks |
IMask.MaskedDynamic |
Mask also can be used without UI, e.g.
const masked = IMask.createMask({
mask: '+7 (000) 000-00-00',
// ...and other options
});
masked.resolve('71234567890');
// now you can access masked value
console.log(masked.value);
// and get unmasked value
console.log(masked.unmaskedValue);Masked
Common Behavior
IMask.Masked is a base class of all other Masked* classes. Almost all base class options are applicable to subclasses.
Example usage:
const digitsMask = IMask(element, {
mask: /^\d+$/
});Update options:
mask.updateOptions({
// while changing mask only same type allowed
mask: /^\w+$/, // ok
// mask: "0000", // ERROR! changing mask type on existing mask is not allowed!
// ... other options
});Get/set value and unmasked value:
masked.value = 'hello world!';
console.log(masked.unmaskedValue);
// or typed value if it makes sense
console.log(masked.typedValue);Use prepare (value, masked) or prepareChar (value, masked) option for preprocessing input and commit (value, masked) option for postprocessing after UI is deactivated:
IMask(element, {
mask: /^\w+$/,
prepareChar: str => str.toUpperCase(),
commit: (value, masked) => {
// Don't change value manually! All changes should be done in mask!
// This example helps to understand what is really changes, only for demo
masked._value = value.toLowerCase(); // Don't do it
}
})The difference between prepare and prepareChar is that when inserting multiple symbols in a batch prepare is called once for the entire string and prepareChar is called for each symbol.
Usually you don't need to create instances of that type manually, because it will be done by IMask internally. But you can subclass from Masked to add some specific behavior.
Additionaly to mask option custom validator can be set as validate (value, masked) option for some complex checks on any mask types excluding Function and RegExp, because they are already validators themselves.
Note: do not mutate Masked instance inside callbacks.
Example of using Function mask to accept any growing sequence from 0 to 9:
IMask(element, {
mask: value => /^\d*$/.test(value) &&
value.split('').every((ch, i) => {
const prevCh = value[i-1];
return !prevCh || prevCh < ch;
})
})Overwrite Mode since 5.0
Enables characters overwriting (replace or shift) instead of inserting.
IMask(
document.getElementById('overwrite-mask'),
{
mask: '000000',
lazy: false,
}
)Skip Invalid Mode since 6.5.0
Enables skipping invalid characters (default is true).
IMask(
document.getElementById('skipInvalid-mask'),
{
mask: '000000',
skipInvalid: <CHECK>,
}
)Number Mask
Number mask restricts input to integer or decimal numbers in many ways:
IMask(element, {
mask: Number, // enable number mask
// other options are optional with defaults below
scale: 2, // digits after point, 0 for integers
thousandsSeparator: '', // any single char
padFractionalZeros: false, // if true, then pads zeros at end to the length of scale
normalizeZeros: true, // appends or removes zeros at ends
radix: ',', // fractional delimiter
mapToRadix: ['.'], // symbols to process as radix
// additional number interval options (e.g.)
min: -10000,
max: 10000,
autofix: true,
})Pattern Mask
Use pattern when:- mask is complex or contains nested masks
- mask is fixed in size (optional symbols can provide some flexibility)
- placeholder is needed
- more reliability or flexibility on processing input is needed
Pattern mask is just a string:
IMask(element, {
mask: '{#}000[aaa]/NIC-`*[**]'
});
// or without UI element
IMask.PatternMasked({
mask: '{#}000[aaa]/NIC-`*[**]'
});0- any digita- any letter*- any char- other chars which is not in custom definitions supposed to be fixed
[]- make input optional{}- include fixed part in unmasked value`- prevent symbols shift back
If definition character should be treated as fixed it should be escaped by \\ (E.g. \\0).
Additionally you can provide custom definitions:
IMask(element, {
mask: '#00000',
definitions: {
// <any single char>: <same type as mask (RegExp, Function, etc.)>
// defaults are '0', 'a', '*'
'#': /[1-6]/
}
})To configure placeholder use:
IMask(element, {
mask: '+{7}(000)000-00-00',
lazy: false, // make placeholder always visible
placeholderChar: '#' // defaults to '_'
})Secure text entry since 6.6.0:
IMask(element, {
mask: '000000',
displayChar: '#',
lazy: false,
overwrite: 'shift',
})For complex nested masks there is blocks option available:
IMask(element, {
mask: 'Ple\\ase fill ye\\ar 19YY, month MM \\and v\\alue VL',
lazy: false, // make placeholder always visible
blocks: {
YY: {
mask: '00',
},
MM: {
mask: IMask.MaskedRange,
from: 1,
to: 12
},
VL: {
mask: IMask.MaskedEnum,
enum: ['TV', 'HD', 'VR']
}
}
})Expose block value since 7.1.0
Block value can be exposed using the expose option on one of the blocks:
IMask(element, {
mask: '$num',
lazy: false, // make placeholder always visible
blocks: {
num: {
mask: Number,
expose: true,
},
}
})Lazy Mode
IMask(
document.getElementById('pattern-lazy-mask'),
{
mask: Date,
lazy: <CHECK>,
autofix: true,
blocks: {
d: {mask: IMask.MaskedRange, placeholderChar: 'd', from: 1, to: 31, maxLength: 2},
m: {mask: IMask.MaskedRange, placeholderChar: 'm', from: 1, to: 12, maxLength: 2},
Y: {mask: IMask.MaskedRange, placeholderChar: 'y', from: 1900, to: 2999, maxLength: 4}
}
}
)Eager Mode since 6.3.0
IMask(
document.getElementById('pattern-eager-mask'),
{
mask: '00##00##',
eager: <SELECT>,
}
)Range Mask
Range mask extends Pattern mask and can be used to restrict input in a number range.
IMask(element, {
mask: IMask.MaskedRange,
from: 1,
to: 90,
// maxLength is optional parameter to set the length of mask. To input smaller values pad zeros at start
maxLength: 3,
autofix: true, // bound value
// pattern options can be set as well
lazy: false
})Unlike Number mask Range mask is fixed in size, accepts only integer values but can use placeholder.
Autofix Mode
IMask(
document.getElementById('range-autofix-mask'),
{
mask: IMask.MaskedRange,
from: 0,
to: 15,
autofix: <SELECT>,
}
)Repeat block (since 7.3.0)
IMask(
document.getElementById('repeat-mask'),
{
mask: 'r',
lazy: false,
blocks: {
r: {
repeat: <SELECT>,
mask: '0',
}
},
}
)Enum Mask
Enum mask extends Pattern mask and can be used to restrict input within characters enum.
IMask(element, {
mask: IMask.MaskedEnum,
enum: Array.from({ length: 12 }, (_, i) =>
new Date(0, i).toLocaleString(window.navigator.language, { month: 'long' })
),
// optionally can set match function
matchValue: (estr, istr, matchFrom) =>
// use case insensitive match
IMask.MaskedEnum.DEFAULTS.matchValue(estr.toLowerCase(), istr.toLowerCase(), matchFrom),
// pattern options can be set as well
lazy: false
})Date Mask
Date mask extends Pattern mask with more options.
IMask(element, {
mask: Date, // enable date mask
// other options are optional
pattern: 'Y-`m-`d', // Pattern mask with defined blocks, default is 'd{.}`m{.}`Y'
// you can provide your own blocks definitions, default blocks for date mask are:
blocks: {
d: {
mask: IMask.MaskedRange,
from: 1,
to: 31,
maxLength: 2,
},
m: {
mask: IMask.MaskedRange,
from: 1,
to: 12,
maxLength: 2,
},
Y: {
mask: IMask.MaskedRange,
from: 1900,
to: 9999,
}
},
// define date -> str convertion
format: date => {
let day = date.getDate();
let month = date.getMonth() + 1;
const year = date.getFullYear();
if (day < 10) day = "0" + day;
if (month < 10) month = "0" + month;
return [year, month, day].join('-');
},
// define str -> date convertion
parse: str => {
const yearMonthDay = str.split('-');
return new Date(yearMonthDay[0], yearMonthDay[1] - 1, yearMonthDay[2]);
},
// optional interval options
min: new Date(2000, 0, 1), // defaults to `1900-01-01`
max: new Date(2020, 0, 1), // defaults to `9999-01-01`
autofix: true, // defaults to `false`
// pattern options can be set as well
lazy: false,
// and other common options
overwrite: true // defaults to `false`
})It is easy to use it with Moment.js:
const momentFormat = 'YYYY/MM/DD HH:mm';
IMask(element, {
mask: Date,
pattern: momentFormat,
lazy: false,
min: new Date(1970, 0, 1),
max: new Date(2030, 0, 1),
format: date => moment(date).format(momentFormat),
parse: str => moment(str, momentFormat),
blocks: {
YYYY: {
mask: IMask.MaskedRange,
from: 1970,
to: 2030
},
MM: {
mask: IMask.MaskedRange,
from: 1,
to: 12
},
DD: {
mask: IMask.MaskedRange,
from: 1,
to: 31
},
HH: {
mask: IMask.MaskedRange,
from: 0,
to: 23
},
mm: {
mask: IMask.MaskedRange,
from: 0,
to: 59
}
}
});Dynamic Mask
Dynamic mask automatically selects appropriate mask from provided array of masks. Mask with the largest number of fitting characters is selected considering provided masks order.
IMask(element, {
mask: [
{
mask: 'RGB,RGB,RGB',
blocks: {
RGB: {
mask: IMask.MaskedRange,
from: 0,
to: 255
}
}
},
{
mask: /^#[0-9a-f]{0,6}$/i
}
]
})It is also possible to select mask manually via dispatch option:
IMask(element, {
mask: [
{
mask: '+00 {21} 0 000 0000',
startsWith: '30',
lazy: false,
country: 'Greece'
},
{
mask: '+0 000 000-00-00',
startsWith: '7',
lazy: false,
country: 'Russia'
},
{
mask: '+00-0000-000000',
startsWith: '91',
lazy: false,
country: 'India'
},
{
mask: '0000000000000',
startsWith: '',
country: 'unknown'
}
],
dispatch: (appended, dynamicMasked) => {
const number = (dynamicMasked.value + appended).replace(/\D/g,'');
return dynamicMasked.compiledMasks.find(m => number.indexOf(m.startsWith) === 0);
}
})Expose mask value since 7.1.0
Nested mask value can be exposed using the expose option on one of the masks:
IMask(element, {
mask: [
{ mask: '' },
{
mask: 'd %',
lazy: false,
expose: true,
blocks: {
d: {
mask: Number,
expose: true,
},
},
},
],
})Pipe since 6.0
Since v6.0.0 IMask can be used for formatting or converting values through mask with pipe and createPipe routines:
const numberPipe = IMask.createPipe({
mask: Number,
scale: 2,
thousandsSeparator: ' ',
normalizeZeros: true,
padFractionalZeros: true,
});
// `numberPipe` is just a function, call it to format values
numberPipe('1'); // "1,00"
// if `numberPipe` will not be reused, then just use `IMask.pipe` inplace:
IMask.pipe('1', {
mask: Number,
scale: 2,
thousandsSeparator: ' ',
normalizeZeros: true,
padFractionalZeros: true,
}); // "1,00"By default pipe expects masked value on input and produces masked input as well. This behavior is exactly the same as if a value was pasted in input.
Pipe also provides a way to customize source and destination types:
// pipe(value, mask|masked, sourcetype, destinationtype);
// PIPE_TYPE = {TYPED, MASKED, UNMASKED}
// converts formated string to number
IMask.pipe(
'1,01',
{
mask: Number,
scale: 2,
thousandsSeparator: ' ',
normalizeZeros: true,
padFractionalZeros: true,
},
IMask.PIPE_TYPE.MASKED,
IMask.PIPE_TYPE.TYPED
); // 1.01 of number typeAngular plugin provides IMaskPipe for convinience.
Advanced
Treeshaking since 6.0
IMask contains a lot of cool features, which makes it pretty big in size. But you might not need all features and want to reduce your bundle size. Since v6.0.0 IMask provides such possibility and ships chunks along side with assembled bundle. Despite mask has been already support esm modules treeshaking did not work on it because of recursive masks and therefore circular dependencies inside. So now you can import only features that you want, for instance if only number mask is used:
// was before
// import IMask from 'imask'; // imports all modules
// enable treeshaking
import IMask from 'imask/holder'; // imports only factory
// add needed features
import 'imask/masked/number';
// now factory can work with number masks, but not any other
// usage is same in both cases
IMask(element, { mask: Number });