Skip to content
This repository was archived by the owner on Sep 4, 2024. It is now read-only.

Commit 97e6fd9

Browse files
authored
Merge pull request #529 from probil/feature/#499--function-usage-on-filter
Feature/#499 function usage on filter
2 parents 3b2d6df + 2c313e3 commit 97e6fd9

File tree

11 files changed

+170
-84
lines changed

11 files changed

+170
-84
lines changed

README.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ npm install v-mask
5050
```javascript
5151
import Vue from 'vue'
5252

53-
// As a plugin
53+
// Prefered: as a plugin (directive + filter) + custom placeholders support
5454
import VueMask from 'v-mask'
5555
Vue.use(VueMask);
5656

57-
// Or as a directive
57+
// Or as a directive-only
5858
import { VueMaskDirective } from 'v-mask'
5959
Vue.directive('mask', VueMaskDirective);
6060

61-
// Or only as a filter
61+
// Or only as a filter-only
6262
import { VueMaskFilter } from 'v-mask'
6363
Vue.filter('VMask', VueMaskFilter)
6464
```
@@ -82,15 +82,20 @@ Vue.directive('mask', VueMask.VueMaskDirective);
8282
```html
8383
<input type="text" v-mask="'####-##'" v-model="myInputModel">
8484
<!-- OR -->
85-
<input type="text" v-mask="nameOfVariableWithMask" v-model="myInputModel">
85+
<input type="text" v-mask="variableWithMask" v-model="myInputModel">
8686
```
8787
**Notice:** `v-model` is required starting from `v1.1.0`, because [a lot](https://github.com/probil/v-mask/issues/16) [of](https://github.com/probil/v-mask/issues/30) [bugs](https://github.com/probil/v-mask/issues/29) with HTMLElement event listeners and sync with Vue internals.
8888

8989
There is no reason to support using this lib for using without `v-model` but open the door for using on [custom inputs](http://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events).
9090

9191
### Filter usage
92+
93+
The filter accepts a mask similarly to the directive, and might be useful when you need to render a raw value as masked without using an input (e.g. formatting currency).
94+
9295
```html
9396
<span>{{ '9999999999' | VMask('(###) ###-####') }}</span>
97+
<!-- or -->
98+
<span>{{ variableWithRawValue | VMask(variableWithMask) </span>
9499
```
95100

96101
## :gear: Configuration
@@ -138,7 +143,7 @@ Here is a list placeholders you can utilize by default:
138143

139144

140145
### Custom placeholders
141-
While default placeholders are easy to use and straightforward in reality we have to deal with more complex cases where validation can be a bit more complex and unpredictable. In such cases it makes sense to define custom placeholders specific to the project or the domain.
146+
While default placeholders are easy to use and straightforward, in reality we have to deal with more complex cases where validation can be tricky and unpredictable. In such cases it makes sense to define custom placeholders specific to the project or the domain.
142147

143148
To define them you should pass them as an object while installing plugin. Where:
144149
* `key` is the character in a mask
@@ -153,7 +158,7 @@ Any valid string character can be used as a placeholder (e.g. Cyrillic or Arabic
153158
import Vue from 'vue'
154159
import VueMask from 'v-mask'
155160

156-
Vue.use(VueMask, {
161+
Vue.use(VueMask, { // (!) custom placeholders support requires registration as a plugin to
157162
placeholders: {
158163
'#': null, // passing `null` removes default placeholder, so `#` is treated as character
159164
D: /\d/, // define new placeholder
@@ -165,6 +170,8 @@ Vue.use(VueMask, {
165170
```vue
166171
<template>
167172
<input type="text" v-mask="'###-DDD-###-DDD'" v-model="myInputModel">
173+
<!-- or with filter -->
174+
<span>{{ 123456 | VMask(mask) }}</span>
168175
</template>
169176
<script>
170177
export default {
@@ -179,7 +186,7 @@ Entering `123456` in that input field will produce value `###-123-###-456` in `m
179186
### Array of RegExp
180187
In some cases you might not want to define global placeholders either because you are dealing with unique input or you are facing conflicts for placeholders in several places.
181188

182-
In such cases you can supply array of per-char regular excursions or static characters to `v-mask`.
189+
In such cases you can supply array of per-char regular expressions or static characters to `v-mask`.
183190

184191
`app.js`:
185192
```js
@@ -192,6 +199,8 @@ Vue.use(VueMask)
192199
```vue
193200
<template>
194201
<input type="text" v-mask="mask" v-model="myInputModel">
202+
<!-- or with filter -->
203+
<span>{{ 5555551234 | VMask(mask) }}</span>
195204
</template>
196205
<script>
197206
export default {
@@ -204,7 +213,7 @@ Vue.use(VueMask)
204213
```
205214
In this example entering `5555551234` in the input field will produce value `(555) 555-1234` in `myInputModel` variable.
206215

207-
**Notice**: Keep in mind that library always verifies _one_ character per regular expression. Trying verify multiple charters in the same RegExp won't work.
216+
**Notice**: Keep in mind that library always verifies _one_ character per regular expression. Trying to verify multiple charters in the same RegExp won't work.
208217

209218
### Function
210219

@@ -225,7 +234,9 @@ Vue.use(VueMask)
225234
`<your_component>.vue`:
226235
```vue
227236
<template>
228-
<input type="text" v-mask="mask" v-model="myInputModel" placeholder="00:00-23:59">
237+
<input type="text" v-mask="timeRangeMask" v-model="myInputModel" placeholder="00:00-23:59">
238+
<!-- or with filter -->
239+
<span>{{ '02532137' | VMask(timeRangeMask) }}</span>
229240
</template>
230241
<script>
231242
/**
@@ -257,7 +268,7 @@ Vue.use(VueMask)
257268
258269
export default {
259270
data: () => ({
260-
mask: timeRangeMask,
271+
timeRangeMask,
261272
myInputModel: ''
262273
})
263274
}
@@ -269,7 +280,7 @@ In this example entering `02532137` in the input field will produce valid time r
269280

270281
Library supports [Text Mask Addons](https://www.npmjs.com/package/text-mask-addons), they are basically pre-generated functions (describe above) for advanced functionality like currency masking.
271282

272-
The usage is simple. Configure the addon as want and pass the result to the `v-mask` as you would to `text-mask-core`.
283+
The usage is simple. Configure the addon as you want and pass the result to the `v-mask` as you would to `text-mask-core`.
273284

274285
`app.js`:
275286
```js
@@ -281,7 +292,9 @@ Vue.use(VueMask)
281292
`<your_component>.vue`:
282293
```vue
283294
<template>
284-
<input type="text" v-mask="mask" v-model="myInputModel" placeholder="$100.00">
295+
<input type="text" v-mask="currencyMask" v-model="myInputModel" placeholder="$100.00">
296+
<!-- or with filter -->
297+
<span>{{ '100' | VMask(currencyMask) }</span>
285298
</template>
286299
<script>
287300
import createNumberMask from 'text-mask-addons/dist/createNumberMask';
@@ -293,7 +306,7 @@ Vue.use(VueMask)
293306
});
294307
export default {
295308
data: () => ({
296-
mask: currencyMask,
309+
currencyMask,
297310
myInputModel: ''
298311
})
299312
}
@@ -335,7 +348,7 @@ We're using [GitHub Releases](https://github.com/probil/v-mask/releases).
335348

336349
We're more than happy to see potential contributions, so don't hesitate. If you have any suggestions, ideas or problems feel free to add new [issue](https://github.com/probil/v-mask/issues), but first please make sure your question does not repeat previous ones.
337350

338-
**Notice:** You should make your changes only in `src` folder, don't try to edit files from `dist` as it compiled from `src` by babel and shouldn't be changes manually.
351+
**Notice:** You should make your changes only in `src` folder, don't try to edit files from `dist` as it compiled from `src` by babel and shouldn't be changes manually. Moreover, adding a proper tests for your PR drastically improves chances of merging.
339352

340353
## :lock: License
341354

src/__tests__/index.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,38 @@ describe('filter usage', () => {
283283
});
284284
expect(wrapper.text()).toBe('');
285285
});
286+
287+
it('should accept an array of regular expressions directly', async () => {
288+
const wrapper = mountWithMask({
289+
data: () => ({ mask: ['(', /\d/, /\d/, /\d/, ') ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/], value: '5555551234' }),
290+
template: '<span>{{ value | VMask(mask) }}</span>',
291+
});
292+
expect(wrapper.text()).toBe('(555) 555-1234');
293+
});
294+
295+
it('should be possible to create a mask for accepting a valid time range', async () => {
296+
const wrapper = mountWithMask({
297+
data: () => ({
298+
mask: timeRangeMask,
299+
value: '02532137',
300+
}),
301+
template: '<span>{{ value | VMask(mask) }}</span>',
302+
});
303+
expect(wrapper.text()).toBe('02:53-21:37');
304+
});
305+
306+
it('should allow for add/removal of global mask placeholders', async () => {
307+
const localVue = createLocalVue();
308+
localVue.use(VueMask, {
309+
placeholders: {
310+
'#': null,
311+
D: /\d/,
312+
},
313+
});
314+
const wrapper = mount({
315+
data: () => ({ mask: '###-DDD-###-DDD', value: '123456' }),
316+
template: '<span>{{ value | VMask(mask) }}</span>',
317+
}, { localVue });
318+
expect(wrapper.text()).toBe('###-123-###-456');
319+
});
286320
});

src/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const NEXT_CHAR_OPTIONAL = {
99
};
1010

1111
/**
12-
* @type {Object<String,RegExp|NEXT_CHAR_OPTIONAL>}
12+
* @type {MaskReplaces}
1313
*/
1414
export const defaultMaskReplacers = {
1515
'#': /\d/,

src/directive.js

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
/* eslint-disable no-param-reassign */
22
// eslint-disable-next-line import/no-extraneous-dependencies
33
import conformToMask from 'text-mask-core/src/conformToMask';
4-
import { stringMaskToRegExpMask, arrayMaskToRegExpMask } from './maskToRegExpMask';
4+
import parseMask from './utils/parseMask';
55
import {
66
trigger, queryInputElementInside, isFunction, isString, isRegexp,
77
} from './utils';
88
import createOptions from './createOptions';
9-
import { defaultMaskReplacers } from './constants';
9+
import extendMaskReplacers from './utils/extendMaskReplacers';
1010

1111
const options = createOptions();
1212

13+
/**
14+
* @typedef {RegExp|NEXT_CHAR_OPTIONAL|null} MaskReplacerValue
15+
*/
16+
17+
/**
18+
* @typedef {Object<string,MaskReplacerValue>} MaskReplaces
19+
*/
20+
1321
/**
1422
* Convenience wrapper for `trigger(el, 'input')`, which raises
1523
* an event for Vue to detect a value change.
@@ -45,49 +53,18 @@ function updateValue(el, force = false) {
4553
/**
4654
* Fires on handler update
4755
* @param {HTMLInputElement} el
48-
* @param {String|Array.<String|RegExp>|Function|null} inputMask
56+
* @param {string|Array.<string|RegExp>|Function|null} inputMask
57+
* @param {MaskReplaces} maskReplacers
4958
*/
5059
function updateMask(el, inputMask, maskReplacers) {
51-
let mask;
52-
53-
if (Array.isArray(inputMask)) {
54-
mask = arrayMaskToRegExpMask(inputMask, maskReplacers);
55-
} else if (isFunction(inputMask)) {
56-
mask = inputMask;
57-
} else if (isString(inputMask) && inputMask.length > 0) {
58-
mask = stringMaskToRegExpMask(inputMask, maskReplacers);
59-
} else {
60-
mask = inputMask;
61-
}
60+
const mask = parseMask(inputMask, maskReplacers);
6261

6362
options.partiallyUpdate(el, { mask });
6463
}
6564

66-
/**
67-
* Merge custom mask replacers with default mask replacers
68-
* @param {Object<string, RegExp>} maskReplacers
69-
* @param {Object<string, RegExp>} baseMaskReplacers
70-
* @return {Object} The extended mask replacers
71-
*/
72-
function extendMaskReplacers(maskReplacers, baseMaskReplacers = defaultMaskReplacers) {
73-
if (maskReplacers === null || Array.isArray(maskReplacers) || typeof maskReplacers !== 'object') {
74-
return baseMaskReplacers;
75-
}
76-
77-
return Object.keys(maskReplacers).reduce((extendedMaskReplacers, key) => {
78-
const value = maskReplacers[key];
79-
80-
if (value !== null && !(value instanceof RegExp)) {
81-
return extendedMaskReplacers;
82-
}
83-
84-
return { ...extendedMaskReplacers, [key]: value };
85-
}, baseMaskReplacers);
86-
}
87-
8865
/**
8966
* Convert a mask into a string for comparison
90-
* @param {String|Array.<String|RegExp>} mask
67+
* @param {string|Array.<string|RegExp>} mask
9168
*/
9269
function maskToString(mask) {
9370
const maskArray = Array.isArray(mask) ? mask : [mask];
@@ -97,8 +74,8 @@ function maskToString(mask) {
9774

9875
/**
9976
* Create the Vue directive
100-
* @param {Object} directiveOptions
101-
* @param {Object.<string, RegExp>} directiveOptions.placeholders
77+
* @param {Object} directiveOptions
78+
* @param {MaskReplaces} directiveOptions.placeholders
10279
* @return {Object} The Vue directive
10380
*/
10481
export function createDirective(directiveOptions = {}) {
@@ -116,7 +93,7 @@ export function createDirective(directiveOptions = {}) {
11693
* This is where you can do one-time setup work.
11794
*
11895
* @param {(HTMLInputElement|HTMLElement)} el
119-
* @param {?String} value
96+
* @param {?string} value
12097
*/
12198
bind(el, { value }) {
12299
el = queryInputElementInside(el);
@@ -133,8 +110,8 @@ export function createDirective(directiveOptions = {}) {
133110
* binding’s current and old values.
134111
*
135112
* @param {(HTMLInputElement|HTMLElement)} el
136-
* @param {?String} value
137-
* @param {?String} oldValue
113+
* @param {?string} value
114+
* @param {?string} oldValue
138115
*/
139116
componentUpdated(el, { value, oldValue }) {
140117
el = queryInputElementInside(el);

src/filter.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
11
// eslint-disable-next-line import/no-extraneous-dependencies
22
import conformToMask from 'text-mask-core/src/conformToMask';
3-
import { stringMaskToRegExpMask } from './maskToRegExpMask';
43
import { isString } from './utils';
4+
import parseMask from './utils/parseMask';
5+
import extendMaskReplacers from './utils/extendMaskReplacers';
56

67
/**
7-
* Vue filter definition
8-
* @param {String} value
9-
* @param {String} stringMask
8+
* Create the Vue filter
9+
* @param {Object} filterOptions
10+
* @param {MaskReplaces} filterOptions.placeholders
1011
*/
11-
export default (value, stringMask) => {
12-
const mask = stringMaskToRegExpMask(stringMask);
13-
if (!isString(value) && !Number.isFinite(value)) return value;
14-
const { conformedValue } = conformToMask(`${value}`, mask, { guide: false });
15-
return conformedValue;
16-
};
12+
export function createFilter(filterOptions = {}) {
13+
const instanceMaskReplacers = extendMaskReplacers(
14+
filterOptions && filterOptions.placeholders,
15+
);
16+
17+
/**
18+
* Vue filter definition
19+
* @param {string|number} value
20+
* @param {string|Array.<string|RegExp>|Function|null} inputMask
21+
*/
22+
return (value, inputMask) => {
23+
if (!isString(value) && !Number.isFinite(value)) return value;
24+
const mask = parseMask(inputMask, instanceMaskReplacers);
25+
const { conformedValue } = conformToMask(`${value}`, mask, { guide: false });
26+
return conformedValue;
27+
};
28+
}
29+
30+
export default createFilter();

src/plugin.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { createDirective } from './directive';
2-
import filter from './filter';
2+
import { createFilter } from './filter';
33

44
/**
55
* Vue plugin definition
66
* @param {Vue} Vue
7-
* @param {Object} options
8-
* @param {Object.<string, RegExp>} options.placeholders
7+
* @param {Object} options
8+
* @param {MaskReplaces} options.placeholders
99
*/
1010
export default (Vue, options = {}) => {
1111
Vue.directive('mask', createDirective(options));
12-
Vue.filter('VMask', filter);
12+
Vue.filter('VMask', createFilter(options));
1313
};

src/utils/extendMaskReplacers.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { defaultMaskReplacers } from '../constants';
2+
3+
/**
4+
* Merge custom mask replacers with default mask replacers
5+
* @param {MaskReplaces} maskReplacers
6+
* @param {MaskReplaces} baseMaskReplacers
7+
* @return {MaskReplaces} The extended mask replacers
8+
*/
9+
function extendMaskReplacers(maskReplacers, baseMaskReplacers = defaultMaskReplacers) {
10+
if (maskReplacers === null || Array.isArray(maskReplacers) || typeof maskReplacers !== 'object') {
11+
return baseMaskReplacers;
12+
}
13+
14+
return Object.keys(maskReplacers).reduce((extendedMaskReplacers, key) => {
15+
const value = maskReplacers[key];
16+
17+
if (value !== null && !(value instanceof RegExp)) {
18+
return extendedMaskReplacers;
19+
}
20+
21+
return { ...extendedMaskReplacers, [key]: value };
22+
}, baseMaskReplacers);
23+
}
24+
25+
export default extendMaskReplacers;

0 commit comments

Comments
 (0)