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

Commit

Permalink
Merge pull request #529 from probil/feature/#499--function-usage-on-f…
Browse files Browse the repository at this point in the history
…ilter

Feature/#499  function usage on filter
  • Loading branch information
probil authored Sep 27, 2021
2 parents 3b2d6df + 2c313e3 commit 97e6fd9
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 84 deletions.
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ npm install v-mask
```javascript
import Vue from 'vue'

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

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

// Or only as a filter
// Or only as a filter-only
import { VueMaskFilter } from 'v-mask'
Vue.filter('VMask', VueMaskFilter)
```
Expand All @@ -82,15 +82,20 @@ Vue.directive('mask', VueMask.VueMaskDirective);
```html
<input type="text" v-mask="'####-##'" v-model="myInputModel">
<!-- OR -->
<input type="text" v-mask="nameOfVariableWithMask" v-model="myInputModel">
<input type="text" v-mask="variableWithMask" v-model="myInputModel">
```
**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.

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).

### Filter usage

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).

```html
<span>{{ '9999999999' | VMask('(###) ###-####') }}</span>
<!-- or -->
<span>{{ variableWithRawValue | VMask(variableWithMask) </span>
```

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


### Custom placeholders
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.
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.

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

Vue.use(VueMask, {
Vue.use(VueMask, { // (!) custom placeholders support requires registration as a plugin to
placeholders: {
'#': null, // passing `null` removes default placeholder, so `#` is treated as character
D: /\d/, // define new placeholder
Expand All @@ -165,6 +170,8 @@ Vue.use(VueMask, {
```vue
<template>
<input type="text" v-mask="'###-DDD-###-DDD'" v-model="myInputModel">
<!-- or with filter -->
<span>{{ 123456 | VMask(mask) }}</span>
</template>
<script>
export default {
Expand All @@ -179,7 +186,7 @@ Entering `123456` in that input field will produce value `###-123-###-456` in `m
### Array of RegExp
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.

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

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

**Notice**: Keep in mind that library always verifies _one_ character per regular expression. Trying verify multiple charters in the same RegExp won't work.
**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.

### Function

Expand All @@ -225,7 +234,9 @@ Vue.use(VueMask)
`<your_component>.vue`:
```vue
<template>
<input type="text" v-mask="mask" v-model="myInputModel" placeholder="00:00-23:59">
<input type="text" v-mask="timeRangeMask" v-model="myInputModel" placeholder="00:00-23:59">
<!-- or with filter -->
<span>{{ '02532137' | VMask(timeRangeMask) }}</span>
</template>
<script>
/**
Expand Down Expand Up @@ -257,7 +268,7 @@ Vue.use(VueMask)
export default {
data: () => ({
mask: timeRangeMask,
timeRangeMask,
myInputModel: ''
})
}
Expand All @@ -269,7 +280,7 @@ In this example entering `02532137` in the input field will produce valid time r

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.

The usage is simple. Configure the addon as want and pass the result to the `v-mask` as you would to `text-mask-core`.
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`.

`app.js`:
```js
Expand All @@ -281,7 +292,9 @@ Vue.use(VueMask)
`<your_component>.vue`:
```vue
<template>
<input type="text" v-mask="mask" v-model="myInputModel" placeholder="$100.00">
<input type="text" v-mask="currencyMask" v-model="myInputModel" placeholder="$100.00">
<!-- or with filter -->
<span>{{ '100' | VMask(currencyMask) }</span>
</template>
<script>
import createNumberMask from 'text-mask-addons/dist/createNumberMask';
Expand All @@ -293,7 +306,7 @@ Vue.use(VueMask)
});
export default {
data: () => ({
mask: currencyMask,
currencyMask,
myInputModel: ''
})
}
Expand Down Expand Up @@ -335,7 +348,7 @@ We're using [GitHub Releases](https://github.com/probil/v-mask/releases).

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.

**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.
**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.

## :lock: License

Expand Down
34 changes: 34 additions & 0 deletions src/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,38 @@ describe('filter usage', () => {
});
expect(wrapper.text()).toBe('');
});

it('should accept an array of regular expressions directly', async () => {
const wrapper = mountWithMask({
data: () => ({ mask: ['(', /\d/, /\d/, /\d/, ') ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/], value: '5555551234' }),
template: '<span>{{ value | VMask(mask) }}</span>',
});
expect(wrapper.text()).toBe('(555) 555-1234');
});

it('should be possible to create a mask for accepting a valid time range', async () => {
const wrapper = mountWithMask({
data: () => ({
mask: timeRangeMask,
value: '02532137',
}),
template: '<span>{{ value | VMask(mask) }}</span>',
});
expect(wrapper.text()).toBe('02:53-21:37');
});

it('should allow for add/removal of global mask placeholders', async () => {
const localVue = createLocalVue();
localVue.use(VueMask, {
placeholders: {
'#': null,
D: /\d/,
},
});
const wrapper = mount({
data: () => ({ mask: '###-DDD-###-DDD', value: '123456' }),
template: '<span>{{ value | VMask(mask) }}</span>',
}, { localVue });
expect(wrapper.text()).toBe('###-123-###-456');
});
});
2 changes: 1 addition & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const NEXT_CHAR_OPTIONAL = {
};

/**
* @type {Object<String,RegExp|NEXT_CHAR_OPTIONAL>}
* @type {MaskReplaces}
*/
export const defaultMaskReplacers = {
'#': /\d/,
Expand Down
61 changes: 19 additions & 42 deletions src/directive.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
/* eslint-disable no-param-reassign */
// eslint-disable-next-line import/no-extraneous-dependencies
import conformToMask from 'text-mask-core/src/conformToMask';
import { stringMaskToRegExpMask, arrayMaskToRegExpMask } from './maskToRegExpMask';
import parseMask from './utils/parseMask';
import {
trigger, queryInputElementInside, isFunction, isString, isRegexp,
} from './utils';
import createOptions from './createOptions';
import { defaultMaskReplacers } from './constants';
import extendMaskReplacers from './utils/extendMaskReplacers';

const options = createOptions();

/**
* @typedef {RegExp|NEXT_CHAR_OPTIONAL|null} MaskReplacerValue
*/

/**
* @typedef {Object<string,MaskReplacerValue>} MaskReplaces
*/

/**
* Convenience wrapper for `trigger(el, 'input')`, which raises
* an event for Vue to detect a value change.
Expand Down Expand Up @@ -45,49 +53,18 @@ function updateValue(el, force = false) {
/**
* Fires on handler update
* @param {HTMLInputElement} el
* @param {String|Array.<String|RegExp>|Function|null} inputMask
* @param {string|Array.<string|RegExp>|Function|null} inputMask
* @param {MaskReplaces} maskReplacers
*/
function updateMask(el, inputMask, maskReplacers) {
let mask;

if (Array.isArray(inputMask)) {
mask = arrayMaskToRegExpMask(inputMask, maskReplacers);
} else if (isFunction(inputMask)) {
mask = inputMask;
} else if (isString(inputMask) && inputMask.length > 0) {
mask = stringMaskToRegExpMask(inputMask, maskReplacers);
} else {
mask = inputMask;
}
const mask = parseMask(inputMask, maskReplacers);

options.partiallyUpdate(el, { mask });
}

/**
* Merge custom mask replacers with default mask replacers
* @param {Object<string, RegExp>} maskReplacers
* @param {Object<string, RegExp>} baseMaskReplacers
* @return {Object} The extended mask replacers
*/
function extendMaskReplacers(maskReplacers, baseMaskReplacers = defaultMaskReplacers) {
if (maskReplacers === null || Array.isArray(maskReplacers) || typeof maskReplacers !== 'object') {
return baseMaskReplacers;
}

return Object.keys(maskReplacers).reduce((extendedMaskReplacers, key) => {
const value = maskReplacers[key];

if (value !== null && !(value instanceof RegExp)) {
return extendedMaskReplacers;
}

return { ...extendedMaskReplacers, [key]: value };
}, baseMaskReplacers);
}

/**
* Convert a mask into a string for comparison
* @param {String|Array.<String|RegExp>} mask
* @param {string|Array.<string|RegExp>} mask
*/
function maskToString(mask) {
const maskArray = Array.isArray(mask) ? mask : [mask];
Expand All @@ -97,8 +74,8 @@ function maskToString(mask) {

/**
* Create the Vue directive
* @param {Object} directiveOptions
* @param {Object.<string, RegExp>} directiveOptions.placeholders
* @param {Object} directiveOptions
* @param {MaskReplaces} directiveOptions.placeholders
* @return {Object} The Vue directive
*/
export function createDirective(directiveOptions = {}) {
Expand All @@ -116,7 +93,7 @@ export function createDirective(directiveOptions = {}) {
* This is where you can do one-time setup work.
*
* @param {(HTMLInputElement|HTMLElement)} el
* @param {?String} value
* @param {?string} value
*/
bind(el, { value }) {
el = queryInputElementInside(el);
Expand All @@ -133,8 +110,8 @@ export function createDirective(directiveOptions = {}) {
* binding’s current and old values.
*
* @param {(HTMLInputElement|HTMLElement)} el
* @param {?String} value
* @param {?String} oldValue
* @param {?string} value
* @param {?string} oldValue
*/
componentUpdated(el, { value, oldValue }) {
el = queryInputElementInside(el);
Expand Down
34 changes: 24 additions & 10 deletions src/filter.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import conformToMask from 'text-mask-core/src/conformToMask';
import { stringMaskToRegExpMask } from './maskToRegExpMask';
import { isString } from './utils';
import parseMask from './utils/parseMask';
import extendMaskReplacers from './utils/extendMaskReplacers';

/**
* Vue filter definition
* @param {String} value
* @param {String} stringMask
* Create the Vue filter
* @param {Object} filterOptions
* @param {MaskReplaces} filterOptions.placeholders
*/
export default (value, stringMask) => {
const mask = stringMaskToRegExpMask(stringMask);
if (!isString(value) && !Number.isFinite(value)) return value;
const { conformedValue } = conformToMask(`${value}`, mask, { guide: false });
return conformedValue;
};
export function createFilter(filterOptions = {}) {
const instanceMaskReplacers = extendMaskReplacers(
filterOptions && filterOptions.placeholders,
);

/**
* Vue filter definition
* @param {string|number} value
* @param {string|Array.<string|RegExp>|Function|null} inputMask
*/
return (value, inputMask) => {
if (!isString(value) && !Number.isFinite(value)) return value;
const mask = parseMask(inputMask, instanceMaskReplacers);
const { conformedValue } = conformToMask(`${value}`, mask, { guide: false });
return conformedValue;
};
}

export default createFilter();
8 changes: 4 additions & 4 deletions src/plugin.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { createDirective } from './directive';
import filter from './filter';
import { createFilter } from './filter';

/**
* Vue plugin definition
* @param {Vue} Vue
* @param {Object} options
* @param {Object.<string, RegExp>} options.placeholders
* @param {Object} options
* @param {MaskReplaces} options.placeholders
*/
export default (Vue, options = {}) => {
Vue.directive('mask', createDirective(options));
Vue.filter('VMask', filter);
Vue.filter('VMask', createFilter(options));
};
25 changes: 25 additions & 0 deletions src/utils/extendMaskReplacers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defaultMaskReplacers } from '../constants';

/**
* Merge custom mask replacers with default mask replacers
* @param {MaskReplaces} maskReplacers
* @param {MaskReplaces} baseMaskReplacers
* @return {MaskReplaces} The extended mask replacers
*/
function extendMaskReplacers(maskReplacers, baseMaskReplacers = defaultMaskReplacers) {
if (maskReplacers === null || Array.isArray(maskReplacers) || typeof maskReplacers !== 'object') {
return baseMaskReplacers;
}

return Object.keys(maskReplacers).reduce((extendedMaskReplacers, key) => {
const value = maskReplacers[key];

if (value !== null && !(value instanceof RegExp)) {
return extendedMaskReplacers;
}

return { ...extendedMaskReplacers, [key]: value };
}, baseMaskReplacers);
}

export default extendMaskReplacers;
Loading

0 comments on commit 97e6fd9

Please sign in to comment.