You should not listen for keyboard events (keydown
/ keypress
/ keyup
) to do that, as the value of the input can also be updated by pasting or dropping text into it and there are many exceptions you should not prevent, such as arrows, delete, escape, shortcuts such as select all, copy, paste...
Instead, just listen for the input
event, parse its value to extract all numbers in it and then update its value to keep only those and one single decimal indicator.
A basic example would look something like this:
const input = document.getElementById('input');
input.oninput = () => {
const matches = input.value.match(/[\d.,]/g);
if (!matches) return;
let hasDecimalSeparator = false;
input.value = matches.filter((d) => {
const isDecimalSeparator = d === '.' || d === ',';
if (!isDecimalSeparator) return true;
if (hasDecimalSeparator) {
// We already have one decimal separator, so ignore this one:
return false;
}
// Remember we have just found our first decimal separator:
hasDecimalSeparator = true;
// But keep this one:
return true;
}).join('');
};
<input id="input" type="text" />
This gets the job done and it works fine with paste and drop, but you will notice there are two issues with it:
When you enter a character that you are not supposed to enter, the cursor moves.
If you try to write a second decimal indicator, it will update the value if the new one is to the left of the previous one; if it is to the right, it will keep the old one though.
Let's fix those:
const input = document.getElementById('input');
let decimalSeparatorPosition = null;
input.onkeydown = ({ key }) => {
decimalSeparatorPosition = key === '.' || key === ',' ? input.selectionStart : null;
};
input.oninput = (e) => {
const matches = input.value.match(/[\d.,]/g);
if (!matches) return;
const cursorPosition = input.selectionStart - 1;
let hasDecimalSeparator = false;
input.value = matches.filter((d, i) => {
const isDecimalSeparator = d === '.' || d === ',';
if (!isDecimalSeparator) return true;
if (decimalSeparatorPosition !== null && i !== decimalSeparatorPosition) {
// Ignore any decimal separators that are not the one we have just typed:
return false;
}
if (hasDecimalSeparator) {
// We already have one decimal separator, so ignore this one:
return false;
}
// Remember we have just found our first decimal separator:
hasDecimalSeparator = true;
// But keep this one:
return true;
}).join('');
// Keep cursor position:
input.setSelectionRange(cursorPosition + 1, cursorPosition + 1);
};
<input id="input" type="text" />
Note there are still some issues with this:
If you press a key that is filtered out, such as a letter, the cursor will still move one position.
If instead of typing the decimal separator you paste or drop it or some text that contains one or multiple of them, the one that is preserved is still the left-most one, which might not be the new one.
However, this should give you a good starting point to implement what you need.
Also, note e.which
and e.keyCode
are deprecated, so you should use e.key
or e.code
instead. You can check their values using https://keyjs.dev:
Disclaimer: I'm the author.
type="number"
and let the browser decide how a number should be formatted correctly for the given location/culture. – Donate<input type="number">
– Respiratorselect2
is important to include in the question. – Celestine