mirror of
https://github.com/tenrok/maska.git
synced 2026-05-24 14:04:08 +03:00
feat: number mask
This commit is contained in:
@@ -1,13 +1,21 @@
|
||||
import { MaskTokens, tokens } from './tokens'
|
||||
import { processNumber } from './number'
|
||||
|
||||
export type MaskType = string | string[] | ((input: string) => string) | null
|
||||
|
||||
interface MaskNumber {
|
||||
locale?: string
|
||||
fraction?: number
|
||||
unsigned?: boolean
|
||||
}
|
||||
|
||||
export interface MaskOptions {
|
||||
mask?: MaskType
|
||||
tokens?: MaskTokens
|
||||
tokensReplace?: boolean
|
||||
eager?: boolean
|
||||
reversed?: boolean
|
||||
number?: MaskNumber
|
||||
}
|
||||
|
||||
export class Mask {
|
||||
@@ -109,6 +117,8 @@ export class Mask {
|
||||
}
|
||||
|
||||
private process (value: string, maskRaw: string | null, masked = true): string {
|
||||
if (this.opts.number != null) return processNumber(value, masked, this.opts)
|
||||
|
||||
if (maskRaw == null) return value
|
||||
|
||||
const memoKey = `v=${value},mr=${maskRaw},m=${masked ? 1 : 0}`
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { MaskOptions } from './mask'
|
||||
|
||||
const prepare = (input: string, group: string, decimal: string): string =>
|
||||
input.replaceAll(group, '').replace(decimal, '.').replace('..', '.').replace(/[^.\d]/g, '')
|
||||
|
||||
const createFormatter = (min: number, max: number, opts: MaskOptions): Intl.NumberFormat =>
|
||||
new Intl.NumberFormat(opts.number?.locale ?? 'en', {
|
||||
minimumFractionDigits: min,
|
||||
maximumFractionDigits: max,
|
||||
// @ts-expect-error
|
||||
roundingMode: 'trunc'
|
||||
})
|
||||
|
||||
export const processNumber = (value: string, masked = true, opts: MaskOptions): string => {
|
||||
const sign = opts.number?.unsigned == null && value.startsWith('-') ? '-' : ''
|
||||
const fraction = opts.number?.fraction ?? 0
|
||||
|
||||
let formatter = createFormatter(0, fraction, opts)
|
||||
const parts = formatter.formatToParts(1000.12)
|
||||
const group = parts.find((part) => part.type === 'group')?.value ?? ' '
|
||||
const decimal = parts.find((part) => part.type === 'decimal')?.value ?? '.'
|
||||
const float = prepare(value, group, decimal)
|
||||
|
||||
if (float === '' || Number.isNaN(float)) return sign
|
||||
|
||||
// allow zero at the end
|
||||
const floatParts = float.split('.')
|
||||
if (floatParts[1] != null && floatParts[1].length >= 1) {
|
||||
const min = floatParts[1].length <= fraction ? floatParts[1].length : fraction
|
||||
formatter = createFormatter(min, fraction, opts)
|
||||
}
|
||||
|
||||
let result = formatter.format(parseFloat(float))
|
||||
|
||||
if (!masked) {
|
||||
result = prepare(result, group, decimal)
|
||||
} else if (fraction > 0 && float.endsWith('.') && !float.slice(0, -1).includes('.')) {
|
||||
// if ends with decimal separator
|
||||
result += decimal
|
||||
}
|
||||
|
||||
return sign + result
|
||||
}
|
||||
@@ -25,6 +25,20 @@ export const parseInput = (
|
||||
opts.tokens = parseTokens(input.dataset.maskaTokens)
|
||||
}
|
||||
|
||||
const number: MaskOptions['number'] = {}
|
||||
if (input.dataset.maskaNumberLocale != null) {
|
||||
number.locale = input.dataset.maskaNumberLocale
|
||||
}
|
||||
if (input.dataset.maskaNumberFraction != null) {
|
||||
number.fraction = parseInt(input.dataset.maskaNumberFraction)
|
||||
}
|
||||
if (input.dataset.maskaNumberUnsigned != null) {
|
||||
number.unsigned = parseBool(input.dataset.maskaNumberUnsigned)
|
||||
}
|
||||
if (Object.values(number).length > 0) {
|
||||
opts.number = number
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user