vuejs-tips / vue-the-mask

Tiny (<2k gzipped) and dependency free mask input for Vue.js
https://vuejs-tips.github.io/vue-the-mask/
1.72k stars 213 forks source link

caret jumps backwards #49

Open wirk opened 6 years ago

wirk commented 6 years ago

I tried to create a mask for numbers with dots as thousands-separator. My mask is in the array-format, and I figured out that each mask entry needs be exactly one character longer:

    [
       '###',
        '####', // workaround to prevent '1.11'
        '#.###',
        '##.###',
        '###.###',
        '####.###', // workaround to prevent '1.000.00'
        '#.###.###',
        '##.###.###',
    ]

The mask itself works fine so far, but if the same character is entered repeatedly, the caret will not end up at the end but move one or two spaces backwards. This seems to happen when the two workaround mask entries are jumped over. If different numbers are entered, the caret stays at the end as intended.

Here is where the caret ends up:

1.0|0 10.000.0|00

pjaer commented 6 years ago

I might have an idea, but not time to code/test it right now.

My analyse and reasoning:

As I understand the code in src/directive.js, when we input, we store the last known position of the caret in the input field, then we apply a new masked value, and then, we recursively try to find our inputted character from where the last known caret position is. This loop is found at src/directive.js:50

My conclusion:

The result is, if you are using a single dot (.) as you are, and enter triple zeroes for example, you wind up one zero off. If you were to try with double dots (making the mask offset the character by another step) you would wind up two zeroes off.

My suggestion for a solution:

If we, when storing the last known caret position, also store the last known value length, call it val_len. When we are to relocate where the caret is supposed to be after masking the new value, all we need to do is compare the new value length to the old, that is how many steps "off" we are with the caret.

Problem illustration (| being the caret):

I'll be using dashes instead of dots or spaces, for visiblity

mask = [####, ##-###]
inputval = 50000|
caretpos = 5 // looking for the last entered 0 from position-1, found one directly
masked = 50-00|0

Double char-mask (double offset issue)

mask = [####, ##--###] // double space
inputval = 50000|
caretpos = 5 // looking for the last entered 0, again, found one directly
masked = 50--0|00

Solution illustration

I'll be using dashes instead of dots or spaces, for visiblity My solution would be to compare value lengths before/after masking, the difference would always make up for the 'masking chars' applied. Examples:

mask = [####, ##-###]
inputval = 50000|
caretpos = 5
valuelengthdiff = 1 // new length - old length
newcaretpos = 5 + valuelengthdiff
masked = 50-000|

Double char-mask

mask = [####, ##--###] // double space
inputval = 50000|
caretpos = 5
valuelengthdiff = 2 // new length(7) - old length(5)
masked = 50--0|00

Example of negative careting (user shortening the masked input value, which might be re-masked too)

mask = [####, ##--###] // double space
inputval = 50--0|00
// user deletes, it becomes to 50--|00 -> triggers new masking due to 4 number mask
// new masked value is:5000, caret should be at 50|00
caretpos = 5
valuelengthdiff = -3 // new length(4) - old length(7)
newcaretpos = caretpos+valuelengthdiff // 2
masked = 50|00

Sounds reasonble?

pjaer commented 6 years ago

Edit: Anybody watching this - downloading repo, running npm install and I cant runt npm run build because of vue-cli deprecated build and some other stuff.. if anyone has solution please link or comment!

Note: This solution might not work for edge-cases like this

111|222 // something like this
###|### // masked with this
111333|222 // user inputs '333'
###-#|##-### // applying an alternative mask like for example this

This case would offset the caret 2 steps with my method, so maybe we're just moving the problem then.. but it would work for continuous input, which is perhaps more common

wirk commented 6 years ago

Thanks for your input @pjaer! I have created a fix based on your thoughts. I have already verified that the approach works by directly editing the minified version. Hopefully someone knows what to do about the broken build task, then I will create a pull request. I'll try to look into it myself if I find the time.

https://github.com/vuejs-tips/vue-the-mask/compare/master...wirk:bugfix/49-caret-jumps-backwards

carlosnakazawa commented 6 years ago

@wirk When the bugfix will be available?

SergioFloresG commented 4 years ago

this bugfix is available?

I have some issue for credit card mask.

Image of bug Imgur

guigagb commented 2 years ago

The problem occurs when typing the same number 2 times. I have same problem in phone mask.

andersonsaraiva commented 2 years ago

I have the same problem. Would you have any solution?