diff --git a/app/src/components/globals/FormField.vue b/app/src/components/globals/FormField.vue
index 138277b8..cc2867a2 100644
--- a/app/src/components/globals/FormField.vue
+++ b/app/src/components/globals/FormField.vue
@@ -1,102 +1,99 @@
-
-
-
+
-
+
+
+
+
+
+
+
+
+ {{ asInputGroup ? label : prepend }}
+
+
+ {{ append }}
+
+
+
-
+
-
diff --git a/app/src/composables/form.ts b/app/src/composables/form.ts
index ff2b6803..fcc25cc3 100644
--- a/app/src/composables/form.ts
+++ b/app/src/composables/form.ts
@@ -1,5 +1,32 @@
-import type { InjectionKey } from 'vue'
+import type { BaseValidation } from '@vuelidate/core'
+import type { InjectionKey, MaybeRefOrGetter } from 'vue'
+import { inject, provide, toValue } from 'vue'
+export const clearServerErrorsSymbol = Symbol() as InjectionKey<
+ (key?: string) => void
+>
export const ValidationTouchSymbol = Symbol() as InjectionKey<
(key?: string) => void
>
+
+export function useTouch(
+ validation: MaybeRefOrGetter,
+) {
+ function touch(key?: string) {
+ const v = toValue(validation)
+ if (v) {
+ // For fields that have multiple elements
+ if (key) {
+ v[key].$touch()
+ clear?.(v[key].$path)
+ } else {
+ v.$touch()
+ clear?.(v.$path)
+ }
+ }
+ }
+ provide(ValidationTouchSymbol, touch)
+ const clear = inject(clearServerErrorsSymbol)
+
+ return touch
+}
diff --git a/app/src/helpers/commons.ts b/app/src/helpers/commons.ts
index c05f3063..53c50501 100644
--- a/app/src/helpers/commons.ts
+++ b/app/src/helpers/commons.ts
@@ -144,3 +144,14 @@ export function getFileContent(
}
})
}
+
+export function omit(
+ obj: T,
+ keys: K,
+): Omit {
+ return Object.fromEntries(
+ Object.keys(obj)
+ .filter((key) => !keys.includes(key))
+ .map((key) => [key, obj[key]]),
+ ) as Omit
+}
diff --git a/app/src/types/form.ts b/app/src/types/form.ts
index b82a34d1..8e840bc0 100644
--- a/app/src/types/form.ts
+++ b/app/src/types/form.ts
@@ -1,5 +1,12 @@
-import type { BaseValidation } from '@vuelidate/core'
+import type {
+ BaseValidation,
+ ValidationArgs,
+ ValidationRuleCollection,
+} from '@vuelidate/core'
+import type { RouteLocationRaw } from 'vue-router'
+
type StateValidation = false | null
+type StateVariant = 'success' | 'info' | 'warning' | 'danger'
// DISPLAY
@@ -120,3 +127,75 @@ export type TagsSelectizeItemProps = BaseWritableItemProps & {
export type TextAreaItemProps = BaseWritableItemProps & {
// type?: string // FIXME unused?
}
+
+// FIELDS
+
+const ANY_WRITABLE_COMPONENTS = [
+ 'AdressItem',
+ 'CheckboxItem',
+ 'FileItem',
+ 'InputItem',
+ 'SelectItem',
+ 'TagsItem',
+ 'TagsSelectizeItem',
+ 'TextAreaItem',
+] as const
+
+export type AnyWritableComponents = (typeof ANY_WRITABLE_COMPONENTS)[number]
+type ItemComponentToItemProps = {
+ // WRITABLE
+ AdressItem: AdressItemProps
+ CheckboxItem: CheckboxItemProps
+ FileItem: FileItemProps
+ InputItem: InputItemProps
+ SelectItem: SelectItemProps
+ TagsItem: TagsItemProps
+ TagsSelectizeItem: TagsSelectizeItemProps
+ TextAreaItem: TextAreaItemProps
+}
+
+type FormFieldRules = MV extends object
+ ? MV extends any[]
+ ? ValidationArgs>>
+ : ValidationArgs>
+ : ValidationRuleCollection
+
+type BaseFormFieldComputedProps = {
+ modelValue?: MV
+ validation?: BaseValidation
+}
+
+type BaseFormField = {
+ component: C
+ hr?: boolean
+ id?: string
+ label?: string
+ props?: ItemComponentToItemProps[C]
+ readonly?: boolean
+ // FIXME compute visible JSExpression
+ visible?: boolean
+}
+
+export type FormField<
+ C extends AnyWritableComponents = AnyWritableComponents,
+ MV extends any = any,
+> = BaseFormField & {
+ append?: string
+ asInputGroup?: boolean
+ description?: string
+ descriptionVariant?: StateVariant
+ labelFor?: string
+ link?:
+ | { text: string; name: RouteLocationRaw }
+ | { text: string; href: string }
+ props: ItemComponentToItemProps[C]
+ rules?: FormFieldRules
+ prepend?: string
+ readonly?: false
+}
+
+export type FormFieldProps<
+ C extends AnyWritableComponents,
+ MV extends any,
+> = Omit, 'hr' | 'visible' | 'readonly'> &
+ BaseFormFieldComputedProps