2023-01-25 20:03:22 +00:00
|
|
|
<template>
|
|
|
|
<div class="flex flex-col space-y-1">
|
|
|
|
<label
|
|
|
|
:for="forId"
|
|
|
|
:class="[
|
|
|
|
'block text-sm font-medium transition-colors duration-200',
|
|
|
|
errors.length ? 'text-red-500' : 'text-gray-700',
|
|
|
|
]"
|
|
|
|
>{{ label }}</label
|
|
|
|
>
|
2023-01-26 16:15:59 +00:00
|
|
|
<slot
|
|
|
|
name="input"
|
|
|
|
:for-id="forId"
|
2023-01-25 20:03:22 +00:00
|
|
|
:type="type"
|
|
|
|
:id="forId"
|
2023-01-26 16:15:59 +00:00
|
|
|
:fieldName="name"
|
2023-01-25 20:03:22 +00:00
|
|
|
:value="value"
|
|
|
|
:placeholder="placeholder"
|
2023-01-26 16:15:59 +00:00
|
|
|
:invalid="!!errors?.length"
|
|
|
|
:onInput="onInput"
|
|
|
|
:onChange="onChange"
|
|
|
|
:onFocus="onFocus"
|
|
|
|
:onBlur="onBlur"
|
|
|
|
:setValue="setValue"
|
|
|
|
>
|
2023-01-26 18:21:41 +00:00
|
|
|
<input
|
2023-01-26 16:15:59 +00:00
|
|
|
:type="type"
|
|
|
|
:id="forId"
|
|
|
|
:name="name"
|
|
|
|
:value="value"
|
2023-01-26 18:21:41 +00:00
|
|
|
:class="inputClass"
|
2023-01-26 16:15:59 +00:00
|
|
|
:placeholder="placeholder"
|
2023-01-26 18:21:41 +00:00
|
|
|
:disabled="disabled"
|
2023-01-27 16:27:14 +00:00
|
|
|
:readonly="readonly"
|
2023-01-26 16:15:59 +00:00
|
|
|
@input="onInput"
|
|
|
|
@change="onChange"
|
|
|
|
@focus="onFocus"
|
|
|
|
@blur="onBlur"
|
|
|
|
/>
|
|
|
|
</slot>
|
2023-01-25 20:03:22 +00:00
|
|
|
<slot />
|
|
|
|
<span
|
|
|
|
class="text-sm text-red-500"
|
|
|
|
v-for="error of errors"
|
|
|
|
aria-live="assertive"
|
|
|
|
>{{ error }}</span
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2023-01-26 16:15:59 +00:00
|
|
|
import set from 'lodash.set';
|
|
|
|
import get from 'lodash.get';
|
|
|
|
import { computed, ref, Ref } from 'vue';
|
2023-01-25 20:03:22 +00:00
|
|
|
import { inject } from 'vue';
|
2023-01-27 16:27:14 +00:00
|
|
|
import { FormData, FormErrors, InputType } from './form.types';
|
2023-01-25 20:03:22 +00:00
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
defineProps<{
|
2023-01-27 16:27:14 +00:00
|
|
|
type?: InputType;
|
|
|
|
forIdPrefix?: string;
|
2023-01-25 20:03:22 +00:00
|
|
|
label: string;
|
|
|
|
name: string;
|
2023-01-26 18:21:41 +00:00
|
|
|
disabled?: boolean;
|
2023-01-27 16:27:14 +00:00
|
|
|
readonly?: boolean;
|
2023-01-25 20:03:22 +00:00
|
|
|
placeholder?: string;
|
|
|
|
}>(),
|
|
|
|
{
|
|
|
|
type: 'text',
|
2023-01-26 18:21:41 +00:00
|
|
|
disabled: false,
|
2023-01-27 16:27:14 +00:00
|
|
|
readonly: false,
|
2023-01-25 20:03:22 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
(e: 'change', value: unknown): void;
|
|
|
|
(e: 'focus'): void;
|
|
|
|
(e: 'blur'): void;
|
|
|
|
}>();
|
|
|
|
|
|
|
|
const formData = inject<Ref<FormData>>('formData');
|
|
|
|
const formErrors = inject<Ref<FormErrors>>('formErrors');
|
2023-01-26 16:15:59 +00:00
|
|
|
const formGroup = inject<Ref<string>>('formGroup', ref(''));
|
2023-01-25 20:03:22 +00:00
|
|
|
const fieldChange = inject<(field: string) => void>('fieldChange');
|
2023-01-26 16:15:59 +00:00
|
|
|
|
|
|
|
const fieldName = computed(() =>
|
|
|
|
formGroup?.value ? `${formGroup.value}.${props.name}` : props.name
|
|
|
|
);
|
|
|
|
|
2023-01-27 16:27:14 +00:00
|
|
|
const forId = computed(
|
|
|
|
() => `${props.forIdPrefix || 'form'}-${fieldName.value}`
|
|
|
|
);
|
2023-01-26 16:15:59 +00:00
|
|
|
const value = computed(() => get(formData?.value, fieldName.value));
|
|
|
|
const errors = computed(() => formErrors?.value[fieldName.value] || []);
|
2023-01-25 20:03:22 +00:00
|
|
|
|
|
|
|
const onInput = (ev: Event) => {
|
|
|
|
if (!formData) return;
|
|
|
|
const value = (ev.target as HTMLInputElement).value;
|
|
|
|
const cleanValue = props.type === 'number' ? parseFloat(value) : value;
|
2023-01-26 16:15:59 +00:00
|
|
|
setValue(cleanValue);
|
2023-01-25 20:03:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const onChange = (ev: Event) => {
|
|
|
|
if (!formData) return;
|
|
|
|
if (props.type === 'checkbox' || props.type === 'radio') {
|
|
|
|
const cleanValue = (ev.target as HTMLInputElement).checked;
|
2023-01-26 16:15:59 +00:00
|
|
|
setValue(cleanValue);
|
2023-01-25 20:03:22 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-26 16:15:59 +00:00
|
|
|
const setValue = (value: unknown) => {
|
|
|
|
if (!formData) return;
|
|
|
|
set(formData.value, fieldName.value, value);
|
|
|
|
emit('change', value);
|
|
|
|
fieldChange?.(fieldName.value);
|
|
|
|
};
|
|
|
|
|
2023-01-25 20:03:22 +00:00
|
|
|
const onFocus = () => emit('focus');
|
|
|
|
|
|
|
|
const onBlur = () => {
|
|
|
|
emit('blur');
|
2023-01-26 16:15:59 +00:00
|
|
|
fieldChange?.(fieldName.value);
|
2023-01-25 20:03:22 +00:00
|
|
|
};
|
2023-01-26 18:21:41 +00:00
|
|
|
|
|
|
|
const inputClass = computed(() => {
|
|
|
|
return [
|
|
|
|
errors.value.length
|
|
|
|
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
|
|
|
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
|
|
|
|
`mt-1 block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
|
|
|
|
];
|
|
|
|
});
|
2023-01-25 20:03:22 +00:00
|
|
|
</script>
|