diff --git a/src/runtime/components/forms/Input.vue b/src/runtime/components/forms/Input.vue
index 8f77e2534b..94e856d4c4 100644
--- a/src/runtime/components/forms/Input.vue
+++ b/src/runtime/components/forms/Input.vue
@@ -14,6 +14,7 @@
v-bind="attrs"
@input="onInput"
@blur="onBlur"
+ @change="onChange"
>
@@ -36,9 +37,10 @@ import { ref, computed, toRef, onMounted, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
+import { defu } from 'defu'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
-import { mergeConfig } from '../../utils'
+import { mergeConfig, looseToNumber } from '../../utils'
import type { NestedKeyOf, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -156,6 +158,10 @@ export default defineComponent({
ui: {
type: Object as PropType>,
default: undefined
+ },
+ modelModifiers: {
+ type: Object as PropType<{ trim?: boolean, lazy?: boolean, number?: boolean }>,
+ default: () => ({})
}
},
emits: ['update:modelValue', 'blur'],
@@ -164,6 +170,8 @@ export default defineComponent({
const { emitFormBlur, emitFormInput, size, color, inputId, name } = useFormGroup(props, config)
+ const modelModifiers = ref(defu({}, props.modelModifiers, { trim: false, lazy: false, number: false }))
+
const input = ref(null)
const autoFocus = () => {
@@ -172,11 +180,40 @@ export default defineComponent({
}
}
- const onInput = (event: InputEvent) => {
- emit('update:modelValue', (event.target as HTMLInputElement).value)
+ // Custom function to handle the v-model properties
+ const updateInput = (value: string) => {
+
+ if (modelModifiers.value.trim) {
+ value = value.trim()
+ }
+
+ if (modelModifiers.value.number || props.type === 'number') {
+ value = looseToNumber(value)
+ }
+
+ emit('update:modelValue', value)
emitFormInput()
}
+ const onInput = (event: InputEvent) => {
+ if (!modelModifiers.value.lazy) {
+ updateInput((event.target as HTMLInputElement).value)
+ }
+ }
+
+ const onChange = (event: InputEvent) => {
+ const value = (event.target as HTMLInputElement).value
+
+ if (modelModifiers.value.lazy) {
+ updateInput(value)
+ }
+
+ // Update trimmed input so that it has same behaviour as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
+ if (modelModifiers.value.trim) {
+ (event.target as HTMLInputElement).value = value.trim()
+ }
+ }
+
const onBlur = (event: FocusEvent) => {
emitFormBlur()
emit('blur', event)
@@ -280,6 +317,7 @@ export default defineComponent({
trailingIconClass,
trailingWrapperIconClass,
onInput,
+ onChange,
onBlur
}
}
diff --git a/src/runtime/components/forms/Textarea.vue b/src/runtime/components/forms/Textarea.vue
index 54cc838b64..d3dff6a31e 100644
--- a/src/runtime/components/forms/Textarea.vue
+++ b/src/runtime/components/forms/Textarea.vue
@@ -14,6 +14,7 @@
v-bind="attrs"
@input="onInput"
@blur="onBlur"
+ @change="onChange"
/>
@@ -22,9 +23,10 @@
import { ref, computed, toRef, watch, onMounted, nextTick, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
+import { defu } from 'defu'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
-import { mergeConfig } from '../../utils'
+import { mergeConfig, looseToNumber } from '../../utils'
import type { NestedKeyOf, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -119,6 +121,10 @@ export default defineComponent({
ui: {
type: Object as PropType>,
default: undefined
+ },
+ modelModifiers: {
+ type: Object as PropType<{ trim?: boolean, lazy?: boolean, number?: boolean }>,
+ default: () => ({})
}
},
emits: ['update:modelValue', 'blur'],
@@ -127,6 +133,8 @@ export default defineComponent({
const { emitFormBlur, emitFormInput, inputId, color, size, name } = useFormGroup(props, config)
+ const modelModifiers = ref(defu({}, props.modelModifiers, { trim: false, lazy: false, number: false }))
+
const textarea = ref(null)
const autoFocus = () => {
@@ -157,11 +165,38 @@ export default defineComponent({
}
}
+ // Custom function to handle the v-model properties
+ const updateInput = (value: string) => {
+ if (modelModifiers.value.trim) {
+ value = value.trim()
+ }
+
+ if (modelModifiers.value.number) {
+ value = looseToNumber(value)
+ }
+
+ emit('update:modelValue', value)
+ emitFormInput()
+ }
+
const onInput = (event: InputEvent) => {
autoResize()
+ if (!modelModifiers.value.lazy) {
+ updateInput((event.target as HTMLInputElement).value)
+ }
+ }
- emit('update:modelValue', (event.target as HTMLInputElement).value)
- emitFormInput()
+ const onChange = (event: InputEvent) => {
+ const value = (event.target as HTMLInputElement).value
+
+ if (modelModifiers.value.lazy) {
+ updateInput(value)
+ }
+
+ // Update trimmed input so that it has same behaviour as native input
+ if (modelModifiers.value.trim) {
+ (event.target as HTMLInputElement).value = value.trim()
+ }
}
const onBlur = (event: FocusEvent) => {
@@ -211,6 +246,7 @@ export default defineComponent({
// eslint-disable-next-line vue/no-dupe-keys
textareaClass,
onInput,
+ onChange,
onBlur
}
}
diff --git a/src/runtime/utils/index.ts b/src/runtime/utils/index.ts
index 9e3e0d0746..1af846261f 100644
--- a/src/runtime/utils/index.ts
+++ b/src/runtime/utils/index.ts
@@ -56,4 +56,13 @@ export function getSlotsChildren (slots: any) {
return children
}
+/**
+ * "123-foo" will be parsed to 123
+ * This is used for the .number modifier in v-model
+ */
+export function looseToNumber (val: any): any {
+ const n = parseFloat(val)
+ return isNaN(n) ? val : n
+}
+
export * from './lodash'