Skip to content

Commit 6bb6002

Browse files
committed
feat(VNumberInput): add grouping prop
1 parent 859e78c commit 6bb6002

File tree

5 files changed

+47
-28
lines changed

5 files changed

+47
-28
lines changed

packages/api-generator/src/locale/en/VNumberInput.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"props": {
33
"controlVariant": "The color of the control. It defaults to the value of `variant` prop.",
44
"decimalSeparator": "Expects single character to be used as decimal separator.",
5+
"grouping ": "Enables grouping using current locale or specific character passed to `group-separator`.",
6+
"group-separator ": "Expects single character to be used for grouping digits in large numbers.",
57
"hideInput": "Hide the input field.",
68
"inset": "Applies an indentation to the dividers used in the stepper buttons.",
79
"max": "Specifies the maximum allowable value for the input.",

packages/docs/src/data/new-in.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@
178178
},
179179
"VNumberInput": {
180180
"props": {
181-
"autocomplete": "3.10.0"
181+
"autocomplete": "3.10.0",
182+
"grouping": "3.11.0",
183+
"groupingSeparator": "3.11.0"
182184
}
183185
},
184186
"VOverlay": {

packages/vuetify/src/components/VNumberInput/VNumberInput.tsx

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ const makeVNumberInputProps = propsFactory({
6868
type: String,
6969
validator: (v: any) => !v || v.length === 1,
7070
},
71+
grouping: {
72+
type: [Boolean, String] as PropType<'always' | 'auto' | 'min2' | boolean>,
73+
default: false,
74+
},
75+
groupSeparator: {
76+
type: String,
77+
validator: (v: any) => !v || v.length === 1,
78+
},
7179

7280
...omit(makeVTextFieldProps(), ['modelValue', 'validationValue']),
7381
}, 'VNumberInput')
@@ -95,32 +103,23 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
95103

96104
const isFocused = shallowRef(props.focused)
97105

98-
const { decimalSeparator: decimalSeparatorFromLocale } = useLocale()
106+
const {
107+
current: locale,
108+
decimalSeparator: decimalSeparatorFromLocale,
109+
numericGroupSeparator: numericGroupSeparatorFromLocale,
110+
} = useLocale()
99111
const decimalSeparator = computed(() => props.decimalSeparator?.[0] || decimalSeparatorFromLocale.value)
100-
101-
function correctPrecision (val: number, precision = props.precision, trim = true) {
102-
const fixed = precision == null
103-
? String(val)
104-
: val.toFixed(precision)
105-
106-
if (isFocused.value && trim) {
107-
return Number(fixed).toString() // trim zeros
108-
.replace('.', decimalSeparator.value)
109-
}
110-
111-
if (props.minFractionDigits === null || (precision !== null && precision < props.minFractionDigits)) {
112-
return fixed.replace('.', decimalSeparator.value)
113-
}
114-
115-
let [baseDigits, fractionDigits] = fixed.split('.')
116-
117-
fractionDigits = (fractionDigits ?? '').padEnd(props.minFractionDigits, '0')
118-
.replace(new RegExp(`(?<=\\d{${props.minFractionDigits}})0+$`, 'g'), '')
119-
120-
return [
121-
baseDigits,
122-
fractionDigits,
123-
].filter(Boolean).join(decimalSeparator.value)
112+
const groupSeparator = computed(() => props.groupSeparator?.[0] || numericGroupSeparatorFromLocale.value)
113+
114+
function correctPrecision (val: number, precision?: number | null, trim = true) {
115+
precision ??= isFocused.value && trim ? undefined : props.precision ?? undefined
116+
return new Intl.NumberFormat(locale.value, {
117+
minimumFractionDigits: props.minFractionDigits ?? precision,
118+
maximumFractionDigits: precision,
119+
useGrouping: props.grouping,
120+
}).format(val)
121+
.replaceAll(numericGroupSeparatorFromLocale.value, groupSeparator.value)
122+
.replace(decimalSeparatorFromLocale.value, decimalSeparator.value)
124123
}
125124

126125
const model = useProxiedModel(props, 'modelValue', null,
@@ -154,7 +153,10 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
154153
_inputText.value = null
155154
return
156155
}
157-
const parsedValue = Number(val.replace(decimalSeparator.value, '.'))
156+
const parsedValue = Number(val
157+
.replaceAll(groupSeparator.value, '')
158+
.replace(decimalSeparator.value, '.')
159+
)
158160
if (!isNaN(parsedValue) && parsedValue <= props.max && parsedValue >= props.min) {
159161
model.value = parsedValue
160162
_inputText.value = val
@@ -315,7 +317,11 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
315317
if (controlsDisabled.value) return
316318
if (!vTextFieldRef.value) return
317319
const actualText = vTextFieldRef.value.value
318-
const parsedValue = Number(actualText.replace(decimalSeparator.value, '.'))
320+
const parsedValue = Number(actualText
321+
.replaceAll(groupSeparator.value, '')
322+
.replace(decimalSeparator.value, '.')
323+
)
324+
console.log('parsed value 2:', actualText, groupSeparator.value, parsedValue)
319325
if (actualText && !isNaN(parsedValue)) {
320326
inputText.value = correctPrecision(clamp(parsedValue, props.min, props.max))
321327
} else {

packages/vuetify/src/composables/locale.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface LocaleMessages {
1111

1212
export interface LocaleOptions {
1313
decimalSeparator?: string
14+
numericGroupSeparator?: string
1415
messages?: LocaleMessages
1516
locale?: string
1617
fallback?: string
@@ -20,6 +21,7 @@ export interface LocaleOptions {
2021
export interface LocaleInstance {
2122
name: string
2223
decimalSeparator: ShallowRef<string>
24+
numericGroupSeparator: ShallowRef<string>
2325
messages: Ref<LocaleMessages>
2426
current: Ref<string>
2527
fallback: Ref<string>

packages/vuetify/src/locale/adapters/vuetify.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ function inferDecimalSeparator (current: Ref<string>, fallback: Ref<string>) {
6868
return format(0.1).includes(',') ? ',' : '.'
6969
}
7070

71+
function inferNumericGroupSeparator (current: Ref<string>, fallback: Ref<string>) {
72+
const format = createNumberFunction(current, fallback)
73+
return format(10000).at(2)!
74+
}
75+
7176
function useProvided <T> (props: any, prop: string, provided: Ref<T>) {
7277
const internal = useProxiedModel(props, prop, props[prop] ?? provided.value)
7378

@@ -95,6 +100,7 @@ function createProvideFunction (state: { current: Ref<string>, fallback: Ref<str
95100
fallback,
96101
messages,
97102
decimalSeparator: toRef(() => inferDecimalSeparator(current, fallback)),
103+
numericGroupSeparator: toRef(() => inferNumericGroupSeparator(current, fallback)),
98104
t: createTranslateFunction(current, fallback, messages),
99105
n: createNumberFunction(current, fallback),
100106
provide: createProvideFunction({ current, fallback, messages }),
@@ -113,6 +119,7 @@ export function createVuetifyAdapter (options?: LocaleOptions): LocaleInstance {
113119
fallback,
114120
messages,
115121
decimalSeparator: toRef(() => options?.decimalSeparator ?? inferDecimalSeparator(current, fallback)),
122+
numericGroupSeparator: toRef(() => options?.numericGroupSeparator ?? inferNumericGroupSeparator(current, fallback)),
116123
t: createTranslateFunction(current, fallback, messages),
117124
n: createNumberFunction(current, fallback),
118125
provide: createProvideFunction({ current, fallback, messages }),

0 commit comments

Comments
 (0)