npm.io
1.15.0 • Published 2h ago

@msbci/form-editor

Licence
SEE LICENSE IN LICENSE.md
Version
1.15.0
Deps
5
Size
2.8 MB
Vulns
0
Weekly
0

@msbci/form-editor

npm version license peer dep

Visual form builder — 3-panel editor with drag & drop. Create and edit forms defined by @msbci/form-core schemas.

Installation

npm install @msbci/form-editor @msbci/form-renderer @msbci/form-core

Usage

import { FormEditor } from '@msbci/form-editor'

<FormEditor
  theme={myTheme}
  dataSources={myConnectors}
  adapter={{
    loadForm: (id) => api.getForm(id),
    saveForm: (form) => api.saveForm(form),
    loadFormTypes: () => api.getFormTypes(),
    saveFormType: (type) => api.saveFormType(type),
  }}
  onChange={(schema) => console.log('Form updated', schema)}
  onSave={() => console.log('Saved')}
/>

IEditorAdapter

The editor makes no direct API calls. All persistence goes through the adapter:

interface IEditorAdapter {
  loadForm?: (id: string) => Promise<IFormDefinition>
  saveForm?: (form: IFormDefinition) => Promise<void>
  loadFormTypes?: () => Promise<IFormType[]>
  saveFormType?: (type: IFormType) => Promise<void>
}

Pass adapter to FormEditor — the host app controls all backend communication.

Features

Feature Description
3-panel layout Toolbox (left) / Canvas (center) / Properties (right)
Drag & drop Drag fields from toolbox to canvas pages (dnd-kit)
Undo / Redo 50-step history with action descriptions
ConditionBuilder Visual AND/OR condition constructor with 15 operators
DataSourceSelector Select connector + map dependency keys to variables
Live preview Switch to Preview mode to test with FormRenderer, in either of two modes (see below)
JSON view Inspect raw form schema in the editor
Export / Import JSON export with schema version envelope
Zustand store Full CRUD: pages, rosters, variables, selection, view
Les deux apercus (v1.12.0+)

L'onglet d'apercu porte une bascule.

  • Rendu reel (par defaut) : le formulaire se comporte comme pour l'utilisateur qui le remplit — conditions evaluees, pages a franchir dans l'ordre, validation exigee.
  • Mise en page : toutes les pages et tous les champs sont affiches, aucune condition n'est evaluee, la navigation est libre. C'est le mode du concepteur qui veut voir la disposition de la quatrieme page sans remplir les trois premieres.

L'editeur ne rend pas le formulaire : il passe designPreview au PreviewComponent que l'hote lui a donne. La propriete est facultative@msbci/form-renderer >= 1.15.0 la comprend directement, et un composant d'apercu ecrit avant ce lot l'ignore sans rien casser.

// Le renderer accepte la propriete telle quelle.
<FormEditor adapter={adapter} PreviewComponent={FormRenderer} />

// Composant d'apercu maison : il suffit de la transmettre.
function Preview({ formSchema, designPreview }) {
  return <FormRenderer formSchema={formSchema} designPreview={designPreview} theme={myTheme} />
}
ConditionBuilder

Build conditions visually — no expression syntax required:

  • Choose action: Show, Hide, Validate, Require, Read-only, Set Value
  • Add rules: Variable → Operator → Value
  • Combine with AND / OR logic
  • Expression preview auto-generated
  • Error message field for validation rules
  • Target variable + value for setValue actions
DataSourceSelector

Connect select/multiselect/radio fields to registered data sources:

  • Dropdown of available IDataSourceConnector instances
  • Add dependency keys (e.g. parentId, countryId)
  • Map each key to a form variable
  • Cascading updates handled automatically by the renderer

Theme

Uses the same IMosobiTheme system as @msbci/form-renderer:

<FormEditor theme={myTheme} dataSources={connectors} adapter={adapter} />

v1.15.0

Un tableau ne pouvait pas etre deplace vers une autre page. Un champ et un panneau le pouvaient, un tableau non : le canevas a glisser-deposer n'a jamais traduit ce geste pour un conteneur.

  • Cause. Le gestionnaire de glissement declarait moveItem — reordonner dans une page — et moveVariable — deplacer entre pages, mais pour un champ seulement. Un panneau echappait au defaut parce qu'il est un champ dans le modele. Un tableau, lui, ne rencontrait aucune branche : depose sur une autre page, il retombait dans le cas des champs, que son type excluait, et le geste se terminait sans rien appeler. Meme asymetrie que la suppression corrigee en 1.13.0, et pour la meme raison.
  • Correctif. Nouvelle action movePageItemToPage(fromPageCode, toPageCode, itemCode, index?). Elle porte n'importe quel element de premier niveau — tableau, panneau, champ — avec tout son contenu, renumerote les deux pages, et se defait par une seule annulation. Elle rend les references que le deplacement fait basculer en avant ; un appelant qui ignore ce retour reste correct.
  • Le glissement seul ne suffisait pas. Sur un formulaire de huit pages, la page d'arrivee est hors ecran : tirer un tableau de la page 2 a la page 7 suppose un defilement pendant le glissement. Le panneau de proprietes porte donc un choix « deplacer vers la page... », la ou vivent deja les gestes structurants sur l'element selectionne, et qui reste visible quel que soit le defilement du canevas. Reperes de test roster-move-to-page et variable-move-to-page ; nouveau composant exporte MoveToPageField.
  • La reference devenue inatteignable est nommee, pas devinee. Deplacer un champ derriere la condition qui l'interroge n'est signale nulle part ailleurs : ni le validateur — le code existe toujours — ni le rendu. Le panneau affiche le constat sous la liste (variable-move-warning, roster-move-warning), sans bloquer le deplacement.
  • Les positions ne derivent plus. moveVariable laissait l'order d'origine en place cote source et recopiait newOrder tel quel cote cible : la page de depart gardait des trous, la page d'arrivee accumulait des order en double, et l'ordre d'affichage changeait d'une relecture a l'autre. Les deux listes sont desormais renumerotees.
  • Un champ regroupe dans un panneau peut changer de page : il en sort et rejoint le premier niveau de la page choisie. Une colonne de tableau n'expose pas le choix — elle appartient au gabarit de ligne et suit son tableau.
  • Deux reglages d'affichage sur les proprietes d'un tableau : masquer le titre de chaque instance (le libelle repete suivi du numero), et, une fois masque, afficher un numero de ligne a gauche. Le second n'apparait que si le premier est actif, et reafficher le titre le retire : le modele ne porte pas de combinaison sans usage. Les reglages ne sont proposes que sur les variantes rendues en cadres — le tableau etendu numerote deja ses lignes, une variante a options porte le libelle de l'option sur chaque ligne. Reperes roster-hide-instance-title et roster-show-row-number.
  • Pourquoi les tests ne l'ont pas vu. moveVariable etait couverte et passait. Aucun test ne verifiait qu'un tableau atteignait une action de deplacement entre pages : la couverture du magasin donnait le change, exactement comme pour la suppression en 1.13.0.
  • Impact : additif. Cinq libelles s'ajoutent au paquet de traductions, remplis par les valeurs par defaut pour un hote qui ne surcharge rien ; movePageItemToPage est facultative sur DragEndActions, un hote qui compose lui-meme DndManager avec des actions anterieures reste fonctionnel. Un hote qui n'appelle rien de nouveau retrouve le comportement anterieur, aux positions corrigees pres. 23 tests ajoutes, 286 passants.

v1.14.0

Un conteneur ne pouvait etre recopie qu'a la main, element par element. Ce lot pose une icone de duplication sur l'en-tete d'un tableau, d'un panneau et d'une page.

  • Le geste vit sur l'en-tete du conteneur, la ou le concepteur le lit, et non dans le panneau de proprietes : recopier quatre rubriques de meme profil se fait d'affilee, sans passer par une selection a chaque fois. Reperes de test canvas-page-duplicate-<code>, canvas-roster-duplicate-<code>, canvas-panel-duplicate-<code> ; libelles canvas.duplicatePage, canvas.duplicateRoster, canvas.duplicatePanel.
  • Deux actions nouvelles sur le magasin : duplicatePage(pageCode) et duplicatePageItem(pageCode, itemCode). Elles delegent la duplication a @msbci/form-core — codes generes uniques a l'echelle du formulaire, references internes reecrites vers la copie, references sortantes intactes — et se defont par l'annulation comme toute autre modification.
  • La copie atterrit juste apres l'original et devient l'element selectionne, pour que le concepteur puisse la renommer immediatement dans le panneau de proprietes.
  • Le clic ne declenche plus un deplacement. L'en-tete d'un tableau et celui d'un panneau portent le capteur de glissement : l'evenement de pointeur du bouton est retenu, faute de quoi le clic aurait ete pris pour un debut de deplacement. L'intitule reste saisissable pour reordonner, le bouton lui est exterieur.
  • Impact : additif. Trois libelles s'ajoutent au paquet de traductions, remplis par les valeurs par defaut pour un hote qui ne surcharge rien ; aucune signature existante modifiee. Un hote qui n'appelle pas les nouvelles actions retrouve exactement le comportement anterieur, a l'icone pres. 11 tests ajoutes, 263 passants.

v1.13.0

Un tableau ne pouvait pas etre supprime. Un champ et une rubrique le pouvaient, un tableau non : aucune commande de l'interface par defaut n'appelait l'action de suppression du magasin, qui existait pourtant et fonctionnait.

  • Cause. Le canevas historique portait une croix de suppression sur l'en-tete d'un tableau. Le canevas a glisser-deposer, devenu celui par defaut, ne l'a jamais eue. Une rubrique n'a pas ete touchee parce qu'elle est un champ : sa suppression vit dans les proprietes du champ, atteignable comme avant. Un tableau, lui, n'avait de commande nulle part. Le refus n'etait pas silencieux : rien n'etait appele.
  • Correctif. Les proprietes d'un tableau portent un bouton de suppression, au meme endroit et de la meme forme que celui d'un champ ou d'une page. Repere de test roster-delete, libelle properties.deleteRoster. Les quatre variantes se suppriment : cases a cocher, liste, collection, collection etendue.
  • La selection ne designe plus un element disparu. Supprimer un tableau la ramene sur sa page, comme le font deja la suppression d'un champ et celle d'une page. Sans cela, une selection pointant sur le tableau supprime — ou sur l'un de ses champs — survivait a l'arbre.
  • Pourquoi les tests ne l'ont pas vu. L'action removeRoster du magasin etait couverte, et elle passait. Aucun test ne verifiait qu'une commande de l'interface l'atteignait. La couverture du magasin donnait le change.
  • Impact : additif (properties.deleteRoster ; les libelles se passent en fragment, un hote qui ne le fournit pas recoit celui par defaut). 9 tests ajoutes, 252 passants.

v1.12.0

  • Deux apercus au lieu d'un. L'onglet d'apercu porte une bascule : rendu reel, le comportement d'un utilisateur qui remplit le formulaire, et mise en page, qui affiche tout sans evaluer une seule condition. Le concepteur verifie ainsi la disposition de la quatrieme page sans avoir a remplir les trois premieres. Le rendu reel reste le mode actif au depart.
  • Le contrat de PreviewComponent gagne une propriete facultative designPreview. @msbci/form-renderer >= 1.15.0 l'accepte telle quelle : un hote qui passe PreviewComponent={FormRenderer} obtient les deux modes sans rien ecrire. Un composant d'apercu ecrit avant ce lot ignore la propriete et se comporte comme avant — la bascule reste alors sans effet, mais rien ne casse.
  • Trois libelles ajoutes : formEditor.previewModeLabel, formEditor.previewModeReal, formEditor.previewModeDesign (FR et EN). Reperes de test preview-mode-toggle, preview-mode-real, preview-mode-design.
  • Impact : additif. 4 tests ajoutes, 243 passants.

v1.11.1

  • Republication de suivi. Aucun changement de code ni de comportement : le paquet est republie pour dependre de @msbci/form-core v1.13.0, qui ajoute le type IStoredFile et les lecteurs bivalents de pieces jointes. L'editeur n'en fait aucun usage — il concoit des formulaires, il n'en remplit pas.
  • Le paquet publie empechait le processus Node qui l'importait de se terminer. L'obfuscation posait une protection anti-debogage a intervalle, c'est-a-dire un setInterval jamais arrete : la boucle d'evenements restait occupee, et tout script, outil en ligne de commande ou tache d'integration continue qui importait le paquet restait suspendu jusqu'a son delai d'expiration, sans message et sans cause visible. La protection est retiree — elle vise la console d'un navigateur et ne protegeait rien sur une cible Node, tandis que les instructions debugger qu'elle injectait interrompaient le debogage legitime d'un consommateur. Ce qui rend le code couteux a relire est conserve : tableau de chaines encode, aplatissement du flot de controle, code mort, renommage des identifiants. Le correctif est commun aux cinq paquets. 239 tests passants, inchanges.

v1.11.0

  • Un enregistrement refuse ne laissait aucune trace a l'ecran. Le rejet de promesse partait dans le vide : le concepteur voyait le bouton reprendre son etat normal et repartait en croyant son travail enregistre, alors que le serveur l'avait refuse — parce qu'un collegue avait enregistre entre-temps, ou parce que le remaniement faisait disparaitre un champ deja repondu. Nouvel etat de store saveError et nouvelle boite de dialogue SaveErrorDialog, montee par FormEditor, qui dit le refus et sa cause.
  • Face a un enregistrement concurrent, rien n'est recharge d'office. Recharger effacerait precisement le travail qui vient d'etre refuse. La boite explique que quelqu'un d'autre a enregistre, date la version concurrente, rappelle que les modifications sont toujours a l'ecran, et propose trois issues : telecharger ses modifications en JSON (rien n'est perdu, la reprise reste possible), recharger la version enregistree apres confirmation explicite, ou continuer sans enregistrer. Le formulaire local n'est jamais touche sans un geste du concepteur.
  • La version du formulaire fait l'aller-retour. Le formulaire charge porte updatedAt (voir @msbci/form-core v1.12.0) et l'editeur le renvoie tel quel a l'enregistrement. IEditorAdapter.saveForm peut desormais renvoyer { updatedAt } — la valeur que le serveur vient d'ecrire — pour enchainer les enregistrements sans recharger. Un adaptateur qui ne renvoie rien reste valide : l'editeur oublie alors la version detenue et le controle ne reprend qu'au prochain chargement, plutot que de refuser a tort un enregistrement legitime.
  • Ce que l'adaptateur doit propager. L'editeur reconnait un conflit de version au code FORM_VERSION_CONFLICT porte par l'erreur levee, et lit l'heure de la version concurrente dans son tableau errors. Un adaptateur qui ne transmet que le message affiche le refus tel quel, sans les issues propres au conflit — mieux que le silence d'avant, moins bien qu'un adaptateur a jour.
  • Nouvelles actions de store : clearSaveError() et reloadFromServer(). Nouveaux libelles saveDialog.*, en francais et en anglais, overridables partiellement comme tout le dictionnaire.
  • Impact : additif (un etat et deux actions de plus, un type de retour elargi sur saveForm, une section de libelles de plus). Un hote qui ne change rien voit desormais les refus au lieu de les ignorer, ce qui etait le defaut. 8 tests ajoutes, 239 passants.

v1.10.0

  • Le plafond de taille d'une piece jointe etait hors de portee du concepteur. Les blocs « Configuration fichier » et « Configuration image » reglaient le nombre de pieces et les types acceptes, mais pas leur poids : seul le plafond par defaut pouvait s'appliquer. Nouveau champ Poids maximal par fichier (Mo) dans les deux blocs (data-testid file-config-max-bytes et image-config-max-bytes), qui ecrit fileConfig.maxBytes / imageConfig.maxBytes en octets. Laisse vide, le champ rappelle sous lui le plafond applique par defaut, sans quoi le concepteur croirait n'avoir aucune limite.
  • Libelles : properties.fileConfigMaxSize et properties.fileConfigMaxSizeHint(defaultSize) s'ajoutent au dictionnaire, en francais et en anglais. Comme tout libelle, ils s'overrident partiellement : un hote qui n'en fournit pas retombe sur le dictionnaire par defaut.
  • Impact : strictement additif (un reglage de plus sur deux blocs existants, deux libelles de plus). Aucun formulaire existant modifie a l'ouverture. 5 tests ajoutes, 231 passants.

v1.9.0

  • Le concepteur ne proposait aucun champ de signature. Le type 'signature' figure desormais dans la boite a outils (groupe « Avance ») et dans le selecteur de type du panneau de proprietes, avec son panneau de reglages dedie : forme de saisie (acceptation nominative horodatee ou trace manuscrite), texte d'engagement multilingue, case d'acceptation exigee ou non, et plafond du trace en Ko en mode manuscrit.
  • Le panneau rappelle la portee du champ, la ou la decision se prend. Un concepteur qui pose une signature dans une piece administrative doit savoir, au moment ou il la pose, que le champ enregistre un engagement nominatif horodate et n'est pas une signature electronique qualifiee : il n'atteste ni l'identite du signataire, ni l'integrite du document. Le rappel est affiche dans le panneau, traduisible comme le reste (labels.properties.signatureDisclaimer).
  • Le rapport de coherence couvre desormais la reference morte. Le controle de conception remonte les codes dupliques depuis la v1.7.1 ; ce lot ajoute la verification de bout en bout d'une expression referencant un code inexistant. Une reference morte ne declenche jamais sa condition et ne se voyait qu'a la saisie, sur un dossier deja depose.
  • Impact : strictement additif — une entree de boite a outils, un panneau de reglages qui n'apparait que sur une variable de type signature, des libelles nouveaux dans les deux langues. Aucun formulaire existant n'est modifie a l'ouverture. 7 tests ajoutes, 226 passants.

v1.8.0

  • On ne pouvait plus rien ajouter sous un conteneur place en fin de page. Quand un panneau ou un tableau etait le dernier element d'une page, tout depot vise en dessous tombait dans le conteneur. Deux causes se conjuguaient. Le corps de page n'avait, sous son dernier element, qu'un remplissage de quelques pixels : il n'y avait litteralement rien a viser. Et l'arbitrage des collisions se faisait par closestCenter, qui compare des centres : un conteneur haut a son centre plus proche que tout ce qui l'entoure, donc il emportait le depot meme lorsque le curseur visait nettement en dehors de lui.
  • Une zone de depot de fin de page, toujours visible et assez haute pour etre visee, ferme chaque page. Elle s'eclaire au survol, sert de repere de fin de page, et depose au niveau de la page quel que soit le conteneur qui la precede. Un element deja range dans un panneau ou un tableau et relache dessus en ressort ; un element de premier niveau y passe en derniere position. Son libelle passe par le mecanisme de libelles existant (labels.canvas.pageEndDropZone), traduisible comme le reste.
  • L'arbitrage des collisions passe a pointerWithin, avec repli sur closestCenter lorsque le curseur ne survole rien ou n'existe pas (deplacement au clavier). Seul ce qui est reellement sous le curseur est candidat, et l'imbrication est classee du plus proche au plus lointain : c'est la recommandation de dnd-kit des qu'il y a des conteneurs. La reciproque est preservee et testee — un depot vise sur un panneau ou un tableau y entre toujours. La fonction est exportee (editorCollisionDetection) pour un hote qui compose son propre DndContext.
  • Un nom de type de formulaire multilingue faisait echouer le rendu. IFormType.name est un LocalizedString : rendu tel quel dans la liste des types et dans le selecteur des proprietes du formulaire, un nom traduit s'affichait [object Object] ou faisait lever React. Il est resolu dans la langue par defaut du formulaire avant affichage.
  • Impact : additif. OverDropData accepte un type de cible supplementaire ('page-end'), IEditorLabels un libelle supplementaire — les libelles etant fusionnes a partir d'un objet partiel, un hote qui surcharge les siens n'a rien a changer. Un formulaire existant s'ouvre et s'edite a l'identique. 10 tests ajoutes, 219 passants.

v1.7.1

  • Realignement sur @msbci/form-core 1.8.0. Le coeur porte desormais le moteur des champs calcules, la validation du formulaire complet et la correction des fonctions d'agregation sur les lignes incompletes. L'editeur n'a aucun changement fonctionnel dans cette version : la publication existe pour que l'editeur et le rendu partagent une seule et meme version du coeur, la dependance etant epinglee a l'exacte version publiee. 209 tests passants, inchanges.

v1.7.0

  • Aucun moyen de delimiter une rubrique a l'interieur d'une page. Le panneau etait un bloc de texte que l'on posait entre deux champs ; regrouper six rubriques imposait six pages de navigation. Le canevas rend desormais le panneau comme un conteneur : ses champs apparaissent a l'interieur, sa zone de depot accepte un champ glisse depuis la boite a outils comme un champ deja pose sur la page, et un bouton ajoute un champ directement dans la rubrique. Un champ regroupe se ressort en le glissant sur la page, et se reordonne a l'interieur du panneau.
  • Le refus se voit avant le relachement, pas apres. Un tableau, un panneau ou le panneau lui-meme glisse sur une zone de panneau la teinte en rouge et affiche la raison, et le depot ne produit aucune mutation. La regle est celle du modele (SchemaValidator de @msbci/form-core) : l'editeur ne doit pas pouvoir composer un arbre que la persistance refusera. Le magasin applique le meme controle, de sorte qu'un appel direct a addVariable ou moveVariableToPanel avec une cible interdite laisse le formulaire inchange.
  • Surface du magasin etendue sans rupture. addVariable, updateVariable et removeVariable prennent un parametre panelCode facultatif, en dernier ; hors panneau, leur appel garde exactement la forme qu'il avait. Nouvelle action moveVariableToPanel(pageCode, variableCode, { panelCode?, index? }) : elle couvre l'entree dans un panneau, la sortie (panelCode absent) et le reordonnancement interne, et retrouve seule le conteneur d'origine du champ. La selection porte un panelCode optionnel, dont le panneau de proprietes se sert pour editer un champ regroupe.
  • Impact : strictement additif — parametres optionnels, une action nouvelle, aucun contrat existant modifie. Un panneau sans enfants s'affiche et se comporte comme avant. 16 tests ajoutes, 209 passants.

v1.6.0

  • Les codes etaient verrouilles dans l'editeur. Le champ « Code » d'une page, d'un tableau et d'une variable etait rendu avec un onChange vide : le concepteur subissait le code genere (TEXT_8WFK, ROSTER_7OGO) alors que ce code est ce qui designe l'element dans les conditions, les expressions, les exports et les circuits de validation en aval. Les trois champs sont desormais saisissables via le nouveau composant CodeField (exporte).
  • Refus explicites, jamais silencieux. La forme est deleguee a checkCodeFormat de @msbci/form-core — la regle vit aupres de la grammaire qui la fonde plutot que d'etre redite ici — et le motif est affiche sous le champ : caracteres invalides, jeton reserve, suffixe de ligne ambigu, code vide. La saisie est normalisee en majuscules a la frappe, de sorte qu'un code valide se tape sans effort. Le suffixe numerique reste tolere sur une page et sur un tableau, qu'aucune expression ne designe.
  • Unicite signalee avant l'enregistrement. Le controle s'appuie sur le SchemaValidator deja utilise par le rapport de coherence de la barre d'outils, applique au formulaire tel qu'il serait apres renommage : un doublon supplementaire vaut refus, affiche a la frappe. Le concepteur ne decouvre plus la collision au moment d'enregistrer.
  • Les references suivent le renommage. Nouvelles actions de store renameVariableCode, renameRosterCode et renamePageCode. La premiere passe par renameVariableCodeInForm du coeur et reecrit les sept emplacements qui citent le code. C'est ce qui rend la fonction utilisable : l'editeur enregistre l'arbre complet du formulaire, et ce chemin ne reecrit rien cote serveur — sans cette reecriture, renommer laisserait derriere lui des conditions pointant dans le vide. La selection courante est reportee sur le nouveau code pour que le panneau de proprietes ne perde pas sa cible, et le renommage entre dans l'historique (annulable).
  • Le code genere reste le defaut. Rien n'est demande a la creation d'un champ : un concepteur qui ne veut pas s'occuper des codes n'a rien a faire. Le code herite d'un modele de variable reste verrouille — il appartient au referentiel partage, le modifier localement detacherait la variable.
  • Libelles : nouvelle section codeEditing (regle de forme, quatre motifs de refus, message de collision), en francais et en anglais. labels restant un DeepPartial, un hote qui ne la fournit pas retombe sur les valeurs par defaut.
  • Impact : aucun contrat existant modifie, aucune prop rendue obligatoire. Un formulaire enregistre avant ce lot reste ouvrable, modifiable et rendu a l'identique. 9 tests ajoutes, 193 passants. Requiert @msbci/form-core >= 1.6.0.

v1.5.0

  • Rouvrir une condition en mode visuel la detruisait — perte de donnees. Cause : parseExpressionToGroup etait un fragment inacheve qui retournait toujours un groupe vide ; la moindre interaction reconstruisait alors une expression vide et ecrasait la condition existante. Le seul garde-fou etait un avertissement consultatif. Correctif : le convertisseur s'appuie sur parseConditionExpression de @msbci/form-core. Une expression reconnue est restituee a l'identique dans le constructeur ; une expression non reconnue verrouille le mode visuel — elle est affichee, conservee telle quelle, et seul un bouton explicite (« repartir d'une condition visuelle vide ») permet de l'abandonner. Changer l'action d'une condition verrouillee ne touche plus a son expression.
  • Les regles de validation n'etaient pas editables. Seule la case « obligatoire » existait ; le seul point d'edition etait une zone de texte JSON brut reservee aux gabarits de bibliotheque. Nouveau composant ValidationRulesEditor (exporte, avec availableRuleTypes), cable dans le panneau de variable : longueur, bornes, motif, format, nombre minimum de fichiers, avec message d'erreur localisable. Le catalogue est filtre par type de variable et n'offre que les regles que le moteur applique reellement — une regle inerte n'est pas proposee.
  • Les conditions de page et de tableau n'avaient aucune interface. Nouveau composant ConditionsField (exporte, avec le hook useAvailableVariables), cable dans PageProperties et RosterProperties. Le constructeur de conditions, jusque-la reserve aux variables, est desormais atteignable pour une page et pour un tableau — dont le coeur sait maintenant evaluer les conditions.
  • Les options et les bornes de lignes d'un tableau n'etaient pas editables. RosterProperties n'exposait que quatre champs sur huit. L'editeur d'options existant est cable pour les variantes a lignes fixes (check, list) — sans options, le rendu basculait silencieusement en mode pilote — et les bornes collectionConfig.min / max sont editables pour les variantes a ajout libre.
  • Aucun controle de coherence a la conception. Le validateur de schema du coeur n'etait appele par aucun code de production. Nouveau bouton de barre d'outils SchemaIssuesButton (exporte) : il affiche en continu le nombre d'incoherences et les detaille dans un panneau (code duplique, expression referencant un code inexistant, pilote qui ne designe rien, champ calcule sans expression, liste sans options). Il ne bloque jamais l'enregistrement — un brouillon incoherent reste enregistrable, c'est la publication qui doit etre exigeante.
  • Libelles : nouvelles entrees properties.validationRule*, properties.rosterMinRows / rosterMaxRows / rosterRowsUnbounded, conditionBuilder.lockedNotice / lockedRewrite et la section schemaIssues, en francais et en anglais. labels restant un DeepPartial, un hote qui ne les fournit pas retombe sur les valeurs par defaut.
  • Impact : aucun contrat existant modifie, aucune prop rendue obligatoire. Un formulaire enregistre avant ce lot reste ouvrable, modifiable et rendu a l'identique. 12 tests ajoutes, 184 passants.

v1.4.0

  • Les actions de condition « obligatoire si », « lecture seule si » et « definir la valeur si » produisaient une expression nue. Cause : groupToExpression n'enveloppait l'expression que pour show, hide et validate. En face, le moteur ne reconnaissait pas la forme nue et retombait sur la visibilite : le concepteur choisissait « Obligatoire si » et obtenait « Afficher si » — le champ disparaissait quand la condition etait fausse. C'est exactement le motif « si oui, remplir ci-dessous » d'une notice administrative.
  • Correctif : require produit require(...), readonly produit readonly(...), et setValue produit setValue(valeur, cible, condition) en consommant la cible et la valeur saisies dans le panneau. Modifier la cible ou la valeur reconstruit l'expression, qui restait auparavant perimee.
  • Le changement d'action ne declenche plus deux mises a jour successives, dont la premiere laissait l'expression desynchronisee de l'action.
  • Impact : aucune prop ni signature publique modifiee. Les conditions deja enregistrees sous forme nue restent interpretees correctement par @msbci/form-core 1.4.0, qui decide sur l'action declaree. 4 tests ajoutes, 172 passants.

v1.3.15

  • Bouton de suppression de champVariableProperties expose un bouton "Supprimer le champ" en bas du panneau, appelant l'action removeVariable du store (annulable via undo). Évite d'avoir à viser la petite croix sur le canvas. Fonctionne aussi pour les champs liés à un template. Label properties.deleteVariable ajouté (FR + EN).
  • removeVariable réinitialise désormais la sélection sur la page lorsque le champ supprimé était sélectionné (cohérent avec removePage).

v1.3.14

  • Documentation : retrait des références à un hôte spécifique dans le changelog au profit d'une formulation générique (l'application hôte). Aucun changement de code ni d'API.

v1.3.13

  • Garde-fou retrait de scopeFormProperties interdit de retirer un scope d'un formulaire tant qu'une variable du formulaire référence un de ses templates (templateId) ou une de ses sources de données (dataSourceId). Le bouton × est désactivé avec une infobulle (labels.properties.scopeInUse) ; les scopes inutilisés restent retirables. Évite les références orphelines que la bibliothèque de variables ne peut plus résoudre.
  • Label properties.scopeInUse ajouté (FR + EN).
  • Aucun changement d'API.

v1.3.12

  • Bouton de suppression de pagePageProperties expose un bouton "Supprimer la page" appelant l'action removePage du store (annulable via undo). Label properties.deletePage ajouté (FR + EN).
  • Création de scope sans tenant codé en durScopeManagerDialog n'envoie plus tenantId: 'default' : le serveur dérive le tenant du contexte de la requête, afin que le scope atterrisse dans le même tenant que les formulaires qui le référencent. IEditorAdapter.createScope rend tenantId optionnel (ajout non cassant).

v1.3.10 – v1.3.11

  • Chrome thématisable — prop theme propagée au chrome de l'éditeur ; theme.colors.primary colore les accents (consommé par l'application hôte).
  • Prop hideFormTypes — masque la section "Type de formulaire" du panneau de propriétés pour les intégrations qui ne l'utilisent pas.
  • Labels injectables selon la langue de l'application — l'hôte passe labels (frLabels/enLabels) pour aligner l'éditeur sur la locale courante.
  • Republication corrigeant le protocole workspace:* dans le tarball publié (installation autonome via pnpm publish).

v1.3.9

  • Toolbox alignée sur VariableType completToolboxSimple et Toolbox (DnD) listent désormais les 22 types sans omission. Ajouts :
    • ToolboxSimple : listradio (Sélection), photo (Médias), panel + richtext (Avancé).
    • Toolbox (DnD) : email (Texte), listradio (Sélection), photo (Médias), rating + panel + richtext (Avancé).
  • Icônes assignées : (listradio), 📷 (photo), (panel), 📝 (richtext) — cohérence avec le mix ASCII/émoji existant.
  • Tous les labels viennent de labels.toolbox.types.* (clés déjà ajoutées en v1.3.8, FR + EN).
  • Aucun changement d'API ; 167 tests inchangés.

v1.3.8

  • VariableProperties aligné sur VariableType complet (form-core v1.3.3) — le <select> Type liste désormais les 22 types dans l'ordre Texte → Sélection → Date/Heure → Médias → Avancé. Conséquence : sélectionner une variable de type image (ou email, photo, listradio, panel, richtext) affiche bien le bon type dans le panneau au lieu de retomber sur text (et le risque associé d'écraser le type via un clic involontaire est éliminé).
  • Labels FR/EN ajoutés pour listradio, photo, panel, richtext dans labels.toolbox.types.*. Single source of truth utilisée à la fois par Toolbox(Simple) et VariableProperties.
  • Aucun changement d'API, 167 tests inchangés.

v1.3.7

  • Hotfix toolbox DnD : le type image n'apparaissait pas dans Toolbox (DnD), bien que la variable soit fonctionnelle dans le renderer et exposée dans ToolboxSimple. La section MÉDIAS contient désormais Fichier · Image · GPS dans les deux variantes ; file et gps sont sortis de Avancé pour cohérence avec ToolboxSimple.
  • Aucun changement d'API, 167 tests inchangés.

v1.3.6

  • Sections de configuration multi-upload dans VariableProperties :
    • Section « Configuration fichier » (visible si type === 'file') — maxFiles, minFiles, accept. Le select maxFiles propose [1, 2, 3, 5, 10, illimité] mappés sur [1, 2, 3, 5, 10, undefined].
    • Section « Configuration image » (visible si type === 'image') — maxImages, minImages, case à cocher allowCamera.
    • Les deux sections nettoient l'objet à chaque édition : un payload vide est remonté à undefined pour rester invisible côté schéma.
  • Labels FR/EN ajoutés : properties.fileConfig* (6 clés) et properties.imageConfig* (4 clés).
  • Pas de nouveau test côté editor (la couverture vit dans form-core et form-renderer). 167 tests inchangés.

v1.3.5

  • ScopeManagerDialog — nouveau dialog modal qui pilote le CRUD des IScope. Bouton "Scopes" ajouté dans la barre d'outils (data-testid="toolbar-scopes") entre et + Page. Création (nom + code auto), édition inline du nom au clic, suppression avec confirmation. Si le serveur renvoie 409 (scope rattaché à des forms/templates/dataSources), le message d'erreur est affiché inline en rouge dans la zone de confirmation.
  • VariableTemplateManager — sous-dialog ouvert depuis le bouton "Gérer les variables →" de chaque scope (data-testid scope-manage-variables-{code}). Recharge adapter.loadTemplates(scopeId) au mount (liste authoritative du scope, indépendamment du formulaire courant). Updates optimistes après chaque mutation (pas de refetch).
  • Formulaire d'édition template progressif :
    • Toujours visible : code (read-only en édition), name (LocalizedField, read-only en édition), variableType, description, placeholder, flags isRequired/isReadonly/isHidden.
    • Conditionnels : OptionsEditor si type ∈ [select, multiselect, radio, checkbox, listradio], expression si calculated, dataSourceId (dropdown alimenté par availableDataSources) si [select, multiselect].
    • Section repliable "Avancé" : validationRules, conditions, style en JSON (validation client avant submit).
  • IEditorAdapter étendu : createScope, updateScope, deleteScope, createTemplate, updateTemplate, deleteTemplate — toutes optionnelles. Les patches updateTemplate excluent typedef-side code et name (immuables côté serveur).
  • Labels FR/EN : toolbar.scopes, toolbar.manageScopesTitle, blocs scopeManager.* et variableTemplateManager.* (titre paramétrable, confirmations, badges read-only, ARIA labels par row).
  • 16 nouveaux tests RTL (scopeManager.test.tsx ×8 + variableTemplateManager.test.tsx ×8) — 167 tests passants.

v1.3.4

  • DataSourceConfigManager — nouveau dialog modal (/dialogs/DataSourceConfigManager.tsx) qui pilote le CRUD des IDataSourceConfig côté admin. Liste filtrable par scope (badge de provenance), formulaire d'ajout/édition (code, name, URL, method, queryParam, value/label fields, headers password-masked, dépendances). Bouton "Sources" ajouté dans la barre d'outils (data-testid="toolbar-datasources").
  • IEditorAdapter étendu : loadDataSources(scopeId), createDataSource(scopeId, data), updateDataSource(id, data), deleteDataSource(id) — toutes optionnelles.
  • Store : nouveau availableDataSources: IDataSourceConfig[] + setter setAvailableDataSources(). Hors undo/redo (UI state). Chargé par FormEditor via Promise.all(scopeIds.map(loadDataSources)) + dédup par code (premier scope gagne, comme les templates).
  • DataSourceSelector lit désormais le store en plus de la prop legacy availableConnectors — les configs admin l'emportent sur les connecteurs hardcodés du host.
  • Labels FR/EN : toolbar.dataSources, toolbar.manageDataSourcesTitle, bloc complet dataSourceManager.* (titre dialog, formulaire, badges, confirmations).
  • 6 nouveaux tests RTL (dataSourceConfigManager.test.tsx) — 151 tests passants.

v1.3.3

  • Régression zone vide canvas corrigée — un clic sur la zone vide entre les pages déclenche désormais select({ type: 'form' }) et ouvre les propriétés du formulaire (garde e.target === e.currentTarget pour ne pas capturer les clics enfants)
  • Nouveau bouton Propriétés du formulaire dans la barre d'outils (header) — accès direct au panneau, indépendamment de l'état du canvas (data-testid="toolbar-form-properties")
  • Multi-scopes : IFormDefinition.scopeIds[] exposé via un ScopesSelector dans FormProperties (ajout par dropdown, suppression par ×, réordonnement par ▲/▼ — l'ordre = priorité)
  • Toolbox Variables : affiche le message noScope si scopeIds est vide ou absent
  • FormEditor : chargement des templates via Promise.all(scopeIds.map(loadTemplates)) + déduplication par code (premier scope gagne)
  • Nouveau adapter optionnel loadScopes?: () => Promise<IScope[]> + état availableScopes (hors undo/redo) consommé par le ScopesSelector
  • Labels FR/EN : toolbar.formProperties, properties.scopes, properties.scopesHint, properties.noScopesAvailable, properties.moveScopeUp, properties.moveScopeDown, properties.removeScope
  • 10 nouveaux tests RTL (scopesMigration.test.tsx) — 145 tests passants

v1.3.2

  • Nouveau ConditionsModal — édition des conditions dans un modal dédié (700 px, focus trap maison, fermeture uniquement via ✕ / Annuler / Appliquer ; l'overlay n'est volontairement pas cliquable)
  • Mode Saisie manuelle dans le modal : <textarea> éditable avec coloration syntaxique sans dépendance externe (technique mirror textarea/div, tokenizer maison pour show/hide/setValue/validate/require/readonly/ConditionEval, ${VARIABLE}, opérateurs, strings, nombres)
  • Bug fix : la condition ConditionEval(...) d'une règle validate est désormais éditable librement via le mode manuel — l'admin peut taper l'expression complète sans passer par le constructeur visuel
  • Toggle [Constructeur visuel] [Saisie manuelle] — warning explicite si l'expression manuelle est trop complexe pour le builder
  • VariableProperties : remplacement du ConditionBuilder inline par un bouton « Configurer les conditions » + aperçu monospace des formules en lecture seule
  • 10 nouveaux tests RTL (conditionsModal.test.tsx) — 641 tests passants

v1.3.1

  • DndManager interne — <FormEditor /> activera désormais le DnD complet sans wrapper externe
  • Canvas DnD : drag-handle ⠿ dédié sur le header des pages (a11y + keyboard sensor)
  • Canvas DnD : bouton « + Ajouter un champ au roster » + zone de drop sur le corps du roster
  • createDragEndHandler(actions, getForm) exporté comme factory pure (testable sans React)
  • resolveTarget corrigé : rosterCode transmis correctement lors d'un drop sur un roster
  • Compat : Toolbox/Canvas (DnD) deviennent les défauts ; ToolboxSimple/CanvasSimple restent disponibles ; Wrapper déprécié (warning console one-shot)

v1.3.0

  • Section ROSTER dans la toolbox (4 types : check, list, collection, collection extended) — drag & drop
  • Onglet Variables : référentiel par scope, recherche par code+nom, ajout par clic ou drag
  • Variable liée : bannière , champs code/name grisés, surcharge locale par champ
  • setVariableOverride, resetVariableOverride — double écriture overrides + miroir sur la variable
  • addVariableFromTemplate(pageCode, template, rosterCode?) — dénormalisation du template
  • IEditorAdapter.loadTemplates?: (scopeId) => Promise<IVariableTemplate[]> (optionnel)
  • availableTemplates + setAvailableTemplates — état UI hors undo/redo
  • Bouton « + Add Roster » supprimé (la toolbox est désormais le seul point d'entrée)
  • IFormPage.items[] source de vérité pour le canvas — variables et rosters réordonnables ensemble via moveItem
  • 34 nouveaux tests (115 total)

v1.2.0

  • LocalizedInput : saisie bilingue avec onglets FR/EN dans le panneau propriétés
  • LocalizedField : wrapper label + LocalizedInput
  • Section « Langues » dans FormProperties : configurer langConfig, ajouter/retirer des langues
  • Confirmation avant suppression d'une langue (stripLangFromForm)
  • DefaultLang verrouillée (UX safety net)
  • flattenLocalized exporté publiquement
  • 15 nouveaux tests (81 total)

What's new in v1.1.0

  • ConditionBuilder gains an optional rosterContext prop. When provided, conditions are validated against the roster scope (variables in the same row, backward-jump detection, self-reference rules, circular dependencies) via RosterConditionEngine from @msbci/form-core v1.1.0. Errors are surfaced inline under the condition preview.
  • VariableProperties auto-detects when a variable lives inside a roster and forwards the scope context to ConditionBuilder. No change required in host applications.
  • Aligned with @msbci/form-core v1.1.0 (4 new variable types, extended IFieldResponseMetadata, IFormRoster.collectionConfig).

License

Copyright (c) 2026 MOSOBI — All rights reserved. Commercial license required. Contact: dev@mosobi.com