WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 9b7d78e

Browse files
committed
feat(QInput): new prop -> mask-tokens #18114 #12759
1 parent 9192ace commit 9b7d78e

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<template>
2+
<div class="q-pa-md" style="max-width: 300px">
3+
<div class="q-gutter-md">
4+
<q-input
5+
filled
6+
v-model="id"
7+
label="Special ID"
8+
mask="AA-CC-XX-CC"
9+
:mask-tokens="customTokens"
10+
hint="Mask: AA-CC-XX-CC, example: BC-12-56-E2"
11+
/>
12+
13+
<div>C (0-4a-eA-E to uppercase), X override (5-8)</div>
14+
</div>
15+
</div>
16+
</template>
17+
18+
<script>
19+
import { ref } from 'vue'
20+
21+
export default {
22+
setup () {
23+
return {
24+
id: ref(null),
25+
customTokens: {
26+
C: { pattern: '[0-4a-eA-E]', negate: '[^0-4a-eA-E]', transform: v => v.toLocaleUpperCase() },
27+
X: { pattern: '[5-8]', negate: '[^5-8]' }
28+
}
29+
}
30+
}
31+
}
32+
</script>

docs/src/pages/vue-components/input.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ You can force/help the user to input a specific format with help from `mask` pro
163163
Mask is only available if the `type` is one of 'text' (default), 'search', 'url', 'tel', or 'password'.
164164
:::
165165

166-
Below are mask tokens:
166+
Below are the default mask tokens. To add your own, see the next section.
167167

168168
| Token | Description |
169169
| --- | --- |
@@ -189,6 +189,14 @@ The `reverse-fill-mask` is useful if you want to force the user to fill the mask
189189

190190
<DocExample title="Filling the mask in reverse" file="MaskFillReverse" />
191191

192+
### Custom mask tokens <q-badge label="v2.18.4+" />
193+
194+
You can also define custom mask tokens on top of the default ones or even override some/all of the [default ones](https://github.com/quasarframework/quasar/blob/dev/ui/src/components/input/use-mask.js#L15).
195+
196+
The custom mask tokens must have the same syntax as the [default ones](https://github.com/quasarframework/quasar/blob/dev/ui/src/components/input/use-mask.js#L15). Please note that the `transform` property is optional.
197+
198+
<DocExample title="Custom tokens" file="MaskCustomTokens" />
199+
192200
### Using third party mask processors
193201

194202
You can easily use any third party mask processor by doing a few small adjustments to your QInput.

ui/playground/src/pages/form/input-mask.vue

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<div>Model: {{ text1 }} | {{ maskedOrNotValue }}</div>
77
<q-toggle v-model="maskedOrNot" @update:model-value="toggleMask" label="Masked or not" />
8-
<q-input :mask="maskedOrNotValue" v-model="text1" filled hint="Date ####/##/##" label="Label" />
8+
<q-input :mask="maskedOrNotValue" v-model="text1" filled hint="Date ##/##/####" label="Label" />
99
<q-input
1010
filled
1111
v-model="id"
@@ -62,6 +62,17 @@
6262
hint="Mask: NNNN - NNNN"
6363
/>
6464

65+
<div>Custom tokens ((C: 0-4a-eA-E, X override: 5-8))</div>
66+
<q-input
67+
filled
68+
v-model="text10"
69+
label="Custom tokens"
70+
mask="AA-CC-XX-CC"
71+
:mask-tokens="customTokens"
72+
clearable
73+
hint="Mask: AA-CC-XX-CC"
74+
/>
75+
6576
<div class="text-h6">
6677
Live mask test: {{ textMask }}
6778
</div>
@@ -128,6 +139,7 @@ export default {
128139
text7: '',
129140
text8: '',
130141
text9: '',
142+
text10: 'KK-A4-76-1A',
131143
variableMaskValue1: '',
132144
variableMaskValue2: '',
133145
variableMaskValue3: '',
@@ -139,7 +151,12 @@ export default {
139151
fillRight: true,
140152
fillMask: true,
141153
fillUnmask: false,
142-
fillMaskText: '0'
154+
fillMaskText: '0',
155+
156+
customTokens: {
157+
C: { pattern: '[0-4a-eA-E]', negate: '[^0-4a-eA-E]', transform: v => v.toLocaleUpperCase() },
158+
X: { pattern: '[5-8]', negate: '[^5-8]' }
159+
}
143160
}
144161
},
145162

ui/src/components/input/use-mask.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ref, watch, nextTick } from 'vue'
1+
import { ref, computed, watch, nextTick } from 'vue'
22

33
import { shouldIgnoreKey } from '../../utils/private.keyboard/key-composition.js'
44

@@ -12,7 +12,7 @@ const NAMED_MASKS = {
1212
card: '#### #### #### ####'
1313
}
1414

15-
const TOKENS = {
15+
const { tokenMap: DEFAULT_TOKEN_MAP, tokenKeys: DEFAULT_TOKEN_MAP_KEYS } = getTokenMap({
1616
'#': { pattern: '[\\d]', negate: '[^\\d]' },
1717

1818
S: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]' },
@@ -23,29 +23,62 @@ const TOKENS = {
2323

2424
X: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleUpperCase() },
2525
x: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleLowerCase() }
26-
}
27-
28-
const KEYS = Object.keys(TOKENS)
29-
KEYS.forEach(key => {
30-
TOKENS[ key ].regex = new RegExp(TOKENS[ key ].pattern)
3126
})
3227

33-
const
34-
tokenRegexMask = new RegExp('\\\\([^.*+?^${}()|([\\]])|([.*+?^${}()|[\\]])|([' + KEYS.join('') + '])|(.)', 'g'),
35-
escRegex = /[.*+?^${}()|[\]\\]/g
28+
function getTokenMap (tokens) {
29+
const tokenKeys = Object.keys(tokens)
30+
const tokenMap = {}
31+
32+
tokenKeys.forEach(key => {
33+
const entry = tokens[ key ]
34+
tokenMap[ key ] = {
35+
...entry,
36+
regex: new RegExp(entry.pattern)
37+
}
38+
})
39+
40+
return { tokenMap, tokenKeys }
41+
}
42+
43+
function getTokenRegexMask (keys) {
44+
return new RegExp('\\\\([^.*+?^${}()|([\\]])|([.*+?^${}()|[\\]])|([' + keys.join('') + '])|(.)', 'g')
45+
}
3646

47+
const escRegex = /[.*+?^${}()|[\]\\]/g
48+
const DEFAULT_TOKEN_REGEX_MASK = getTokenRegexMask(DEFAULT_TOKEN_MAP_KEYS)
3749
const MARKER = String.fromCharCode(1)
3850

3951
export const useMaskProps = {
4052
mask: String,
4153
reverseFillMask: Boolean,
4254
fillMask: [ Boolean, String ],
43-
unmaskedValue: Boolean
55+
unmaskedValue: Boolean,
56+
maskTokens: Object
4457
}
4558

4659
export default function (props, emit, emitValue, inputRef) {
4760
let maskMarked, maskReplaced, computedMask, computedUnmask, pastedTextStart, selectionAnchor
4861

62+
const tokens = computed(() => {
63+
if (props.maskTokens === void 0 || props.maskTokens === null) {
64+
return {
65+
tokenMap: DEFAULT_TOKEN_MAP,
66+
tokenRegexMask: DEFAULT_TOKEN_REGEX_MASK
67+
}
68+
}
69+
70+
const { tokenMap: customTokens } = getTokenMap(props.maskTokens)
71+
const tokenMap = {
72+
...DEFAULT_TOKEN_MAP,
73+
...customTokens
74+
}
75+
76+
return {
77+
tokenMap,
78+
tokenRegexMask: getTokenRegexMask(Object.keys(tokenMap))
79+
}
80+
})
81+
4982
const hasMask = ref(null)
5083
const innerValue = ref(getInitialMaskedValue())
5184

@@ -137,9 +170,9 @@ export default function (props, emit, emitValue, inputRef) {
137170
unmaskChar = '',
138171
negateChar = ''
139172

140-
localComputedMask.replace(tokenRegexMask, (_, char1, esc, token, char2) => {
173+
localComputedMask.replace(tokens.value.tokenRegexMask, (_, char1, esc, token, char2) => {
141174
if (token !== void 0) {
142-
const c = TOKENS[ token ]
175+
const c = tokens.value.tokenMap[ token ]
143176
mask.push(c)
144177
negateChar = c.negate
145178
if (firstMatch === true) {

ui/src/components/input/use-mask.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,34 @@
66
"examples": [
77
"'###-##'", "'date'", "'datetime'", "'time'", "'fulltime'", "'phone'", "'card'"
88
],
9-
"category": "behavior"
9+
"category": "mask"
1010
},
1111

1212
"fill-mask": {
1313
"type": [ "Boolean", "String" ],
1414
"desc": "Fills string with specified characters (or underscore if value is not string) to fill mask's length",
1515
"examples": [ "true", "'0'", "'_'" ],
16-
"category": "behavior"
16+
"category": "mask"
1717
},
1818

1919
"reverse-fill-mask": {
2020
"type": "Boolean",
2121
"desc": "Fills string from the right side of the mask",
22-
"category": "behavior"
22+
"category": "mask"
2323
},
2424

2525
"unmasked-value": {
2626
"type": "Boolean",
2727
"desc": "Model will be unmasked (won't contain tokens/separation characters)",
28-
"category": "behavior"
28+
"category": "mask"
29+
},
30+
31+
"mask-tokens": {
32+
"type": "Object",
33+
"desc": "Object of custom mask tokens to be added on top of the default ones; Can also override any of the default ones; The 'transform' function is optional",
34+
"examples": [ "{ C: { pattern: '[0-4a-eA-E]', negate: '[^0-4a-eA-E]', transform: v => v.toLocaleUpperCase() } }" ],
35+
"category": "mask",
36+
"addedIn": "v2.18.4"
2937
}
3038
}
3139
}

0 commit comments

Comments
 (0)