2
0
mirror of https://github.com/tenrok/maska.git synced 2026-06-11 18:02:27 +03:00

feat!: separate packages

This commit is contained in:
Alexander Shabunevich
2024-04-15 22:07:47 +03:00
parent 685f477fe1
commit 4de44ef888
58 changed files with 1173 additions and 225 deletions
-572
View File
@@ -1,572 +0,0 @@
<object data="maska.svg" type="image/svg+xml" style="max-width: 90%"></object>
---
# Support
Do you like **Maska**? Please support me via [Boosty](https://boosty.to/beholdr).
# 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
```
## **CDN / Global build**
You can use **Maska** directly from CDN with simple script tag.
Library API will be exposed on the global `Maska` object:
``` html
<script src="https://cdn.jsdelivr.net/npm/maska@2/dist/maska.umd.js"></script>
<script>
const { Mask, MaskInput, vMaska } = Maska
new MaskInput("[data-maska]") // for masked input
const mask = new Mask({ mask: "#-#" }) // for programmatic use
Vue.createApp({ directives: { maska: vMaska }}).mount('#app') // Vue directive
</script>
```
## **CDN / ES modules**
For modern browsers you can use ES modules build with (optional) [import maps](https://caniuse.com/import-maps):
``` html
<script type="importmap">
{
"imports": {
"maska": "https://cdn.jsdelivr.net/npm/maska@2/dist/maska.js"
}
}
</script>
<script type="module">
import { Mask, MaskInput, vMaska } from 'maska'
new MaskInput("[data-maska]") // for masked input
const mask = new Mask({ mask: "#-#" }) // for programmatic use
Vue.createApp({ directives: { maska: vMaska }}).mount('#app') // Vue directive
</script>
```
Notice that we are using `<script type="module">` in this case.
<!-- 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:
``` ts
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):
``` ts
new MaskInput("input", { mask: "#-#" })
```
To destroy mask use `destroy()` method:
``` ts
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 -->
### Bind value
To get masked value you can use standard `v-model` directive.
But if you want to access an unmasked (raw) value, you can pass a variable as `v-maska` directive value.
This variable should be a reactive object that will contains three fields after mask processing:
- `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, ref } from "vue"
import { vMaska } from "maska"
const maskedValue = ref('')
const boundObject = reactive({})
</script>
<template>
<input v-maska="boundObject" v-model="maskedValue">
Masked value: {{ maskedValue }} or {{ boundObject.masked }}
Unmasked value: {{ boundObject.unmasked }}
<span v-if="boundObject.completed">✅ Mask completed</span>
</template>
```
### **Options API**
``` html
<script>
import { vMaska } from "maska"
export default {
directives: { maska: vMaska },
data: () => ({
maskedValue: "",
boundObject: {
masked: "",
unmasked: "",
completed: false
}
})
}
</script>
<template>
<input v-maska="boundObject" v-model="maskedValue">
Masked value: {{ maskedValue }} or {{ boundObject.masked }}
Unmasked value: {{ boundObject.unmasked }}
<span v-if="boundObject.completed">✅ Mask completed</span>
</template>
```
<!-- tabs:end -->
### Set mask options
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 -->
?> Please see [issue#149](https://github.com/beholdr/maska/issues/149): options object should be assigned in the current file.
You can set options and bind to an object at the same time:
``` html
<input v-maska:[options]="boundObject">
```
#### Global registration of directive
<!-- tabs:start -->
### **Vue 3**
``` ts
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**
``` ts
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 -->
## **Nuxt 3**
To use Maska in Nuxt 3 you can make a simple plugin. Create file `maska.ts` in `plugins` folder:
``` ts
import { vMaska } from "maska"
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.directive("maska", vMaska)
})
```
Now you can use `v-maska` directive in your app:
``` html
<input v-model="value" v-maska data-maska="#-#" />
```
<!-- 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**). If you pass empty string or `null` it will disable a mask
- `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 will show static characters before you type them, e.g. when you type `1`, eager mask `#-#` will show `1-` and non-eager will show `1`
- `reversed / data-maska-reversed` — in reversed mode mask will grow backwards, e.g. for numbers
### **Types**
``` ts
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**
``` ts
interface MaskInputOptions extends MaskOptions {
onMaska?: (detail: MaskaDetail) => void
preProcess?: (value: string) => string
postProcess?: (value: string) => string
}
```
<!-- tabs:end -->
``` ts
new MaskInput("input", {
mask: "#-#",
reversed: true,
onMaska: (detail: MaskaDetail) => console.log(detail.completed)
})
```
# Tokens
There are 3 tokens defined by default:
``` ts
{
'#': { 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 cant 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:
``` ts
new MaskInput("[data-maska]", {
mask: "A-A",
tokens: {
A: { pattern: /[A-Z]/, transform: (chr: string) => chr.toUpperCase() }
}
})
```
<!-- tabs:end -->
## Token modifiers
Every token can have a modifier, for example:
``` ts
{
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:
``` ts
{
A: { pattern: /[A-Z]/, transform: (chr: string) => 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
``` ts
new MaskInput("input", {
mask: (value: string) => 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:
``` ts
new MaskInput("input", {
postProcess: (value: string) => value.slice(0, 5)
})
```
# Events
There are two events you can subscribe to get the masking result:
- `maska` event
- `input` event
They are essentially the same, but the `input` event could be fired by any input logic, and the `maska` event is library specific.
<!-- tabs:start -->
## **Vanilla JS**
``` ts
document.querySelector("input").addEventListener("maska", onMaska)
```
## **Vue**
``` html
<input v-maska data-maska="#-#" @maska="onMaska" />
```
<!-- tabs:end -->
Both events contains `detail` property with given structure:
<!-- tabs:start -->
### **Description**
- `masked`: masked value
- `unmasked`: unmasked value
- `completed`: flag that current mask is completed
### **Types**
``` ts
interface MaskaDetail {
masked: string
unmasked: string
completed: boolean
}
```
<!-- tabs:end -->
``` ts
const onMaska = (event: CustomEvent<MaskaDetail>) => {
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**
``` ts
new MaskInput("input", {
onMaska: (detail: MaskaDetail) => console.log(detail.completed)
})
```
### **Vue**
``` html
<script setup>
const options = {
onMaska: (detail: MaskaDetail) => 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
``` ts
import { Mask } from "maska"
const mask = new Mask({ mask: "#-#" })
mask.masked("12") // = 1-2
mask.unmasked("12") // = 12
mask.completed("12") // = true
```
# Known issues
When used on input of type `number`, could have inconsistent behavior in different browsers. Use attribute `inputmode="numeric"` with `type="text"` if you need a numeric keyboard.
-67
View File
@@ -1,67 +0,0 @@
<!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: 281;
--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 {
display: none;
}
.sidebar-nav li.active {
background: #990bdb;
color: #fff;
margin-left: -10px;
padding-left: 10px;
border-radius: 2px;
}
.sidebar-nav li.active a {
color: #fff;
}
@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>
-13
View File
@@ -1,13 +0,0 @@
<svg width="304" height="56" viewBox="0 0 304 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>@media(prefers-color-scheme:dark){.f{fill:#bbb}.s{stroke:#bbb}}</style>
<rect class="s" x="2" y="2" width="51.0638" height="51.0638" rx="6" stroke="#666" stroke-width="4"/>
<rect class="s" x="64.2341" y="2" width="51.0638" height="51.0638" rx="6" stroke="#666" stroke-width="4"/>
<rect class="s" x="126.468" y="2" width="51.0638" height="51.0638" rx="6" stroke="#666" stroke-width="4"/>
<rect class="s" x="188.702" y="2" width="51.0638" height="51.0638" rx="6" stroke="#666" stroke-width="4"/>
<rect x="250.936" y="2" width="51.0638" height="51.0638" rx="6" fill="#9E24D7" stroke="#9E24D7" stroke-width="4"/>
<path class="f" d="M35.5545 13.992C37.1104 13.992 38.3723 14.5452 39.3404 15.6516C40.3431 16.7234 40.8444 18.6941 40.8444 21.5638V41.8936H36.851V22.2899C36.851 20.3883 36.73 19.109 36.488 18.4521C36.246 17.7606 35.6582 17.4149 34.7247 17.4149C32.9614 17.4149 31.371 18.504 29.9534 20.6822V41.8936H25.9082V22.2899C25.9082 20.3883 25.7872 19.109 25.5452 18.4521C25.3032 17.7606 24.7154 17.4149 23.7819 17.4149C22.0186 17.4149 20.4282 18.504 19.0106 20.6822V41.8936H15.0173V14.5625H18.3883L18.6995 17.7779C19.5292 16.5678 20.3936 15.6343 21.2925 14.9774C22.226 14.3205 23.3324 13.992 24.6117 13.992C27.1702 13.992 28.8125 15.2021 29.5386 17.6223C30.3683 16.4468 31.25 15.5479 32.1835 14.9255C33.117 14.3032 34.2407 13.992 35.5545 13.992Z" fill="#666"/>
<path class="f" d="M99.376 35.9295C99.376 37.0013 99.5489 37.7965 99.8946 38.3152C100.24 38.7992 100.811 39.1622 101.606 39.4042L100.517 42.5159C97.8547 42.1702 96.1951 40.8737 95.5382 38.6263C94.5701 39.871 93.3428 40.8218 91.8561 41.4787C90.4039 42.1356 88.7789 42.4641 86.9811 42.4641C84.2497 42.4641 82.0888 41.7034 80.4983 40.1822C78.9425 38.6609 78.1646 36.621 78.1646 34.0625C78.1646 31.262 79.2537 29.1011 81.4319 27.5798C83.6446 26.0585 86.8082 25.2979 90.9225 25.2979H94.9678V23.0678C94.9678 21.1316 94.4146 19.7487 93.3082 18.9189C92.2018 18.0545 90.5941 17.6223 88.485 17.6223C86.4106 17.6223 84.0422 18.0891 81.38 19.0226L80.1872 15.7035C83.4026 14.5625 86.3933 13.992 89.1592 13.992C92.4784 13.992 95.0023 14.7699 96.7311 16.3258C98.4944 17.8816 99.376 20.0425 99.376 22.8085V35.9295ZM88.0701 39.1968C89.4186 39.1968 90.6978 38.8511 91.9079 38.1596C93.1526 37.4681 94.1725 36.5173 94.9678 35.3072V28.3058H91.0263C88.122 28.3058 86.0303 28.7899 84.751 29.758C83.4717 30.7261 82.8321 32.1436 82.8321 34.0106C82.8321 37.4681 84.5781 39.1968 88.0701 39.1968Z" fill="#666"/>
<path class="f" d="M151.321 38.8856C153.396 38.8856 155.038 38.488 156.248 37.6928C157.458 36.8976 158.063 35.8431 158.063 34.5292C158.063 33.6303 157.908 32.9042 157.596 32.3511C157.285 31.7633 156.663 31.2447 155.729 30.7952C154.796 30.3112 153.378 29.8271 151.477 29.3431C148.296 28.5479 145.945 27.5971 144.424 26.4907C142.902 25.3497 142.142 23.6729 142.142 21.4601C142.142 19.1782 143.092 17.363 144.994 16.0146C146.896 14.6662 149.402 13.992 152.514 13.992C156.317 13.992 159.636 14.9947 162.471 17L160.501 20.008C159.36 19.2128 158.167 18.6077 156.922 18.1928C155.678 17.7433 154.243 17.5186 152.618 17.5186C148.676 17.5186 146.705 18.7114 146.705 21.0971C146.705 21.9269 146.913 22.6011 147.328 23.1197C147.777 23.6383 148.486 24.105 149.454 24.5199C150.457 24.9003 151.944 25.367 153.914 25.9202C156.888 26.7154 159.1 27.7354 160.553 28.98C162.039 30.1902 162.783 31.9707 162.783 34.3218C162.783 37.0532 161.676 39.0931 159.463 40.4415C157.251 41.7899 154.537 42.4641 151.321 42.4641C147.034 42.4641 143.473 41.2367 140.638 38.7819L143.075 35.9295C144.182 36.8284 145.444 37.5545 146.861 38.1077C148.279 38.6263 149.765 38.8856 151.321 38.8856Z" fill="#666"/>
<path class="f" d="M208.867 41.8936H204.511V3.56781L208.867 3.04919V41.8936ZM214.831 26.9056L228.523 41.8936H222.766L209.334 26.9574L221.47 14.5625H227.071L214.831 26.9056Z" fill="#666"/>
<path d="M264.242 40.3662L272.982 29.3375L272.786 28.9471L260.511 25.6287L262.18 20.456L274.16 25.4335L274.455 25.1407L273.768 11.5745H279.169L278.383 25.1407L278.776 25.4335L290.756 20.456L292.426 25.6287L280.052 28.9471L279.954 29.3375L288.694 40.3662L284.275 43.4894L276.812 31.8751H276.124L268.661 43.4894L264.242 40.3662Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB