Les layouts
Un layout sert à composer des FormField pour construire un formulaire plus riche.
Un input crée déjà un FormField, mais un layout crée lui aussi un FormField. La différence est la suivante :
- un input gère une unité de saisie ;
- un layout organise, transforme ou conditionne un ou plusieurs
FormField; - le formulaire final reste toujours piloté par un unique
FormFieldracine.
Autrement dit, un layout est une brique de composition. Il permet de décrire la structure d'un formulaire sans sortir du modèle principal de la librairie.
Ce qu'apporte un layout
Un layout peut servir à :
- grouper plusieurs champs dans un objet ;
- répéter un même champ ou un même bloc ;
- alterner entre plusieurs sous-formulaires ;
- découper un formulaire en étapes ;
- injecter du rendu personnalisé avec un slot ;
- encapsuler un bloc avec un titre ou une présentation ;
- ajouter une validation transverse ;
- désactiver conditionnellement une partie du formulaire.
L'idée clé est la suivante : la structure du formulaire reste lisible et stable dans le code, mais le comportement peut devenir très dynamique.
Les layouts fournis
useMultiLayoutcompose plusieurs champs sous des clés connues.useRepeatLayoutrépète un champ ou un sous-formulaire dans un tableau.useUnionLayoutpermet de choisir une variante active parmi plusieurs branches.useStepLayoutdécoupe un formulaire en plusieurs étapes successives.useSectionLayoutencapsule un bloc dans une section avec titre.useSlotLayoutdélègue une partie du rendu à un slot Vue.useCheckLayoutajoute une validation supplémentaire autour d'un champ existant.useDisabledLayoutmasque et neutralise conditionnellement un champ.
Pour la référence détaillée de chaque layout, consultez aussi l'index API des layouts.
Structure fixe, comportement dynamique
useMultiLayout est souvent le point d'entrée le plus utile, parce qu'il permet de définir une structure stable avec des clés explicites.
Dans l'exemple suivant, les clés identity, contacts, preferredChannel et message existent toujours. Ensuite, chaque clé peut contenir un input simple ou un autre layout.
import {
useMultiLayout,
useRepeatLayout,
useSectionLayout,
useSlotLayout,
useUnionLayout,
} from "@duplojs/form/vue";
import { useForm } from "./init";
import {
useTextInput,
useTextareaInput,
} from "@duplojs/form/vueDesignSystem";
export function useProfileLayoutForm() {
const { component, check } = useForm(
useSectionLayout(
useMultiLayout({
identity: useMultiLayout({
firstName: useTextInput({ label: "First Name" }),
lastName: useTextInput({ label: "Last Name" }),
}),
contacts: useRepeatLayout(
useTextInput({ label: "Email" }),
{
min: 1,
max: 3,
},
),
preferredChannel: useUnionLayout(
[
["email", useTextInput({ label: "Email" })],
["phone", useTextInput({ label: "Phone" })],
],
{ defaultKind: "email" },
),
message: useSlotLayout(
"customMessage",
useTextareaInput({ label: "Message" }),
),
}),
{ title: "Profile" },
),
);
return {
TheForm: component,
checkForm: check,
};
}Dans ce même exemple :
useSectionLayoutencapsule tout le formulaire dans une sectionProfile;useMultiLayoutstructure les données par blocs ;useRepeatLayoutrendcontactsdynamique ;useUnionLayoutfait varier le champ actif entreemailetphone;useSlotLayoutlaisse reprendre précisément le rendu demessage.
Ce modèle est utile pour les formulaires évolués : on garde une architecture prévisible, mais chaque bloc peut devenir adaptatif.
Implémentation Vue
<script setup lang="ts">
import * as EE from "@duplojs/utils/either";
import { PrimaryButton } from "@duplojs/form/vueDesignSystem";
import { useProfileLayoutForm } from "./profileLayoutForm";
const { TheForm, checkForm } = useProfileLayoutForm();
function onSubmit() {
void EE.whenIsRight(
checkForm(),
(result) => {
alert(JSON.stringify(result));
},
);
}
</script>
<template>
<TheForm @submit="onSubmit">
<PrimaryButton
type="submit"
label="Submit"
/>
<template #customMessage="{ formField, value, update }">
<div class="DFV-grid-element">
<p>Custom render with slot</p>
<button
type="button"
@click="update('')"
>
reset
</button>
{{ value }}
<component :is="formField" />
</div>
</template>
</TheForm>
</template>Le slot #customMessage montre bien le rôle de useSlotLayout :
- la valeur courante est disponible dans
value; - vous pouvez la modifier avec
update(...); - vous pouvez réinjecter le champ d'origine via
formField.
Résultat
Layouts de contrôle
Tous les layouts ne servent pas seulement à la structure. Certains servent plutôt à contrôler le comportement du formulaire.
L'exemple suivant montre trois cas :
useStepLayoutimpose une progression par étapes ;useCheckLayoutajoute une validation supplémentaire sur un champ existant ;useDisabledLayoutneutralise un champ tant qu'une condition externe n'est pas remplie.
import * as EE from "@duplojs/utils/either";
import { ref } from "vue";
import {
useCheckLayout,
useDisabledLayout,
useMultiLayout,
useStepLayout,
} from "@duplojs/form/vue";
import {
useTextInput,
useTextareaInput,
} from "@duplojs/form/vueDesignSystem";
import { useForm } from "./init";
export function useFlowLayoutForm() {
const hasCompany = ref(false);
const { component, check, currentValue } = useForm(
useStepLayout(
[
useMultiLayout({
fullName: useTextInput({ label: "Full name" }),
email: useCheckLayout(
useTextInput({ label: "Email" }),
{
refine: (value) => value.includes("@")
? EE.ok()
: EE.error("Email must contain @."),
},
),
}),
useMultiLayout({
company: useDisabledLayout(
useTextInput({ label: "Company" }),
{
isDisabled: () => !hasCompany.value,
},
),
notes: useTextareaInput({ label: "Notes" }),
}),
],
{
errorMessageNotAtLastStep: "Complete all steps before submitting.",
},
),
);
return {
FlowForm: component,
checkFlowForm: check,
currentFlowValue: currentValue,
hasCompany,
};
}Ici :
emailreste un simple input texte, maisuseCheckLayoutlui ajoute une règle métier locale ;companyreste dans la structure du formulaire, maisuseDisabledLayoutle masque et le retire du flux tant quehasCompanyvautfalse;useStepLayouttransforme deux blocs en parcours séquentiel avec contrôle du changement d'étape.
Comment choisir un layout
- Utilisez
useMultiLayoutquand la forme de l'objet final est connue à l'avance. - Utilisez
useRepeatLayoutquand vous avez une liste d'éléments homogènes. - Utilisez
useUnionLayoutquand une seule branche doit être active à la fois. - Utilisez
useStepLayoutquand l'ordre de saisie fait partie de l'expérience. - Utilisez
useSectionLayoutquand vous voulez clarifier visuellement un bloc. - Utilisez
useSlotLayoutquand le rendu standard ne suffit plus. - Utilisez
useCheckLayoutquand la validation dépend de plusieurs règles autour d'un champ déjà existant. - Utilisez
useDisabledLayoutquand un champ doit temporairement disparaître du formulaire.
Ce qu'il faut retenir
- un layout retourne lui aussi un
FormField; - les layouts sont donc librement imbriquables ;
- ils servent autant à structurer les données qu'à piloter le comportement ;
- le formulaire final reste toujours défini par un seul arbre de
FormField.
