Skip to content

Instantly share code, notes, and snippets.

@jagoncalves14
Last active June 6, 2024 18:35
Show Gist options
  • Save jagoncalves14/c6ef4ff56155a093bd5978d7e05ed8e8 to your computer and use it in GitHub Desktop.
Save jagoncalves14/c6ef4ff56155a093bd5978d7e05ed8e8 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Number Input Mask</title>
</head>
<body>
<p>EN/US</p>
<input type="text" class="numberInput" placeholder="1,123.0000" locale="en-US">
<br />
<p>GB</p>
<input type="text" class="numberInput" placeholder="1,123.0000" locale="en-GB">
<br />
<p>BR FR</p>
<input type="text" class="numberInput" placeholder="1,123.0000" locale="br-FR">
<br />
<p>FR</p>
<input type="text" class="numberInput" placeholder="1 123,0000" locale="fr-FR" value="1123">
<br />
<p>PT</p>
<input type="text" class="numberInput" placeholder="1 123,0000" locale="pt-PT" value="1123">
<br />
<p>SE</p>
<input type="text" class="numberInput" placeholder="1 123,0000" locale="sv-SE">
<br />
<p>FI</p>
<input type="text" class="numberInput" placeholder="1 123,0000" locale="fi-FI">
<br />
<p>DE</p>
<input type="text" class="numberInput" placeholder="1.123,0000" locale="de-DE">
<script>
function getDecimalSeparator(locale) {
const number = 1.1 // A number with a decimal part
const formattedNumber = new Intl.NumberFormat(locale).formatToParts(number)
// Find the decimal separator
const decimalSeparator = formattedNumber.find(part => part.type === 'decimal').value
return decimalSeparator
}
function formatInputValue(value, decimalSeparator, locale) {
const fractionDigits = value.split(decimalSeparator)[1]?.length || ''
const numberToFormat = useCurrencyLocaleToMajorUnit(value, locale)
if (value.endsWith(`${decimalSeparator}`)) {
return new Intl.NumberFormat(locale).format(numberToFormat) + `${decimalSeparator}`
}
if (fractionDigits) {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: fractionDigits
}).format(numberToFormat)
}
return new Intl.NumberFormat(locale).format(numberToFormat)
}
function applyInputMask(inputElement) {
if (!inputElement.value?.length) return
const locale = inputElement.getAttribute('locale')
const decimalSeparator = getDecimalSeparator(locale)
const cursorPosition = inputElement.selectionStart // Store cursor position
let value = inputElement.value
// Remove non-numeric characters except dot
value = value.replace(new RegExp(`[^\\d${decimalSeparator}]`, 'g'), '')
// Allow only one dot
const separatorIndex = value.indexOf(`${decimalSeparator}`)
if (separatorIndex !== -1 && value.indexOf(`${decimalSeparator}`, separatorIndex + 1) !== -1) {
// If there's more than one dot, remove extras
value = value.slice(0, separatorIndex + 1) + value.slice(separatorIndex + 1).replace(
new RegExp(`\\${decimalSeparator}g`),
''
)
}
// Allow maximum four digits after the dot
const parts = value.split(`${decimalSeparator}`)
if (parts[1] && parts[1].length > 4) {
parts[1] = parts[1].slice(0, 4)
value = parts.join(`${decimalSeparator}`)
}
// Store old value for cursor positioning
const oldValue = inputElement.value
const newInputValue = formatInputValue(value, decimalSeparator, locale)
inputElement.value = newInputValue
// Adjust cursor position after adding separators
const separatorAdded = newInputValue.length - oldValue.length
const newPosition = cursorPosition + separatorAdded
inputElement.setSelectionRange(newPosition, newPosition)
}
function applyFormatOnBlur(inputElement) {
const locale = inputElement.getAttribute('locale')
const decimalSeparator = getDecimalSeparator(locale)
let numberInputValue = inputElement.value
if (!numberInputValue?.length) return
if (numberInputValue.endsWith(`${decimalSeparator}`)) {
numberInputValue += 0
}
const numberFormat = useCurrencyLocaleToMajorUnit(numberInputValue, locale)
inputElement.value = new Intl.NumberFormat(locale, {
minimumFractionDigits: 4
}).format(numberFormat)
}
function useCurrencyLocaleToMajorUnit(value, locale) {
// Obtain information about the grouping and decimal separators for the current locale
const parts = new Intl.NumberFormat(locale).formatToParts(1111.1);
const group = parts.find((part) => part.type === 'group')?.value
const decimal = parts.find((part) => part.type === 'decimal')?.value
// Remove all occurrences of the group separator
let normalizedValue = value.replace(new RegExp(`\\${group}`, 'g'), '');
// Replace the decimal separator with a dot
normalizedValue = normalizedValue.replace(
new RegExp(`\\${decimal}`, 'g'),
'.'
)
// Replace the narrow no-break space with a regular space
normalizedValue = normalizedValue.replace(/\u202F/g, ' ');
// Remove any currency symbols and non-numeric characters except for the decimal point and space
normalizedValue = normalizedValue.replace(/[^\d. -]/g, '');
// Convert the resulting string into a number
const result = parseFloat(normalizedValue.replace(/\s+/g, '')) || 0;
return isNaN(result) ? 0 : result;
}
document.addEventListener('DOMContentLoaded', function () {
const numberInput = document.querySelectorAll('.numberInput')
numberInput.forEach((el) => {
if (el.value) applyInputMask(el)
el.addEventListener('input', (event) => applyInputMask(event.target))
el.addEventListener('blur', (event) => applyFormatOnBlur(event.target))
})
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment