Skip to content

Commit

Permalink
fix(VNumberInput): use VTextField as the base component (#19714)
Browse files Browse the repository at this point in the history
fixes #19659
fixes #19757
  • Loading branch information
yuwu9145 committed May 6, 2024
1 parent 67b30e7 commit 53d6bb9
Showing 1 changed file with 72 additions and 112 deletions.
184 changes: 72 additions & 112 deletions packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx
Expand Up @@ -5,27 +5,24 @@ import './VNumberInput.sass'
import { VBtn } from '../../components/VBtn'
import { VDefaultsProvider } from '../../components/VDefaultsProvider'
import { VDivider } from '../../components/VDivider'
import { filterFieldProps, makeVFieldProps, VField } from '@/components/VField/VField'
import { makeVInputProps, VInput } from '@/components/VInput/VInput'
import { makeVTextFieldProps, VTextField } from '@/components/VTextField/VTextField'

// Composables
import { makeFocusProps, useFocus } from '@/composables/focus'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, ref, watchEffect } from 'vue'
import { clamp, filterInputAttrs, genericComponent, getDecimals, only, propsFactory, useRender } from '@/util'
import { computed, watchEffect } from 'vue'
import { clamp, genericComponent, getDecimals, omit, propsFactory, useRender } from '@/util'

// Types
import type { PropType } from 'vue'
import type { VFieldSlots } from '@/components/VField/VField'
import type { VInputSlots } from '@/components/VInput/VInput'
import type { VTextFieldSlots } from '@/components/VTextField/VTextField'

type ControlSlot = {
click: () => void
}

type VNumberInputSlots = Omit<VInputSlots & VFieldSlots, 'default'> & {
type VNumberInputSlots = Omit<VTextFieldSlots, 'default'> & {
increment: ControlSlot
decrement: ControlSlot
}
Expand All @@ -52,31 +49,7 @@ const makeVNumberInputProps = propsFactory({
default: 1,
},

...only(makeVInputProps(), [
'density',
'disabled',
'focused',
'hideDetails',
'hint',
'label',
'persistentHint',
'readonly',
]),
...only(makeVFieldProps(), [
'baseColor',
'bgColor',
'class',
'color',
'disabled',
'error',
'loading',
'reverse',
'rounded',
'style',
'theme',
'variant',
]),
...makeFocusProps(),
...omit(makeVTextFieldProps(), ['appendInnerIcon', 'prependInnerIcon']),
}, 'VNumberInput')

export const VNumberInput = genericComponent<VNumberInputSlots>()({
Expand All @@ -86,11 +59,6 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({

props: {
...makeVNumberInputProps(),

modelValue: {
type: Number,
default: undefined,
},
},

emits: {
Expand All @@ -99,8 +67,6 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({

setup (props, { attrs, emit, slots }) {
const model = useProxiedModel(props, 'modelValue')
const { isFocused, focus, blur } = useFocus(props)
const inputRef = ref<HTMLInputElement>()

const stepDecimals = computed(() => getDecimals(props.step))
const modelDecimals = computed(() => model.value != null ? getDecimals(model.value) : 0)
Expand All @@ -120,10 +86,6 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
}
})

function onFocus () {
if (!isFocused.value) focus()
}

const controlVariant = computed(() => {
return props.hideInput ? 'stacked' : props.controlVariant
})
Expand Down Expand Up @@ -156,7 +118,7 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({

function onKeydown (e: KeyboardEvent) {
if (
['Enter', 'ArrowLeft', 'ArrowRight', 'Backspace'].includes(e.key) ||
['Enter', 'ArrowLeft', 'ArrowRight', 'Backspace', 'Tab'].includes(e.key) ||
e.ctrlKey
) return

Expand All @@ -177,15 +139,12 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
}
}

function onInput (e: Event) {
const el = e.target as HTMLInputElement
model.value = el.value ? +(el.value) : undefined
function onModelUpdate (v: string) {
model.value = v ? +(v) : undefined
}

useRender(() => {
const fieldProps = filterFieldProps(props)
const [rootAttrs, inputAttrs] = filterInputAttrs(attrs)
const { modelValue: _, ...inputProps } = VInput.filterProps(props)
const { modelValue: _, ...textFieldProps } = VTextField.filterProps(props)

function controlNode () {
const defaultHeight = controlVariant.value === 'stacked' ? 'auto' : '100%'
Expand All @@ -201,6 +160,7 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
name="decrement-btn"
icon="$expand"
size="small"
tabindex="-1"
onClick={ onClickDown }
/>
) : (
Expand Down Expand Up @@ -236,6 +196,7 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
icon="$collapse"
onClick={ onClickUp }
size="small"
tabindex="-1"
/>
) : (
<VDefaultsProvider
Expand All @@ -262,8 +223,53 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
return !props.hideInput && !props.inset ? <VDivider vertical /> : undefined
}

const appendInnerControl =
controlVariant.value === 'split'
? (
<div class="v-number-input__control">
<VDivider vertical />

<VBtn
flat
height="100%"
icon="$plus"
tile
tabindex="-1"
onClick={ onClickUp }
/>
</div>
) : (!props.reverse
? <>{ dividerNode() }{ controlNode() }</>
: undefined)

const hasAppendInner = slots['append-inner'] || appendInnerControl

const prependInnerControl =
controlVariant.value === 'split'
? (
<div class="v-number-input__control">
<VBtn
flat
height="100%"
icon="$minus"
tile
tabindex="-1"
onClick={ onClickDown }
/>

<VDivider vertical />
</div>
) : (props.reverse
? <>{ controlNode() }{ dividerNode() }</>
: undefined)

const hasPrependInner = slots['prepend-inner'] || prependInnerControl

return (
<VInput
<VTextField
modelValue={ model.value }
onUpdate:modelValue={ onModelUpdate }
onKeydown={ onKeydown }
class={[
'v-number-input',
{
Expand All @@ -276,71 +282,25 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
},
props.class,
]}
{ ...rootAttrs }
{ ...inputProps }
focused={ isFocused.value }
{ ...textFieldProps }
style={ props.style }
>
{{
...slots,
default: () => (
<VField
{ ...fieldProps }
active
focused={ isFocused.value }
>
{{
...slots,
default: ({
props: { class: fieldClass, ...slotProps },
}) => (
<input
ref={ inputRef }
type="text"
value={ model.value }
onInput={ onInput }
onKeydown={ onKeydown }
class={ fieldClass }
onFocus={ onFocus }
onBlur={ blur }
{ ...inputAttrs }
/>
),
'append-inner': controlVariant.value === 'split' ? () => (
<div class="v-number-input__control">
<VDivider vertical />

<VBtn
flat
height="100%"
icon="$plus"
tile
onClick={ onClickUp }
/>
</div>
) : (!props.reverse
? () => <>{ dividerNode() }{ controlNode() }</>
: undefined),
'prepend-inner': controlVariant.value === 'split' ? () => (
<div class="v-number-input__control">
<VBtn
flat
height="100%"
icon="$minus"
tile
onClick={ onClickDown }
/>

<VDivider vertical />
</div>
) : (props.reverse
? () => <>{ controlNode() }{ dividerNode() }</>
: undefined),
}}
</VField>
),
'append-inner': hasAppendInner ? (...args) => (
<>
{ slots['append-inner']?.(...args) }
{ appendInnerControl }
</>
) : undefined,
'prepend-inner': hasPrependInner ? (...args) => (
<>
{ prependInnerControl }
{ slots['prepend-inner']?.(...args) }
</>
) : undefined,
}}
</VInput>
</VTextField>
)
})
},
Expand Down

0 comments on commit 53d6bb9

Please sign in to comment.