Skip to content

Commit 3a3d938

Browse files
committed
feat(component): adds a pipe prop that allows for custom formatting on the input
1 parent 6cdf15a commit 3a3d938

File tree

8 files changed

+133
-52
lines changed

8 files changed

+133
-52
lines changed

docs/component.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,27 @@ let hexTokens = {
5151
<checkbox v-model="masked" />
5252
<display :value="value" />
5353
```
54+
55+
### Pipe through another function
56+
57+
```js
58+
const piper = (value, event) => {
59+
// do not format on deletion, this could leave the input in bad state
60+
// but allows user to delete the leading 0 if needed for some reason
61+
if (event.inputType !== 'deleteContentBackward') {
62+
const [ month ] = value.masked.split('/')
63+
64+
if (month > 12) {
65+
return '0' + value.unmasked
66+
}
67+
}
68+
}
69+
70+
let value = ''
71+
72+
<example label="Date as MM/YY">
73+
<input-facade v-model="value" mask="##/##" :pipe="piper" />
74+
</example>
75+
76+
<display :value="value" />
77+
```

src/component.vue

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ export default {
4545
type: Boolean,
4646
default: false
4747
},
48+
/**
49+
* A function to pipe the value through before commiting the value to the input element. The function
50+
* will receipt an object with the masked and unmasked value after passing through the masker function.
51+
* The result of this pipe function will determine what happens with the value.
52+
* <br />
53+
* If a string is returned, then that string will pass through the masker function once more and its value
54+
* will be set to the input. If false (boolean) is returned, the input will be rejected and the
55+
* previous value will be restored. Otherwise the facade logic will continue as usual.
56+
* @since v1.2
57+
*/
58+
pipe: Function,
4859
/**
4960
* Token object to override the defaults with
5061
*/
@@ -85,7 +96,8 @@ export default {
8596
config() {
8697
return {
8798
mask: this.mask,
88-
tokens: this.tokens
99+
tokens: this.tokens,
100+
pipe: this.pipe
89101
}
90102
},
91103
emittedValue() {

src/core.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import masker from './masker'
22
export const CONFIG_KEY = '__input-facade__'
33

44
export function FacadeValue(val = '') {
5-
this.masked = this.raw = val
5+
this.masked = this.unmasked = val
66
}
77

88
/**
@@ -39,8 +39,8 @@ export function normalizeConfig(config = {}) {
3939
export function getInputElement(el) {
4040
const inputElement = el instanceof HTMLInputElement ? el : el.querySelector('input')
4141

42+
/* istanbul ignore next */
4243
if (!inputElement) {
43-
/* istanbul ignore next */
4444
throw new Error('facade directive requires an input element')
4545
}
4646

@@ -68,7 +68,7 @@ export function inputHandler(event) {
6868
const originalPosition = target.selectionEnd
6969
const { oldValue } = target[CONFIG_KEY]
7070

71-
updateValue(target, { emit: false })
71+
updateValue(target, { emit: false }, event)
7272
updateCursor(event, originalValue, originalPosition)
7373

7474
if (oldValue !== target.value) {
@@ -128,19 +128,31 @@ export function updateCursor(event, originalValue, originalPosition) {
128128
* Updates the element's value and unmasked value based on the masking config rules
129129
*
130130
* @param {HTMLInputElement} el The input element to update
131-
* @param {object} options
131+
* @param {object} [options]
132132
* @param {Boolean} options.emit Wether to dispatch a new InputEvent or not
133133
* @param {Boolean} options.force Forces the update even if the old value and the new value are the same
134+
* @param {Event} [event] The event that triggered this this update, null if not triggered by an input event
134135
*/
135-
export function updateValue(el, { emit = true, force = false } = {}) {
136+
export function updateValue(el, { emit = true, force = false } = {}, event) {
136137
const { config, oldValue } = el[CONFIG_KEY]
137138

138139
if (force || oldValue !== el.value) {
139-
const newValue = masker(el.value, config)
140+
let newValue = masker(el.value, config)
141+
142+
if (event && typeof config.pipe === 'function') {
143+
const pipeValue = config.pipe(newValue, event)
144+
145+
if (typeof pipeValue === 'string') {
146+
newValue = masker(pipeValue, config)
147+
} else if (pipeValue === false) {
148+
el.value = oldValue
149+
return
150+
}
151+
}
140152

141153
el[CONFIG_KEY].oldValue = newValue.masked
142154
el.value = newValue.masked
143-
el.unmaskedValue = newValue.raw
155+
el.unmaskedValue = newValue.unmasked
144156
emit && el.dispatchEvent(FacadeInputEvent())
145157
}
146158
}

src/masker.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function formatter(value = '', config = {}) {
8484

8585
if (masker.pattern.test(char)) {
8686
char = masker.transform ? masker.transform(char) : char
87-
output.raw += char
87+
output.unmasked += char
8888
output.masked += accumulator + char
8989

9090
accumulator = ''
@@ -100,9 +100,9 @@ export function formatter(value = '', config = {}) {
100100
}
101101
}
102102

103-
// if there is no raw value, set masked to empty to avoid
103+
// if there is no unmasked value, set masked to empty to avoid
104104
// showing masking characters in an otherwise empty input
105-
if (output.raw && !short) {
105+
if (output.unmasked && !short) {
106106
output.masked += accumulator
107107
}
108108

tests/component.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,34 @@ describe('Component', () => {
6161
input.trigger('change')
6262
expect(wrapper.emitted().input).toBeTruthy()
6363
})
64+
65+
test('Adding a pipe function should call that function on input', async () => {
66+
const piper = jest.fn()
67+
const wrapper = createWrapper({ pipe: piper})
68+
69+
wrapper.element.value = '5555'
70+
wrapper.find('input').trigger('input')
71+
72+
expect(piper).toHaveBeenCalled()
73+
})
74+
75+
test('When pipe function returns false, do not change input value', async () => {
76+
const piper = jest.fn().mockReturnValue(false)
77+
const wrapper = createWrapper({ value: '1234', pipe: piper })
78+
79+
wrapper.element.value = '5555'
80+
wrapper.find('input').trigger('input')
81+
82+
expect(wrapper.vm.maskedValue).toBe('1234')
83+
})
84+
85+
test('When pipe function returns string, set value to masked string', async () => {
86+
const piper = jest.fn().mockReturnValue('3344')
87+
const wrapper = createWrapper({ mask: '##-##', pipe: piper })
88+
89+
wrapper.element.value = '5555'
90+
wrapper.find('input').trigger('input')
91+
92+
expect(wrapper.vm.maskedValue).toBe('33-44')
93+
})
6494
})

tests/dynamic.test.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,49 @@ import { dynamic } from '../src/masker'
22

33
test('empty', () => {
44
var masks = []
5-
expect(dynamic('12345', { masks })).toMatchObject({ masked: '', raw: '' })
5+
expect(dynamic('12345', { masks })).toMatchObject({ masked: '', unmasked: '' })
66
})
77

88
test('single', () => {
99
var masks = ['#.#']
10-
expect(dynamic('12', { masks })).toMatchObject({ masked: '1.2', raw: '12' })
11-
expect(dynamic('321', { masks })).toMatchObject({ masked: '3.2', raw: '32' })
10+
expect(dynamic('12', { masks })).toMatchObject({ masked: '1.2', unmasked: '12' })
11+
expect(dynamic('321', { masks })).toMatchObject({ masked: '3.2', unmasked: '32' })
1212
})
1313

1414
test('CEP USA/BR', () => {
1515
var masks = ['#####', '#####-###']
16-
expect(dynamic('12345', { masks })).toMatchObject({ masked: '12345', raw: '12345' })
17-
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', raw: '123456' })
18-
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '12345-678', raw: '12345678' })
16+
expect(dynamic('12345', { masks })).toMatchObject({ masked: '12345', unmasked: '12345' })
17+
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', unmasked: '123456' })
18+
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '12345-678', unmasked: '12345678' })
1919
})
2020

2121
test('Reverse CEP USA/BR', () => {
2222
var masks = ['#####-###', '#####']
23-
expect(dynamic('12345', { masks })).toMatchObject({ masked: '12345', raw: '12345' })
24-
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', raw: '123456' })
25-
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '12345-678', raw: '12345678' })
23+
expect(dynamic('12345', { masks })).toMatchObject({ masked: '12345', unmasked: '12345' })
24+
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', unmasked: '123456' })
25+
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '12345-678', unmasked: '12345678' })
2626
})
2727

2828
test('cpf/cnpj', () => {
2929
var masks = ['###.###.###-##', '##.###.###/####-##']
30-
expect(dynamic('12345678901', { masks })).toMatchObject({ masked: '123.456.789-01', raw: '12345678901' })
31-
expect(dynamic('123456789012', { masks })).toMatchObject({ masked: '12.345.678/9012-', raw: '123456789012' })
30+
expect(dynamic('12345678901', { masks })).toMatchObject({ masked: '123.456.789-01', unmasked: '12345678901' })
31+
expect(dynamic('123456789012', { masks })).toMatchObject({ masked: '12.345.678/9012-', unmasked: '123456789012' })
3232
})
3333

3434
test('bank agency', () => {
3535
var masks = ['####', '####-#', '####-##']
36-
expect(dynamic('1234', { masks })).toMatchObject({ masked: '1234', raw: '1234' })
37-
expect(dynamic('1234a', { masks })).toMatchObject({ masked: '1234', raw: '1234' })
38-
expect(dynamic('12345', { masks })).toMatchObject({ masked: '1234-5', raw: '12345' })
39-
expect(dynamic('123456', { masks })).toMatchObject({ masked: '1234-56', raw: '123456' })
36+
expect(dynamic('1234', { masks })).toMatchObject({ masked: '1234', unmasked: '1234' })
37+
expect(dynamic('1234a', { masks })).toMatchObject({ masked: '1234', unmasked: '1234' })
38+
expect(dynamic('12345', { masks })).toMatchObject({ masked: '1234-5', unmasked: '12345' })
39+
expect(dynamic('123456', { masks })).toMatchObject({ masked: '1234-56', unmasked: '123456' })
4040
})
4141

4242
test('bank account', () => {
4343
// 12345 123456 1234567 12345678 123456789
4444
var masks = ['#####-#', '######-#', '#######-#', '########-#', '#########-#']
45-
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', raw: '123456' })
46-
expect(dynamic('1234567', { masks })).toMatchObject({ masked: '123456-7', raw: '1234567' })
47-
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '1234567-8', raw: '12345678' })
48-
expect(dynamic('123456789', { masks })).toMatchObject({ masked: '12345678-9', raw: '123456789' })
49-
expect(dynamic('1234567890', { masks })).toMatchObject({ masked: '123456789-0', raw: '1234567890' })
45+
expect(dynamic('123456', { masks })).toMatchObject({ masked: '12345-6', unmasked: '123456' })
46+
expect(dynamic('1234567', { masks })).toMatchObject({ masked: '123456-7', unmasked: '1234567' })
47+
expect(dynamic('12345678', { masks })).toMatchObject({ masked: '1234567-8', unmasked: '12345678' })
48+
expect(dynamic('123456789', { masks })).toMatchObject({ masked: '12345678-9', unmasked: '123456789' })
49+
expect(dynamic('1234567890', { masks })).toMatchObject({ masked: '123456789-0', unmasked: '1234567890' })
5050
})

tests/formatter.test.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,66 @@
11
import { formatter } from '../src/masker'
22

33
test('default vals', () => {
4-
expect(formatter('123')).toMatchObject({ masked: '', raw: '' })
4+
expect(formatter('123')).toMatchObject({ masked: '', unmasked: '' })
55
})
66

77
test('12 -> #.#', () => {
8-
expect(formatter('12', { mask: '#.#' })).toMatchObject({ masked: '1.2', raw: '12' })
8+
expect(formatter('12', { mask: '#.#' })).toMatchObject({ masked: '1.2', unmasked: '12' })
99
})
1010

1111
test('1 -> (#)', () => {
1212
// placeholder at the end
13-
expect(formatter('1', { mask: '(#)' })).toMatchObject({ masked: '(1)', raw: '1' })
13+
expect(formatter('1', { mask: '(#)' })).toMatchObject({ masked: '(1)', unmasked: '1' })
1414
})
1515

1616
test('1 -> [(#)]', () => {
1717
// two placeholder at the end
18-
expect(formatter('1', { mask: '[(#)]' })).toMatchObject({ masked: '[(1)]', raw: '1' })
18+
expect(formatter('1', { mask: '[(#)]' })).toMatchObject({ masked: '[(1)]', unmasked: '1' })
1919
})
2020

2121
test('1 -> #..#', () => {
22-
expect(formatter('1', { mask: '#..#', short: true })).toMatchObject({ masked: '1', raw: '1' })
22+
expect(formatter('1', { mask: '#..#', short: true })).toMatchObject({ masked: '1', unmasked: '1' })
2323
})
2424

2525
test('1 -> #.#', () => {
26-
expect(formatter('1', { mask: '#.#' })).toMatchObject({ masked: '1.', raw: '1' })
26+
expect(formatter('1', { mask: '#.#' })).toMatchObject({ masked: '1.', unmasked: '1' })
2727
})
2828

2929
test('1. -> #.#', () => {
30-
expect(formatter('1.', { mask: '#.#' })).toMatchObject({ masked: '1.', raw: '1' })
30+
expect(formatter('1.', { mask: '#.#' })).toMatchObject({ masked: '1.', unmasked: '1' })
3131
})
3232

3333
test('123 -> #.#', () => {
34-
expect(formatter('123', { mask: '#.#' })).toMatchObject({ masked: '1.2', raw: '12' })
34+
expect(formatter('123', { mask: '#.#' })).toMatchObject({ masked: '1.2', unmasked: '12' })
3535
})
3636

3737
test('abc1234567 -> AAa-####', () => {
38-
expect(formatter('abc1234567', { mask: 'AAa-####' })).toMatchObject({ masked: 'ABc-1234', raw: 'ABc1234' })
38+
expect(formatter('abc1234567', { mask: 'AAa-####' })).toMatchObject({ masked: 'ABc-1234', unmasked: 'ABc1234' })
3939
})
4040

4141
test('a5-12-34 -> (XX) - ## - ##', () => {
42-
expect(formatter('a5-12-34', { mask: '(XX) - ## - ##' })).toMatchObject({ masked: '(a5) - 12 - 34', raw: 'a51234' })
42+
expect(formatter('a5-12-34', { mask: '(XX) - ## - ##' })).toMatchObject({
43+
masked: '(a5) - 12 - 34',
44+
unmasked: 'a51234'
45+
})
4346
})
4447

4548
test('123 -> ##(#)', () => {
46-
expect(formatter('123', { mask: '##(#)' })).toMatchObject({ masked: '12(3)', raw: '123' })
49+
expect(formatter('123', { mask: '##(#)' })).toMatchObject({ masked: '12(3)', unmasked: '123' })
4750
})
4851

4952
test('123 -> ##(#)', () => {
50-
expect(formatter('12', { mask: '#\\#(#)' })).toMatchObject({ masked: '1#(2)', raw: '12' })
53+
expect(formatter('12', { mask: '#\\#(#)' })).toMatchObject({ masked: '1#(2)', unmasked: '12' })
5154
})
5255

5356
test('12 -> +1 #', () => {
54-
expect(formatter('12', { mask: '+1 #' })).toMatchObject({ masked: '+1 2', raw: '2' })
57+
expect(formatter('12', { mask: '+1 #' })).toMatchObject({ masked: '+1 2', unmasked: '2' })
5558
})
5659

5760
test('2 -> +1 #', () => {
58-
expect(formatter('2', { mask: '+1 #' })).toMatchObject({ masked: '+1 2', raw: '2' })
61+
expect(formatter('2', { mask: '+1 #' })).toMatchObject({ masked: '+1 2', unmasked: '2' })
5962
})
6063

6164
test('2 -> +1 # 5', () => {
62-
expect(formatter('2', { mask: '+1 # 5' })).toMatchObject({ masked: '+1 2 5', raw: '2' })
65+
expect(formatter('2', { mask: '+1 # 5' })).toMatchObject({ masked: '+1 2 5', unmasked: '2' })
6366
})

tests/masker.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ const tokens = {
55
}
66

77
test('no mask given', () => {
8-
expect(masker('123')).toMatchObject({ masked: '123', raw: '123' })
8+
expect(masker('123')).toMatchObject({ masked: '123', unmasked: '123' })
99
})
1010

1111
test('single mask given', () => {
12-
expect(masker('12', { mask: '#.#' })).toMatchObject({ masked: '1.2', raw: '12' })
12+
expect(masker('12', { mask: '#.#' })).toMatchObject({ masked: '1.2', unmasked: '12' })
1313
})
1414

1515
test('multiple mask given', () => {
1616
const config = { mask: ['#.#', '##.#'] }
17-
expect(masker('12', config)).toMatchObject({ masked: '1.2', raw: '12' })
18-
expect(masker('123', config)).toMatchObject({ masked: '12.3', raw: '123' })
17+
expect(masker('12', config)).toMatchObject({ masked: '1.2', unmasked: '12' })
18+
expect(masker('123', config)).toMatchObject({ masked: '12.3', unmasked: '123' })
1919
})
2020

2121
test('should just append to the tokens when passing them in locally', () => {
22-
expect(masker('456DDS', { mask: '###-FFFF', tokens })).toMatchObject({ masked: '456-DD', raw: '456DD' })
22+
expect(masker('456DDS', { mask: '###-FFFF', tokens })).toMatchObject({ masked: '456-DD', unmasked: '456DD' })
2323
})
2424

2525
test('should override default tokens globally', () => {
2626
setTokens(tokens)
27-
expect(masker('456DDS', { mask: '###-FFFF' })).toMatchObject({ masked: '###-456D', raw: '456D' })
27+
expect(masker('456DDS', { mask: '###-FFFF' })).toMatchObject({ masked: '###-456D', unmasked: '456D' })
2828
})

0 commit comments

Comments
 (0)