Files
vikunja/frontend/src/components/input/FormField.vue
T
kolaente 5ca600506b fix(frontend): expose focus method on FormField component
Components using FormField with a ref need to call .focus() on it.
Without exposing the method, these calls would fail since the ref
points to the component instance, not the underlying input element.
2026-01-10 21:59:06 +01:00

106 lines
2.0 KiB
Vue

<script setup lang="ts">
import {computed, useSlots, useId, ref} from 'vue'
interface Props {
modelValue?: string | number
label?: string
error?: string | null
id?: string
disabled?: boolean
loading?: boolean
}
const props = defineProps<Props>()
defineEmits<{
'update:modelValue': [value: string]
}>()
defineOptions({
inheritAttrs: false,
})
const slots = useSlots()
const generatedId = useId()
const inputId = computed(() => props.id ?? generatedId)
const hasAddon = computed(() => !!slots.addon)
const fieldClasses = computed(() => [
'field',
{'has-addons': hasAddon.value},
])
const controlClasses = computed(() => [
'control',
{'is-expanded': hasAddon.value},
])
const inputClasses = computed(() => [
'input',
{
'disabled': props.disabled,
'is-loading': props.loading,
},
])
// Only bind value when modelValue is explicitly provided (not undefined)
// This allows the component to be used without v-model for native input behavior
const inputBindings = computed(() => {
const bindings: Record<string, unknown> = {}
if (props.modelValue !== undefined) {
bindings.value = props.modelValue
}
return bindings
})
// Expose input element for direct access (needed for browser autofill workarounds)
const inputRef = ref<HTMLInputElement | null>(null)
defineExpose({
get value() {
return inputRef.value?.value ?? ''
},
focus() {
inputRef.value?.focus()
},
})
</script>
<template>
<div :class="fieldClasses">
<label
v-if="label"
:for="inputId"
class="label"
>
{{ label }}
</label>
<div :class="controlClasses">
<slot>
<input
:id="inputId"
ref="inputRef"
v-bind="{ ...$attrs, ...inputBindings }"
:class="inputClasses"
:disabled="disabled || undefined"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
>
</slot>
</div>
<div
v-if="$slots.addon"
class="control"
>
<slot name="addon" />
</div>
<p
v-if="error"
class="help is-danger"
>
{{ error }}
</p>
</div>
</template>