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

New version code prepare

This commit is contained in:
Alexander Shabunevich
2022-12-04 16:50:18 +03:00
parent fadf78f5bb
commit 31c286bb6b
48 changed files with 15195 additions and 5502 deletions
+153
View File
@@ -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>
+8
View File
@@ -0,0 +1,8 @@
import { createApp } from 'vue'
import { vMaska } from '..'
import Demo from './Demo.vue'
createApp(Demo)
.directive('maska', vMaska)
.mount('#demo-app')
+7
View File
@@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
-40
View File
@@ -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()
+25
View File
@@ -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))
}
-25
View File
@@ -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
}
+13
View File
@@ -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
}
+149
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
}
+29
View File
@@ -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
}
-10
View File
@@ -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 }
}
+15
View File
@@ -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]/ }
}
-40
View File
@@ -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
}