mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat(Input/Textarea): add default-value prop (#4404)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import type { AcceptableValue, ComponentConfig } from '../types/utils'
|
|||||||
|
|
||||||
type Input = ComponentConfig<typeof theme, AppConfig, 'input'>
|
type Input = ComponentConfig<typeof theme, AppConfig, 'input'>
|
||||||
|
|
||||||
export interface InputProps extends UseComponentIconsProps {
|
export interface InputProps<T extends AcceptableValue = AcceptableValue> extends UseComponentIconsProps {
|
||||||
/**
|
/**
|
||||||
* The element or component this component should render as.
|
* The element or component this component should render as.
|
||||||
* @defaultValue 'div'
|
* @defaultValue 'div'
|
||||||
@@ -38,6 +38,8 @@ export interface InputProps extends UseComponentIconsProps {
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
/** Highlight the ring color like a focus state. */
|
/** Highlight the ring color like a focus state. */
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
|
modelValue?: T
|
||||||
|
defaultValue?: T
|
||||||
modelModifiers?: {
|
modelModifiers?: {
|
||||||
string?: boolean
|
string?: boolean
|
||||||
number?: boolean
|
number?: boolean
|
||||||
@@ -65,6 +67,7 @@ export interface InputSlots {
|
|||||||
<script setup lang="ts" generic="T extends AcceptableValue">
|
<script setup lang="ts" generic="T extends AcceptableValue">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { Primitive } from 'reka-ui'
|
import { Primitive } from 'reka-ui'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
import { useAppConfig } from '#imports'
|
import { useAppConfig } from '#imports'
|
||||||
import { useButtonGroup } from '../composables/useButtonGroup'
|
import { useButtonGroup } from '../composables/useButtonGroup'
|
||||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||||
@@ -76,7 +79,7 @@ import UAvatar from './Avatar.vue'
|
|||||||
|
|
||||||
defineOptions({ inheritAttrs: false })
|
defineOptions({ inheritAttrs: false })
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputProps>(), {
|
const props = withDefaults(defineProps<InputProps<T>>(), {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
autofocusDelay: 0
|
autofocusDelay: 0
|
||||||
@@ -84,13 +87,12 @@ const props = withDefaults(defineProps<InputProps>(), {
|
|||||||
const emits = defineEmits<InputEmits<T>>()
|
const emits = defineEmits<InputEmits<T>>()
|
||||||
const slots = defineSlots<InputSlots>()
|
const slots = defineSlots<InputSlots>()
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
const modelValue = useVModel<InputProps<T>, 'modelValue', 'update:modelValue'>(props, 'modelValue', emits, { defaultValue: props.defaultValue })
|
||||||
const [modelValue, modelModifiers] = defineModel<T>()
|
|
||||||
|
|
||||||
const appConfig = useAppConfig() as Input['AppConfig']
|
const appConfig = useAppConfig() as Input['AppConfig']
|
||||||
|
|
||||||
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
|
const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps<T>>(props, { deferInputValidation: true })
|
||||||
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
|
const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps<T>>(props)
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
||||||
|
|
||||||
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
|
||||||
@@ -111,15 +113,15 @@ const inputRef = ref<HTMLInputElement | null>(null)
|
|||||||
|
|
||||||
// Custom function to handle the v-model properties
|
// Custom function to handle the v-model properties
|
||||||
function updateInput(value: string | null) {
|
function updateInput(value: string | null) {
|
||||||
if (modelModifiers.trim) {
|
if (props.modelModifiers?.trim) {
|
||||||
value = value?.trim() ?? null
|
value = value?.trim() ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelModifiers.number || props.type === 'number') {
|
if (props.modelModifiers?.number || props.type === 'number') {
|
||||||
value = looseToNumber(value)
|
value = looseToNumber(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelModifiers.nullify) {
|
if (props.modelModifiers?.nullify) {
|
||||||
value ||= null
|
value ||= null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@ function updateInput(value: string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onInput(event: Event) {
|
function onInput(event: Event) {
|
||||||
if (!modelModifiers.lazy) {
|
if (!props.modelModifiers?.lazy) {
|
||||||
updateInput((event.target as HTMLInputElement).value)
|
updateInput((event.target as HTMLInputElement).value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,12 +138,12 @@ function onInput(event: Event) {
|
|||||||
function onChange(event: Event) {
|
function onChange(event: Event) {
|
||||||
const value = (event.target as HTMLInputElement).value
|
const value = (event.target as HTMLInputElement).value
|
||||||
|
|
||||||
if (modelModifiers.lazy) {
|
if (props.modelModifiers?.lazy) {
|
||||||
updateInput(value)
|
updateInput(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update trimmed input so that it has same behavior as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
// Update trimmed input so that it has same behavior as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
||||||
if (modelModifiers.trim) {
|
if (props.modelModifiers?.trim) {
|
||||||
(event.target as HTMLInputElement).value = value.trim()
|
(event.target as HTMLInputElement).value = value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type Textarea = ComponentConfig<typeof theme, AppConfig, 'textarea'>
|
|||||||
|
|
||||||
type TextareaValue = string | number | null
|
type TextareaValue = string | number | null
|
||||||
|
|
||||||
export interface TextareaProps extends UseComponentIconsProps {
|
export interface TextareaProps<T extends TextareaValue = TextareaValue> extends UseComponentIconsProps {
|
||||||
/**
|
/**
|
||||||
* The element or component this component should render as.
|
* The element or component this component should render as.
|
||||||
* @defaultValue 'div'
|
* @defaultValue 'div'
|
||||||
@@ -41,8 +41,11 @@ export interface TextareaProps extends UseComponentIconsProps {
|
|||||||
maxrows?: number
|
maxrows?: number
|
||||||
/** Highlight the ring color like a focus state. */
|
/** Highlight the ring color like a focus state. */
|
||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
|
modelValue?: T
|
||||||
|
defaultValue?: T
|
||||||
modelModifiers?: {
|
modelModifiers?: {
|
||||||
string?: boolean
|
string?: boolean
|
||||||
|
number?: boolean
|
||||||
trim?: boolean
|
trim?: boolean
|
||||||
lazy?: boolean
|
lazy?: boolean
|
||||||
nullify?: boolean
|
nullify?: boolean
|
||||||
@@ -67,6 +70,7 @@ export interface TextareaSlots {
|
|||||||
<script setup lang="ts" generic="T extends TextareaValue">
|
<script setup lang="ts" generic="T extends TextareaValue">
|
||||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||||
import { Primitive } from 'reka-ui'
|
import { Primitive } from 'reka-ui'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
import { useAppConfig } from '#imports'
|
import { useAppConfig } from '#imports'
|
||||||
import { useComponentIcons } from '../composables/useComponentIcons'
|
import { useComponentIcons } from '../composables/useComponentIcons'
|
||||||
import { useFormField } from '../composables/useFormField'
|
import { useFormField } from '../composables/useFormField'
|
||||||
@@ -77,7 +81,7 @@ import UAvatar from './Avatar.vue'
|
|||||||
|
|
||||||
defineOptions({ inheritAttrs: false })
|
defineOptions({ inheritAttrs: false })
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TextareaProps>(), {
|
const props = withDefaults(defineProps<TextareaProps<T>>(), {
|
||||||
rows: 3,
|
rows: 3,
|
||||||
maxrows: 0,
|
maxrows: 0,
|
||||||
autofocusDelay: 0,
|
autofocusDelay: 0,
|
||||||
@@ -86,12 +90,11 @@ const props = withDefaults(defineProps<TextareaProps>(), {
|
|||||||
const emits = defineEmits<TextareaEmits<T>>()
|
const emits = defineEmits<TextareaEmits<T>>()
|
||||||
const slots = defineSlots<TextareaSlots>()
|
const slots = defineSlots<TextareaSlots>()
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
const modelValue = useVModel<TextareaProps<T>, 'modelValue', 'update:modelValue'>(props, 'modelValue', emits, { defaultValue: props.defaultValue })
|
||||||
const [modelValue, modelModifiers] = defineModel<T>()
|
|
||||||
|
|
||||||
const appConfig = useAppConfig() as Textarea['AppConfig']
|
const appConfig = useAppConfig() as Textarea['AppConfig']
|
||||||
|
|
||||||
const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps>(props, { deferInputValidation: true })
|
const { emitFormFocus, emitFormBlur, emitFormInput, emitFormChange, size, color, id, name, highlight, disabled, ariaAttrs } = useFormField<TextareaProps<T>>(props, { deferInputValidation: true })
|
||||||
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
|
||||||
|
|
||||||
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.textarea || {}) })({
|
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.textarea || {}) })({
|
||||||
@@ -109,15 +112,15 @@ const textareaRef = ref<HTMLTextAreaElement | null>(null)
|
|||||||
|
|
||||||
// Custom function to handle the v-model properties
|
// Custom function to handle the v-model properties
|
||||||
function updateInput(value: string | null) {
|
function updateInput(value: string | null) {
|
||||||
if (modelModifiers.trim) {
|
if (props.modelModifiers?.trim) {
|
||||||
value = value?.trim() ?? null
|
value = value?.trim() ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelModifiers.number) {
|
if (props.modelModifiers?.number) {
|
||||||
value = looseToNumber(value)
|
value = looseToNumber(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelModifiers.nullify) {
|
if (props.modelModifiers?.nullify) {
|
||||||
value ||= null
|
value ||= null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +131,7 @@ function updateInput(value: string | null) {
|
|||||||
function onInput(event: Event) {
|
function onInput(event: Event) {
|
||||||
autoResize()
|
autoResize()
|
||||||
|
|
||||||
if (!modelModifiers.lazy) {
|
if (!props.modelModifiers?.lazy) {
|
||||||
updateInput((event.target as HTMLInputElement).value)
|
updateInput((event.target as HTMLInputElement).value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,12 +139,12 @@ function onInput(event: Event) {
|
|||||||
function onChange(event: Event) {
|
function onChange(event: Event) {
|
||||||
const value = (event.target as HTMLInputElement).value
|
const value = (event.target as HTMLInputElement).value
|
||||||
|
|
||||||
if (modelModifiers.lazy) {
|
if (props.modelModifiers?.lazy) {
|
||||||
updateInput(value)
|
updateInput(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update trimmed textarea so that it has same behavior as native textarea https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
// Update trimmed textarea so that it has same behavior as native textarea https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
|
||||||
if (modelModifiers.trim) {
|
if (props.modelModifiers?.trim) {
|
||||||
(event.target as HTMLInputElement).value = value.trim()
|
(event.target as HTMLInputElement).value = value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user