mirror of
https://github.com/tenrok/maska.git
synced 2026-05-15 11:59:38 +03:00
New version code prepare
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
+25
-3
@@ -1,3 +1,25 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
/.idea/
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
coverage
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2018 Form.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,196 +1,37 @@
|
||||
# Maska
|
||||
|
||||
Simple zero-dependency input mask for Vue.js and vanilla JS. [Demo and examples](https://beholdr.github.io/maska/).
|
||||
Simple zero-dependency input mask for Vue 2/3 or Vanilla JS.
|
||||
|
||||
- No dependencies
|
||||
- Small size (~2 Kb gziped)
|
||||
[](https://bundlephobia.com/package/maska)
|
||||
[](https://github.com/beholdr/pos/actions/workflows/build.yml)
|
||||

|
||||
|
||||
**[Documentation & demo →](https://beholdr.github.io/maska/)**
|
||||
|
||||
## Features ✨
|
||||
|
||||
- No dependencies and small size: ~2.5 Kb gziped
|
||||
- Works with any native input
|
||||
- Ability to define custom tokens
|
||||
- Supports repeat symbols and dynamic masks
|
||||
- Works on any input (custom or native)
|
||||
- Dynamic, reversed and eager masks
|
||||
- Token modifiers, transform functions and hooks
|
||||
|
||||
## Install
|
||||
## What’s new in v2 🎉
|
||||
|
||||
npm install maska
|
||||
- Fully rewritten in TypeScript
|
||||
- Autobind to vue variable
|
||||
- Eager and reversed masking modes
|
||||
- Optonal and repeated tokens
|
||||
- Simplified syntax for custom tokens
|
||||
- Dynamic masks with custom function logic
|
||||
- Hooks for pre/post- processing
|
||||
- Ability to replace or merge custom tokens
|
||||
|
||||
To load latest version from CDN you can use:
|
||||
|
||||
``` html
|
||||
<script src="https://cdn.jsdelivr.net/npm/maska@latest/dist/maska.js"></script>
|
||||
```
|
||||
|
||||
## Usage with Vue 2.x
|
||||
|
||||
If you load Vue.js via `<script>` then just add `v-maska` directive to your input:
|
||||
|
||||
``` html
|
||||
<input v-maska="'###'">
|
||||
```
|
||||
|
||||
You can add custom tokens by passing in object instead of string to directive:
|
||||
|
||||
``` html
|
||||
<input v-maska="{ mask: 'Z*', tokens: { 'Z': { pattern: /[а-яА-Я]/ }}}">
|
||||
```
|
||||
|
||||
With bundlers you can add global directive:
|
||||
|
||||
``` javascript
|
||||
import Maska from 'maska'
|
||||
Vue.use(Maska)
|
||||
```
|
||||
|
||||
or import `maska` directive for local usage in component:
|
||||
|
||||
``` html
|
||||
<template>
|
||||
<form>
|
||||
<input v-maska="'###'">
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { maska } from 'maska'
|
||||
|
||||
export default {
|
||||
directives: { maska }
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
With Vue you could use computed property as mask value. In this case mask will be reactive.
|
||||
|
||||
## Usage with Vue 3.x
|
||||
|
||||
With Vue 3.x you need to explicitly add Maska `plugin` or `directive` to your app:
|
||||
|
||||
``` javascript
|
||||
const app = Vue.createApp({...})
|
||||
// use as plugin
|
||||
app.use(Maska);
|
||||
// or as directive
|
||||
// app.directive('maska', Maska.maska);
|
||||
app.mount('#app');
|
||||
```
|
||||
|
||||
## Usage with vanilla JS
|
||||
|
||||
Just load script `maska.js` and init it, passing element(s) or `document.querySelector` expression:
|
||||
|
||||
``` javascript
|
||||
var mask = Maska.create('.masked');
|
||||
```
|
||||
|
||||
Mask could be set as `data-mask` attribute on element:
|
||||
|
||||
``` html
|
||||
<input data-mask='##/##/####'>
|
||||
```
|
||||
|
||||
or can be set by `mask` option on initialization:
|
||||
|
||||
``` javascript
|
||||
var mask = Maska.create('.masked', {
|
||||
mask: '##/##/####'
|
||||
});
|
||||
```
|
||||
|
||||
You can pass custom tokens while initialization:
|
||||
|
||||
``` javascript
|
||||
var mask = Maska.create('.masked', {
|
||||
tokens: { 'Z': { pattern: /[а-яА-Я]/ }}
|
||||
});
|
||||
```
|
||||
|
||||
You also can pass custom preprocessing transformation function for entire input:
|
||||
|
||||
``` javascript
|
||||
var mask = Maska.create('.masked', {
|
||||
tokens: { 'Z': { pattern: /[а-яА-Я]/ }},
|
||||
preprocessor: value => {
|
||||
return value.toUpperCase();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can destroy mask like that:
|
||||
|
||||
``` javascript
|
||||
var mask = Maska.create('.masked');
|
||||
mask.destroy();
|
||||
```
|
||||
|
||||
## Mask syntax
|
||||
|
||||
Default tokens:
|
||||
|
||||
``` javascript
|
||||
{
|
||||
'#': { pattern: /[0-9]/ },
|
||||
'X': { pattern: /[0-9a-zA-Z]/ },
|
||||
'S': { pattern: /[a-zA-Z]/ },
|
||||
'A': { pattern: /[a-zA-Z]/, uppercase: true },
|
||||
'a': { pattern: /[a-zA-Z]/, lowercase: true },
|
||||
'!': { escape: true },
|
||||
'*': { repeat: true }
|
||||
}
|
||||
```
|
||||
|
||||
- Escape symbol escapes next token (mask `!#` will render `#`)
|
||||
- Repeat symbol allows repeating current token until it’s valid (e.g. mask `#*` for all digits or `A* A*` for `CARDHOLDER NAME`)
|
||||
|
||||
You can add your own tokens by passing them in `maska` directive or `create` method at initialization (see above). **Important**: `pattern` field should be JS *regular expression* (`/[0-9]/`) or *string without delimiters* (`"[0-9]"`).
|
||||
|
||||
### Transform function for tokens
|
||||
|
||||
While specifying custom tokens you can also add a symbol-transformation behavior such as uppercase, lowercase, or even define a transform function:
|
||||
|
||||
``` javascript
|
||||
{
|
||||
'T': { pattern: /[0-9]/, transform: (char) => String(Number(char) % 2) } // '1234567890' -> '1010101010'
|
||||
}
|
||||
```
|
||||
|
||||
## Use mask programmatically
|
||||
|
||||
You can use `mask` function directly by importing it (or using `Maska.mask` if you use script tag)
|
||||
|
||||
``` javascript
|
||||
import { mask } from 'maska'
|
||||
|
||||
const maskedValue = mask(value, '###')
|
||||
```
|
||||
|
||||
## Getting raw (unmasked) value
|
||||
|
||||
To get raw value read `data-mask-raw-value` property of input. You can subscribe to `@maska` event to know when this value updates. Please see [examples page](https://beholdr.github.io/maska/).
|
||||
```html
|
||||
@maska="rawValue = $event.target.dataset.maskRawValue"
|
||||
```
|
||||
|
||||
## Dynamic masks
|
||||
|
||||
To use several masks on single input, pass array instead of string as mask value.
|
||||
|
||||
You could use it with Vue directives:
|
||||
|
||||
``` html
|
||||
<input v-maska="['+1 (###) ##-##-##', '+1 (###) ###-##-##']">
|
||||
|
||||
<input v-maska="{ mask: ['!#HHHHHH', '!#HHHHHH-HH'], tokens: { 'H': { pattern: /[0-9a-fA-F]/, uppercase: true }}}">
|
||||
```
|
||||
|
||||
and with vanilla JS attribute, but make sure that mask value is proper `JSON`, so use double quotes inside array:
|
||||
|
||||
``` html
|
||||
<input data-mask='["# cm", "#.# cm", "#.## cm"]'>
|
||||
```
|
||||
|
||||
## Known issues
|
||||
|
||||
When used on input of type `number` could have inconsistent behavior in different browsers. Use attribute `inputmode` if you just need a numeric keyboard for given input.
|
||||
|
||||
## Source of Inspiration
|
||||
## Source of Inspiration 💡
|
||||
|
||||
- [vue-the-mask](https://vuejs-tips.github.io/vue-the-mask/)
|
||||
- [jQuery Mask Plugin](http://igorescobar.github.io/jQuery-Mask-Plugin/)
|
||||
|
||||
## License 📄
|
||||
|
||||
[MIT](LICENSE.txt)
|
||||
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
# Live Demo
|
||||
|
||||
Here are several examples of basic masks that you could create with **Maska**.
|
||||
This demo is interactive: feel free to edit the examples.
|
||||
|
||||
<div id="demo-app"></div>
|
||||
<script src="dist/demo.js"></script>
|
||||
|
||||
# Install
|
||||
|
||||
<!-- tabs:start -->
|
||||
## **Via npm**
|
||||
|
||||
```
|
||||
npm i maska
|
||||
```
|
||||
|
||||
## **From CDN**
|
||||
|
||||
To include library from CDN, use UMD format and prefix all classes and directives with `Maska.`
|
||||
|
||||
``` html
|
||||
<script src="https://cdn.jsdelivr.net/npm/maska@latest/dist/maska.umd.js"></script>
|
||||
<script>
|
||||
new Maska.MaskInput("[data-maska]") // for masked input
|
||||
const mask = new Maska.Mask({ mask: "#-#" }) // for programmatic use
|
||||
</script>
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
# Usage
|
||||
|
||||
**Maska** library consists of three main components:
|
||||
|
||||
- `Mask` class to mask string values
|
||||
- `MaskInput` class to apply `Mask` processing to `<input>`
|
||||
- `vMaska` directive to simplify using of library within Vue components
|
||||
|
||||
<!-- tabs:start -->
|
||||
## **Vanilla JS**
|
||||
|
||||
Start with simple input element and `data-maska` attribute:
|
||||
|
||||
``` html
|
||||
<input data-maska="#-#">
|
||||
```
|
||||
|
||||
Then import and init `MaskInput`, passing input element(s) or `querySelector` expression as first argument:
|
||||
|
||||
``` js
|
||||
import { MaskInput } from "maska"
|
||||
new MaskInput("[data-maska]")
|
||||
```
|
||||
|
||||
Usually you set mask via `data-maska` attribute. But you can pass mask and other [options](#options) via second argument (it will be a default options that can be overriden by `data-maska-` attributes):
|
||||
|
||||
``` js
|
||||
new MaskInput("input", { mask: "#-#" })
|
||||
```
|
||||
|
||||
To destroy mask use `destroy()` method:
|
||||
|
||||
``` js
|
||||
const mask = new MaskInput(...)
|
||||
mask.destroy()
|
||||
```
|
||||
|
||||
## **Vue**
|
||||
|
||||
Import `vMaska` directive and apply it to the input along with `data-maska` attribite:
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Composition API**
|
||||
|
||||
``` html
|
||||
<script setup>
|
||||
import { vMaska } from "maska"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska data-maska="#-#">
|
||||
</template>
|
||||
```
|
||||
|
||||
### **Options API**
|
||||
|
||||
``` html
|
||||
<script>
|
||||
import { vMaska } from "maska"
|
||||
|
||||
export default {
|
||||
directives: { maska: vMaska }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska data-maska="#-#">
|
||||
</template>
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
### Set options with directive
|
||||
|
||||
To set default options for the mask you could use directive *argument* (part after `v-maska:`). It can be plain or reactive object and should be wrapped in `[]`:
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Composition API**
|
||||
|
||||
``` html
|
||||
<script setup>
|
||||
import { reactive } from "vue"
|
||||
import { vMaska } from "maska"
|
||||
|
||||
const options = reactive({
|
||||
mask: "#-#",
|
||||
eager: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska:[options]>
|
||||
</template>
|
||||
```
|
||||
|
||||
### **Options API**
|
||||
|
||||
``` html
|
||||
<script>
|
||||
import { vMaska } from "maska"
|
||||
|
||||
export default {
|
||||
directives: { maska: vMaska },
|
||||
data: () => ({
|
||||
options: {
|
||||
mask: "#-#",
|
||||
eager: true
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska:[options]>
|
||||
</template>
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
### Bind to variable
|
||||
|
||||
It’s very easy to bind mask result to a variable.
|
||||
This variable should be reactive object and will contains three fields:
|
||||
|
||||
- `masked`: string with masked result
|
||||
- `unmasked`: string with unmasked result
|
||||
- `completed`: boolean flag indicating that mask is completed
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Composition API**
|
||||
|
||||
``` html
|
||||
<script setup>
|
||||
import { reactive } from "vue"
|
||||
import { vMaska } from "maska"
|
||||
|
||||
const binded = reactive({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded">
|
||||
<p v-if="binded.completed">
|
||||
Masked value: {{ binded.masked }}
|
||||
Unmasked value: {{ binded.unmasked }}
|
||||
</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
### **Options API**
|
||||
|
||||
``` html
|
||||
<script>
|
||||
import { vMaska } from "maska"
|
||||
|
||||
export default {
|
||||
directives: { maska: vMaska },
|
||||
data: () => ({
|
||||
binded: {
|
||||
masked: "",
|
||||
unmasked: "",
|
||||
completed: false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded">
|
||||
<p v-if="binded.completed">
|
||||
Masked value: {{ binded.masked }}
|
||||
Unmasked value: {{ binded.unmasked }}
|
||||
</p>
|
||||
</template>
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
#### Global registration of directive
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Vue 3**
|
||||
|
||||
``` js
|
||||
import { createApp } from "vue"
|
||||
import { vMaska } from "maska"
|
||||
|
||||
createApp({}).directive("maska", vMaska)
|
||||
|
||||
// or in case of CDN load
|
||||
Vue.createApp({}).directive("maska", Maska.vMaska)
|
||||
```
|
||||
|
||||
### **Vue 2**
|
||||
|
||||
``` js
|
||||
import Vue from "vue"
|
||||
import { vMaska } from "maska"
|
||||
|
||||
Vue.directive("maska", vMaska)
|
||||
|
||||
// or in case of CDN load
|
||||
Vue.directive("maska", Maska.vMaska)
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
<!-- tabs:end -->
|
||||
|
||||
# Options
|
||||
|
||||
## `Mask` options
|
||||
|
||||
Every option of `Mask` class can be set via `data-maska-` attributes or by passing on init.
|
||||
Options passed on init will be used as defaults and could be overriden by `data-maska-` attributes.
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Description**
|
||||
|
||||
- `mask` / `data-maska` — mask to apply (**string**, **array of strings** or **function**)
|
||||
- `tokens` / `data-maska-tokens` — custom tokens object
|
||||
- `tokensReplace` / `data-maska-tokens-replace` — if custom tokens should replace default tokens (if not set they will merge)
|
||||
- `eager` / `data-maska-eager` — eager mode
|
||||
- `reversed` / `data-maska-reversed` — reversed mode
|
||||
|
||||
### **Types**
|
||||
``` js
|
||||
interface MaskOptions {
|
||||
mask?: MaskType
|
||||
tokens?: MaskTokens
|
||||
tokensReplace?: boolean
|
||||
eager?: boolean
|
||||
reversed?: boolean
|
||||
}
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
``` html
|
||||
<input data-maska="A-A" data-maska-tokens="A:[A-Z]" data-maska-eager>
|
||||
```
|
||||
|
||||
## `MaskInput` options
|
||||
|
||||
`MaskInput` options could be set only by passing second argument on init.
|
||||
Along with `MaskInput` options you could pass any `Mask` options as well (or set them via `data-maska-` attributes).
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Description**
|
||||
|
||||
- `onMaska` — [callback](#events) on every mask processing
|
||||
- `preProcess` — [hook](#hooks) before mask processing
|
||||
- `postProcess` — [hook](#hooks) after mask processing
|
||||
|
||||
### **Types**
|
||||
``` js
|
||||
interface MaskInputOptions extends MaskOptions {
|
||||
onMaska?: (detail: MaskaDetail) => void
|
||||
preProcess?: (value: string) => string
|
||||
postProcess?: (value: string) => string
|
||||
}
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
``` js
|
||||
new MaskInput("input", {
|
||||
mask: "#-#",
|
||||
reversed: true,
|
||||
onMaska: (detail) => console.log(detail.completed)
|
||||
})
|
||||
```
|
||||
|
||||
# Tokens
|
||||
|
||||
There are 3 tokens defined by default:
|
||||
|
||||
``` js
|
||||
{
|
||||
'#': { pattern: /[0-9]/ }, // digits
|
||||
'@': { pattern: /[a-zA-Z]/ }, // letters
|
||||
'*': { pattern: /[a-zA-Z0-9]/ }, // letters & digits
|
||||
}
|
||||
```
|
||||
|
||||
?> Use `!` before token to escape symbol. For example `!#` will render `#` instead of a digit.
|
||||
|
||||
## Custom tokens
|
||||
|
||||
Add custom tokens via `data-maska-tokens` attribute or by `tokens` option:
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **By data-attr**
|
||||
|
||||
When using `data-maska-tokens`, there are two possible formats:
|
||||
|
||||
- **Full form** should be a valid JSON-string (but can use both single and double quotes) with pattern in string format without delimiters
|
||||
- **Simple form** should be a string in format: `T:P:M|T:P:M` where:
|
||||
- `T` is token
|
||||
- `P` is pattern in string form
|
||||
- `M` is optional modifier (see below)
|
||||
- `|` is separator for multiple tokens
|
||||
|
||||
``` html
|
||||
<input data-maska="Z-Z" data-maska-tokens="{ 'Z': { 'pattern': '[A-Z]' }}">
|
||||
<input data-maska="Z-Z" data-maska-tokens="Z:[A-Z]">
|
||||
<input data-maska="Z-z" data-maska-tokens="Z:[A-Z]|z:[a-z]:multiple">
|
||||
```
|
||||
|
||||
?> You can’t set `transform` function for token via `data-maska-tokens`.
|
||||
If you need this, use `tokens` option instead.
|
||||
|
||||
### **By option**
|
||||
|
||||
When using `tokens` option, pattern should be a valid regular expression object:
|
||||
|
||||
``` js
|
||||
new MaskInput("[data-maska]", {
|
||||
mask: "A-A",
|
||||
tokens: {
|
||||
A: { pattern: /[A-Z]/, transform: (chr) => chr.toUpperCase() }
|
||||
}
|
||||
})
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
## Token modifiers
|
||||
|
||||
Every token can have a modifier, for example:
|
||||
|
||||
``` js
|
||||
{
|
||||
0: { pattern: /[0-9]/, optional: true },
|
||||
9: { pattern: /[0-9]/, repeated: true },
|
||||
}
|
||||
```
|
||||
|
||||
- `optional` for optional token
|
||||
- `multiple` for token that can match multiple characters till the next token starts
|
||||
- `repeated` for tokens that should be repeated. This token will match only one character, but the token itself (or group of such tokens) can be repeated any number of times
|
||||
|
||||
| Modifier | Example usage | Mask | Tokens
|
||||
| --- | --- | --- | ---
|
||||
| `optional` | IP address | `#00.#00.#00.#00` | `0:[0-9]:optional`
|
||||
| `multiple` | CARDHOLDER NAME | `A A` | `A:[A-Z]:multiple`
|
||||
| `repeated` | Money (1 234,56) | `9 99#,##` <small>reversed</small> | `9:[0-9]:repeated`
|
||||
|
||||
## Transform tokens
|
||||
|
||||
For custom tokens you can define `transform` function, applied to a character before masking.
|
||||
For example, transforming letters to uppercase on input:
|
||||
|
||||
``` js
|
||||
{
|
||||
A: { pattern: /[A-Z]/, transform: (chr) => chr.toUpperCase() }
|
||||
}
|
||||
```
|
||||
|
||||
?> You can also use [hooks](#hooks) for transforming whole value before/after masking.
|
||||
|
||||
# Dynamic masks
|
||||
|
||||
Pass `mask` as **array** or **function** to make it dynamic:
|
||||
|
||||
- With **array** a suitable mask will be chosen by length (smallest first)
|
||||
- With **function** you can select mask with custom logic using value
|
||||
|
||||
``` js
|
||||
new MaskInput("input", {
|
||||
mask: (value) => value.startsWith('1') ? '#-#' : '##-##'
|
||||
})
|
||||
```
|
||||
|
||||
# Hooks
|
||||
|
||||
Use hooks for transforming whole value:
|
||||
|
||||
- `preProcess` hook called before mask processing
|
||||
- `postProcess` hook called after mask processing, but before setting input's value
|
||||
|
||||
For example, you can use it to check for a maximum length of masked string:
|
||||
|
||||
``` js
|
||||
new MaskInput("input", {
|
||||
postProcess: (value) => value.slice(0, 5)
|
||||
})
|
||||
```
|
||||
|
||||
# Events
|
||||
|
||||
You can subscribe to `maska` event, fired every time when mask applied:
|
||||
|
||||
<!-- tabs:start -->
|
||||
## **Vanilla JS**
|
||||
|
||||
``` js
|
||||
document.querySelector("input").addEventListener("maska", onMaska)
|
||||
```
|
||||
|
||||
## **Vue**
|
||||
|
||||
``` html
|
||||
<input v-maska data-maska="#-#" @maska="onMaska" />
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
Callback will receive custom event contains `detail` property with given structure:
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Description**
|
||||
|
||||
- `masked`: masked value
|
||||
- `unmasked`: unmasked value
|
||||
- `completed`: flag that current mask is completed
|
||||
|
||||
### **Types**
|
||||
``` js
|
||||
interface MaskaDetail {
|
||||
masked: string
|
||||
unmasked: string
|
||||
completed: boolean
|
||||
}
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
``` js
|
||||
const onMaska = (event) => {
|
||||
console.log({
|
||||
masked: event.detail.masked,
|
||||
unmasked: event.detail.unmasked,
|
||||
completed: event.detail.completed
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can pass callback function directly with `MaskInput` option `onMaska`:
|
||||
|
||||
<!-- tabs:start -->
|
||||
### **Vanilla JS**
|
||||
``` js
|
||||
new MaskInput("input", {
|
||||
onMaska: (detail) => console.log(detail.completed)
|
||||
})
|
||||
```
|
||||
|
||||
### **Vue**
|
||||
``` html
|
||||
<script setup>
|
||||
const options = {
|
||||
onMaska: (detail) => console.log(detail.completed)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska:[options]>
|
||||
</template>
|
||||
```
|
||||
<!-- tabs:end -->
|
||||
|
||||
# Programmatic use
|
||||
|
||||
You can use mask function programmatically by importing `Mask` class.
|
||||
There are three methods available:
|
||||
|
||||
- `masked(value)` returns masked version of given value
|
||||
- `unmasked(value)` returns unmasked version of given value
|
||||
- `completed(value)` returns `true` if given value makes mask complete
|
||||
|
||||
``` js
|
||||
import { Mask } from "maska"
|
||||
|
||||
const mask = new Mask({ mask: "#-#" })
|
||||
|
||||
mask.masked("12") // = 1-2
|
||||
mask.unmasked("12") // = 12
|
||||
mask.completed("12") // = true
|
||||
```
|
||||
-200
@@ -1,200 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Maska demo</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" integrity="sha256-vK3UTo/8wHbaUn+dTQD0X6dzidqc5l7gczvH+Bnowwk=" crossorigin="anonymous" />
|
||||
<style>
|
||||
body { background: #fafafa }
|
||||
body > section > .container { max-width: 800px }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="is-pulled-right"><a href="https://github.com/beholdr/maska" class="button is-link is-medium">Github</a></div>
|
||||
|
||||
<h1 class="is-size-2 is-marginless">Maska demo</h1>
|
||||
|
||||
<h2 class="is-size-4">Vue.js examples</h2>
|
||||
<div class="box">
|
||||
<form id="vue-form">
|
||||
<div class="field">
|
||||
<label class="label">Phone with code: {{ phone }} (raw value: {{ phoneRaw }})</label>
|
||||
<div class="control">
|
||||
<input v-maska="['+1 (###) ##-##-##', '+1 (###) ###-##-##']" class="input" type="tel" autocomplete="tel" v-model="phone" @maska="phoneRaw = $event.target.dataset.maskRawValue">
|
||||
</div>
|
||||
<p class="help is-family-code">v-maska="['+1 (###) ##-##-##', '+1 (###) ###-##-##']"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Reactive mask: <input type="checkbox" v-model="dotFormat"> use dot as date separator?</label>
|
||||
<div class="control">
|
||||
<input v-maska="dateMask" class="input">
|
||||
</div>
|
||||
<p class="help is-family-code">v-maska="dateMask" — reactive mask by `dateMask` computed property</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Hex color (custom tokens): {{ color }}</label>
|
||||
<div class="control">
|
||||
<input v-maska="{ mask: '!#HHHHHH', tokens: { 'H': { pattern: /[0-9a-fA-F]/, uppercase: true }}}" class="input" v-model="color">
|
||||
</div>
|
||||
<p class="help is-family-code">v-maska="{ mask: '!#HHHHHH', tokens: { 'H': { pattern: /[0-9a-fA-F]/, uppercase: true }}}"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label">Masked input:</label>
|
||||
<div class="control">
|
||||
<input v-maska="customMask" class="input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">mask to apply:</label>
|
||||
<div class="control">
|
||||
<input class="input" v-model="customMask">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2 class="is-size-4">Vanilla JS examples</h2>
|
||||
<div class="box">
|
||||
<form id="vanilla-form">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label">Phone with code</label>
|
||||
<div class="control">
|
||||
<input data-mask="+1 (###) ###-####" class="masked input" type="tel" autocomplete="tel">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="+1 (###) ###-####"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Cardholder name</label>
|
||||
<div class="control">
|
||||
<input data-mask="A* A*" class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="A* A*"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Leet speak (tokens with transform)</label>
|
||||
<div class="control">
|
||||
<input data-mask="T*" class="transform-masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="T*"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">All digits</label>
|
||||
<div class="control">
|
||||
<input data-mask="#*" class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="#*"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Dynamic mask</label>
|
||||
<div class="control">
|
||||
<input data-mask='["# cm", "#.# cm", "#.## cm"]' class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask='["# cm", "#.# cm", "#.## cm"]'</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label">Date</label>
|
||||
<div class="control">
|
||||
<input data-mask="##/##/####" class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="##/##/####"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Credit card</label>
|
||||
<div class="control">
|
||||
<input data-mask="#### #### #### ####" class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="#### #### #### ####"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Hex color (custom tokens)</label>
|
||||
<div class="control">
|
||||
<input data-mask="!#HHHHHH" class="custom-masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="!#HHHHHH"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Without mask (destroyed)</label>
|
||||
<div class="control">
|
||||
<input data-mask="###" class="unmasked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask="###"</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">CPF/CNPJ</label>
|
||||
<div class="control">
|
||||
<input data-mask='["###.###.###-##", "##.###.###/####-##"]' class="masked input">
|
||||
</div>
|
||||
<p class="help is-family-code">data-mask='["###.###.###-##", "##.###.###/####-##"]'</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6/dist/vue.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/maska@1.5.1/dist/maska.js"></script>
|
||||
<script>
|
||||
// vue
|
||||
new Vue({
|
||||
el: '#vue-form',
|
||||
data: {
|
||||
phone: '19992345678',
|
||||
phoneRaw: '',
|
||||
dotFormat: false,
|
||||
color: null,
|
||||
customMask: '#*'
|
||||
},
|
||||
|
||||
computed: {
|
||||
dateMask: function() {
|
||||
return this.dotFormat ? '##.##.####' : '##/##/####'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function leetSpeak(char) {
|
||||
const letters = Object.entries({
|
||||
'a': '4', 'b': '8', 'e': '3', 'f': 'ph', 'g': '9', 'i': '1', 'o': '0', 's': '5', 't': '7'
|
||||
}).reduce((acc, [from, to]) => {
|
||||
acc[from] = to
|
||||
acc[from.toLocaleUpperCase()] = to
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return letters[char] ? letters[char] : char
|
||||
}
|
||||
|
||||
// vanilla default
|
||||
Maska.create('#vanilla-form .masked');
|
||||
|
||||
// vanilla custom tokens
|
||||
Maska.create('#vanilla-form .custom-masked', {
|
||||
tokens: { 'H': { pattern: '[0-9a-fA-F]', uppercase: true }}
|
||||
});
|
||||
|
||||
Maska.create('#vanilla-form .transform-masked', {
|
||||
tokens: { 'T': { pattern: '[ 0-9a-zA-Z]', transform: leetSpeak }}
|
||||
});
|
||||
|
||||
// vanilla destroy
|
||||
var mask = Maska.create('#vanilla-form .unmasked');
|
||||
mask.destroy();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, shrink-to-fit=no, viewport-fit=cover">
|
||||
<title>maska docs</title>
|
||||
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%220.9em%22 font-size=%22128%22>*️⃣</text></svg>">
|
||||
|
||||
<!-- Themes (light + dark) -->
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple-dark.css">
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: light)" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
|
||||
<link rel="stylesheet" href="dist/demo.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--theme-hue: 42;
|
||||
--heading-h1-font-size: var(--modular-scale-3);
|
||||
--heading-h2-font-size: var(--modular-scale-2);
|
||||
--heading-h3-font-size: var(--modular-scale-1);
|
||||
}
|
||||
|
||||
.sidebar > .app-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.markdown-section {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'maska',
|
||||
repo: 'beholdr/maska',
|
||||
maxLevel: 2
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Required -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/js/docsify-themeable.min.js"></script>
|
||||
|
||||
<!-- Recommended -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code@2"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/external-script.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
const config = {
|
||||
testEnvironment: "jsdom",
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Generated
+11106
File diff suppressed because it is too large
Load Diff
+43
-34
@@ -1,50 +1,59 @@
|
||||
{
|
||||
"name": "maska",
|
||||
"version": "1.5.1",
|
||||
"description": "Simple zero-dependency input mask for Vue.js and vanilla JS",
|
||||
"description": "Simple zero-dependency input mask for Vue 2/3 or Vanilla JS",
|
||||
"keywords": [
|
||||
"mask",
|
||||
"input",
|
||||
"inputmask",
|
||||
"vue"
|
||||
],
|
||||
"author": "Alexander Shabunevich <alex@aether.ru>",
|
||||
"author": "Alexander Shabunevich",
|
||||
"homepage": "https://beholdr.github.io/maska/",
|
||||
"repository": {
|
||||
"url": "https://github.com/beholdr/maska",
|
||||
"type": "git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"serve": "rimraf dist && NODE_ENV=development rollup -c --watch",
|
||||
"build": "rimraf dist && NODE_ENV=production rollup -c && NODE_ENV=production DISABLE_BABEL=yes rollup -c",
|
||||
"test": "jest",
|
||||
"lint": "standard 'src/**'",
|
||||
"lint:fix": "standard 'src/**' --fix"
|
||||
},
|
||||
"main": "dist/maska.umd.js",
|
||||
"module": "dist/maska.esm.js",
|
||||
"unpkg": "dist/maska.umd.js",
|
||||
"jsdelivr": "dist/maska.umd.js",
|
||||
"types": "types/index.d.ts",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/preset-env": "^7.7.5",
|
||||
"babel-jest": "^27.2.4",
|
||||
"jest": "^27.2.4",
|
||||
"rimraf": "^3.0.0",
|
||||
"rollup": "^2.0.2",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"standard": "^16.0.3",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 0.25%",
|
||||
"ie 11"
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
]
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"main": "./dist/maska.umd.js",
|
||||
"module": "./dist/maska.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/maska.js",
|
||||
"require": "./dist/maska.umd.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"build:demo": "vite build --config vite.config.demo.ts",
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"lint": "ts-standard src",
|
||||
"lint:fix": "ts-standard --fix src",
|
||||
"preversion": "npm run build && npm test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/node": "^18.6.4",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitest/coverage-c8": "^0.25.0",
|
||||
"@vue/test-utils": "^2.2.1",
|
||||
"happy-dom": "^7.4.0",
|
||||
"ts-standard": "^12.0.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.1.0",
|
||||
"vite-plugin-banner": "^0.6.1",
|
||||
"vite-plugin-dts": "^1.6.5",
|
||||
"vitest": "^0.25.0",
|
||||
"vue": "^3.2.41",
|
||||
"vue-live": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
const { version } = require('./package.json')
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const useBabel = process.env.DISABLE_BABEL !== 'yes'
|
||||
|
||||
const banner = `/*!
|
||||
* maska v${version}
|
||||
* (c) 2019-${(new Date()).getFullYear()} Alexander Shabunevich
|
||||
* Released under the MIT License.
|
||||
*/`
|
||||
|
||||
const getDistFolder = (fileName = '') => `dist/${useBabel ? '' : 'es6/'}${fileName}`
|
||||
const makeFileName = (format) => getDistFolder(`maska.${format}.js`)
|
||||
|
||||
const makeOutputConfig = (format = true) => {
|
||||
return {
|
||||
banner,
|
||||
format,
|
||||
file: makeFileName(format),
|
||||
name: 'Maska'
|
||||
}
|
||||
}
|
||||
|
||||
const plugins = [
|
||||
useBabel && babel({
|
||||
exclude: 'node_modules/**'
|
||||
}),
|
||||
isProduction && terser()
|
||||
].filter(Boolean)
|
||||
|
||||
|
||||
export default {
|
||||
input: 'src/index.js',
|
||||
plugins,
|
||||
output: [
|
||||
{
|
||||
...makeOutputConfig('esm')
|
||||
},
|
||||
{
|
||||
...makeOutputConfig('umd'),
|
||||
exports: 'named',
|
||||
},
|
||||
{
|
||||
...makeOutputConfig('umd'),
|
||||
file: getDistFolder('maska.js'),
|
||||
exports: 'named',
|
||||
},
|
||||
],
|
||||
watch: {
|
||||
exclude: 'node_modules/**'
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { VueLive } from 'vue-live'
|
||||
import 'vue-live/style.css'
|
||||
|
||||
const examples = [
|
||||
{
|
||||
label: 'Simple mask',
|
||||
code: `<input v-maska data-maska="#-#" value="12">`
|
||||
},
|
||||
{
|
||||
label: 'Phone mask',
|
||||
code: `<input v-maska data-maska="+1 ### ###-##-##">`
|
||||
},
|
||||
{
|
||||
label: 'HEX-color',
|
||||
code: `<input\n v-maska\n data-maska="!#HHHHHH"\n data-maska-tokens="H:[0-9a-fA-F]"\n>`
|
||||
},
|
||||
{
|
||||
label: 'IP address with optional digits',
|
||||
code: `<input\n v-maska\n data-maska="100.100.100.100"\n data-maska-tokens="1:[0-2]|0:[0-9]:optional"\n>`
|
||||
},
|
||||
{
|
||||
label: 'Dynamic mask: CPF/CNPJ',
|
||||
code: `<input\n v-maska\n data-maska="[\n '###.###.###-##',\n '##.###.###/####-##'\n ]"\n>`
|
||||
},
|
||||
{
|
||||
label: 'Cardholder name: via hook',
|
||||
code: `const options = {\n preProcess: (val) => val.toUpperCase()\n}\n\n<input\n v-maska:[options]\n data-maska="A A"\n data-maska-tokens="A:[A-Z]:multiple"\n>`
|
||||
},
|
||||
{
|
||||
label: 'Cardholder name: via token transform',
|
||||
code: `const options = {\n tokens: {\n A: {\n pattern: /[A-Z]/,\n multiple: true,\n transform: (chr) => chr.toUpperCase()\n }\n }\n}\n\n<input v-maska:[options] data-maska="A A">`
|
||||
},
|
||||
{
|
||||
label: 'Year: with current year as a limit',
|
||||
code: `const options = {\n postProcess: (val) => {\n const max = "" + new Date().getFullYear()\n return val > max ? max : val\n }\n}\n\n<input v-maska:[options] data-maska="####">`
|
||||
},
|
||||
{
|
||||
label: 'Money format: repeated and reversed',
|
||||
code: `<input\n v-maska\n data-maska="9 99#,##"\n data-maska-tokens="9:[0-9]:repeated"\n data-maska-reversed\n>`
|
||||
}
|
||||
]
|
||||
|
||||
const selectedExample = ref(0)
|
||||
const code = computed(() => examples[selectedExample.value].code)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="demo-select">
|
||||
<label for="demo-example-select">Choose mask example:</label>
|
||||
<select v-model="selectedExample" id="demo-example-select">
|
||||
<option v-for="(example, idx) in examples" :value="idx">
|
||||
{{ example.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<VueLive :code="code" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.demo-select {
|
||||
border: 1px solid var(--docsifytabs-border-color);
|
||||
padding: 1rem var(--docsifytabs-content-padding) 1.5rem;
|
||||
border-bottom: none;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
.demo-select > label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: var(--search-input-placeholder-color);
|
||||
}
|
||||
.demo-select > select {
|
||||
width: 100%;
|
||||
padding: 7px;
|
||||
border-radius: 4px;
|
||||
color: var(--search-input-color);
|
||||
border: 1px solid var(--search-input-border-color);
|
||||
background-color: var(--search-input-background-color);
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
appearance: none;
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
}
|
||||
|
||||
.VueLive-container {
|
||||
border: 1px solid var(--docsifytabs-border-color);
|
||||
margin: var(--docsifytabs-margin);
|
||||
margin-top: 0;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
.VueLive-container .VueLive-editor {
|
||||
padding: 2.3rem var(--docsifytabs-content-padding);
|
||||
background-color: var(--code-theme-background);
|
||||
border-radius: 0 0 0 5px;
|
||||
width: 85%;
|
||||
position: relative;
|
||||
}
|
||||
.VueLive-container .VueLive-editor::after {
|
||||
content: 'Сode';
|
||||
position: absolute;
|
||||
top: 0.75em;
|
||||
right: 0.75em;
|
||||
opacity: 0.6;
|
||||
color: inherit;
|
||||
font-size: var(--font-size-s);
|
||||
line-height: 1;
|
||||
}
|
||||
.VueLive-container .VueLive-editor .prism-editor__editor,
|
||||
.VueLive-container .VueLive-editor .prism-editor__textarea {
|
||||
font-family: var(--code-font-family);
|
||||
}
|
||||
.VueLive-container .VueLivePreview {
|
||||
padding: 2rem var(--docsifytabs-content-padding);
|
||||
border-radius: 0 0 5px 0;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
}
|
||||
.VueLive-container .VueLivePreview::after {
|
||||
content: 'Result';
|
||||
position: absolute;
|
||||
top: 0.75em;
|
||||
right: var(--docsifytabs-content-padding);
|
||||
opacity: 0.6;
|
||||
color: inherit;
|
||||
font-size: var(--font-size-s);
|
||||
line-height: 1;
|
||||
}
|
||||
.VueLive-container .VueLivePreview input {
|
||||
font-size: var(--modular-scale-1);
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--search-input-border-color);
|
||||
border-radius: 4px;
|
||||
background-color: var(--search-input-background-color);
|
||||
color: var(--search-input-color);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.VueLive-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
.VueLive-container .VueLive-editor,
|
||||
.VueLive-container .VueLivePreview {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import { vMaska } from '..'
|
||||
import Demo from './Demo.vue'
|
||||
|
||||
createApp(Demo)
|
||||
.directive('maska', vMaska)
|
||||
.mount('#demo-app')
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import Maska from './maska'
|
||||
import { isString } from './utils'
|
||||
|
||||
function getOpts (mask) {
|
||||
const opts = {}
|
||||
|
||||
if (mask.mask) {
|
||||
opts.mask = Array.isArray(mask.mask) ? JSON.stringify(mask.mask) : mask.mask
|
||||
opts.tokens = mask.tokens ? { ...mask.tokens } : {}
|
||||
opts.preprocessor = mask.preprocessor
|
||||
} else {
|
||||
opts.mask = Array.isArray(mask) ? JSON.stringify(mask) : mask
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
function needUpdate (mask) {
|
||||
return !(
|
||||
(isString(mask.value) && mask.value === mask.oldValue) ||
|
||||
(Array.isArray(mask.value) && JSON.stringify(mask.value) === JSON.stringify(mask.oldValue)) ||
|
||||
(mask.value && mask.value.mask && mask.oldValue && mask.oldValue.mask && mask.value.mask === mask.oldValue.mask)
|
||||
)
|
||||
}
|
||||
|
||||
const directive = () => {
|
||||
const state = new WeakMap()
|
||||
|
||||
return (el, mask) => {
|
||||
if (!mask.value) return
|
||||
|
||||
if (state.has(el) && !needUpdate(mask)) {
|
||||
return
|
||||
}
|
||||
|
||||
state.set(el, new Maska(el, getOpts(mask.value)))
|
||||
}
|
||||
}
|
||||
|
||||
export default directive()
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Directive } from 'vue'
|
||||
import { MaskaDetail, MaskInput, MaskInputOptions } from './mask-input'
|
||||
|
||||
type MaskaDirective = Directive<HTMLInputElement, MaskaDetail | undefined>
|
||||
|
||||
const masks = new WeakMap<HTMLInputElement, MaskInput>()
|
||||
|
||||
export const vMaska: MaskaDirective = (el, binding) => {
|
||||
if (masks.get(el) != null) {
|
||||
masks.get(el)?.destroy()
|
||||
}
|
||||
|
||||
const opts = { ...(binding.arg as MaskInputOptions) } ?? {}
|
||||
|
||||
if (binding.value != null) {
|
||||
const binded = binding.value
|
||||
opts.onMaska = (detail: MaskaDetail) => {
|
||||
binded.masked = detail.masked
|
||||
binded.unmasked = detail.unmasked
|
||||
binded.completed = detail.completed
|
||||
}
|
||||
}
|
||||
|
||||
masks.set(el, new MaskInput(el, opts))
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import directive from './directive'
|
||||
import mask from './mask'
|
||||
import Maska from './maska'
|
||||
import tokens from './tokens'
|
||||
|
||||
function install (Vue) {
|
||||
Vue.directive('maska', directive)
|
||||
}
|
||||
// Install by default if included from script tag (only Vue 2)
|
||||
if (typeof window !== 'undefined' && window.Vue && window.Vue.use) {
|
||||
window.Vue.use(install)
|
||||
}
|
||||
|
||||
function create (el, options) {
|
||||
return new Maska(el, options)
|
||||
}
|
||||
|
||||
export default install
|
||||
export {
|
||||
install,
|
||||
create,
|
||||
mask,
|
||||
directive as maska,
|
||||
tokens
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Mask, MaskType, MaskOptions } from './mask'
|
||||
import { MaskInput, MaskInputOptions, MaskaDetail } from './mask-input'
|
||||
import { vMaska } from './directive'
|
||||
import { tokens, MaskTokens } from './tokens'
|
||||
|
||||
export { Mask, MaskInput, tokens, vMaska }
|
||||
export type {
|
||||
MaskaDetail,
|
||||
MaskInputOptions,
|
||||
MaskOptions,
|
||||
MaskTokens,
|
||||
MaskType
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Mask, MaskOptions } from './mask'
|
||||
import { parseMask, parseOpts, parseTokens } from './parser'
|
||||
|
||||
export interface MaskInputOptions extends MaskOptions {
|
||||
onMaska?: (detail: MaskaDetail) => void
|
||||
preProcess?: (value: string) => string
|
||||
postProcess?: (value: string) => string
|
||||
}
|
||||
|
||||
export interface MaskaDetail {
|
||||
masked: string
|
||||
unmasked: string
|
||||
completed: boolean
|
||||
}
|
||||
|
||||
export class MaskInput {
|
||||
readonly items = new Map<HTMLInputElement, Mask>()
|
||||
|
||||
constructor (
|
||||
target: string | NodeListOf<HTMLInputElement> | HTMLInputElement,
|
||||
readonly options: MaskInputOptions = {}
|
||||
) {
|
||||
const { onMaska, preProcess, postProcess, ...opts } = options
|
||||
|
||||
if (typeof target === 'string') {
|
||||
this.init(Array.from(document.querySelectorAll(target)), opts)
|
||||
} else {
|
||||
this.init('length' in target ? Array.from(target) : [target], opts)
|
||||
}
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
for (const input of this.items.keys()) {
|
||||
input.removeEventListener('input', this.inputEvent)
|
||||
input.removeEventListener('beforeinput', this.beforeinputEvent)
|
||||
}
|
||||
this.items.clear()
|
||||
}
|
||||
|
||||
private init (inputs: HTMLInputElement[], defaults: MaskOptions): void {
|
||||
for (const input of inputs) {
|
||||
const opts = { ...defaults }
|
||||
if (input.dataset.maska != null && input.dataset.maska !== '') {
|
||||
opts.mask = parseMask(input.dataset.maska)
|
||||
}
|
||||
if (input.dataset.maskaEager != null) {
|
||||
opts.eager = parseOpts(input.dataset.maskaEager)
|
||||
}
|
||||
if (input.dataset.maskaReversed != null) {
|
||||
opts.reversed = parseOpts(input.dataset.maskaReversed)
|
||||
}
|
||||
if (input.dataset.maskaTokensReplace != null) {
|
||||
opts.tokensReplace = parseOpts(input.dataset.maskaTokensReplace)
|
||||
}
|
||||
if (input.dataset.maskaTokens != null) {
|
||||
opts.tokens = parseTokens(input.dataset.maskaTokens)
|
||||
}
|
||||
|
||||
const mask = new Mask(opts)
|
||||
this.items.set(input, mask)
|
||||
|
||||
if (input.value !== '') {
|
||||
this.setMaskedValue(input, input.value)
|
||||
}
|
||||
|
||||
input.addEventListener('input', this.inputEvent)
|
||||
input.addEventListener('beforeinput', this.beforeinputEvent)
|
||||
}
|
||||
}
|
||||
|
||||
private readonly beforeinputEvent = (e: Event | InputEvent): void => {
|
||||
const input = e.target as HTMLInputElement
|
||||
const mask = this.items.get(input) as Mask
|
||||
|
||||
// delete first character in eager mask when it's the only left
|
||||
if (
|
||||
mask.eager &&
|
||||
'inputType' in e &&
|
||||
e.inputType.startsWith('delete') &&
|
||||
mask.unmasked(input.value).length <= 1
|
||||
) {
|
||||
this.setMaskedValue(input, '')
|
||||
}
|
||||
}
|
||||
|
||||
private readonly inputEvent = (e: Event | InputEvent): void => {
|
||||
const input = e.target as HTMLInputElement
|
||||
const mask = this.items.get(input) as Mask
|
||||
|
||||
const valueOld = input.value
|
||||
const ss = input.selectionStart
|
||||
const se = input.selectionEnd
|
||||
let value = valueOld
|
||||
|
||||
if (mask.eager) {
|
||||
const unmasked = mask.unmasked(valueOld)
|
||||
const maskedUnmasked = mask.masked(unmasked)
|
||||
|
||||
if (unmasked === '' && 'data' in e && e.data != null) {
|
||||
// empty state and something like `space` pressed
|
||||
value = e.data
|
||||
} else if (
|
||||
maskedUnmasked.startsWith(valueOld) ||
|
||||
mask.completed(unmasked)
|
||||
) {
|
||||
value = unmasked
|
||||
}
|
||||
}
|
||||
|
||||
this.setMaskedValue(input, value)
|
||||
|
||||
// set caret position
|
||||
if ('inputType' in e) {
|
||||
if (
|
||||
e.inputType.startsWith('delete') ||
|
||||
(ss != null && ss < valueOld.length)
|
||||
) {
|
||||
input.setSelectionRange(ss, se)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setMaskedValue (input: HTMLInputElement, value: string): void {
|
||||
const mask = this.items.get(input) as Mask
|
||||
|
||||
if (this.options.preProcess != null) {
|
||||
value = this.options.preProcess(value)
|
||||
}
|
||||
|
||||
value = mask.masked(value)
|
||||
|
||||
if (this.options.postProcess != null) {
|
||||
value = this.options.postProcess(value)
|
||||
}
|
||||
|
||||
input.value = value
|
||||
|
||||
const detail = {
|
||||
masked: mask.masked(value),
|
||||
unmasked: mask.unmasked(value),
|
||||
completed: mask.completed(value)
|
||||
}
|
||||
|
||||
if (this.options.onMaska != null) {
|
||||
this.options.onMaska(detail)
|
||||
}
|
||||
input.dispatchEvent(new CustomEvent<MaskaDetail>('maska', { detail }))
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
import defaultTokens from './tokens'
|
||||
|
||||
export default function mask (value, mask, tokens = defaultTokens, masked = true) {
|
||||
return (processMask(mask).length > 1)
|
||||
? dynamic(mask)(value, mask, tokens, masked)
|
||||
: process(value, mask, tokens, masked)
|
||||
}
|
||||
|
||||
function processMask (mask) {
|
||||
try {
|
||||
return JSON.parse(mask)
|
||||
} catch {
|
||||
return [mask]
|
||||
}
|
||||
}
|
||||
|
||||
function dynamic (mask) {
|
||||
const masks = processMask(mask).sort((a, b) => a.length - b.length)
|
||||
|
||||
return function (value, mask, tokens, masked = true) {
|
||||
const processed = masks.map(m => process(value, m, tokens, false))
|
||||
const last = processed.pop()
|
||||
|
||||
for (const i in masks) {
|
||||
if (checkMask(last, masks[i], tokens)) {
|
||||
return process(value, masks[i], tokens, masked)
|
||||
}
|
||||
}
|
||||
|
||||
return '' // empty masks
|
||||
}
|
||||
|
||||
function checkMask (variant, mask, tokens) {
|
||||
for (const tok in tokens) {
|
||||
if (tokens[tok].escape) {
|
||||
mask = mask.replace(new RegExp(tok + '.{1}', 'g'), '')
|
||||
}
|
||||
}
|
||||
|
||||
return (mask.split('').filter(el => tokens[el] && tokens[el].pattern).length >= variant.length)
|
||||
}
|
||||
}
|
||||
|
||||
function process (value, mask, tokens, masked = true) {
|
||||
let im = 0
|
||||
let iv = 0
|
||||
let ret = ''
|
||||
let rest = ''
|
||||
|
||||
while (im < mask.length && iv < value.length) {
|
||||
let maskChar = mask[im]
|
||||
const valueChar = value[iv]
|
||||
const token = tokens[maskChar]
|
||||
|
||||
if (token && token.pattern) {
|
||||
if (token.pattern.test(valueChar)) {
|
||||
ret += tokenTransform(valueChar, token)
|
||||
im++
|
||||
// check next char
|
||||
if (masked && mask[im]) {
|
||||
if (!tokens[mask[im]]) {
|
||||
ret += mask[im]
|
||||
im++
|
||||
} else if (tokens[mask[im]] && tokens[mask[im]].escape) {
|
||||
ret += mask[im + 1]
|
||||
im = im + 2
|
||||
}
|
||||
}
|
||||
}
|
||||
iv++
|
||||
} else if (token && token.repeat) {
|
||||
const tokenPrev = tokens[mask[im - 1]]
|
||||
if (tokenPrev && !tokenPrev.pattern.test(valueChar)) {
|
||||
im++
|
||||
} else {
|
||||
im--
|
||||
}
|
||||
} else {
|
||||
if (token && token.escape) {
|
||||
im++
|
||||
maskChar = mask[im]
|
||||
}
|
||||
if (masked) ret += maskChar
|
||||
if (valueChar === maskChar) iv++
|
||||
im++
|
||||
}
|
||||
}
|
||||
|
||||
// fix mask that ends with parenthesis
|
||||
while (masked && im < mask.length) { // eslint-disable-line no-unmodified-loop-condition
|
||||
const maskCharRest = mask[im]
|
||||
if (tokens[maskCharRest]) {
|
||||
rest = ''
|
||||
break
|
||||
}
|
||||
rest += maskCharRest
|
||||
im++
|
||||
}
|
||||
|
||||
return ret + rest
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} value
|
||||
* @param {'uppercase' | 'lowercase' | 'transform'} token
|
||||
*/
|
||||
function tokenTransform (value, token) {
|
||||
if (token.transform) {
|
||||
value = token.transform(value)
|
||||
}
|
||||
|
||||
if (token.uppercase) {
|
||||
return value.toLocaleUpperCase()
|
||||
} else if (token.lowercase) {
|
||||
return value.toLocaleLowerCase()
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
import { MaskTokens, tokens } from './tokens'
|
||||
|
||||
export type MaskType = string | string[] | ((input: string) => string)
|
||||
|
||||
export interface MaskOptions {
|
||||
mask?: MaskType
|
||||
tokens?: MaskTokens
|
||||
tokensReplace?: boolean
|
||||
eager?: boolean
|
||||
reversed?: boolean
|
||||
}
|
||||
|
||||
export class Mask {
|
||||
readonly mask: MaskType = ''
|
||||
readonly tokens = tokens
|
||||
readonly eager = false
|
||||
readonly reversed = false
|
||||
private readonly memo = new Map()
|
||||
|
||||
constructor (opts: MaskOptions = {}) {
|
||||
if (opts.tokens != null) {
|
||||
opts.tokens = (opts.tokensReplace as boolean)
|
||||
? { ...opts.tokens }
|
||||
: { ...tokens, ...opts.tokens }
|
||||
|
||||
for (const token of Object.values(opts.tokens)) {
|
||||
if (typeof token.pattern === 'string') {
|
||||
token.pattern = new RegExp(token.pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.mask == null) {
|
||||
opts.mask = ''
|
||||
} else if (typeof opts.mask === 'object') {
|
||||
if (opts.mask.length > 1) {
|
||||
opts.mask.sort((a, b) => a.length - b.length)
|
||||
} else {
|
||||
opts.mask = opts.mask[0] ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(this, opts)
|
||||
}
|
||||
|
||||
masked (value: string): string {
|
||||
return this.process(value, this.findMask(value))
|
||||
}
|
||||
|
||||
unmasked (value: string): string {
|
||||
return this.process(value, this.findMask(value), false)
|
||||
}
|
||||
|
||||
completed (value: string): boolean {
|
||||
const length = this.process(value, this.findMask(value)).length
|
||||
|
||||
if (typeof this.mask === 'string') {
|
||||
return length >= this.mask.length
|
||||
} else if (typeof this.mask === 'function') {
|
||||
return length >= this.findMask(value).length
|
||||
} else {
|
||||
return (
|
||||
this.mask.filter((m) => length >= m.length).length === this.mask.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private findMask (value: string): string {
|
||||
if (typeof this.mask === 'string') {
|
||||
return this.mask
|
||||
} else if (typeof this.mask === 'function') {
|
||||
return this.mask(value)
|
||||
}
|
||||
|
||||
const last = this.process(value, this.mask.slice(-1).pop() ?? '', false)
|
||||
|
||||
return (
|
||||
this.mask.find(
|
||||
(mask) => this.process(value, mask, false).length >= last.length
|
||||
) ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
private escapeMask (maskRaw: string): {
|
||||
mask: string
|
||||
escaped: number[]
|
||||
} {
|
||||
const chars: string[] = []
|
||||
const escaped: number[] = []
|
||||
maskRaw.split('').forEach((ch, i) => {
|
||||
if (ch === '!' && maskRaw[i - 1] !== '!') {
|
||||
escaped.push(i - escaped.length)
|
||||
} else {
|
||||
chars.push(ch)
|
||||
}
|
||||
})
|
||||
|
||||
return { mask: chars.join(''), escaped }
|
||||
}
|
||||
|
||||
private process (value: string, maskRaw: string, masked = true): string {
|
||||
const key = `value=${value},mask=${maskRaw},masked=${masked ? 1 : 0}`
|
||||
if (this.memo.has(key)) return this.memo.get(key)
|
||||
|
||||
const { mask, escaped } = this.escapeMask(maskRaw)
|
||||
const result: string[] = []
|
||||
const offset = this.reversed ? -1 : 1
|
||||
const method = this.reversed ? 'unshift' : 'push'
|
||||
const lastMaskChar = this.reversed ? 0 : mask.length - 1
|
||||
|
||||
const check = this.reversed
|
||||
? () => m > -1 && v > -1
|
||||
: () => m < mask.length && v < value.length
|
||||
|
||||
const notLastMaskChar = (m: number): boolean =>
|
||||
(!this.reversed && m <= lastMaskChar) ||
|
||||
(this.reversed && m >= lastMaskChar)
|
||||
|
||||
let lastRawMaskChar
|
||||
let repeatedPos = -1
|
||||
let m = this.reversed ? mask.length - 1 : 0
|
||||
let v = this.reversed ? value.length - 1 : 0
|
||||
|
||||
while (check()) {
|
||||
const maskChar = mask.charAt(m)
|
||||
const token = this.tokens[maskChar]
|
||||
const valueChar =
|
||||
token?.transform != null
|
||||
? token.transform(value.charAt(v))
|
||||
: value.charAt(v)
|
||||
|
||||
if (!escaped.includes(m) && token != null) {
|
||||
if (valueChar.match(token.pattern) != null) {
|
||||
result[method](valueChar)
|
||||
|
||||
if (token.repeated as boolean) {
|
||||
if (repeatedPos === -1) {
|
||||
repeatedPos = m
|
||||
} else if (m === lastMaskChar && m !== repeatedPos) {
|
||||
m = repeatedPos - offset
|
||||
}
|
||||
|
||||
if (lastMaskChar === repeatedPos) {
|
||||
m -= offset
|
||||
}
|
||||
} else if (token.multiple as boolean) {
|
||||
m -= offset
|
||||
}
|
||||
|
||||
m += offset
|
||||
} else if (token.multiple as boolean) {
|
||||
const hasValue = result[v - offset]?.match(token.pattern) != null
|
||||
const nextMask = mask.charAt(m + offset)
|
||||
if (hasValue && nextMask !== '' && this.tokens[nextMask] == null) {
|
||||
m += offset
|
||||
v -= offset
|
||||
} else {
|
||||
result[method]('')
|
||||
}
|
||||
} else if (valueChar === lastRawMaskChar) {
|
||||
// matched the last untranslated (raw) mask character that we encountered
|
||||
// likely an insert offset the mask character from the last entry;
|
||||
// fall through and only increment v
|
||||
lastRawMaskChar = undefined
|
||||
} else if (token.optional as boolean) {
|
||||
m += offset
|
||||
v -= offset
|
||||
} else {
|
||||
// invalid input
|
||||
}
|
||||
|
||||
v += offset
|
||||
} else {
|
||||
if (masked && !this.eager) {
|
||||
result[method](maskChar)
|
||||
}
|
||||
|
||||
if (valueChar === maskChar && !this.eager) {
|
||||
v += offset
|
||||
} else {
|
||||
lastRawMaskChar = maskChar
|
||||
}
|
||||
|
||||
if (!this.eager) {
|
||||
m += offset
|
||||
}
|
||||
}
|
||||
|
||||
if (this.eager) {
|
||||
while (
|
||||
notLastMaskChar(m) &&
|
||||
(this.tokens[mask.charAt(m)] == null || escaped.includes(m))
|
||||
) {
|
||||
if (masked) {
|
||||
result[method](mask.charAt(m))
|
||||
} else if (mask.charAt(m) === value.charAt(v)) {
|
||||
v += offset
|
||||
}
|
||||
m += offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.memo.set(key, result.join(''))
|
||||
|
||||
return this.memo.get(key)
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
import mask from './mask'
|
||||
import tokens from './tokens'
|
||||
import { event, findInputElement, fixInputSelection, isString } from './utils'
|
||||
|
||||
export default class Maska {
|
||||
constructor (el, opts = {}) {
|
||||
if (!el) throw new Error('Maska: no element for mask')
|
||||
|
||||
if (opts.preprocessor != null && typeof opts.preprocessor !== 'function') {
|
||||
throw new Error('Maska: preprocessor must be a function')
|
||||
}
|
||||
|
||||
if (opts.tokens) {
|
||||
for (const i in opts.tokens) {
|
||||
opts.tokens[i] = { ...opts.tokens[i] }
|
||||
if (opts.tokens[i].pattern && isString(opts.tokens[i].pattern)) {
|
||||
opts.tokens[i].pattern = new RegExp(opts.tokens[i].pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._opts = {
|
||||
mask: opts.mask,
|
||||
tokens: { ...tokens, ...opts.tokens },
|
||||
preprocessor: opts.preprocessor
|
||||
}
|
||||
this._el = isString(el) ? document.querySelectorAll(el) : !el.length ? [el] : el
|
||||
this.inputEvent = (e) => this.updateValue(e.target, e)
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
for (let i = 0; i < this._el.length; i++) {
|
||||
const el = findInputElement(this._el[i])
|
||||
if (this._opts.mask && (!el.dataset.mask || el.dataset.mask !== this._opts.mask)) {
|
||||
el.dataset.mask = this._opts.mask
|
||||
}
|
||||
setTimeout(() => this.updateValue(el), 0)
|
||||
if (!el.dataset.maskInited) {
|
||||
el.dataset.maskInited = true
|
||||
el.addEventListener('input', this.inputEvent)
|
||||
el.addEventListener('beforeinput', this.beforeInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
for (let i = 0; i < this._el.length; i++) {
|
||||
const el = findInputElement(this._el[i])
|
||||
el.removeEventListener('input', this.inputEvent)
|
||||
el.removeEventListener('beforeinput', this.beforeInput)
|
||||
delete el.dataset.mask
|
||||
delete el.dataset.maskInited
|
||||
}
|
||||
}
|
||||
|
||||
updateValue (el, evt) {
|
||||
if (!el || !el.type) return
|
||||
|
||||
const wrongNum = el.type.match(/^number$/i) && el.validity.badInput
|
||||
if ((!el.value && !wrongNum) || !el.dataset.mask) {
|
||||
el.dataset.maskRawValue = ''
|
||||
this.dispatch('maska', el, evt)
|
||||
return
|
||||
}
|
||||
|
||||
let position = el.selectionEnd
|
||||
const oldValue = el.value
|
||||
const digit = oldValue[position - 1]
|
||||
|
||||
el.dataset.maskRawValue = mask(el.value, el.dataset.mask, this._opts.tokens, false)
|
||||
let elValue = el.value
|
||||
|
||||
if (this._opts.preprocessor) {
|
||||
elValue = this._opts.preprocessor(elValue)
|
||||
}
|
||||
|
||||
el.value = mask(elValue, el.dataset.mask, this._opts.tokens)
|
||||
|
||||
if (evt && evt.inputType === 'insertText' && position === oldValue.length) {
|
||||
position = el.value.length
|
||||
}
|
||||
fixInputSelection(el, position, digit)
|
||||
|
||||
this.dispatch('maska', el, evt)
|
||||
if (el.value !== oldValue) {
|
||||
this.dispatch('input', el, evt)
|
||||
}
|
||||
}
|
||||
|
||||
beforeInput (e) {
|
||||
if (e && e.target && e.target.type && e.target.type.match(/^number$/i) && e.data && isNaN(e.target.value + e.data)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
dispatch (name, el, evt) {
|
||||
el.dispatchEvent(event(name, (evt && evt.inputType) || null))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MaskType } from './mask'
|
||||
import { MaskTokens } from './tokens'
|
||||
|
||||
const parseJson = (value: string): any => JSON.parse(value.replaceAll("'", '"'))
|
||||
|
||||
export const parseOpts = (value: string): boolean =>
|
||||
value !== '' ? Boolean(JSON.parse(value)) : true
|
||||
|
||||
export const parseMask = (value: string): MaskType =>
|
||||
value.startsWith('[') && value.endsWith(']') ? parseJson(value) : value
|
||||
|
||||
export const parseTokens = (value: string): MaskTokens => {
|
||||
if (value.startsWith('{') && value.endsWith('}')) {
|
||||
return parseJson(value)
|
||||
}
|
||||
|
||||
const tokens: MaskTokens = {}
|
||||
value.split('|').forEach((token) => {
|
||||
const parts = token.split(':')
|
||||
tokens[parts[0]] = {
|
||||
pattern: new RegExp(parts[1]),
|
||||
optional: parts[2] === 'optional',
|
||||
multiple: parts[2] === 'multiple',
|
||||
repeated: parts[2] === 'repeated'
|
||||
}
|
||||
})
|
||||
|
||||
return tokens
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint quote-props: ["error", "consistent"] */
|
||||
export default {
|
||||
'#': { pattern: /[0-9]/ },
|
||||
'X': { pattern: /[0-9a-zA-Z]/ },
|
||||
'S': { pattern: /[a-zA-Z]/ },
|
||||
'A': { pattern: /[a-zA-Z]/, uppercase: true },
|
||||
'a': { pattern: /[a-zA-Z]/, lowercase: true },
|
||||
'!': { escape: true },
|
||||
'*': { repeat: true }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
interface MaskToken {
|
||||
pattern: RegExp
|
||||
multiple?: boolean
|
||||
optional?: boolean
|
||||
repeated?: boolean
|
||||
transform?: (char: string) => string
|
||||
}
|
||||
|
||||
export type MaskTokens = Record<string, MaskToken>
|
||||
|
||||
export const tokens: MaskTokens = {
|
||||
'#': { pattern: /[0-9]/ },
|
||||
'@': { pattern: /[a-zA-Z]/ },
|
||||
'*': { pattern: /[a-zA-Z0-9]/ }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/* global HTMLInputElement */
|
||||
|
||||
function event (name, inputType = null) {
|
||||
const event = document.createEvent('Event')
|
||||
event.initEvent(name, true, true)
|
||||
if (inputType) {
|
||||
event.inputType = inputType
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
function findInputElement (el) {
|
||||
return (el instanceof HTMLInputElement) ? el : el.querySelector('input') || el
|
||||
}
|
||||
|
||||
function fixInputSelection (el, position, digit) {
|
||||
while (position && position < el.value.length && el.value.charAt(position - 1) !== digit) {
|
||||
position++
|
||||
}
|
||||
|
||||
const selectionRange = el.type ? el.type.match(/^(text|search|password|tel|url)$/i) : !el.type
|
||||
if (selectionRange && el === document.activeElement) {
|
||||
el.setSelectionRange(position, position)
|
||||
setTimeout(function () {
|
||||
el.setSelectionRange(position, position)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function isString (val) {
|
||||
return Object.prototype.toString.call(val) === '[object String]'
|
||||
}
|
||||
|
||||
export {
|
||||
event,
|
||||
findInputElement,
|
||||
fixInputSelection,
|
||||
isString
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { MaskaDetail, vMaska } from '../../src'
|
||||
|
||||
const binded = reactive<Partial<MaskaDetail>>({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded" data-maska="#-#" value="123" />
|
||||
<div>{{ binded.masked }}</div>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { MaskaDetail, vMaska } from '../../src'
|
||||
|
||||
const binded = reactive<Partial<MaskaDetail>>({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded" data-maska="#-#" />
|
||||
<div>{{ binded.masked }}</div>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { MaskaDetail, vMaska } from '../../src'
|
||||
|
||||
const binded = reactive<Partial<MaskaDetail>>({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded" data-maska="#-#" />
|
||||
<div>{{ binded.unmasked }}</div>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { MaskaDetail, vMaska } from '../../src'
|
||||
|
||||
const binded = reactive<Partial<MaskaDetail>>({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded" data-maska="#-#-#" />
|
||||
<div v-if="binded.completed">Completed</div>
|
||||
<div v-else>Uncompleted</div>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { vMaska, MaskaDetail, MaskInputOptions } from '../../src'
|
||||
|
||||
const binded = reactive<Partial<MaskaDetail>>({})
|
||||
const config = reactive<MaskInputOptions>({
|
||||
mask: 'A A',
|
||||
tokens: {
|
||||
A: {
|
||||
pattern: /[A-Z]/,
|
||||
multiple: true,
|
||||
transform: (chr) => chr.toUpperCase()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska:[config]="binded" />
|
||||
<div>{{ binded.masked }}</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { vMaska } from '../../src'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska data-maska="#-#" data-maska-eager />
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { vMaska } from '../../src'
|
||||
|
||||
const mask = ref('["#--#", "#-#--#"]')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska :data-maska="mask" />
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { MaskaDetail, vMaska } from '../../src'
|
||||
|
||||
const emit = defineEmits(['mask'])
|
||||
|
||||
const onMaska = (e: CustomEvent<MaskaDetail>) => {
|
||||
emit('mask', e.detail)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska data-maska="#-#" @maska="onMaska" />
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { MaskInputOptions, vMaska } from '../../src'
|
||||
|
||||
const config = reactive<MaskInputOptions>({
|
||||
preProcess: (val) => val.toUpperCase()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska:[config] data-maska="A A" data-maska-tokens="A:[A-Z]:multiple" />
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { vMaska } from '../../src'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
binded: {
|
||||
masked: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
maska: vMaska
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska="binded" data-maska="#-#" />
|
||||
<div>{{ binded.masked }}</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { vMaska } from '../../src'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input v-maska data-maska="#-#" />
|
||||
</template>
|
||||
@@ -0,0 +1,152 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { expect, test } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import BindInitial from './components/BindInitial.vue'
|
||||
import BindMasked from './components/BindMasked.vue'
|
||||
import BindUnmasked from './components/BindUnmasked.vue'
|
||||
import Completed from './components/Completed.vue'
|
||||
import Config from './components/Config.vue'
|
||||
import DataAttr from './components/DataAttr.vue'
|
||||
import Dynamic from './components/Dynamic.vue'
|
||||
import Events from './components/Events.vue'
|
||||
import Hooks from './components/Hooks.vue'
|
||||
import Options from './components/Options.vue'
|
||||
import Simple from './components/Simple.vue'
|
||||
|
||||
test('simple directive', async () => {
|
||||
const wrapper = mount(Simple)
|
||||
expect(wrapper.exists).toBeTruthy()
|
||||
|
||||
const input = wrapper.get('input')
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2')
|
||||
})
|
||||
|
||||
test('data-attr', async () => {
|
||||
const wrapper = mount(DataAttr)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('1')
|
||||
expect(input.element.value).toBe('1-')
|
||||
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2')
|
||||
})
|
||||
|
||||
test('dynamic mask', async () => {
|
||||
const wrapper = mount(Dynamic)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('12')
|
||||
expect(input.element.value).toBe('1--2')
|
||||
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2--3')
|
||||
})
|
||||
|
||||
test('initial value', async () => {
|
||||
const wrapper = mount(BindInitial)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(input.element.value).toBe('1-2')
|
||||
expect(wrapper.get('div').element.textContent).toBe('1-2')
|
||||
})
|
||||
|
||||
test('bind masked', async () => {
|
||||
const wrapper = mount(BindMasked)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2')
|
||||
expect(wrapper.get('div').element.textContent).toBe('1-2')
|
||||
})
|
||||
|
||||
test('bind unmasked', async () => {
|
||||
const wrapper = mount(BindUnmasked)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2')
|
||||
expect(wrapper.get('div').element.textContent).toBe('12')
|
||||
})
|
||||
|
||||
test('bind completed', async () => {
|
||||
const wrapper = mount(Completed)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('12')
|
||||
await nextTick()
|
||||
|
||||
expect(input.element.value).toBe('1-2')
|
||||
expect(wrapper.get('div').element.textContent).toBe('Uncompleted')
|
||||
|
||||
await input.setValue('123')
|
||||
await nextTick()
|
||||
|
||||
expect(input.element.value).toBe('1-2-3')
|
||||
expect(wrapper.get('div').element.textContent).toBe('Completed')
|
||||
})
|
||||
|
||||
test('config and bind', async () => {
|
||||
const wrapper = mount(Config)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('1')
|
||||
expect(input.element.value).toBe('')
|
||||
expect(wrapper.get('div').element.textContent).toBe('')
|
||||
|
||||
await input.setValue('ab')
|
||||
expect(input.element.value).toBe('AB')
|
||||
expect(wrapper.get('div').element.textContent).toBe('AB')
|
||||
|
||||
await input.setValue('ab cd ')
|
||||
expect(input.element.value).toBe('AB CD')
|
||||
expect(wrapper.get('div').element.textContent).toBe('AB CD')
|
||||
|
||||
await input.setValue('ab cd1')
|
||||
expect(input.element.value).toBe('AB CD')
|
||||
expect(wrapper.get('div').element.textContent).toBe('AB CD')
|
||||
})
|
||||
|
||||
test('hooks', async () => {
|
||||
const wrapper = mount(Hooks)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('1')
|
||||
expect(input.element.value).toBe('')
|
||||
|
||||
await input.setValue('ab')
|
||||
expect(input.element.value).toBe('AB')
|
||||
|
||||
await input.setValue('ab cd ')
|
||||
expect(input.element.value).toBe('AB CD')
|
||||
|
||||
await input.setValue('ab cd1')
|
||||
expect(input.element.value).toBe('AB CD')
|
||||
})
|
||||
|
||||
test('events', async () => {
|
||||
const wrapper = mount(Events)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('1')
|
||||
expect(wrapper.emitted()).toHaveProperty('mask')
|
||||
expect(wrapper.emitted('mask')).toHaveLength(1)
|
||||
expect(wrapper.emitted('mask')[0][0]).toHaveProperty('completed', false)
|
||||
|
||||
await input.setValue('12')
|
||||
expect(wrapper.emitted('mask')).toHaveLength(2)
|
||||
expect(wrapper.emitted('mask')[1][0]).toHaveProperty('completed', true)
|
||||
})
|
||||
|
||||
test('options api component', async () => {
|
||||
const wrapper = mount(Options)
|
||||
const input = wrapper.get('input')
|
||||
|
||||
await input.setValue('123')
|
||||
expect(input.element.value).toBe('1-2')
|
||||
expect(wrapper.get('div').element.textContent).toBe('1-2')
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,224 +0,0 @@
|
||||
import mask from './../src/mask'
|
||||
import tokens from './../src/tokens'
|
||||
import Maska from './../src/maska'
|
||||
|
||||
test('12 #.#', () => {
|
||||
expect(mask('12', '#.#', tokens)).toBe('1.2')
|
||||
})
|
||||
|
||||
test('1 (#)', () => {
|
||||
expect(mask('1', '(#)', tokens)).toBe('(1)')
|
||||
})
|
||||
|
||||
test('1 [(#)]', () => {
|
||||
expect(mask('1', '[(#)]', tokens)).toBe('[(1)]')
|
||||
})
|
||||
|
||||
test('1 #.#', () => {
|
||||
expect(mask('1', '#.#', tokens)).toBe('1.')
|
||||
})
|
||||
|
||||
test('1. #.#', () => {
|
||||
expect(mask('1.', '#.#', tokens)).toBe('1.')
|
||||
})
|
||||
|
||||
test('1-23A #-##!A', () => {
|
||||
expect(mask('123', '#-##!A', tokens)).toBe('1-23A')
|
||||
})
|
||||
|
||||
test('123 #.#', () => {
|
||||
expect(mask('123', '#.#', tokens)).toBe('1.2')
|
||||
})
|
||||
|
||||
test('Raw phone number', () => {
|
||||
expect(mask('44998765432', '+55 (##) #####-####', tokens, false)).toBe('44998765432')
|
||||
})
|
||||
|
||||
test('abcd12345 AAA-####', () => {
|
||||
expect(mask('abcd12345', 'AAA-####', tokens)).toBe('ABC-1234')
|
||||
})
|
||||
|
||||
test('a5-12-34 (XX) - ## - ##', () => {
|
||||
expect(mask('a5-12-34', '(XX) - ## - ##', tokens)).toBe('(a5) - 12 - 34')
|
||||
})
|
||||
|
||||
test('123 ##(#)', () => {
|
||||
expect(mask('123', '##(#)', tokens)).toBe('12(3)')
|
||||
})
|
||||
|
||||
test('12 #!#(#)', () => {
|
||||
expect(mask('12', '#!#(#)', tokens)).toBe('1#(2)')
|
||||
})
|
||||
|
||||
test('12 #!!#', () => {
|
||||
expect(mask('12', '#!!#', tokens)).toBe('1!2')
|
||||
})
|
||||
|
||||
test('12 +1 #', () => {
|
||||
expect(mask('12', '+1 #', tokens)).toBe('+1 2')
|
||||
})
|
||||
|
||||
test('2 +1 #', () => {
|
||||
expect(mask('2', '+1 #', tokens)).toBe('+1 2')
|
||||
})
|
||||
|
||||
test('abc DEF AAA aaa', () => {
|
||||
expect(mask('abc DEF', 'AAA aaa', tokens)).toBe('ABC def')
|
||||
})
|
||||
|
||||
test('123abc #*', () => {
|
||||
expect(mask('123abc', '#*', tokens)).toBe('123')
|
||||
})
|
||||
|
||||
test('123abc A*', () => {
|
||||
expect(mask('123abc', 'A*', tokens)).toBe('ABC')
|
||||
})
|
||||
|
||||
test('123abc #A*', () => {
|
||||
expect(mask('123abc', '#A*', tokens)).toBe('1ABC')
|
||||
})
|
||||
|
||||
test('123abc ## A*', () => {
|
||||
expect(mask('123abc', '## A*', tokens)).toBe('12 ABC')
|
||||
})
|
||||
|
||||
test('123abc #*A', () => {
|
||||
expect(mask('123abc', '#*A', tokens)).toBe('123A')
|
||||
})
|
||||
|
||||
test('123abc #*A*', () => {
|
||||
expect(mask('123abc', '#*A*', tokens)).toBe('123ABC')
|
||||
})
|
||||
|
||||
test('123 abc DEF 456 -> A* a*', () => {
|
||||
expect(mask('123 abc DEF 456', 'A* a*', tokens)).toBe('ABC def')
|
||||
})
|
||||
|
||||
test('abc123 A!*', () => {
|
||||
expect(mask('abc123', 'A!*', tokens)).toBe('A*')
|
||||
})
|
||||
|
||||
test('123abc #!*A*', () => {
|
||||
expect(mask('123abc', '#!*A*', tokens)).toBe('1*ABC')
|
||||
})
|
||||
|
||||
test('123abc (#*)A*', () => {
|
||||
expect(mask('123abc', '(#*)A*', tokens)).toBe('(123)ABC')
|
||||
})
|
||||
|
||||
test('123abc -> # (A*)', () => {
|
||||
expect(mask('123abc', '# (A*)', tokens)).toBe('1 (ABC')
|
||||
expect(mask('123abc ', '# (A*)', tokens)).toBe('1 (ABC)')
|
||||
})
|
||||
|
||||
test('Raw 123abc ##(A*)', () => {
|
||||
expect(mask('123abc', '##(A*)', tokens, false)).toBe('12ABC')
|
||||
})
|
||||
|
||||
test('Dynamic floats', () => {
|
||||
expect(mask('1', '["# cm", "#.# cm", "#.## cm"]', tokens)).toBe('1 cm')
|
||||
expect(mask('12', '["# cm", "#.# cm", "#.## cm"]', tokens)).toBe('1.2 cm')
|
||||
expect(mask('123', '["# cm", "#.# cm", "#.## cm"]', tokens)).toBe('1.23 cm')
|
||||
})
|
||||
|
||||
test('Dynamic CPF/CNPJ', () => {
|
||||
expect(mask('12345678901', '["###.###.###-##", "##.###.###/####-##"]', tokens)).toBe('123.456.789-01')
|
||||
expect(mask('12345678901234', '["###.###.###-##", "##.###.###/####-##"]', tokens)).toBe('12.345.678/9012-34')
|
||||
})
|
||||
|
||||
test('Dynamic boundaries', () => {
|
||||
expect(mask('12', '["!###", "!###-##", "!###-##-##"]', tokens)).toBe('#12')
|
||||
expect(mask('1234', '["!###", "!###-##", "!###-##-##"]', tokens)).toBe('#12-34')
|
||||
expect(mask('1234567', '["!###", "!###-##", "!###-##-##"]', tokens)).toBe('#12-34-56')
|
||||
expect(mask('123', '["###", "###-##", "###.##.##"]', tokens)).toBe('123')
|
||||
expect(mask('12345', '["###", "###-##", "###.##.##"]', tokens)).toBe('123-45')
|
||||
expect(mask('12345678', '["###", "###-##", "###.##.##"]', tokens)).toBe('123.45.67')
|
||||
})
|
||||
|
||||
test('Custom transform: odd number -> 1, even number -> 0', () => {
|
||||
// isOdd
|
||||
const transform = (numberLike) => String(Number(numberLike) % 2)
|
||||
|
||||
expect((mask('1234567890', '#*', {
|
||||
...tokens,
|
||||
...{
|
||||
'#': {
|
||||
pattern: /[0-9]/,
|
||||
transform
|
||||
}
|
||||
}
|
||||
}))).toBe('1010101010')
|
||||
})
|
||||
|
||||
function transliterate(char) {
|
||||
const rule = Object.entries({
|
||||
a: 'а',
|
||||
b: 'в',
|
||||
k: 'к',
|
||||
m: 'м',
|
||||
h: 'н',
|
||||
o: 'о',
|
||||
p: 'р',
|
||||
c: 'с',
|
||||
t: 'т',
|
||||
y: 'у',
|
||||
x: 'х',
|
||||
}).reduce((acc, [from, to]) => {
|
||||
acc[from] = to
|
||||
acc[from.toLocaleUpperCase()] = to.toLocaleUpperCase()
|
||||
return acc
|
||||
}, {})
|
||||
return rule[char]
|
||||
}
|
||||
|
||||
test('Custom transform: transliterate abkTYX -> авкТУХ', () => {
|
||||
expect(mask('abkTYX', 'T*', {
|
||||
...tokens,
|
||||
...{
|
||||
'T': {
|
||||
pattern: /[a-zA-Z]/,
|
||||
transform: transliterate
|
||||
}
|
||||
}
|
||||
})).toBe('авкТУХ')
|
||||
})
|
||||
|
||||
test('Custom transform with `uppercase` and `lowercase` enabled: abkTYX -> АВКТУХ, abkTYX -> авктух', () => {
|
||||
expect(mask('abkTYX', 'T*', {
|
||||
...tokens,
|
||||
...{
|
||||
'T': {
|
||||
pattern: /[a-zA-Z]/,
|
||||
transform: transliterate,
|
||||
uppercase: true,
|
||||
}
|
||||
}
|
||||
})).toBe('АВКТУХ')
|
||||
|
||||
expect(mask('abkTYX', 'T*', {
|
||||
...tokens,
|
||||
...{
|
||||
'T': {
|
||||
pattern: /[a-zA-Z]/,
|
||||
transform: transliterate,
|
||||
lowercase: true,
|
||||
}
|
||||
}
|
||||
})).toBe('авктух')
|
||||
})
|
||||
|
||||
test('Custom value preprocessing', () => {
|
||||
const elem = document.createElement('input')
|
||||
document.body.appendChild(elem)
|
||||
|
||||
const mask = new Maska(elem, {
|
||||
mask: 'S*',
|
||||
preprocessor: function(val) {
|
||||
return val.toLocaleUpperCase()
|
||||
}
|
||||
})
|
||||
elem.value = "abkTYX"
|
||||
elem.dispatchEvent(new Event('input', {bubbles: true}))
|
||||
|
||||
expect(elem.value).toBe("ABKTYX")
|
||||
})
|
||||
@@ -0,0 +1,790 @@
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { Mask } from '../src/mask'
|
||||
|
||||
test('null mask', () => {
|
||||
// @ts-ignore
|
||||
const mask = new Mask({ mask: null })
|
||||
|
||||
expect(mask.masked('1a')).toBe('')
|
||||
})
|
||||
|
||||
test('undefined mask', () => {
|
||||
const mask = new Mask({ mask: undefined })
|
||||
|
||||
expect(mask.masked('1a')).toBe('')
|
||||
})
|
||||
|
||||
test('@ @ mask', () => {
|
||||
const mask = new Mask({ mask: '@ @' })
|
||||
|
||||
expect(mask.masked('1')).toBe('')
|
||||
expect(mask.masked('a')).toBe('a')
|
||||
expect(mask.masked('ab')).toBe('a b')
|
||||
expect(mask.masked('abc')).toBe('a b')
|
||||
expect(mask.masked('1abc')).toBe('a b')
|
||||
|
||||
expect(mask.unmasked('1abc')).toBe('ab')
|
||||
|
||||
expect(mask.completed('a')).toBe(false)
|
||||
expect(mask.completed('ab')).toBe(true)
|
||||
})
|
||||
|
||||
test('@ @ eager mask', () => {
|
||||
const mask = new Mask({ mask: '@ @', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('')
|
||||
expect(mask.masked('a')).toBe('a ')
|
||||
expect(mask.masked('ab')).toBe('a b')
|
||||
expect(mask.masked('abc')).toBe('a b')
|
||||
expect(mask.masked('1abc')).toBe('a b')
|
||||
|
||||
expect(mask.unmasked('1abc')).toBe('ab')
|
||||
})
|
||||
|
||||
test('#.# mask', () => {
|
||||
const mask = new Mask({ mask: '#.#' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('1.')).toBe('1.')
|
||||
expect(mask.masked('12')).toBe('1.2')
|
||||
expect(mask.masked('123')).toBe('1.2')
|
||||
expect(mask.masked('a123')).toBe('1.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#.# eager mask', () => {
|
||||
const mask = new Mask({ mask: '#.#', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1.')
|
||||
expect(mask.masked('1.')).toBe('1.')
|
||||
expect(mask.masked('1 ')).toBe('1.')
|
||||
expect(mask.masked('12')).toBe('1.2')
|
||||
expect(mask.masked('123')).toBe('1.2')
|
||||
expect(mask.masked('a123')).toBe('1.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('@@-## mask', () => {
|
||||
const mask = new Mask({ mask: '@@-##' })
|
||||
|
||||
expect(mask.masked('12')).toBe('')
|
||||
expect(mask.masked('ab')).toBe('ab')
|
||||
expect(mask.masked('ab12')).toBe('ab-12')
|
||||
expect(mask.masked('ab-12')).toBe('ab-12')
|
||||
expect(mask.masked('abc123')).toBe('ab-12')
|
||||
expect(mask.masked('a1b2a1b2')).toBe('ab-21')
|
||||
|
||||
expect(mask.unmasked('a1b2a1b2')).toBe('ab21')
|
||||
})
|
||||
|
||||
test('@@-## eager mask', () => {
|
||||
const mask = new Mask({ mask: '@@-##', eager: true })
|
||||
|
||||
expect(mask.masked('12')).toBe('')
|
||||
expect(mask.masked('ab')).toBe('ab-')
|
||||
expect(mask.masked('ab12')).toBe('ab-12')
|
||||
expect(mask.masked('ab-12')).toBe('ab-12')
|
||||
expect(mask.masked('abc123')).toBe('ab-12')
|
||||
expect(mask.masked('a1b2a1b2')).toBe('ab-21')
|
||||
|
||||
expect(mask.unmasked('a1b2a1b2')).toBe('ab21')
|
||||
})
|
||||
|
||||
test('(#) mask', () => {
|
||||
const mask = new Mask({ mask: '(#)' })
|
||||
|
||||
expect(mask.masked('1')).toBe('(1')
|
||||
expect(mask.masked('(1')).toBe('(1')
|
||||
expect(mask.masked('1 ')).toBe('(1)')
|
||||
expect(mask.masked('12')).toBe('(1)')
|
||||
expect(mask.masked('a12')).toBe('(1)')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('1')
|
||||
})
|
||||
|
||||
test('(#) eager mask', () => {
|
||||
const mask = new Mask({ mask: '(#)', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('(1)')
|
||||
expect(mask.masked('(1')).toBe('(1)')
|
||||
expect(mask.masked('12')).toBe('(1)')
|
||||
expect(mask.masked('a12')).toBe('(1)')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('1')
|
||||
})
|
||||
|
||||
test('#-#--# mask', () => {
|
||||
const mask = new Mask({ mask: '#-#--#' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('1-2')
|
||||
expect(mask.masked('123')).toBe('1-2--3')
|
||||
expect(mask.masked('a1234')).toBe('1-2--3')
|
||||
|
||||
expect(mask.unmasked('a1234')).toBe('123')
|
||||
})
|
||||
|
||||
test('#-#--# eager mask', () => {
|
||||
const mask = new Mask({ mask: '#-#--#', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1-')
|
||||
expect(mask.masked('12')).toBe('1-2--')
|
||||
expect(mask.masked('123')).toBe('1-2--3')
|
||||
expect(mask.masked('a1234')).toBe('1-2--3')
|
||||
|
||||
expect(mask.unmasked('a1234')).toBe('123')
|
||||
})
|
||||
|
||||
test('!##.# mask', () => {
|
||||
const mask = new Mask({ mask: '!##.#' })
|
||||
|
||||
expect(mask.masked('1')).toBe('#1')
|
||||
expect(mask.masked('#1')).toBe('#1')
|
||||
expect(mask.masked('12')).toBe('#1.2')
|
||||
expect(mask.masked('1.2')).toBe('#1.2')
|
||||
expect(mask.masked('#1.2')).toBe('#1.2')
|
||||
expect(mask.masked('123')).toBe('#1.2')
|
||||
expect(mask.masked('a123')).toBe('#1.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('!##.# eager mask', () => {
|
||||
const mask = new Mask({ mask: '!##.#', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('#1.')
|
||||
expect(mask.masked('#1')).toBe('#1.')
|
||||
expect(mask.masked('12')).toBe('#1.2')
|
||||
expect(mask.masked('1.2')).toBe('#1.2')
|
||||
expect(mask.masked('#1.2')).toBe('#1.2')
|
||||
expect(mask.masked('123')).toBe('#1.2')
|
||||
expect(mask.masked('a123')).toBe('#1.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('0#.# mask', () => {
|
||||
const mask = new Mask({ mask: '0#.#' })
|
||||
|
||||
expect(mask.masked('1')).toBe('01')
|
||||
expect(mask.masked('01')).toBe('01')
|
||||
expect(mask.masked('12')).toBe('01.2')
|
||||
expect(mask.masked('1.2')).toBe('01.2')
|
||||
expect(mask.masked('01.2')).toBe('01.2')
|
||||
expect(mask.masked('123')).toBe('01.2')
|
||||
expect(mask.masked('a123')).toBe('01.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('0#.# eager mask', () => {
|
||||
const mask = new Mask({ mask: '0#.#', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('01.')
|
||||
expect(mask.masked('01')).toBe('00.1')
|
||||
expect(mask.masked('12')).toBe('01.2')
|
||||
expect(mask.masked('1.2')).toBe('01.2')
|
||||
expect(mask.masked('01.2')).toBe('00.1')
|
||||
expect(mask.masked('123')).toBe('01.2')
|
||||
expect(mask.masked('a123')).toBe('01.2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#.#!* mask', () => {
|
||||
const mask = new Mask({ mask: '#.#!*' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('1.2')
|
||||
expect(mask.masked('1.2')).toBe('1.2')
|
||||
expect(mask.masked('12*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2 ')).toBe('1.2*')
|
||||
expect(mask.masked('123')).toBe('1.2*')
|
||||
expect(mask.masked('a123')).toBe('1.2*')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#.#!* eager mask', () => {
|
||||
const mask = new Mask({ mask: '#.#!*', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1.')
|
||||
expect(mask.masked('12')).toBe('1.2*')
|
||||
expect(mask.masked('1.2')).toBe('1.2*')
|
||||
expect(mask.masked('12*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2*')).toBe('1.2*')
|
||||
expect(mask.masked('123')).toBe('1.2*')
|
||||
expect(mask.masked('a123')).toBe('1.2*')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#.#!** mask', () => {
|
||||
const mask = new Mask({ mask: '#.#!**' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('1.2')
|
||||
expect(mask.masked('1.2')).toBe('1.2')
|
||||
expect(mask.masked('12*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2 ')).toBe('1.2*')
|
||||
expect(mask.masked('123')).toBe('1.2*3')
|
||||
expect(mask.masked('12*3')).toBe('1.2*3')
|
||||
expect(mask.masked('a123')).toBe('1.2*3')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('123')
|
||||
})
|
||||
|
||||
test('#.#!** eager mask', () => {
|
||||
const mask = new Mask({ mask: '#.#!**', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1.')
|
||||
expect(mask.masked('12')).toBe('1.2*')
|
||||
expect(mask.masked('1.2')).toBe('1.2*')
|
||||
expect(mask.masked('12*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2*')).toBe('1.2*')
|
||||
expect(mask.masked('1.2*3')).toBe('1.2*3')
|
||||
expect(mask.masked('123')).toBe('1.2*3')
|
||||
expect(mask.masked('a123')).toBe('1.2*3')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('123')
|
||||
})
|
||||
|
||||
test('0#!-# mask', () => {
|
||||
const mask = new Mask({ mask: '0#!-#' })
|
||||
|
||||
expect(mask.masked('a')).toBe('0')
|
||||
expect(mask.masked('0')).toBe('0')
|
||||
expect(mask.masked('1')).toBe('01')
|
||||
expect(mask.masked('12')).toBe('01-2')
|
||||
expect(mask.masked('01-2')).toBe('01-2')
|
||||
expect(mask.masked('123')).toBe('01-2')
|
||||
expect(mask.masked('a123')).toBe('01-2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('!0#!-# eager mask', () => {
|
||||
const mask = new Mask({ mask: '!0#!-#', eager: true })
|
||||
|
||||
expect(mask.masked('a')).toBe('0')
|
||||
expect(mask.masked('0')).toBe('00-')
|
||||
expect(mask.masked('1')).toBe('01-')
|
||||
expect(mask.masked('12')).toBe('01-2')
|
||||
expect(mask.masked('01-2')).toBe('00-1')
|
||||
expect(mask.masked('123')).toBe('01-2')
|
||||
expect(mask.masked('a123')).toBe('01-2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#2 ## mask', () => {
|
||||
const mask = new Mask({ mask: '#2 ##' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('12 ')).toBe('12 ')
|
||||
expect(mask.masked('13')).toBe('12 3')
|
||||
expect(mask.masked('123')).toBe('12 3')
|
||||
expect(mask.masked('134')).toBe('12 34')
|
||||
expect(mask.masked('1234')).toBe('12 34')
|
||||
expect(mask.masked('1345')).toBe('12 34')
|
||||
expect(mask.masked('12345')).toBe('12 34')
|
||||
expect(mask.masked('a1')).toBe('1')
|
||||
expect(mask.masked('a13')).toBe('12 3')
|
||||
|
||||
expect(mask.unmasked('12345')).toBe('134')
|
||||
})
|
||||
|
||||
test('#2 ## eager mask', () => {
|
||||
const mask = new Mask({ mask: '#2 ##', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('12 ')
|
||||
expect(mask.masked('12')).toBe('12 2')
|
||||
expect(mask.masked('12 ')).toBe('12 2')
|
||||
expect(mask.masked('13')).toBe('12 3')
|
||||
expect(mask.masked('123')).toBe('12 23')
|
||||
expect(mask.masked('134')).toBe('12 34')
|
||||
expect(mask.masked('1234')).toBe('12 23')
|
||||
expect(mask.masked('1345')).toBe('12 34')
|
||||
expect(mask.masked('12345')).toBe('12 23')
|
||||
expect(mask.masked('a1')).toBe('12 ')
|
||||
expect(mask.masked('a13')).toBe('12 3')
|
||||
|
||||
expect(mask.unmasked('12345')).toBe('134')
|
||||
})
|
||||
|
||||
test('(#) 3## mask', () => {
|
||||
const mask = new Mask({ mask: '(#) 3##' })
|
||||
|
||||
expect(mask.masked('1')).toBe('(1')
|
||||
expect(mask.masked('12')).toBe('(1) 32')
|
||||
expect(mask.masked('123')).toBe('(1) 323')
|
||||
expect(mask.masked('1234')).toBe('(1) 323')
|
||||
expect(mask.masked('13')).toBe('(1) 3')
|
||||
expect(mask.masked('134')).toBe('(1) 34')
|
||||
expect(mask.masked('(1) 23')).toBe('(1) 323')
|
||||
expect(mask.masked('(1) 34')).toBe('(1) 34')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('1')
|
||||
expect(mask.unmasked('1234')).toBe('123')
|
||||
expect(mask.unmasked('(1) 3')).toBe('1')
|
||||
expect(mask.unmasked('(1) 32')).toBe('12')
|
||||
})
|
||||
|
||||
test('(#) 3## eager mask', () => {
|
||||
const mask = new Mask({ mask: '(#) 3##', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('(1) 3')
|
||||
expect(mask.masked('12')).toBe('(1) 32')
|
||||
expect(mask.masked('123')).toBe('(1) 323')
|
||||
expect(mask.masked('1234')).toBe('(1) 323')
|
||||
expect(mask.masked('13')).toBe('(1) 33')
|
||||
expect(mask.masked('134')).toBe('(1) 334')
|
||||
expect(mask.masked('(1) 23')).toBe('(1) 323')
|
||||
expect(mask.masked('(1) 34')).toBe('(1) 334')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('1')
|
||||
expect(mask.unmasked('1234')).toBe('123')
|
||||
expect(mask.unmasked('(1) 3')).toBe('1')
|
||||
expect(mask.unmasked('(1) 32')).toBe('12')
|
||||
})
|
||||
|
||||
test('(1) 2# mask', () => {
|
||||
const mask = new Mask({ mask: '(1) 2#' })
|
||||
|
||||
expect(mask.masked(' ')).toBe('(1) ')
|
||||
expect(mask.masked('.')).toBe('(1) 2')
|
||||
expect(mask.masked('1')).toBe('(1')
|
||||
expect(mask.masked('12')).toBe('(1) 2')
|
||||
expect(mask.masked('123')).toBe('(1) 23')
|
||||
expect(mask.masked('1234')).toBe('(1) 23')
|
||||
expect(mask.masked('13')).toBe('(1) 23')
|
||||
expect(mask.masked('(1) 23')).toBe('(1) 23')
|
||||
expect(mask.masked('(1) 34')).toBe('(1) 23')
|
||||
expect(mask.masked('2')).toBe('(1) 2')
|
||||
expect(mask.masked('23')).toBe('(1) 23')
|
||||
expect(mask.masked('3')).toBe('(1) 23')
|
||||
expect(mask.masked('4')).toBe('(1) 24')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('')
|
||||
expect(mask.unmasked('12')).toBe('')
|
||||
expect(mask.unmasked('123')).toBe('3')
|
||||
expect(mask.unmasked('(1) 23')).toBe('3')
|
||||
})
|
||||
|
||||
test('(1) 2## eager mask', () => {
|
||||
const mask = new Mask({ mask: '(1) 2##', eager: true })
|
||||
|
||||
expect(mask.masked(' ')).toBe('(1) 2')
|
||||
expect(mask.masked('.')).toBe('(1) 2')
|
||||
expect(mask.masked('1')).toBe('(1) 21')
|
||||
expect(mask.masked('12')).toBe('(1) 212')
|
||||
expect(mask.masked('123')).toBe('(1) 212')
|
||||
expect(mask.masked('13')).toBe('(1) 213')
|
||||
expect(mask.masked('(1) 23')).toBe('(1) 212')
|
||||
expect(mask.masked('(1) 34')).toBe('(1) 213')
|
||||
expect(mask.masked('2')).toBe('(1) 22')
|
||||
expect(mask.masked('23')).toBe('(1) 223')
|
||||
expect(mask.masked('3')).toBe('(1) 23')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('')
|
||||
expect(mask.unmasked('12')).toBe('')
|
||||
expect(mask.unmasked('123')).toBe('3')
|
||||
expect(mask.unmasked('(1) 23')).toBe('3')
|
||||
})
|
||||
|
||||
test('12## mask', () => {
|
||||
const mask = new Mask({ mask: '12##' })
|
||||
|
||||
expect(mask.masked('.')).toBe('12')
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('2')).toBe('12')
|
||||
expect(mask.masked('3')).toBe('123')
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('123')).toBe('123')
|
||||
expect(mask.masked('13')).toBe('123')
|
||||
expect(mask.masked('134')).toBe('1234')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('')
|
||||
expect(mask.unmasked('12')).toBe('')
|
||||
expect(mask.unmasked('123')).toBe('3')
|
||||
expect(mask.unmasked('3')).toBe('3')
|
||||
expect(mask.unmasked('(1) 23')).toBe('12')
|
||||
})
|
||||
|
||||
test('12## eager mask', () => {
|
||||
const mask = new Mask({ mask: '12##', eager: true })
|
||||
|
||||
expect(mask.masked(' ')).toBe('12')
|
||||
expect(mask.masked('.')).toBe('12')
|
||||
expect(mask.masked('1')).toBe('121')
|
||||
expect(mask.masked('2')).toBe('122')
|
||||
expect(mask.masked('3')).toBe('123')
|
||||
expect(mask.masked('12')).toBe('1212')
|
||||
expect(mask.masked('123')).toBe('1212')
|
||||
expect(mask.masked('13')).toBe('1213')
|
||||
expect(mask.masked('134')).toBe('1213')
|
||||
|
||||
expect(mask.unmasked('1')).toBe('')
|
||||
expect(mask.unmasked('12')).toBe('')
|
||||
expect(mask.unmasked('123')).toBe('3')
|
||||
expect(mask.unmasked('3')).toBe('3')
|
||||
expect(mask.unmasked('(1) 23')).toBe('12')
|
||||
})
|
||||
|
||||
test('#!!# mask', () => {
|
||||
const mask = new Mask({ mask: '#!!#' })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('1!2')
|
||||
expect(mask.masked('1!2')).toBe('1!2')
|
||||
expect(mask.masked('123')).toBe('1!2')
|
||||
expect(mask.masked('a123')).toBe('1!2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('#!!# eager mask', () => {
|
||||
const mask = new Mask({ mask: '#!!#', eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1!')
|
||||
expect(mask.masked('12')).toBe('1!2')
|
||||
expect(mask.masked('1!2')).toBe('1!2')
|
||||
expect(mask.masked('123')).toBe('1!2')
|
||||
expect(mask.masked('a123')).toBe('1!2')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('12')
|
||||
})
|
||||
|
||||
test('+1 (###) ###-##-## mask', () => {
|
||||
const mask = new Mask({ mask: '+1 (###) ###-##-##' })
|
||||
|
||||
expect(mask.masked('999')).toBe('+1 (999')
|
||||
expect(mask.masked('999123')).toBe('+1 (999) 123')
|
||||
expect(mask.masked('19991234567')).toBe('+1 (999) 123-45-67')
|
||||
expect(mask.masked('+19991234567')).toBe('+1 (999) 123-45-67')
|
||||
expect(mask.masked('9991234567')).toBe('+1 (999) 123-45-67')
|
||||
expect(mask.masked('a9991234567')).toBe('+1 (999) 123-45-67')
|
||||
|
||||
expect(mask.unmasked('+19991234567')).toBe('9991234567')
|
||||
})
|
||||
|
||||
test('+1 (###) ###-##-## eager mask', () => {
|
||||
const mask = new Mask({ mask: '+1 (###) ###-##-##', eager: true })
|
||||
|
||||
expect(mask.masked('99')).toBe('+1 (99')
|
||||
expect(mask.masked('999')).toBe('+1 (999) ')
|
||||
expect(mask.masked('99912')).toBe('+1 (999) 12')
|
||||
expect(mask.masked('999123')).toBe('+1 (999) 123-')
|
||||
expect(mask.masked('19991234567')).toBe('+1 (199) 912-34-56')
|
||||
expect(mask.masked('+19991234567')).toBe('+1 (199) 912-34-56')
|
||||
expect(mask.masked('9991234567')).toBe('+1 (999) 123-45-67')
|
||||
expect(mask.masked('a9991234567')).toBe('+1 (999) 123-45-67')
|
||||
|
||||
expect(mask.unmasked('+19991234567')).toBe('9991234567')
|
||||
})
|
||||
|
||||
test('1 (###) ###-##-## eager mask', () => {
|
||||
const mask = new Mask({ mask: '1 (###) ###-##-##', eager: true })
|
||||
|
||||
expect(mask.masked('19991234567')).toBe('1 (199) 912-34-56')
|
||||
expect(mask.masked('+19991234567')).toBe('1 (199) 912-34-56')
|
||||
expect(mask.masked('9991234567')).toBe('1 (999) 123-45-67')
|
||||
expect(mask.masked('a9991234567')).toBe('1 (999) 123-45-67')
|
||||
|
||||
expect(mask.unmasked('19991234567')).toBe('9991234567')
|
||||
expect(mask.unmasked('+19991234567')).toBe('1999123456')
|
||||
})
|
||||
|
||||
test('transform mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: 'ZZzz',
|
||||
tokens: {
|
||||
Z: { pattern: /[a-zA-Z]/, transform: (char) => char.toUpperCase() },
|
||||
z: { pattern: /[a-zA-Z]/, transform: (char) => char.toLowerCase() }
|
||||
}
|
||||
})
|
||||
expect(mask.masked('abcd')).toBe('ABcd')
|
||||
expect(mask.masked('ABCD')).toBe('ABcd')
|
||||
expect(mask.masked('1AB2CD')).toBe('ABcd')
|
||||
|
||||
expect(mask.unmasked('1AB2CD')).toBe('ABcd')
|
||||
})
|
||||
|
||||
test('transform strict mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: 'ZZzz',
|
||||
tokens: {
|
||||
Z: { pattern: /[A-Z]/, transform: (char) => char.toUpperCase() },
|
||||
z: { pattern: /[a-z]/, transform: (char) => char.toLowerCase() }
|
||||
}
|
||||
})
|
||||
expect(mask.masked('abcd')).toBe('ABcd')
|
||||
expect(mask.masked('ABCD')).toBe('ABcd')
|
||||
expect(mask.masked('1AB2CD')).toBe('ABcd')
|
||||
|
||||
expect(mask.unmasked('1AB2CD')).toBe('ABcd')
|
||||
})
|
||||
|
||||
test('IP mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '#00.#00.#00.#00',
|
||||
tokens: {
|
||||
0: { pattern: /[\d]/, optional: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('127.0.0.1')).toBe('127.0.0.1')
|
||||
expect(mask.masked('254254254254')).toBe('254.254.254.254')
|
||||
expect(mask.masked('1.23.456.7890')).toBe('1.23.456.789')
|
||||
expect(mask.masked('a1.23.456.7890')).toBe('1.23.456.789')
|
||||
|
||||
expect(mask.unmasked('a254.254.254.254')).toBe('254254254254')
|
||||
})
|
||||
|
||||
test('repeated simple mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '9',
|
||||
tokens: {
|
||||
9: { pattern: /[\d]/, repeated: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('12 ')).toBe('12')
|
||||
|
||||
expect(mask.unmasked('a12')).toBe('12')
|
||||
})
|
||||
|
||||
test('repeated 99.9 mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '99.9',
|
||||
tokens: {
|
||||
9: { pattern: /[\d]/, repeated: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('123')).toBe('12.3')
|
||||
expect(mask.masked('1234')).toBe('12.34')
|
||||
expect(mask.masked('12345')).toBe('12.345')
|
||||
expect(mask.masked('123456')).toBe('12.345.6')
|
||||
expect(mask.masked('1234567')).toBe('12.345.67')
|
||||
expect(mask.masked('12345678')).toBe('12.345.678')
|
||||
expect(mask.masked('123456789')).toBe('12.345.678.9')
|
||||
expect(mask.masked('a123456789')).toBe('12.345.678.9')
|
||||
|
||||
expect(mask.unmasked('a123456789')).toBe('123456789')
|
||||
})
|
||||
|
||||
test('repeated 99.9 eager mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '99.9',
|
||||
eager: true,
|
||||
tokens: {
|
||||
9: { pattern: /[\d]/, repeated: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('12')).toBe('12.')
|
||||
expect(mask.masked('123')).toBe('12.3')
|
||||
expect(mask.masked('1234')).toBe('12.34')
|
||||
expect(mask.masked('12345')).toBe('12.345.')
|
||||
expect(mask.masked('123456')).toBe('12.345.6')
|
||||
expect(mask.masked('1234567')).toBe('12.345.67')
|
||||
expect(mask.masked('12345678')).toBe('12.345.678.')
|
||||
expect(mask.masked('123456789')).toBe('12.345.678.9')
|
||||
|
||||
expect(mask.unmasked('123456789')).toBe('123456789')
|
||||
})
|
||||
|
||||
test('#-## reversed mask', () => {
|
||||
const mask = new Mask({ mask: '#-##', reversed: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('123')).toBe('1-23')
|
||||
expect(mask.masked('1234')).toBe('2-34')
|
||||
expect(mask.masked('a1234')).toBe('2-34')
|
||||
|
||||
expect(mask.unmasked('a1234')).toBe('234')
|
||||
})
|
||||
|
||||
test('#-## reversed eager mask', () => {
|
||||
const mask = new Mask({ mask: '#-##', reversed: true, eager: true })
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('-12')
|
||||
expect(mask.masked('123')).toBe('1-23')
|
||||
expect(mask.masked('1234')).toBe('2-34')
|
||||
expect(mask.masked('a1234')).toBe('2-34')
|
||||
|
||||
expect(mask.unmasked('a1234')).toBe('234')
|
||||
})
|
||||
|
||||
test('repeated reversed mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '9 99#,##',
|
||||
reversed: true,
|
||||
tokens: {
|
||||
9: { pattern: /[\d]/, repeated: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('123')).toBe('1,23')
|
||||
expect(mask.masked('1234')).toBe('12,34')
|
||||
expect(mask.masked('12345')).toBe('123,45')
|
||||
expect(mask.masked('123456')).toBe('1 234,56')
|
||||
expect(mask.masked('1234567')).toBe('12 345,67')
|
||||
expect(mask.masked('12345678')).toBe('123 456,78')
|
||||
expect(mask.masked('123456789')).toBe('1 234 567,89')
|
||||
|
||||
expect(mask.unmasked('123456789')).toBe('123456789')
|
||||
})
|
||||
|
||||
test('repeated reversed eager mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '9 99#,##',
|
||||
reversed: true,
|
||||
eager: true,
|
||||
tokens: {
|
||||
9: { pattern: /[\d]/, repeated: true }
|
||||
}
|
||||
})
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe(',12')
|
||||
expect(mask.masked('123')).toBe('1,23')
|
||||
expect(mask.masked('1234')).toBe('12,34')
|
||||
expect(mask.masked('12345')).toBe(' 123,45')
|
||||
expect(mask.masked('123456')).toBe('1 234,56')
|
||||
expect(mask.masked('1234567')).toBe('12 345,67')
|
||||
expect(mask.masked('12345678')).toBe(' 123 456,78')
|
||||
expect(mask.masked('123456789')).toBe('1 234 567,89')
|
||||
|
||||
expect(mask.unmasked('123456789')).toBe('123456789')
|
||||
})
|
||||
|
||||
test('multiple numbers mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '+ +',
|
||||
tokens: { '+': { pattern: /\d/, multiple: true } }
|
||||
})
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('1 2')).toBe('1 2')
|
||||
expect(mask.masked('1 2 3')).toBe('1 23')
|
||||
expect(mask.masked('12')).toBe('12')
|
||||
expect(mask.masked('12 ')).toBe('12 ')
|
||||
expect(mask.masked('12 3')).toBe('12 3')
|
||||
expect(mask.masked('12 34')).toBe('12 34')
|
||||
expect(mask.masked('12 34 ')).toBe('12 34')
|
||||
expect(mask.masked('12 34 5')).toBe('12 345')
|
||||
expect(mask.masked('a12 34 5')).toBe('12 345')
|
||||
expect(mask.masked('12.34.5')).toBe('12 345')
|
||||
|
||||
expect(mask.unmasked('a1 2 3')).toBe('123')
|
||||
expect(mask.unmasked('12 3')).toBe('123')
|
||||
expect(mask.unmasked('12 34 5')).toBe('12345')
|
||||
})
|
||||
|
||||
test('multiple letters mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: '+ +',
|
||||
tokens: { '+': { pattern: /[a-zA-Z]/, multiple: true } }
|
||||
})
|
||||
|
||||
expect(mask.masked('a')).toBe('a')
|
||||
expect(mask.masked('ab')).toBe('ab')
|
||||
expect(mask.masked('a b')).toBe('a b')
|
||||
expect(mask.masked('a b с')).toBe('a b')
|
||||
expect(mask.masked('ab ')).toBe('ab ')
|
||||
expect(mask.masked('ab c')).toBe('ab c')
|
||||
expect(mask.masked('ab cd')).toBe('ab cd')
|
||||
expect(mask.masked('ab cd ')).toBe('ab cd')
|
||||
expect(mask.masked('ab cd e')).toBe('ab cde')
|
||||
expect(mask.masked('1ab cd e')).toBe('ab cde')
|
||||
expect(mask.masked('ab.cd.e')).toBe('ab cde')
|
||||
|
||||
expect(mask.unmasked('1a b c')).toBe('abc')
|
||||
expect(mask.unmasked('ab c')).toBe('abc')
|
||||
expect(mask.unmasked('ab cd e')).toBe('abcde')
|
||||
})
|
||||
|
||||
test('dynamic empty mask', () => {
|
||||
const mask = new Mask({ mask: [] })
|
||||
|
||||
expect(mask.masked('1')).toBe('')
|
||||
})
|
||||
|
||||
test('dynamic single mask', () => {
|
||||
const mask = new Mask({ mask: ['#-#'] })
|
||||
|
||||
expect(mask.masked('123')).toBe('1-2')
|
||||
expect(mask.unmasked('123')).toBe('12')
|
||||
})
|
||||
|
||||
test('dynamic mask', () => {
|
||||
const mask = new Mask({ mask: ['###.###.###-##', '##.###.###/####-##'] })
|
||||
|
||||
expect(mask.masked('12345678901')).toBe('123.456.789-01')
|
||||
expect(mask.masked('123456789012')).toBe('12.345.678/9012')
|
||||
expect(mask.masked('12345678901234')).toBe('12.345.678/9012-34')
|
||||
expect(mask.masked('a123456789012345')).toBe('12.345.678/9012-34')
|
||||
|
||||
expect(mask.unmasked('a123456789012345')).toBe('12345678901234')
|
||||
|
||||
expect(mask.completed('12345678901')).toBe(false)
|
||||
expect(mask.completed('12345678901234')).toBe(true)
|
||||
})
|
||||
|
||||
test('dynamic function mask', () => {
|
||||
const mask = new Mask({
|
||||
mask: (value) => (value.startsWith('1') ? '#-#--#' : '## ##')
|
||||
})
|
||||
|
||||
expect(mask.masked('12')).toBe('1-2')
|
||||
expect(mask.masked('1234')).toBe('1-2--3')
|
||||
expect(mask.masked('23')).toBe('23')
|
||||
expect(mask.masked('23456')).toBe('23 45')
|
||||
expect(mask.masked('a23456')).toBe('23 45')
|
||||
|
||||
expect(mask.unmasked('a123')).toBe('123')
|
||||
expect(mask.unmasked('a2345')).toBe('2345')
|
||||
|
||||
expect(mask.completed('12')).toBe(false)
|
||||
expect(mask.completed('123')).toBe(true)
|
||||
expect(mask.completed('234')).toBe(false)
|
||||
expect(mask.completed('2345')).toBe(true)
|
||||
})
|
||||
|
||||
test('dynamic escaped mask', () => {
|
||||
const mask = new Mask({ mask: ['!###', '!###-##', '!###.##.##'] })
|
||||
|
||||
expect(mask.masked('12')).toBe('#12')
|
||||
expect(mask.masked('1234')).toBe('#12-34')
|
||||
expect(mask.masked('12345')).toBe('#12.34.5')
|
||||
expect(mask.masked('1234567')).toBe('#12.34.56')
|
||||
})
|
||||
|
||||
test('tokens replaced', () => {
|
||||
const mask = new Mask({
|
||||
mask: 'Z-#',
|
||||
tokens: { Z: { pattern: /[0-9]/ } },
|
||||
tokensReplace: true
|
||||
})
|
||||
|
||||
expect(mask.masked('1')).toBe('1')
|
||||
expect(mask.masked('12')).toBe('1-#')
|
||||
expect(mask.masked('123')).toBe('1-#')
|
||||
expect(mask.masked('1-#')).toBe('1-#')
|
||||
|
||||
expect(mask.unmasked('12')).toBe('1')
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
import { expect, test } from 'vitest'
|
||||
|
||||
import { parseMask, parseOpts, parseTokens } from '../src/parser'
|
||||
|
||||
test('mask: empty', () => {
|
||||
expect(parseMask('')).toBe('')
|
||||
})
|
||||
|
||||
test('mask: string', () => {
|
||||
expect(parseMask('#-#')).toBe('#-#')
|
||||
})
|
||||
|
||||
test('mask: array', () => {
|
||||
expect(parseMask('["#", "##"]')).toEqual(expect.arrayContaining(['#', '##']))
|
||||
})
|
||||
|
||||
test('opts: empty', () => {
|
||||
expect(parseOpts('')).toBe(true)
|
||||
})
|
||||
|
||||
test('opts: true', () => {
|
||||
expect(parseOpts('true')).toBe(true)
|
||||
})
|
||||
|
||||
test('opts: truthy', () => {
|
||||
expect(parseOpts('1')).toBe(true)
|
||||
})
|
||||
|
||||
test('opts: false', () => {
|
||||
expect(parseOpts('false')).toBe(false)
|
||||
})
|
||||
|
||||
test('opts: falsy', () => {
|
||||
expect(parseOpts('0')).toBe(false)
|
||||
})
|
||||
|
||||
test('tokens: json', () => {
|
||||
expect(parseTokens('{ "Z": { "pattern": "[0-9]" } }')).toEqual(
|
||||
expect.objectContaining({ Z: { pattern: '[0-9]' } })
|
||||
)
|
||||
})
|
||||
|
||||
test('tokens: code', () => {
|
||||
expect(parseTokens('Z:[0-9]')).toEqual(
|
||||
expect.objectContaining({
|
||||
Z: { pattern: /[0-9]/, multiple: false, optional: false, repeated: false }
|
||||
})
|
||||
)
|
||||
|
||||
expect(parseTokens('Z:[0-9]:multiple')).toEqual(
|
||||
expect.objectContaining({
|
||||
Z: { pattern: /[0-9]/, multiple: true, optional: false, repeated: false }
|
||||
})
|
||||
)
|
||||
|
||||
expect(parseTokens('Z:[0-9]:optional')).toEqual(
|
||||
expect.objectContaining({
|
||||
Z: { pattern: /[0-9]/, multiple: false, optional: true, repeated: false }
|
||||
})
|
||||
)
|
||||
|
||||
expect(parseTokens('Z:[0-9]:repeated')).toEqual(
|
||||
expect.objectContaining({
|
||||
Z: { pattern: /[0-9]/, multiple: false, optional: false, repeated: true }
|
||||
})
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
import matchers from '@testing-library/jest-dom/matchers'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
// src: https://markus.oberlehner.net/blog/using-testing-library-jest-dom-with-vitest/
|
||||
expect.extend(matchers)
|
||||
+16
-7
@@ -1,9 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "types"
|
||||
},
|
||||
"include": ["src/index.js"],
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({
|
||||
base: '',
|
||||
build: {
|
||||
outDir: 'docs/dist',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
demo: 'src/demo/demo.ts'
|
||||
},
|
||||
output: {
|
||||
entryFileNames: '[name].js',
|
||||
assetFileNames: '[name][extname]'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [vue()]
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
/// <reference types="vitest" />
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import banner from 'vite-plugin-banner'
|
||||
import dts from 'vite-plugin-dts'
|
||||
|
||||
import pkg from './package.json'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'Maska',
|
||||
fileName: (format) =>
|
||||
format !== 'es' ? `${pkg.name}.${format}.js` : `${pkg.name}.js`
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
dts({
|
||||
outputDir: 'dist/types',
|
||||
exclude: 'src/demo'
|
||||
}),
|
||||
banner(
|
||||
`/*! ${pkg.name} v${pkg.version} | (c) ${pkg.author} | Released under the ${pkg.license} license */`
|
||||
)
|
||||
],
|
||||
test: {
|
||||
setupFiles: 'test/setup.ts',
|
||||
environment: 'happy-dom',
|
||||
coverage: {
|
||||
provider: 'c8',
|
||||
reporter: ['text', 'json-summary']
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user