mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-07-31 10:47:57 -05:00
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.
106 lines
2.0 KiB
Vue
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>
|