Last active
February 22, 2023 10:16
-
-
Save jonasraoni/9dea65e270495158393f54e36ee6b78d to your computer and use it in GitHub Desktop.
Numeric directive for Vue (v-decimal and v-integer, with an "unsigned" modifier). Useful to improve existing components which you can't modify.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//+ Jonas Raoni Soares Silva | |
//@ http://raoni.org | |
export default class NumericDirective { | |
constructor(input, binding) { | |
Object.assign(this, { | |
input, | |
binding | |
}); | |
input.addEventListener('keydown', this); | |
input.addEventListener('change', this); | |
} | |
static install(Vue) { | |
Vue.directive('decimal', this.directive); | |
Vue.directive('integer', this.directive); | |
} | |
static directive = { | |
bind(el, binding) { | |
el = el instanceof HTMLInputElement ? el : el.querySelector("input"); | |
if (el) { | |
return new NumericDirective(el, binding); | |
} | |
} | |
} | |
handleEvent(event) { | |
this[event.type](event); | |
} | |
keydown(event) { | |
const { target, key, keyCode, ctrlKey, metaKey } = event; | |
if (!( | |
// Is numeric | |
(key >= '0' && key <= '9') || | |
// Is special symbol allowed (. and -) | |
( | |
((key === '.' && this.binding.name === 'decimal') || (key === '-' && !this.binding.modifiers.unsigned)) && | |
!~target.value.indexOf(key) | |
) || | |
// Is system key | |
[ | |
'Delete', 'Backspace', 'Tab', 'Esc', 'Escape', 'Enter', | |
'Home', 'End', 'PageUp', 'PageDown', 'Del', 'Delete', | |
'Left', 'ArrowLeft', 'Right', 'ArrowRight', 'Insert', | |
'Up', 'ArrowUp', 'Down', 'ArrowDown' | |
].includes(key) || | |
// Is ctrl + a, c, x, v | |
((ctrlKey || metaKey) && [65, 67, 86, 88].includes(keyCode)) | |
)) { | |
event.preventDefault(); | |
} | |
} | |
change({ target }) { | |
const isDecimal = this.binding.name === 'decimal'; | |
let value = target.value; | |
if (!value) { | |
return; | |
} | |
// Is it a negative number and is it allowed? | |
const isNegative = /^\s*-/.test(value) && !this.binding.modifiers.unsigned; | |
// Remove invalid digits (if it's a decimal, then allows "," and "." to stay) | |
value = value.replace(isDecimal ? /[^\d,.]/g : /\D/g, ''); | |
if (isDecimal) { | |
// Naive adjustment for decimal values, breaks the number by ",." and considers the last group as the decimal part | |
const pieces = value.split(/[,.]/); | |
// Removes useless zeroes on the right | |
const decimal = pieces.pop().replace(/0+$/, ''); | |
if (pieces.length) { | |
value = `${pieces.join('') || (decimal ? '0' : '')}${decimal ? `.${decimal}` : ''}`; | |
} | |
} | |
// Removes useless zeroes on the left | |
value = value.replace(/^(?:0(?!\b))+/, ''); | |
if (value && isNegative) { | |
value = `-${value}`; | |
} | |
// Raise a fake event to signal others that we've updated the field | |
if (target.value !== value) { | |
target.value = value; | |
const event = document.createEvent('UIEvent'); | |
event.initEvent('input', true, false, window, 0); | |
target.dispatchEvent(event); | |
} | |
} | |
} |
Thanks @rogeriotaques! I'm unable to test on Apple devices, but it works on IE11 π©
- I think that filtering special keys smells bad haha (the issue you found confirms that), so I've created an improved version, using another technique, I just left this one here to avoid throwing code in trash π€
- Enforcing formatting is also something that I don't like... But I didn't try to solve this issue yet, as it wasn't needed for my use case.
Thanks for confirming and accepting the suggestion, @jonasraoni! πββοΈ Perhaps you can add the link to your improved version here, as a comment, so people who find this can also check the enhanced version as well. π
Hi @jonasraoni, thanks for the good example! π
By adding the following line at the beginning of the bind hook, you could enable the directive conditionally:
bind (el, binding) {
if (binding.hasOwnProperty('value') && ! binding.value) {
return;
}
el = el instanceof HTMLInputElement ? el : el.querySelector("input");
if (el) {
return new NumericDirective(el, binding);
}
}
That way you could do both v-integer
or v-integer="false"
.
Thank you for your case!!!
Thank you brother this helpfull I will try understand .
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, @jonasraoni!
Thanks for the good work! π
May I suggest an improvement to make it work smoothly also on Macs?
Line 30, replace with:
And replace line 44 with:
That should be enough for enabling
CTRL+A, C , X , V
on Macs (which uses theCMD
instead ofCTRL
for these actions). π€