2
0
mirror of https://github.com/tenrok/maska.git synced 2026-06-23 20:40:35 +03:00

feat: simple mode for mask directive

Allow string passing to set a mask without data-maska attribute
This commit is contained in:
Alexander Shabunevich
2024-06-12 17:52:55 +03:00
parent cb87ea76c0
commit 488a52f760
16 changed files with 150 additions and 102 deletions
+21 -17
View File
@@ -24,38 +24,42 @@
<main> <main>
<article> <article>
<div x-data="{ <div x-data="{ enabled: true }">
maskedvalue: '123', <div><label><input type="checkbox" x-model="enabled"> enabled?</label></div>
unmaskedvalue: '', <template x-if="enabled">
options: { eager: true } <div x-data="{
}"> maskedvalue: '123',
<input unmaskedvalue: '',
x-model="maskedvalue" options: { mask: '#-#', eager: true }
x-maska:unmaskedvalue.unmasked="options" }">
x-on:maska="console.log($event.detail)" <input
data-maska="##-##" x-model="maskedvalue"
> x-maska:unmaskedvalue.unmasked="options"
<div><label><input type="checkbox" x-model="options.eager"> eager?</label></div> >
<div>masked value: <span x-text="maskedvalue"></span></div> <div><label><input type="checkbox" x-model="options.eager"> eager?</label></div>
<div>unmasked value: <span x-text="unmaskedvalue"></span></div> <div>masked value: <span x-text="maskedvalue"></span></div>
<div>unmasked value: <span x-text="unmaskedvalue"></span></div>
</div>
</template>
</div> </div>
<div x-data="{ <div x-data="{
maskedvalue: '-1234.90', maskedvalue: '-1234.90',
unmaskedvalue: '', unmaskedvalue: '',
options: { number: { locale: 'ru', fraction: 2 }}
}" style="margin-top: 1em"> }" style="margin-top: 1em">
<input <input
x-model="maskedvalue" x-model="maskedvalue"
x-maska:unmaskedvalue.unmasked="options" x-maska:unmaskedvalue.unmasked
x-on:maska="console.log($event.detail)" x-on:maska="console.log($event.detail)"
data-maska-number-locale="ru"
data-maska-number-fraction="2"
> >
<div>masked value: <span x-text="maskedvalue"></span></div> <div>masked value: <span x-text="maskedvalue"></span></div>
<div>unmasked value: <span x-text="unmaskedvalue"></span></div> <div>unmasked value: <span x-text="unmaskedvalue"></span></div>
</div> </div>
<div x-data style="margin-top: 1em"> <div x-data style="margin-top: 1em">
<input x-maska data-maska="+1 (###) ###-####" data-maska-eager value="1234567"> <input x-maska="'+1 (###) ###-####'" data-maska-eager value="1234567">
</div> </div>
<script type="module"> <script type="module">
+15 -6
View File
@@ -3,7 +3,7 @@
Maska provides custom Alpine.js directive for use with input: Maska provides custom Alpine.js directive for use with input:
```html ```html
<input x-maska:argument.modifier="options"> <input x-maska:argument.modifier="value">
``` ```
- `argument` is a name of the bound variable (see example below) - `argument` is a name of the bound variable (see example below)
@@ -11,21 +11,24 @@ Maska provides custom Alpine.js directive for use with input:
- `masked` (default): variable will get a masked value (as in v-model) - `masked` (default): variable will get a masked value (as in v-model)
- `unmasked`: variable will get an unmasked (raw) value - `unmasked`: variable will get an unmasked (raw) value
- `completed`: variable will be boolean, showing that mask is completed - `completed`: variable will be boolean, showing that mask is completed
- `options` is object with default options - `value` could be one of:
- `string` for the mask value (should be enclosed in additional quotation marks: `"'#-#'"`)
- `object` with a default options
## Minimal example ## Minimal example
Apply `xMaska` directive to the input along with `data-maska` attribite: Apply `xMaska` directive to the input:
```html ```html
<input x-maska data-maska="#-#"> <input x-maska="'#-#'">
``` ```
?> Please note that the mask value is enclosed in additional quotation marks: `"'#-#'"`.
## Set mask options ## Set mask options
To set default [options](/options) for the mask, pass options via **directive value**: To set a default [options](/options) for the mask, pass options via **directive value**:
```html ```html
<div x-data="{ options: { mask: '#-#', eager: true }}"> <div x-data="{ options: { mask: '#-#', eager: true }}">
@@ -33,6 +36,12 @@ To set default [options](/options) for the mask, pass options via **directive va
</div> </div>
``` ```
Or you can pass an options object directly:
```html
<input x-maska="{ mask: '#-#', eager: true }" data-maska-reversed>
```
You can override default options with `data-maska-` attributes on the input. In the example above we set **eager** mode using options and **reversed** mode using `data-maska-reversed` attribute. You can override default options with `data-maska-` attributes on the input. In the example above we set **eager** mode using options and **reversed** mode using `data-maska-reversed` attribute.
@@ -42,7 +51,7 @@ To get masked value you can use standard `x-model` directive on the input. But i
```html ```html
<div x-data="{ maskedvalue: '', unmaskedvalue: '' }"> <div x-data="{ maskedvalue: '', unmaskedvalue: '' }">
<input x-maska:unmaskedvalue.unmasked data-maska="#-#" x-model="maskedvalue"> <input x-maska:unmaskedvalue.unmasked="'#-#'" x-model="maskedvalue">
Masked value: <span x-text="maskedvalue"></span> Masked value: <span x-text="maskedvalue"></span>
Unmasked value: <span x-text="unmaskedvalue"></span> Unmasked value: <span x-text="unmaskedvalue"></span>
+15 -11
View File
@@ -12,7 +12,7 @@ npm install maska
And then import it in your code: And then import it in your code:
```js ```js
import { Mask, MaskInput } from 'maska' import { Mask, MaskInput } from "maska"
new MaskInput("[data-maska]") // for masked input new MaskInput("[data-maska]") // for masked input
const mask = new Mask({ mask: "#-#" }) // for programmatic use const mask = new Mask({ mask: "#-#" }) // for programmatic use
@@ -23,22 +23,26 @@ const mask = new Mask({ mask: "#-#" }) // for programmatic use
And then register it as custom plugin: And then register it as custom plugin:
```js ```js
import Alpine from 'alpinejs' import Alpine from "alpinejs"
import { xMaska } from 'maska/alpine' import { xMaska } from "maska/alpine"
Alpine.plugin(xMaska) Alpine.plugin(xMaska)
``` ```
```html
<input x-maska="'#-#'">
```
### **Svelte** ### **Svelte**
And then use it in your `.svelte` component: And then use it in your `.svelte` component:
```svelte ```svelte
<script> <script>
import { maska } from 'maska/svelte' import { maska } from "maska/svelte"
</script> </script>
<input use:maska data-maska="#-#" /> <input use:maska={'#-#'}>
``` ```
### **Vue** ### **Vue**
@@ -47,11 +51,11 @@ And then use it in your `.vue` component:
```js ```js
<script setup> <script setup>
import { vMaska } from 'maska/vue' import { vMaska } from "maska/vue"
</script> </script>
<template> <template>
<input v-maska data-maska="#-#" /> <input v-maska="'#-#'">
</template> </template>
``` ```
<!-- tabs:end --> <!-- tabs:end -->
@@ -118,7 +122,7 @@ For modern browsers you can use ES modules build with (optional) [import maps](h
} }
</script> </script>
<script type="module"> <script type="module">
import { Mask, MaskInput } from 'maska' import { Mask, MaskInput } from "maska"
new MaskInput("[data-maska]") // for masked input new MaskInput("[data-maska]") // for masked input
const mask = new Mask({ mask: "#-#" }) // for programmatic use const mask = new Mask({ mask: "#-#" }) // for programmatic use
@@ -136,8 +140,8 @@ For modern browsers you can use ES modules build with (optional) [import maps](h
} }
</script> </script>
<script type="module"> <script type="module">
import Alpine from 'alpinejs' import Alpine from "alpinejs"
import { xMaska } from 'maska/alpine' import { xMaska } from "maska/alpine"
Alpine.plugin(xMaska) Alpine.plugin(xMaska)
</script> </script>
@@ -154,7 +158,7 @@ For modern browsers you can use ES modules build with (optional) [import maps](h
} }
</script> </script>
<script type="module"> <script type="module">
import { vMaska } from 'maska/vue' import { vMaska } from "maska/vue"
Vue.createApp({ directives: { maska: vMaska }}).mount('#app') Vue.createApp({ directives: { maska: vMaska }}).mount('#app')
</script> </script>
+12 -8
View File
@@ -3,34 +3,38 @@
Maska provides simple Svelte action for use with input: Maska provides simple Svelte action for use with input:
```html ```html
<input use:maska={options}> <input use:maska={value}>
``` ```
- `options` is object with default options - `value` could be one of:
- `string` for the mask value (should be enclosed in additional quotation marks: `{'#-#'}`)
- `object` with a default options
## Minimal example ## Minimal example
Apply `maska` action to the input along with `data-maska` attribite: Apply `maska` action to the input:
```svelte ```svelte
<script> <script>
import { maska } from '@maskajs/svelte' import { maska } from "maska/svelte"
</script> </script>
<input use:maska data-maska="#-#"> <input use:maska={'#-#'}>
``` ```
?> Please note that the mask value is enclosed in additional quotation marks: `"'#-#'"`.
## Set mask options ## Set mask options
To set default [options](/options) for the mask, pass options via **directive value**: To set default [options](/options) for the mask, pass options via **directive value**:
```svelte ```svelte
<script> <script lang="ts">
import { maska } from '@maskajs/svelte' import { maska } from "maska/svelte"
import type { MaskInputOptions } from "maska"
const options = { const options: MaskInputOptions = {
mask: "#-#", mask: "#-#",
eager: true eager: true
} }
+2 -2
View File
@@ -6,13 +6,13 @@ Maska v3 has different entries for framework-specific exports.
Import of vue directive in v2: Import of vue directive in v2:
```js ```js
import { vMaska } from 'maska' import { vMaska } from "maska"
``` ```
Now you should import vue directive from `maska/vue`: Now you should import vue directive from `maska/vue`:
```js ```js
import { vMaska } from 'maska/vue' import { vMaska } from "maska/vue"
``` ```
!> Filenames have also been changed. Please see the [Installation](install) for more information. !> Filenames have also been changed. Please see the [Installation](install) for more information.
+14 -10
View File
@@ -3,7 +3,7 @@
Maska provides custom Vue directive for use with input: Maska provides custom Vue directive for use with input:
```html ```html
<input v-maska:argument.modifier="options"> <input v-maska:argument.modifier="value">
``` ```
- `argument` is a name of the bound variable (see example below) - `argument` is a name of the bound variable (see example below)
@@ -11,11 +11,13 @@ Maska provides custom Vue directive for use with input:
- `masked` (default): variable will get a masked value (as in v-model) - `masked` (default): variable will get a masked value (as in v-model)
- `unmasked`: variable will get an unmasked (raw) value - `unmasked`: variable will get an unmasked (raw) value
- `completed`: variable will be boolean, showing that mask is completed - `completed`: variable will be boolean, showing that mask is completed
- `options` is object with default options - `value` could be one of:
- `string` for the mask value (should be enclosed in additional quotation marks: `"'#-#'"`)
- `object` with a default options
## Minimal example ## Minimal example
Import `vMaska` directive and apply it to the input along with `data-maska` attribite: Import `vMaska` directive and apply it to the input:
<!-- tabs:start --> <!-- tabs:start -->
### **Composition API** ### **Composition API**
@@ -26,7 +28,7 @@ Import `vMaska` directive and apply it to the input along with `data-maska` attr
</script> </script>
<template> <template>
<input v-maska data-maska="#-#"> <input v-maska="'#-#'">
</template> </template>
``` ```
@@ -42,11 +44,12 @@ Import `vMaska` directive and apply it to the input along with `data-maska` attr
</script> </script>
<template> <template>
<input v-maska data-maska="#-#"> <input v-maska="'#-#'">
</template> </template>
``` ```
<!-- tabs:end --> <!-- tabs:end -->
?> Please note that the mask value is enclosed in additional quotation marks: `"'#-#'"`.
## Set mask options ## Set mask options
@@ -56,12 +59,13 @@ To set default [options](/options) for the mask, pass options via **directive va
### **Composition API** ### **Composition API**
```vue ```vue
<script setup> <script setup lang="ts">
import { reactive } from "vue" import { reactive } from "vue"
import { vMaska } from "maska/vue" import { vMaska } from "maska/vue"
import type { MaskInputOptions } from "maska"
// could be plain object too // could be plain object too
const options = reactive({ const options = reactive<MaskInputOptions>({
mask: "#-#", mask: "#-#",
eager: true eager: true
}) })
@@ -119,7 +123,7 @@ To get masked value you can use standard `v-model` directive on the input. But i
</script> </script>
<template> <template>
<input v-maska:unmaskedValue.unmasked data-maska="#-#" v-model="maskedValue"> <input v-maska:unmaskedValue.unmasked="'#-#'" v-model="maskedValue">
Masked value: {{ maskedValue }} Masked value: {{ maskedValue }}
Unmasked value: {{ unmaskedValue }} Unmasked value: {{ unmaskedValue }}
@@ -144,7 +148,7 @@ To get masked value you can use standard `v-model` directive on the input. But i
</script> </script>
<template> <template>
<input v-maska:unmaskedValue.unmasked data-maska="#-#" v-model="maskedValue"> <input v-maska:unmaskedValue.unmasked="'#-#'" v-model="maskedValue">
Masked value: {{ maskedValue }} Masked value: {{ maskedValue }}
Unmasked value: {{ unmaskedValue }} Unmasked value: {{ unmaskedValue }}
@@ -168,7 +172,7 @@ export default defineNuxtPlugin((nuxtApp) => {
Now you can use v-maska directive in your app: Now you can use v-maska directive in your app:
```html ```html
<input v-model="value" v-maska data-maska="#-#" /> <input v-maska="'#-#'" />
``` ```
+9 -6
View File
@@ -9,11 +9,16 @@ export const xMaska = (Alpine: Alpine): void => {
if (input == null || input?.type === 'file') return if (input == null || input?.type === 'file') return
let opts: MaskInputOptions = {}
const evaluator = directive.expression !== ''
? utilities.evaluateLater<MaskInputOptions | string>(directive.expression)
: () => {}
utilities.effect(() => { utilities.effect(() => {
const opts = evaluator((expr) => {
directive.expression !== '' opts = typeof expr === 'string' ? { mask: expr } : { ...expr }
? { ...utilities.evaluate<MaskInputOptions>(directive.expression) } })
: {}
if (directive.value != null) { if (directive.value != null) {
const updateArg = (detail: MaskaDetail): void => { const updateArg = (detail: MaskaDetail): void => {
@@ -43,7 +48,5 @@ export const xMaska = (Alpine: Alpine): void => {
masks.set(input, new MaskInput(input, opts)) masks.set(input, new MaskInput(input, opts))
} }
}) })
utilities.cleanup(() => masks.get(input)?.destroy())
}).before('model') }).before('model')
} }
+12 -2
View File
@@ -3,19 +3,29 @@ import { MaskaDetail, MaskInput, MaskInputOptions } from '..'
const masks = new WeakMap<HTMLInputElement, MaskInput>() const masks = new WeakMap<HTMLInputElement, MaskInput>()
type MaskaAction = Action<HTMLElement, MaskInputOptions | undefined, { type MaskaAction = Action<HTMLElement, MaskInputOptions | string | undefined, {
'on:maska': (detail: CustomEvent<MaskaDetail>) => void 'on:maska': (detail: CustomEvent<MaskaDetail>) => void
}> }>
export const maska: MaskaAction = (node, opts = {}) => { export const maska: MaskaAction = (node, value = {}) => {
const input = node instanceof HTMLInputElement ? node : node.querySelector('input') const input = node instanceof HTMLInputElement ? node : node.querySelector('input')
if (input == null || input?.type === 'file') return if (input == null || input?.type === 'file') return
let opts = value
if (typeof opts === 'string') {
opts = { mask: opts }
}
masks.set(input, new MaskInput(input, opts)) masks.set(input, new MaskInput(input, opts))
return { return {
update (opts) { update (opts) {
if (typeof opts === 'string') {
opts = { mask: opts }
}
masks.get(input)?.update(opts) masks.get(input)?.update(opts)
}, },
+7 -2
View File
@@ -1,7 +1,7 @@
import { Directive, DirectiveBinding } from 'vue' import { Directive, DirectiveBinding } from 'vue'
import { MaskaDetail, MaskInput, MaskInputOptions } from '..' import { MaskaDetail, MaskInput, MaskInputOptions } from '..'
type MaskaDirective = Directive<HTMLElement, MaskInputOptions | undefined> type MaskaDirective = Directive<HTMLElement, MaskInputOptions | string | undefined>
const masks = new WeakMap<HTMLInputElement, MaskInput>() const masks = new WeakMap<HTMLInputElement, MaskInput>()
@@ -20,10 +20,15 @@ const setArg = (binding: DirectiveBinding, value: string | boolean): void => {
export const vMaska: MaskaDirective = (el, binding) => { export const vMaska: MaskaDirective = (el, binding) => {
const input = el instanceof HTMLInputElement ? el : el.querySelector('input') const input = el instanceof HTMLInputElement ? el : el.querySelector('input')
const opts = binding.value != null ? { ...binding.value } : {}
if (input == null || input?.type === 'file') return if (input == null || input?.type === 'file') return
let opts: MaskInputOptions = {}
if (binding.value != null) {
opts = typeof binding.value === 'string' ? { mask: binding.value } : { ...binding.value }
}
if (binding.arg != null) { if (binding.arg != null) {
const updateArg = (detail: MaskaDetail): void => { const updateArg = (detail: MaskaDetail): void => {
const value = binding.modifiers.unmasked const value = binding.modifiers.unmasked
+6
View File
@@ -20,6 +20,12 @@ const prepareInput = async (markup: string) => {
} }
describe('init', () => { describe('init', () => {
test('with string', async () => {
input = await prepareInput(`<input x-maska="'#-#'" value="123">`)
expect(input).toHaveValue('1-2')
})
test('with data attr', async () => { test('with data attr', async () => {
input = await prepareInput(`<input x-maska data-maska="#-#" value="123">`) input = await prepareInput(`<input x-maska data-maska="#-#" value="123">`)
+7
View File
@@ -0,0 +1,7 @@
<script lang="ts">
import { maska } from '../../src/svelte'
</script>
<main>
<input use:maska data-maska="#-#" data-maska-eager />
</main>
+2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import BindValue from './BindValue.svelte' import BindValue from './BindValue.svelte'
import DataAttr from './DataAttr.svelte'
import Event from './Event.svelte' import Event from './Event.svelte'
import InitialValue from './InitialValue.svelte' import InitialValue from './InitialValue.svelte'
import Number from './Number.svelte' import Number from './Number.svelte'
@@ -12,6 +13,7 @@
<main> <main>
<select bind:value={component}> <select bind:value={component}>
<option value={BindValue}>BindValue</option> <option value={BindValue}>BindValue</option>
<option value={DataAttr}>DataAttr</option>
<option value={Event}>Event</option> <option value={Event}>Event</option>
<option value={InitialValue}>InitialValue</option> <option value={InitialValue}>InitialValue</option>
<option value={Number}>Number</option> <option value={Number}>Number</option>
+1 -1
View File
@@ -8,6 +8,6 @@
</script> </script>
<main> <main>
<input type="checkbox" bind:checked={options.eager} /> <input type="checkbox" bind:checked={options.eager} /> Eager<br>
<input type="text" use:maska={options} /> <input type="text" use:maska={options} />
</main> </main>
+1 -1
View File
@@ -3,5 +3,5 @@
</script> </script>
<main> <main>
<input use:maska data-maska="#-#" /> <input use:maska={'#-#'} />
</main> </main>
+1 -1
View File
@@ -3,5 +3,5 @@ import { vMaska } from '../../src/vue'
</script> </script>
<template> <template>
<input v-maska data-maska="#-#" /> <input v-maska="'#-#'" />
</template> </template>
+25 -35
View File
@@ -1,62 +1,52 @@
<script setup> <script setup lang="ts">
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { vMaska } from '../../src/vue' import { vMaska } from '../../src/vue'
import type { MaskInputOptions, MaskaDetail } from '../../src'
const mask = ref('+1 (###) ###-####')
const show = ref(true) const show = ref(true)
const eager = ref(true)
const valueMasked = ref('1234567') const valueMasked = ref('1234567')
const valueUnmasked = ref('1') const valueUnmasked = ref('1')
const onMaska = (e) => console.log(e.detail) const onMaska = (e: CustomEvent<MaskaDetail>) => console.log(e.detail)
const options = reactive({ const options = reactive<MaskInputOptions>({
mask, mask: '+1 (###) ###-####',
eager eager: true
}) })
const options2 = {
preProcess: val => val.replace(/[$,]/g, ''),
postProcess: val => {
if (!val) return ''
const sub = 3 - (val.includes('.') ? val.length - val.indexOf('.') : 0)
return Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(val)
.slice(0, sub ? -sub : undefined)
}
}
defineExpose({ valueUnmasked }) defineExpose({ valueUnmasked })
</script> </script>
<template> <template>
<div class="row"> <p>
<div> <div>
show: <input type="checkbox" v-model="show"> show: <input type="checkbox" v-model="show">
eager: <input type="checkbox" v-model="eager"> eager: <input type="checkbox" v-model="options.eager">
</div> </div>
<div><input v-model="mask"></div>
<input v-model="options.mask">
<div v-if="show"> <div v-if="show">
<input v-maska:valueUnmasked.unmasked="options" v-model="valueMasked" @maska="onMaska"> <input v-maska:valueUnmasked.unmasked="options" v-model="valueMasked" @maska="onMaska">
<div>Masked: {{ valueMasked }}</div> <div>Masked: {{ valueMasked }}</div>
<div>Unmasked: {{ valueUnmasked }}</div> <div>Unmasked: {{ valueUnmasked }}</div>
</div> </div>
</div> </p>
<div class="row"><input type="email" v-maska data-maska="##-##" data-maska-eager value="12"></div> <p>
<input v-maska data-maska="##-##" data-maska-eager value="123">
</p>
<div class="row"><input v-maska data-maska="####" value="1234"></div> <p>
<input v-maska="'####'" value="12345">
</p>
<div><input v-maska="options2" value="1234567" data-maska="0.99" data-maska-tokens="0:\d:multiple|9:\d:optional"></div> <p>
<input
v-maska
value="1234567.89"
data-maska-number-locale="en"
data-maska-number-fraction="2"
>
</p>
</template> </template>
<style scoped>
.row {
margin-bottom: 1em;
}
</style>