Formulaires et validation
Relier champs, valeurs initiales, validation et soumission partielle.
Un catalogue de formulaire décrit l’édition d’une fiche. Donnez-lui un identifiant stable, définissez les champs puis reliez-le au type de formulaire de création ou d’édition du tableau.
Utilisez TableConfig.presentation à la racine pour une surface commune de consultation, création, édition et édition groupée, avec un choix mobile facultatif. Le défaut est un drawer à droite sur tous les écrans. Consultez la présentation commune des fiches pour la configuration et la migration.
Exemple de configuration
Partez du démarrage React ou du démarrage Vue. Le fichier suivant complète leur catalogue de produits. Passez cette configuration via getTableConfig en React ou config en Vue.
import { z } from "zod";
import type { FormConfig } from "@/components/ui/yayaw-table/components/forms/types";
export const productForm: FormConfig = {
id: "products",
title: "Edit product",
presentation: "drawer",
width: "38rem",
submitMode: "patch",
fields: [
{
name: "name",
label: "Name",
type: "text",
required: true,
bulkEdit: false,
schema: z.string().trim().min(1),
},
{
name: "price",
label: "Price",
type: "number",
min: 0,
schema: z.number().min(0),
},
{
name: "stock",
label: "Stock",
type: "number",
min: 0,
step: 1,
schema: z.number().int().min(0),
},
{
name: "status",
label: "Status",
type: "select",
options: [
{ value: "draft", label: "Draft" },
{ value: "active", label: "Active" },
{ value: "archived", label: "Archived" },
],
},
],
blocks: [
{
type: "content",
id: "help",
text: "Only changed fields are saved.",
tone: "info",
},
{ type: "field", name: "name" },
{
type: "section",
id: "inventory",
title: "Inventory",
columns: 2,
blocks: [
{ type: "field", name: "price" },
{ type: "field", name: "stock" },
],
},
{ type: "field", name: "status" },
],
};import { z } from "zod";
import type { FormConfig } from "@/components/ui/yayaw-table-vue/types";
export const productForm: FormConfig = {
id: "products",
title: "Edit product",
presentation: "drawer",
width: "38rem",
submitMode: "patch",
fields: [
{
name: "name",
label: "Name",
type: "text",
required: true,
bulkEdit: false,
schema: z.string().trim().min(1),
},
{
name: "price",
label: "Price",
type: "number",
min: 0,
schema: z.number().min(0),
},
{
name: "stock",
label: "Stock",
type: "number",
min: 0,
step: 1,
schema: z.number().int().min(0),
},
{
name: "status",
label: "Status",
type: "select",
options: [
{ value: "draft", label: "Draft" },
{ value: "active", label: "Active" },
{ value: "archived", label: "Archived" },
],
},
],
blocks: [
{
type: "content",
id: "help",
text: "Only changed fields are saved.",
tone: "info",
},
{ type: "field", name: "name" },
{
type: "section",
id: "inventory",
title: "Inventory",
columns: 2,
blocks: [
{ type: "field", name: "price" },
{ type: "field", name: "stock" },
],
},
{ type: "field", name: "status" },
],
};import type {
FieldValues,
FormConfig,
} from "@/components/ui/yayaw-table/components/forms/types";
import { productForm } from "./product-form";
/** The registry's generic lookup boundary resolves this known product catalogue. */
export function getFormConfig<T extends FieldValues>(
formType: string
): FormConfig<T> | undefined {
const form = formType === "products" ? productForm : undefined;
const resolved = form;
return resolved as FormConfig<T> | undefined;
}import { productForm } from "./product-form";
export const getFormConfig = (formType: string) =>
formType === "products" ? productForm : undefined;Brancher le catalogue
Définissez form.editFormType: "products" dans le catalogue du tableau et passez getFormConfig au composant. Fournissez update via le catalogue d’actions. La recette administration branche les trois. Pour créer, utilisez createFormType, des valeurs initiales et une action create renvoyant la fiche enregistrée.
Initialiser, valider, transformer
defaultValues initialise la création. loadInitialValues peut hydrater une fiche de manière asynchrone avec un signal d’annulation. Les schémas de champ valident chaque contrôle ; le schéma de formulaire valide l’ensemble. transform adapte les valeurs au contrat d’écriture. submitMode: "patch" transmet les champs déclarés modifiés ; la soumission complète reste le comportement par défaut.
Champs conditionnels
FormConfig.rules affiche, masque, rend obligatoires ou renseigne des champs d’après d’autres valeurs, dans les formulaires de création, de modification et de modification groupée. Les règles ont la même forme et les mêmes comparaisons que les questions conditionnelles de la vue Formulaire : les conditions lisent des noms de champs (fieldId), et les effets listent les champs visés dans then.fieldIds. Les règles des formulaires de fiche se configurent en code ; aucun panneau de réglages ne les édite.
import type { FormConfig } from "@/components/ui/yayaw-table/components/forms/types";
// Vue : import type { FormConfig } from "@/components/ui/yayaw-table-vue/types";
import { productForm } from "./product-form";
const statusIs = (value: string) => ({
join: "and" as const,
items: [{ fieldId: "status", operator: "is" as const, value }],
});
export const exampleForm: FormConfig = {
...productForm,
rules: [
// Un produit actif doit avoir un prix.
{ id: "active-price", when: statusIs("active"), then: { action: "require", fieldIds: ["price"] } },
// Un produit archivé ne suit plus son stock.
{ id: "archived-stock", when: statusIs("archived"), then: { action: "hide", fieldIds: ["stock"] } },
],
};showmasque ses champs tant qu’aucune de ses règles ne correspond ;hidel’emporte surshow.requirerend obligatoire un champ affiché, en plus de son propre indicateurrequiredet de son schéma.set({ action: "set", fieldIds, value }) écrit une valeur avant la validation du formulaire.Les champs masqués ne sont ni validés ni envoyés, en soumission complète comme en mode
patch, et l’édition en cellule refuse un champ que ses règles masquent. Une valeur masquée compte comme vide pour les autres conditions.Une erreur déjà affichée sous un champ est revérifiée quand les valeurs changent : une règle qui masque un champ ou ne l’exige plus ne laisse jamais une erreur périmée bloquer Enregistrer.
Les champs des éléments de collection n’héritent pas des règles du formulaire parent.
Les règles qui ne peuvent pas agir, comme une condition sur un champ inconnu ou une comparaison qui ne convient pas à son type, sont écartées au chargement du formulaire.
Comparaisons par type de champ
type du champ | Comparé comme | Comparaisons (operator) |
|---|---|---|
text, textarea et autres champs texte | Texte | is, isNot, contains, notContains, startsWith |
number, currency, percent | Nombre | eq, neq, lt, lte, gt, gte, between |
date, datetime | Date, au jour près | on, before, after, between, inLast, inNext (un nombre de jours) |
select, radio, select-with-add-new, tag | Choix unique | is, isNot, isAnyOf, isNoneOf |
multiSelect, tags | Choix multiple | containsAny, containsAll, containsNone |
boolean, checkbox, switch | Oui/non | isChecked, isUnchecked |
Tous les types sauf oui/non acceptent aussi isEmpty et isNotEmpty. between prend [from, to], dont une borne peut valoir null ; les comparaisons de liste prennent un tableau de valeurs d’options.
Modification groupée et valeurs mixtes
En modification groupée, les conditions lisent la valeur saisie dans le brouillon, ou à défaut la valeur commune à toutes les lignes sélectionnées. Quand les lignes sélectionnées ont des valeurs différentes pour un champ absent du brouillon, ce champ est « mixte » et toute condition qui le lit est considérée comme non remplie :
Un champ masqué uniquement à cause de valeurs mixtes est listé, désactivé, dans Ajouter un champ, avec une note comme « Dépend de Catégorie, dont les valeurs diffèrent entre les lignes. Modifiez d’abord Catégorie. ».
Un champ déjà ajouté dont les règles lisent des valeurs mixtes affiche dessous « Les valeurs de Catégorie diffèrent entre les lignes : la condition est considérée comme non remplie. ».
Ajouter le champ mixte au brouillon lui donne une même valeur pour toutes les lignes, et les conditions lisent alors cette valeur. Les notes ont un texte intégré en anglais et en français.
Migrer depuis l’option hidden
Les champs gardent leur option hidden, true ou une fonction (context) => boolean. Au chargement du formulaire, elle est convertie en règle avec une condition réservée au code, qui reçoit le contexte complet et les valeurs telles quelles : les formulaires existants se comportent comme avant. Ces conditions en code ne sont jamais enregistrées en JSON. Le code qui lit hidden hors d’un formulaire, comme un rendu de cellule, continue d’appeler la fonction directement.
Une fonction hidden qui ne lit que d’autres valeurs peut devenir une règle :
// Avant : un prédicat sur le champ.
{ name: "stock", label: "Stock", type: "number", hidden: ({ values }) => values?.status === "archived" }
// Après : une règle déclarative sur le formulaire.
rules: [
{
id: "archived-stock",
when: { join: "and", items: [{ fieldId: "status", operator: "is", value: "archived" }] },
then: { action: "hide", fieldIds: ["stock"] },
},
],Les règles déclaratives valent la migration : elles comptent les valeurs masquées comme vides, si bien que les enchaînements se stabilisent ; elles peuvent rendre obligatoires ou renseigner des champs ; et en modification groupée elles lisent les valeurs communes à la sélection et expliquent les valeurs mixtes, alors qu’un ancien prédicat continue de s’exécuter ligne par ligne. Gardez une fonction quand la décision dépend d’autre chose que des valeurs, comme la ligne, le mode ou l’utilisateur.
Choisir la suite
Continuez avec la composition, les options asynchrones, les sélecteurs par tableau, les collections, l’édition en cellule ou l’édition groupée. La référence conserve les contrats détaillés et les exemples de champs.
Sections du guide précédent
Formulaires, sections, multi-select et champs collection
Contrat de formulaire commun à React et Vue
Sélecteur avec table
Initialisation, options et envoi
Champs multi-select
Sections de formulaire
Ce que permettent les champs collection
Modèle mental
Exemple rapide
Référence API
Plusieurs types d'items
Collections imbriquées
Couches de validation
Formulaires en modal et drawer
Traduction et labels
Quand garder custom
Pièges fréquents
Docs liées
Champs générés et JSON
Form blocks
Consultation d’une entrée
Historique conservé et annulation
Tester les comportements dans l’exemple Yayaw
Une modale compacte à deux champs
Renvoyez exampleForm depuis le résolveur getFormConfig existant pour products ; conservez la même action update. Chaque variante ci-dessous est un fichier complet qui étend le formulaire produit présenté plus haut.
import type { FormConfig } from "@/components/ui/yayaw-table/components/forms/types";
import { productForm } from "./product-form";
export const exampleForm: FormConfig = {
...productForm,
presentation: "modal",
width: "32rem",
blocks: undefined,
fields: productForm.fields.filter((field) =>
["name", "price"].includes(field.name)
),
};import type { FormConfig } from "@/components/ui/yayaw-table-vue/types";
import { productForm } from "./product-form";
export const exampleForm: FormConfig = {
...productForm,
presentation: "modal",
width: "32rem",
blocks: undefined,
fields: productForm.fields.filter((field) =>
["name", "price"].includes(field.name)
),
};Exiger une valeur de stock explicite
Conservez le schéma entier et positif ou nul, et exigez une valeur. Zéro est valide ; un champ vide ou une quantité fractionnaire ne le sont pas. Un échec de l’action conserve le brouillon.
import type { FormConfig } from "@/components/ui/yayaw-table/components/forms/types";
import { productForm } from "./product-form";
export const exampleForm: FormConfig = {
...productForm,
fields: productForm.fields.map((field) =>
field.name === "stock" ? { ...field, required: true } : field
),
};import type { FormConfig } from "@/components/ui/yayaw-table-vue/types";
import { productForm } from "./product-form";
export const exampleForm: FormConfig = {
...productForm,
fields: productForm.fields.map((field) =>
field.name === "stock" ? { ...field, required: true } : field
),
};