npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@msbci/form-renderer

v1.19.0

Published

React form renderer — themeable, data-source-aware, zero UI dependency

Readme

@msbci/form-renderer

npm version license peer dep

React form renderer — themeable, data-source-aware, zero UI dependency. Renders forms defined by @msbci/form-core schemas.

Installation

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

Minimal Usage

import { FormRenderer } from '@msbci/form-renderer'

<FormRenderer
  formSchema={myFormDefinition}
  onSubmit={(responses) => api.saveSubmission(responses)}
  mode="fill"
  labels={{ submit: 'Submit', next: 'Next', previous: 'Previous' }}
/>

Theme System

Inject your own colors, typography, and entire React components:

import type { IMosobiTheme } from '@msbci/form-renderer'

const myTheme: Partial<IMosobiTheme> = {
  colors: {
    primary: '#3B82F6',
    secondary: '#8B5CF6',
    background: '#FFFFFF',
    surface: '#F9FAFB',
    error: '#EF4444',
    warning: '#F59E0B',
    success: '#10B981',
    text: '#111827',
    textSecondary: '#6B7280',
    border: '#E5E7EB',
  },
  // Override built-in components with your design system
  components: {
    Button: MyCustomButton,
    Input: MyCustomInput,
    Select: MyCustomSelect,
  },
}

<FormRenderer formSchema={schema} theme={myTheme} />

If theme.components.Select is provided, it replaces the default <select>. Otherwise, the built-in HTML component is used.

DataSource Connectors

Connect selection fields to live data with inter-field filtering:

import type { IDataSourceConnector } from '@msbci/form-core'

const dataSources: Record<string, IDataSourceConnector> = {
  countries: {
    id: 'countries',
    name: 'Countries',
    fetch: async ({ search }) => api.getCountries({ search }),
  },
  cities: {
    id: 'cities',
    name: 'Cities (filtered by country)',
    fetch: async ({ dependsOn }) =>
      api.getCities({ countryId: dependsOn?.countryId }),
  },
}

<FormRenderer
  formSchema={schema}
  dataSources={dataSources}
  theme={myTheme}
  onSubmit={handleSubmit}
/>

When a dependency value changes, the connected field automatically re-fetches with 150ms debounce and clears its current selection.

Modes

| Mode | Description | |------|-------------| | fill | Default — user can enter data and submit | | readonly | All fields disabled, no submit button | | review | Read-only with submission data pre-loaded |

<FormRenderer formSchema={schema} initialResponses={savedData} mode="readonly" />

Apercu de mise en page (v1.15.0+)

designPreview est orthogonal a mode : il ne change pas la facon de saisir, il suspend l'evaluation des conditions. Il s'adresse au concepteur qui verifie une disposition, jamais a l'utilisateur qui remplit.

<FormRenderer formSchema={schema} designPreview />

| | Rendu reel (defaut) | designPreview | |---|---|---| | Pages masquees par une condition | absentes | affichees | | Champs masques par une condition | absents | affiches | | Tableau sans ligne | aucune ligne | une ligne, pour montrer les colonnes | | Champs calcules | calcules | calcules, a l'identique | | « Suivant » | valide la page | ne valide rien | | Depot | disponible | indisponible | | Bandeau d'avertissement | absent | permanent |

Le bandeau n'est pas decoratif : sans lui, un concepteur croirait avoir teste le comportement du formulaire alors qu'aucune condition n'a ete evaluee. Son texte se remplace par labels.designPreview.notice.

@msbci/form-editor >= 1.12.0 offre la bascule entre les deux modes dans son onglet d'apercu et transmet designPreview a son PreviewComponent.

Field Registry

MOSOBI Forms ships with built-in field components for all standard variable types. For custom or domain-specific fields, use the field registry to register your own components.

Built-in types

text · textarea · number · date · datetime · time · select · multiselect ·
checkbox · radio · file · image · email · gps · rating · calculated ·
hidden · label · panel · richtext · listradio · photo

Behind the scenes 21 variable types are mapped to 13 components (e.g. text/textarea/email/calculated/hidden/label all share TextField; date/datetime/time share DateField; panel, richtext, listradio and photo each have a dedicated display-only or input component added in v1.1.0).

Registering a custom component

import { useState } from 'react'
import {
  FormRenderer,
  registerFieldComponent,
  type FieldProps,
} from '@msbci/form-renderer'
import { useFormContext } from '@msbci/form-renderer'

// 1. Create a custom field component
function PhoneField({ variable, instanceNumber }: FieldProps) {
  const { setValue, getValue, mode } = useFormContext()
  const value = (getValue(variable.code, instanceNumber) as string) ?? ''
  const readOnly = mode === 'readonly' || variable.isReadonly

  const sanitize = (input: string) => input.replace(/[^0-9+\s-]/g, '')

  return (
    <input
      type="tel"
      value={value}
      onChange={(e) => setValue(variable.code, sanitize(e.target.value), instanceNumber)}
      placeholder={variable.placeholder ?? '+1 555 0100'}
      readOnly={readOnly}
      disabled={readOnly}
      style={{ width: '100%', padding: 8, borderRadius: 4, border: '1px solid #ccc' }}
    />
  )
}

// 2. Register it once at app initialization (e.g. in your entry file)
registerFieldComponent('phone', PhoneField)

// 3. Use it in a schema like any other type
const schema = {
  id: 'F', code: 'CONTACT', name: 'Contact', version: '1.0.0', isPublished: true,
  pages: [
    {
      id: 'P1', code: 'P1', name: 'Contact details', order: 0, isRepeatable: false,
      variables: [
        {
          id: 'mobile', code: 'MOBILE', name: 'Mobile phone',
          // The custom type registered above
          type: 'phone' as const,
          order: 0, isRequired: true, isReadonly: false, isHidden: false,
        },
      ],
      rosters: [],
    },
  ],
}

function MyApp() {
  return <FormRenderer formSchema={schema} onSubmit={(data) => console.log(data)} />
}

Overriding a built-in type

registerFieldComponent overwrites whatever component is registered for the same key. Useful for replacing a built-in with a richer alternative:

import { registerFieldComponent, type FieldProps } from '@msbci/form-renderer'

function FancyDateField(props: FieldProps) {
  // Wrap your favourite date picker library here
  return <MyFavouriteDatePicker {...props} />
}

registerFieldComponent('date', FancyDateField)

To restore a built-in, re-register the original component (re-imported from the package) or import it from @msbci/form-renderer directly.

FieldProps interface

export interface FieldProps {
  /** The variable schema being rendered (code, type, options, validation, …). */
  variable: IFormVariable
  /** 1-based instance index when the variable lives in a repeatable page or roster row. */
  instanceNumber?: number
}

To access the surrounding form state inside a custom field, use the form context hook:

import { useFormContext } from '@msbci/form-renderer'

function MyField({ variable, instanceNumber }: FieldProps) {
  const { getValue, setValue, mode, errors } = useFormContext()
  const value = getValue(variable.code, instanceNumber)
  const error = errors.find((e) => e.variableCode === variable.code)
  // … render however you like
}

TypeScript support

FieldProps is the canonical type for any field component:

import type { FieldProps } from '@msbci/form-renderer'
import type { ComponentType } from 'react'

const PhoneField: ComponentType<FieldProps> = ({ variable, instanceNumber }) => {
  // …
  return null
}

If your custom type needs to be statically known to TypeScript (e.g. for autocomplete in schemas), you can broaden VariableType via module augmentation in your app:

// types/msbci-form-core.d.ts
declare module '@msbci/form-core' {
  type VariableType = 'phone' | 'currency' // your additions
}

Or simply use a built-in type (e.g. 'text') and discriminate in your component via variable.metadata.

Notes

  • Registration is global. Call registerFieldComponent once at app startup; subsequent calls for the same key overwrite the previous binding.
  • Order of imports matters. Register before mounting <FormRenderer> so the renderer sees your binding.
  • Built-in field components are also re-exported (TextField, NumberField, etc.) and can be composed inside your custom one.

Impression et sommaire (v1.9.0+)

Trois options, toutes desactivees par defaut. Un hote qui n'en demande aucune obtient le rendu anterieur, sans un noeud ni un attribut de style de plus.

<FormRenderer
  formSchema={schema}
  enablePrint            // feuille @media print + doublon textuel des valeurs
  enableToc              // sommaire des pages visibles
  enableViewToggle       // bascule « page par page » / « tout le formulaire »
  view="paged"           // vue initiale : 'paged' (defaut) ou 'full'
  onViewChange={setView}
  printOptions={{ pageMargin: '15mm', pageBreakBetweenPages: true }}
  labels={{
    toc: { title: 'Sommaire', errorCount: (n) => `${n} erreur(s)` },
    view: { paged: 'Vue page par page', full: 'Tout le formulaire', print: 'Imprimer' },
    summary: { title: 'Recapitulatif', rowLabel: 'Ligne', emptyValue: '—' },
  }}
/>

En vue 'full', le formulaire porte son propre depot : les boutons de page n'y ont pas d'objet, mais le declarant doit pouvoir deposer sans repasser par la vue page par page. Le controle reste celui de toutes les pages visibles, comme pour un depot ordinaire (v1.13.0+).

Pourquoi une feuille !important

Toute la mise en forme du rendu est portee par des styles en ligne, qu'aucune feuille de style d'hote ne peut surcharger. Dans la cascade CSS, une seule chose passe devant un style en ligne ordinaire : une declaration !important d'une feuille d'auteur. Chaque !important de la feuille d'impression est donc la condition pour que la regle s'applique, et non une facilite d'ecriture. Les regles sont toutes portees par @media print et par la classe racine du rendu : elles n'existent qu'a l'impression, et seulement sous un formulaire.

La feuille retire les elements d'interface (navigation, progression, barres d'instances, aides de saisie, boutons, fenetres modales), neutralise les couleurs de fond, empeche la coupure d'un champ ou d'une ligne de tableau, et repete l'en-tete d'un tableau qui se poursuit sur la feuille suivante (thead { display: table-header-group }).

Le doublon textuel des valeurs

Un champ de saisie imprime son cadre et tronque son contenu : aucune regle CSS ne fait grandir un <input> pour reveler ce qu'il contient. Sous enablePrint, chaque champ rend donc, a cote de son controle, un <span class="msbci-form-print-value"> portant la valeur mise en forme — display: none a l'ecran, revele a l'impression, ou la feuille masque le controle lui-meme. Le doublon est pose par FieldWrapper, donc il vaut aussi pour un composant de saisie injecte par l'hote (theme.components.*) ou enregistre par registerFieldComponent.

Un hote qui remplace FieldWrapper lui-meme compose le meme resultat avec les exports buildPrintStylesheet, PRINT_ROOT_CLASS, PRINT_UI_CLASS, PRINT_PAGE_CLASS, PRINT_FIELD_CLASS, PRINT_VALUE_CLASS, formatResponseValue et formatVariableValue. Poser PRINT_UI_CLASS sur un element propre a l'hote suffit a le retirer du document imprime.

Le sommaire

FormToc liste les pages visibles — une page masquee par une condition n'y figure pas — permet d'en atteindre une directement et affiche le nombre d'erreurs qu'elle porte. Ces erreurs sont celles du contexte, telles que la validation du formulaire complet les a produites (validateAllPages, v1.7.0+) : aucun controle n'est refait, deux mecanismes de validation qui se contredisent seraient pires qu'un seul. Le sommaire ne signale donc une page qu'apres une tentative de depot ou de navigation.

Exported Components

FormRenderer · FormGroup · RosterRenderer · VariableRenderer · FieldWrapper · TextField · NumberField · DateField · CheckboxField · RadioField · SelectField · FormProgress · FormNavigation · FormSummary · FormToc · ValidationErrorsModal

All components are individually importable for advanced layouts.

hideLabel (optionnelle)

FieldWrapper et VariableRenderer acceptent hideLabel?: boolean. A true, le libelle du champ est masque visuellement mais reste expose aux technologies d'assistance. A reserver aux mises en page ou l'intitule est deja porte par un autre element — c'est ce que fait le rendu collection_extend, dont l'en-tete de colonne tient lieu de libelle. Valeur par defaut : false.

Enregistrement automatique du brouillon (v1.19.0+)

onChange ne remonte qu'un code de variable et une valeur. Sur un tableau, deux lignes emettent le meme code : un hote qui reconstruirait la carte des reponses a partir de ces notifications ecraserait la premiere ligne par la seconde. Trois proprietes facultatives donnent acces a la carte complete, sans passer par un clic simule sur le bouton de brouillon.

onResponsesChange — la carte complete a chaque changement

<FormRenderer formSchema={schema} onResponsesChange={(responses) => cache.current = responses} />

Elle recoit la carte telle que le moteur la detient : clefs indexees (MONTANT_1, MONTANT_2), compteurs de lignes, champs calcules et valeurs posees par les actions automatiques. Les valeurs sont brutes : le contexte d'affichage (displayValue, intitules, page) n'est pas calcule ici, parce qu'il le serait a chaque frappe.

Elle est emise a chaque changement. Un hote qui veut espacer son traitement conserve la derniere carte recue et n'agit qu'apres une pause de saisie : chaque emission portant l'etat complet, aucune valeur n'est perdue par un traitement differe.

autoSave — l'enregistrement tenu par le rendu

<FormRenderer
  formSchema={schema}
  onDraftSave={(responses) => api.saveDraft(responses)}
  autoSave={{ idleMs: 5000, minIntervalMs: 30000 }}
/>

L'hote ne fournit que onDraftSave : le rendu attend la fin de la saisie (idleMs, defaut 5000 ms), espace deux envois (minIntervalMs, defaut 30000 ms) et appelle onDraftSave avec la meme carte enrichie que le bouton de brouillon. enabled: false suspend sans demonter le rendu. Sans onDraftSave, la declaration reste sans effet.

onDraftSaveHandle — la commande, pour l'hote qui tient sa propre cadence

const commande = useRef<RequestDraftSave | null>(null)

<FormRenderer
  formSchema={schema}
  onDraftSave={(responses) => api.saveDraft(responses)}
  onDraftSaveHandle={(c) => { commande.current = c }}
/>

Le rendu publie sa commande d'enregistrement au montage, et null lorsqu'il n'y a rien a enregistrer — pas de onDraftSave, ou mode autre que la saisie. L'appel rend 'started', ou 'busy' si un enregistrement est deja en vol ; la carte transmise a onDraftSave est celle du bouton de brouillon. C'est le chemin des hotes dont la cadence depend d'autre chose que le temps (bail entre onglets, passage en arriere-plan, retour du reseau, poids du dernier envoi).

data-testid="form-draft-button" reste pose sur le bouton, mais cesse d'etre un point d'accroche officiel : le declenchement passe desormais par ces proprietes.

v1.19.0

Un hote qui voulait enregistrer un brouillon sans geste de l'utilisateur n'avait aucun moyen propre d'obtenir la carte des reponses. onChange ne remonte qu'un code et une valeur, et sur un tableau de deux lignes il emet deux fois le meme code : reconstruire la carte a partir de ces notifications ecrasait la premiere ligne par la seconde — corruption silencieuse dans un formulaire a tableaux. Le seul chemin restant etait de simuler un clic sur le bouton de brouillon, repere par son attribut de test : l'hote dependait alors d'un detail d'implementation du rendu.

  • onResponsesChange(responses), nouvelle propriete facultative de FormRenderer (et option de useFormEngine). Elle remonte la carte complete apres chaque changement, avec ses clefs indexees (MONTANT_1, MONTANT_2), les compteurs de lignes, les champs calcules et les valeurs posees par les actions automatiques. Elle est emise apres les actions automatiques, jamais sur un etat intermediaire, et pas du tout tant que rien n'a change. Les valeurs sont brutes : enrichir le contexte d'affichage a chaque frappe couterait un parcours complet des reponses pour une carte que l'hote n'enregistre pas a cette cadence.
  • autoSave, nouvelle propriete facultative. L'hote ne fournit que onDraftSave ; le rendu attend la fin de la saisie (idleMs, defaut 5000 ms), espace deux envois (minIntervalMs, defaut 30000 ms), reporte d'une seconde si un enregistrement est en vol, et transmet la meme carte enrichie que le bouton de brouillon. enabled: false suspend sans demonter le rendu.
  • onDraftSaveHandle, nouvelle propriete facultative. Le rendu publie sa commande d'enregistrement — () => 'started' | 'busy' — et null quand il n'y a rien a enregistrer. Elle s'adresse aux hotes dont la cadence ne depend pas que du temps : bail entre onglets, passage en arriere-plan, retour du reseau, poids du dernier envoi. La condition de publication est celle de l'affichage du bouton de brouillon : la commande existe exactement quand le bouton existe.
  • data-testid="form-draft-button" est conserve. Le retirer serait une rupture pour qui s'y appuie ; il cesse simplement d'etre le chemin officiel.
  • Impact : additif. Les trois proprietes sont facultatives ; un hote qui n'en declare aucune obtient un rendu et un comportement identiques. La carte transmise a onDraftSave est inchangee, par le bouton comme par les nouveaux chemins. 12 tests ajoutes, 363 passants.

v1.18.0

Un refus de navigation annoncait un nombre de champs a corriger sans jamais dire lesquels. Quand le champ refuse n'etait pas affichable, l'utilisateur n'avait plus rien pour avancer, et le support rien pour diagnostiquer.

  • Le motif du refus sort enfin du moteur. Nouvelle propriete facultative onValidationBlocked(errors) sur FormRenderer et FormNavigation : elle rend la liste des champs en defaut, chacun avec son code et la regle qui a refuse. En complement, chaque refus est trace en console — page, code de champ, regle, message — parce que l'information existait deja et n'atteignait personne.
  • Un refus qui ne peut pas etre montre le dit. Le deplacement vers le premier champ fautif rapporte desormais s'il a trouve sa cible. Quand il ne la trouve pas, le message nomme le champ et signale qu'il n'apparait pas sur la page, au lieu d'afficher un compte que rien ne permet de resoudre. Libelle surchargeable navigation.blockedUnreachable.
  • Un champ de type hidden ne retient plus l'enchainement. Il ne pose aucun controle a l'ecran : l'utilisateur n'a aucun moyen de lever le refus. Le depot continue de le valider — c'est la navigation qui ne doit jamais buter sur ce qu'on ne peut pas atteindre.
  • Republication d'alignement. @msbci/form-core passe en 1.16.0 (un conteneur ne peut plus etre rendu obligatoire, un panneau masque emporte ses enfants) ; la dependance etant epinglee a l'exacte version, ce paquet est republie pour rester installable avec elle.
  • Impact : additif. La propriete et le libelle sont facultatifs ; un hote qui ne les fournit pas retrouve le message et le comportement anterieurs, a la trace console pres. 7 tests ajoutes, 351 passants.

v1.17.0

Sur un formulaire administratif, chaque instance encadree d'un tableau portait un titre reprenant integralement l'intitule du tableau, suivi du numero. Un intitule de deux lignes doublait donc la hauteur de chaque cadre, sans rien apporter quand il n'y avait qu'une instance.

  • Deux reglages de schema, honores ici. roster.hideInstanceTitle retire cette ligne repetee ; roster.showRowNumber pose alors un numero de ligne a gauche de l'instance. Le numero n'a d'effet que si le titre est masque : ensemble ils feraient doublon.
  • Ou ils s'appliquent. Aux variantes rendues en cadres : collection, et le rendu pilote par pilotVariableCode. Le tableau etendu numerote deja ses lignes dans une colonne dediee ; une variante check / list a options porte le libelle de l'option sur chaque ligne. Ni l'un ni l'autre ne change, et le reglage n'y est pas propose par l'editeur.
  • Masquer un titre ne prive pas du reperage. Le cadre prend alors role="group" et un aria-label portant le nom de l'instance : sans lui, deux instances deviendraient indiscernables a la lecture par synthese vocale. Quand le titre est visible, il joue deja ce role et rien n'est ajoute.
  • L'impression suit. Le numero est un bloc de texte, non un controle : il figure sur la sortie imprimee comme a l'ecran, et la regle qui empeche une instance d'etre coupee entre deux pages est inchangee.
  • Republication d'alignement. @msbci/form-core passe en 1.15.0 ; la dependance etant epinglee a l'exacte version, ce paquet est republie pour rester installable avec elle.
  • Impact : additif, les deux reglages etant absents par defaut. Un tableau qui ne les porte pas est rendu a l'identique, y compris ses instances, son bouton de suppression et ses champs. 7 tests ajoutes, 344 passants.

v1.16.1

  • Republication d'alignement. @msbci/form-core passe en 1.14.0 (duplication d'un sous-arbre de formulaire) ; la dependance etant epinglee a l'exacte version, ce paquet est republie pour rester installable avec elle. Aucun changement de code de rendu, aucun changement de comportement : 337 tests passants, inchanges.

v1.16.0

Dans un tableau, le contenu des cellules pouvait deriver au milieu de sa colonne sous un intitule reste, lui, aligne a gauche. Ce lot rend l'alignement du rendu explicite plutot qu'herite.

  • Cause. Aucun element du rendu ne declarait son alignement horizontal, a l'exception des en-tetes de colonnes du tableau etendu. Le contenu des cellules heritait donc de l'alignement de l'application hote : une seule regle text-align posee sur un conteneur parent centrait le numero de ligne, les boutons, les groupes de cases et le texte saisi dans les champs, pendant que les intitules de colonnes, proteges par leur propre declaration, restaient a gauche. Cette asymetrie est ce que l'on voyait. Mesure, sous un hote centre : numero de ligne a 200,2 px pour un intitule a 174 px.
  • Correctif. La racine du rendu declare text-align: left. Chaque cellule du tableau etendu — numero de ligne, champ, actions — le declare aussi, ainsi que l'en-tete de la colonne d'actions, que le navigateur centrait par defaut faute de style (regle propre a th). Un champ d'une cellule s'aligne donc sur l'intitule de sa colonne et sur un champ hors tableau, quelle que soit la feuille de style de l'hote.
  • Les lignes ne sont plus creusees. La cellule du numero de ligne s'aligne en haut comme les cellules de champ, au lieu d'etre centree verticalement et de flotter au milieu d'une ligne haute. Le champ d'une cellule perd la marge basse qui separe deux champs empiles sur une page : elle laissait un vide sous chaque controle. Hauteur de ligne mesuree : 72 px avant, 56 px apres pour une ligne d'un seul niveau ; 283 px avant, 267 px apres pour une ligne melant tous les types de champs.
  • Nouvelle propriete dense de FieldWrapper et VariableRenderer, facultative et fausse par defaut. Elle porte la suppression de cette marge. Un hote qui ne la passe pas obtient la marge d'avant.
  • Ce qui reste a l'hote. Un composant de saisie injecte par theme.components.* decide seul de sa largeur et de la disposition de son contenu : le paquet garantit que la cellule qui l'accueille commence a gauche et fait toute la largeur de sa colonne, pas que le composant la remplisse.
  • Impact : additif (dense). Les largeurs de colonnes declarees par colSpan et la disposition fixe qui les accompagne sont inchangees, avec et sans largeurs declarees. L'impression est inchangee : les valeurs imprimees s'alignent sur l'intitule de leur colonne. Un formulaire sans tableau est rendu a l'identique. 10 tests ajoutes, 337 passants.

v1.15.0

Un concepteur qui transcrit un formulaire ne peut pas verifier la quatrieme page sans remplir les trois premieres, ni voir un champ que sa condition masque. Ce lot ajoute un apercu de mise en page, et rend audible un refus de navigation qui ne l'etait pas.

  • Nouvelle propriete designPreview de FormRenderer, facultative et fausse par defaut. Un hote qui ne la passe pas obtient exactement le rendu d'avant : memes conditions evaluees, meme navigation, meme validation. Passee a true, aucune condition d'affichage n'est evaluee — toutes les pages et tous les champs sont rendus, un tableau montre au moins une ligne (sans quoi ses colonnes, qui sont justement ce qu'on vient verifier, n'apparaitraient pas), la navigation ne valide rien et le depot est indisponible.
  • L'ecart se voit a l'ecran. Un bandeau permanent rappelle que le rendu affiche n'est pas celui d'un utilisateur. Il ne suit pas le theme de l'hote : c'est un repere d'outil, pas un element du formulaire. Libelle remplacable par labels.designPreview.notice, repere de test form-design-preview-banner.
  • Les champs calcules continuent de se calculer. Leur expression n'interdit d'atteindre aucune page ; les neutraliser afficherait une case qui n'existe pas dans le formulaire reel. Seules les conditions d'affichage sont suspendues.
  • Un refus de « Suivant » dit desormais pourquoi. Le bouton refusait la page sans un mot des lors que l'hote n'avait pas active la fenetre de validation : le champ fautif portait bien son message, mais hors de l'ecran il etait introuvable et le bouton passait pour casse. Le refus annonce maintenant le nombre de champs a corriger a cote du bouton, et amene le premier d'entre eux a l'ecran avec le focus. Libelle remplacable par labels.navigation.blocked(nombre), repere de test form-navigation-blocked.
  • Impact : additif (designPreview, labels.navigation, labels.designPreview, types FormNavigationLabels et DesignPreviewLabels exportes). Le seul changement visible sans rien demander est le message de refus, qui remplace un silence. 10 tests ajoutes, 327 passants.

v1.14.0

Le rendu sait desormais lire une piece deposee dans un stockage, et y deposer quand l'hote le lui permet. Par defaut, rien ne change : sans branchement, une piece est encodee en base64 dans la valeur et relue ainsi, exactement comme avant.

  • Deux points d'entree, tous deux optionnels. Nouvelle propriete attachments de FormRenderer (type IAttachmentSupport, exporte) : upload(file, scope) fait de la valeur une reference IStoredFile au lieu d'une data URL, resolveUrl(ref, file) rend l'URL par laquelle une piece deposee s'affiche, maxBytes impose la borne de taille de l'exploitation, scope ajoute au contexte de depot ce que l'hote seul connait. Le rendu ne parle jamais a un stockage : il appelle ces fonctions, que l'hote cable sur ses propres routes — aucun secret de stockage ne descend dans le navigateur.
  • Le nom d'origine survit enfin. Une data URL ne portait aucun nom : en mode fichier unique, le champ affichait litteralement « Fichier televerse », et le recapitulatif comme l'impression n'avaient rien de mieux a dire. Une piece deposee restitue son nom partout — champ, liste du mode multiple, recapitulatif avant depot, doublon d'impression.
  • Tous les lecteurs d'une valeur de fichier sont devenus bivalents, pas un echantillon : FileField (nom affiche, apercu d'image, galerie, boite lumineuse, liste du mode multiple, mesure de taille, chemin theme.components.FileUpload), PhotoCaptureField, formatResponseValue — donc le recapitulatif et l'impression, qui en dependent — et la mesure de taille du coeur. Une valeur en data URL, une reference, et une soumission qui porte les deux a la fois : les trois se lisent.
  • Un apercu ne s'invente pas a partir d'une reference. Une reference est opaque : sans resolveUrl, la piece reste nommee et mesuree mais n'est pas previsualisee, plutot que d'afficher une image cassee. La resolution n'est demandee qu'une fois par reference et son resultat conserve, sans quoi chaque rendu la relancerait et ferait clignoter l'apercu. Nouveaux exports useAttachmentUrls et attachmentDisplayUrl, pour qu'un hote qui remplace un composant de depot resolve comme le rendu par defaut.
  • Un depot qui echoue se voit. Sans message, le fichier selectionne disparaissait sans raison apparente. L'echec est annonce a cote de la zone de depot et journalise ; la valeur reste vide. Nouveaux libelles optionnels labels.file.uploadFailed et labels.image.uploadFailed.
  • Le plafond de taille peut venir de l'hote. attachments.maxBytes porte la borne de l'exploitation ; un maxBytes declare dans le formulaire ne peut que la resserrer, jamais l'elargir. La borne vaut au depot et a la validation — sans quoi un fichier accepte a la selection serait refuse a la soumission. Sans maxBytes, le defaut de 5 Mio s'applique comme avant.
  • Impact : additif sur l'API (attachments, deux libelles optionnels, deux exports, les types IAttachmentSupport, IStoredFile et IAttachmentScope). Un seul changement de rendu, correctif : en mode fichier unique, une valeur qui declare un nom l'affiche au lieu de « Fichier televerse » — une data URL nue, qui n'en declare aucun, est rendue mot pour mot comme avant. 22 tests ajoutes, 317 passants.

v1.13.1

  • Republication de suivi. Aucun changement de code ni de comportement : le paquet est republie pour dependre de @msbci/form-core v1.12.0, qui ajoute la propriete facultative IFormDefinition.updatedAt. Le rendu n'en fait aucun usage. 295 tests passants, inchanges.

v1.13.0

Second passage en navigateur reel sur la meme notice administrative. Le lot precedent avait corrige ce que la construction du formulaire revelait ; celui-ci corrige ce que sa relecture avant depot revele — plus deux finitions d'impression et d'accessibilite.

  • Le recapitulatif ignorait les tableaux. FormSummary ne parcourait que les champs de premier niveau : aucune ligne de tableau n'y figurait, et une page dont tout le contenu est un tableau disparaissait de la relecture. Le declarant validait un dossier dont il ne voyait pas la moitie. Le recapitulatif parcourt desormais la page telle qu'elle est saisie — champs, panneaux, tableaux — et rend chaque tableau sous forme de tableau, une ligne par occurrence, avec ses en-tetes de colonnes. Nouveaux reperes de test : summary-roster-{CODE}, summary-roster-row-{CODE}-{N}, summary-cell-{COLONNE}-{N}.
  • Un panneau y apparaissait comme une ligne vide. Le panneau est un regroupement, il ne porte aucune valeur : il produisait une ligne « Exploitant — » que le lecteur prenait pour une reponse manquante. Un panneau est desormais un intitule de rubrique (summary-panel-{CODE}) suivi de ses champs ; un panneau sans champ visible n'apparait plus du tout. Meme traitement pour les blocs de mise en page (label, richtext), qui n'ont pas de valeur a relire.
  • Le recapitulatif affichait la valeur interne d'une option. Un choix ferme s'y lisait OUI la ou l'impression affichait Oui : le recapitulatif appelait formatResponseValue au lieu de formatVariableValue, qui seul remplace une valeur d'option par son libelle. Les deux vues du meme dossier disent maintenant la meme chose.
  • Un champ calcule etait exclu de la relecture. Un total calcule est une donnee du dossier, affichee a la saisie et imprimee : une page qui n'en portait qu'un disparaissait entierement du recapitulatif. Les champs calcules y figurent desormais ; seuls les champs masques restent exclus.
  • La vue « tout le formulaire » n'offrait aucun moyen de deposer. La bascule etant proposee en mode saisie, l'utilisateur pouvait tout remplir et se retrouver sans bouton de depot, sans autre issue que de revenir a la vue page par page. La vue complete porte maintenant le depot et l'enregistrement de brouillon, sans les boutons de page qui n'y ont pas d'objet — le controle reste celui de toutes les pages visibles, comme pour un depot ordinaire. Nouvelle propriete optionnelle hidePageNavigation sur FormNavigation, pour un hote qui compose sa propre mise en page.
  • Les erreurs etaient effacees a chaque changement de page. Le sommaire designait une page en defaut ; en la rejoignant, les pastilles disparaissaient et la page atteinte ne signalait plus rien. Suivre une erreur menait donc a un ecran muet. Les erreurs sont desormais conservees d'une page a l'autre : chacune porte le code de sa page, la revalidation d'une page ne remplace que les siennes, et une erreur disparait quand son champ est corrige ou quand sa page est revalidee sans defaut.
  • A l'impression, un choix ferme imprimait la liste des options et la valeur retenue (« Oui / Non / Oui »). La feuille d'impression masquait les input, pas les libelles d'options qui les accompagnent. Les listes de boutons radio et de cases a cocher portent desormais la classe d'interface : seul le doublon textuel du champ, qui porte deja la valeur retenue, subsiste sur le papier.
  • Les input type="radio" et type="checkbox" d'une liste d'options portent leur value. L'etat etait porte par React seul : sans consequence fonctionnelle, mais un outil d'accessibilite, un test automatise ou un remplissage assiste n'avait aucun moyen de lire l'option designee par un bouton.
  • Le nom d'un fichier joint s'affiche lorsqu'il est disponible. dataUrlFileName (exporte) lit un parametre ;name= porte par une data URL, et formatResponseValue reconnait deja un objet { name }. Rien dans ces paquets n'ecrit ce nom : la valeur d'une piece jointe reste la data URL nue, telle que tout hote existant la lit et la stocke. Un hote qui declare lui-meme le nom le voit desormais au recapitulatif, a l'impression et sur la vignette de depot.

Impact : additif sur l'API (hidePageNavigation, labels.summary.rowLabel, export dataUrlFileName). Trois changements de rendu, tous correctifs : le contenu du recapitulatif, la persistance des erreurs entre les pages, et le retrait des listes d'options du document imprime. 14 tests ajoutes, 295 passants — FormSummary n'en avait qu'un.

v1.12.0

Cinq defauts trouves a l'usage dans un navigateur reel, en construisant puis en remplissant une notice administrative complete. Aucun n'etait visible sous jsdom.

  • Un champ place sous un tableau remontait au-dessus de lui. Le rendu affichait tous les champs d'une page, puis tous ses tableaux. Un total calcule pose sous le tableau qui l'alimente apparaissait donc avant lui, a la saisie comme a l'impression : le formulaire livre ne ressemblait pas a celui qu'on avait dessine. Le contenu d'une page suit desormais l'ordre voulu, en alternant grilles de champs et tableaux. Une page servie a l'ancienne forme (variables[] et rosters[] separes) est recomposee par les rangs quand ils forment une seule suite ; s'ils se recouvrent — deux suites independantes, cas d'un schema ancien — la page garde sa presentation anterieure.
  • Une signature tracee avant la saisie du nom etait perdue sans un mot. La valeur n'etant enregistree qu'une fois l'engagement complet, le trait pose en premier etait rejete au relachement du pointeur. Le signataire voyait pourtant son trait a l'ecran ; il saisissait son nom ensuite, et le champ restait « non signe », sans explication ni moyen de s'en sortir autrement qu'en recommencant dans le bon ordre. Le trace est maintenant conserve jusqu'a ce que l'engagement soit complet, quel que soit l'ordre des gestes.
  • Le recapitulatif n'affichait que la page ou l'on se trouvait. FormSummary evaluait la visibilite des champs sans nommer leur page : la page de navigation courante faisait foi, et les reponses de toutes les autres disparaissaient. Le dossier se relisait presque vide juste avant d'etre depose.
  • Une piece jointe imprimait son contenu encode. La valeur d'un champ fichier est une data URL : le recapitulatif et le doublon d'impression en rendaient les milliers de caracteres de base64 — plusieurs feuilles pour un seul document joint. Seuls la nature et le poids sont desormais rendus. La zone « glissez vos fichiers ici » porte enfin la classe d'interface et disparait a l'impression, comme les autres aides de saisie.
  • Une ligne de tableau en disposition compacte pouvait etre coupee entre deux feuilles. Les regles d'impression ne protegeaient que les <tr> d'un tableau etendu. Nouvelle classe PRINT_ROW_CLASS (msbci-form-row), exportee, posee sur chaque ligne rendue en bloc.

Impact : additif sur l'API (une constante de classe exportee en plus). Trois changements de rendu, tous correctifs : l'ordre des elements d'une page, la description d'une piece jointe en toutes lettres, la zone de depot retiree du papier. 7 tests ajoutes, 281 passants.

v1.11.0

  • Un fichier hors plafond etait encode et stocke sans un mot. FileField ne mesurait la taille que pour l'afficher a cote du nom du fichier : rien ne refusait le depot. Le champ refuse desormais chaque piece avant encodage, sur la taille du fichier d'origine. Encoder d'abord reviendrait a charger en memoire ce qu'on s'apprete a rejeter, et a le faire grossir d'un tiers au passage. Le plafond est celui de fileConfig.maxBytes / imageConfig.maxBytes, ou le plafond par defaut de @msbci/form-core quand le formulaire n'en declare aucun.
  • Le refus est visible et nomme la limite. Un encadre data-testid="file-field-{CODE}-too-large", portant role="alert", liste les pieces refusees avec leur nom et la limite appliquee, en francais ou en anglais selon la langue de saisie. Sans lui, rien n'expliquerait pourquoi le fichier selectionne n'apparait pas dans la liste. En depot multiple, les pieces sous le plafond sont conservees et seules les autres sont refusees.
  • Un composant de depot injecte n'echappe pas au plafond. Quand l'hote fournit theme.components.FileUpload, la valeur qu'il produit est mesuree a son tour avant d'etre ecrite : le plafond ne depend pas du composant de saisie.
  • IFileFieldLabels.tooLarge / IImageFieldLabels.tooLarge (optionnels) recoivent le nom du fichier et la limite deja mise en forme, pour que l'hote formule le refus a sa maniere sans avoir a recalculer le plafond.
  • Impact : additif sur l'API (deux libelles optionnels de plus). Un seul changement de comportement, assume et voulu : une piece au-dela du plafond par defaut est refusee sur un formulaire qui n'en declarait aucun. Le rendu d'un formulaire existant est par ailleurs inchange. 5 tests ajoutes, 274 passants.

v1.10.0

  • Le type 'signature' se saisit, se relit et s'imprime. Nouveau SignatureField, inscrit au registre des champs. En mode typed (defaut), le signataire ecrit son nom et coche l'engagement ; la valeur n'est enregistree qu'une fois les deux poses, avec son horodatage. En mode drawn, une zone de trace a la souris ou au doigt (pointer events, donc tactile compris) s'y ajoute ; une fois le trait pose, c'est l'image enregistree qui est affichee, et non le canvas — un canvas repart vide a chaque rendu, la ou l'image survit a un changement de page comme a la reprise d'un brouillon.
  • La signature figure sur l'exemplaire imprime. Le doublon textuel pose sous chaque champ rend « nom — horodatage », jamais la data URL : formatResponseValue reconnait desormais la valeur de signature. La trace manuscrite, elle, est une <img> que la feuille d'impression ne masque pas ; la zone de saisie et la case d'acceptation portent la classe d'interface et disparaissent.
  • Le champ ne duplique pas le message d'erreur deja porte par l'enveloppe de champ, et son libelle est associe a la zone de nom (signature rejoint les types etiquetables par htmlFor).
  • Ce que le champ etablit : le nom declare, la date et l'heure de la saisie, le texte d'engagement en vigueur. Ce n'est pas une signature electronique qualifiee — rien n'y certifie l'identite du signataire ni ne scelle le document.
  • Composable par l'hote. SignatureField est exporte, ainsi que le type de libelles ISignatureFieldLabels (labels.signature de FormRenderer, relaye par fieldLabels.signature). Un hote qui injecte son propre composant de saisie compose la meme valeur avec buildSignatureValue de @msbci/form-core.
  • Impact : strictement additif — un composant de plus au registre, un bloc de libelles optionnel. Un formulaire sans champ signature est rendu a l'identique. 11 tests ajoutes, 269 passants.

v1.9.0

  • Aucune preparation a l'impression. Pas une seule regle @media print, et toute la mise en forme en styles en ligne qu'aucune feuille d'impression ne peut surcharger. Un formulaire rempli est pourtant une piece a produire : l'agent instructeur, le signataire et le declarant doivent pouvoir l'imprimer. Nouvelle option enablePrint : elle injecte une feuille @media print dont chaque !important est la condition pour passer devant les styles en ligne — c'est la seule declaration que la cascade CSS place au-dessus d'eux. La feuille retire les elements d'interface, neutralise les couleurs de fond, empeche la coupure d'un champ ou d'une ligne, et repete l'en-tete d'un tableau qui se poursuit sur la feuille suivante.
  • Une valeur saisie ne s'imprimait pas lisiblement. Un champ de saisie imprime son cadre et tronque son contenu, et aucune regle CSS ne fait grandir un <input>. Sous enablePrint, chaque champ rend donc un doublon textuel de sa valeur (span.msbci-form-print-value), masque a l'ecran et revele a l'impression, ou la feuille masque le controle. Le doublon est pose par FieldWrapper : il vaut aussi pour un composant de saisie injecte par l'hote (theme.components.*) ou enregistre par registerFieldComponent. buildPrintStylesheet, les cinq constantes de classe, formatResponseValue et formatVariableValue sont exportes pour un hote qui remplace FieldWrapper lui-meme.
  • Aucun mode « tout le formulaire ». Le rendu n'affichait qu'une page a la fois : rien a imprimer d'un seul tenant, et aucune vue d'ensemble. Nouvelle prop view ('paged' par defaut, 'full' pour rendre toutes les pages visibles a la suite, sans navigation), avec onViewChange et une bascule integree optionnelle (enableViewToggle). Une page masquee par une condition n'y figure pas, et elle y apparait des que sa condition devient vraie.
  • La visibilite d'un champ se lit maintenant par la page qui le rend. Tant qu'une seule page etait affichee, la page courante de la navigation suffisait ; plusieurs pages rendues a la suite devaient chacune declarer la sienne. isVariableVisible accepte un troisieme argument optionnel pageCode, et FormGroup publie le code de sa page par un contexte (PageScopeProvider / usePageScope, exportes). Omis, le comportement est celui d'avant.
  • Aucune vue d'ensemble du parcours. Sur un formulaire de six rubriques, on ne savait ni ou l'on en etait ni ce qu'il restait. Nouveau FormToc (enableToc) : il liste les pages visibles, en atteint une directement et signale celles qui portent une erreur. Il s'appuie sur la validation du formulaire complet introduite en v1.7.0 — les erreurs du contexte portent deja le code de leur page — et n'en cree pas un second mecanisme. IFormContextValue gagne un membre optionnel visiblePages ; un hote qui fournit son propre contexte sans l'implementer retombe sur form.pages.
  • Impact : strictement additif et opt-in. Toutes les nouvelles props sont desactivees par defaut, aucune signature existante n'est modifiee et le recapitulatif reprend la mise en forme des valeurs telle quelle, desormais partagee avec l'impression. Un hote qui ne demande ni impression ni sommaire obtient le rendu anterieur. 11 tests ajoutes, 258 passants.

v1.8.0

  • Les largeurs de colonnes etaient ignorees dans le tableau. La mise en page sur 12 colonnes (colSpan) etait honoree partout — page, panneau, lignes de tableau en presentation compacte — sauf dans la presentation etendue (collection_extend), ou toutes les colonnes recevaient la meme largeur. Un tableau melant un libelle long, une quantite et une unite courte y devenait illisible. Chaque en-tete porte desormais la largeur derivee de son colSpan, par la formule deja utilisee partout ailleurs (colSpan / 12), et la disposition fixe n'est imposee que si au moins une largeur est declaree — sans quoi le navigateur ignorerait une colonne plus etroite que son contenu. Un tableau sans aucun colSpan est rendu a l'identique.
  • Aucun champ de saisie ne portait d'identifiant. Le libelle designait, par htmlFor, un identifiant que personne ne portait : un lecteur d'ecran n'annoncait pas le libelle, et aucun outillage de test ou d'assistance ne pouvait viser un champ. L'identifiant ne pouvait pas etre le code seul — dans un tableau de N lignes il aurait ete duplique N fois, ce qui est invalide en HTML et rompt justement l'association qu'il porte. Il integre donc le numero de ligne, sous la forme CODE_N deja utilisee par les clefs de reponses, et reste stable d'un rendu a l'autre. Le libelle s'y rattache par htmlFor quand le controle est nativement etiquetable, par aria-labelledby pour les composites (liste deroulante, groupe de boutons radio). Le message d'erreur porte le meme identifiant suffixe et est designe par aria-describedby. fieldElementId, fieldLabelId, fieldErrorId et isLabelableType sont exportes pour un hote qui remplace un composant de saisie.
  • Les boutons radio d'une colonne de tableau partageaient le meme groupe HTML. L'attribut name valait le code de la variable, identique sur toutes les lignes : cocher « Oui » ligne 2 decochait la ligne 1. Le groupe integre desormais le numero de ligne. Le champ « oui/non par option » n'etait pas concerne, son groupe portait deja le numero de ligne.
  • Les regles de validation n'etaient pas posees sur les champs. Elles etaient evaluees en JavaScript sans qu'aucun attribut correspondant n'atteigne le controle : bornes numeriques et de date, longueur, motif, mode de saisie. Elles le sont maintenant, avec une exception assumee : le caractere obligatoire est expose par aria-required et jamais par l'attribut required. C'est le seul attribut qui rend invalide un champ vide, donc l'etat normal de toute page pas encore atteinte : pose sur le controle, il donnerait au navigateur de quoi refuser une soumission avec ses propres bulles, dans sa langue, en doublon des messages de l'application et sans connaitre sa validation par pages. Les autres attributs ne contraignent qu'une valeur effectivement saisie et hors bornes — un cas que l'application refuse deja — et apportent en echange une aide reelle a la saisie. Le message de la regle est repris dans title, de sorte qu'un signalement du navigateur affiche le texte de l'application.
  • Impact : additif. FieldWrapper accepte un instanceNumber optionnel, les champs gagnent des attributs, aucun n'en perd. Un formulaire sans largeur de colonne, sans regle de validation et hors tableau est rendu exactement comme avant. 17 tests ajoutes, 247 passants.

v1.7.0

  • Le champ calcule restait vide. Le rendu affichait le type calculated en lecture seule avec une pastille de formule, mais rien ne l'evaluait : aucun total de colonne, aucun sous-total, aucun report de montant. useFormEngine resout desormais les champs calcules par CalculationEngine (@msbci/form-core 1.8.0+) a l'initialisation — un brouillon rouvert affiche ses totaux sans attendre une saisie — et a chaque changement d'une dependance. La valeur est rangee dans les reponses (metadata.source: 'auto', comme toute valeur produite par le moteur) et part donc avec la soumission. Un champ calcule n'est pas saisissable : il l'etait deja, en lecture seule et desactive, et le reste.
  • La soumission ne validait que la derniere page. handleSubmit n'appelait que la validation de la page courante : un formulaire de six pages partait en n'ayant controle que la sixieme, alors que le serveur valide toutes les pages visibles (@msbci/form-server 1.5.0+). L'investisseur decouvrait au depot un refus portant sur une page qu'il croyait finie. La soumission passe maintenant par validateAllPages, qui delegue au parcours unique du coeur — le meme que celui du serveur.
  • L'erreur nomme sa page. ValidationErrorsModal accepte pageLabels (optionnel, code de variable vers libelle de page) et affiche le nom de la page sous chaque erreur ; cliquer une erreur ramene sur la page concernee, la ou le clic ne faisait que fermer la fenetre. Le passage d'une page a l'autre reste libre : c'est la soumission qui controle, pas la navigation.
  • Le navigateur n'est plus plus severe que le serveur. validateCurrentPage ecarte les erreurs portant sur un champ masque par une condition : elles bloquaient la navigation sur un champ que le serveur, lui, accepte.
  • Nouveau membre optionnel de IFormContextValuevalidateAllPages. Optionnel : un hote qui fournit son propre contexte de formulaire n'a rien a implementer, la soumission retombe alors sur la validation de la page courante.
  • Impact : aucune prop de FormRenderer modifiee, aucune signature existante changee. Un formulaire sans champ calcule est rendu a l'identique — le hook rend l'objet de reponses recu tel quel, sans nouvelle clef ni nouvelle reference. 11 tests ajoutes, 230 passants.

v1.6.0

  • Les champs d'un panneau sont rendus a l'interieur de l'encadre. Jusqu'ici PanelField n'affichait qu'un titre et une description : un panneau porteur de champs (IFormVariable.children, @msbci/form-core 1.7.0+) les rend maintenant dans son cadre, par la meme LayoutGrid que la page — la mise en page sur 12 colonnes (colSpan, startWithNewLine) y est donc honoree a l'identique. Un panneau masque par une condition emporte ses champs avec lui, et la condition d'un champ regroupe s'applique comme ailleurs.
  • Les champs regroupes comptent partout ou les champs comptent. Le recapitulatif, la recherche d'une variable par son code et le decalage des valeurs a la suppression d'une instance de page repetable parcourent desormais tous les champs de la page, panneaux aplatis.
  • Impact : strictement additif. Un panneau sans enfants rend exactement ce qu'il rendait, et un formulaire enregistre avant ce lot est affiche a l'identique. 4 tests ajoutes, 219 passants.

v1.5.0

  • Les conditions de tableau n'etaient evaluees nulle part — ni par le moteur, ni par le rendu. Rendre ces conditions editables sans les evaluer aurait ete pire que rien : un concepteur aurait cru avoir conditionne son tableau. IFormContextValue gagne un isRosterVisible?(rosterCode, instanceNumber?) optionnel, implemente par useFormEngine au-dessus de FormTree.isRosterVisible ; FormGroup n'affiche plus que les tableaux visibles, sur une page simple comme sur une page repetable.
  • Validation : un tableau masque par une condition ne valide plus aucune ligne. Sans cela, ses colonnes obligatoires auraient bloque une page ou le tableau n'apparait pas.
  • Compatibilite : isRosterVisible est optionnel. Un hote qui fournit son propre contexte de formulaire sans l'implementer conserve le comportement anterieur — tous les tableaux affiches.
  • Impact : aucune signature existante modifiee. 8 tests ajoutes, 215 passants.

v1.4.0

  • Les lignes de tableau n'etaient jamais validees. Cause : useFormEngine.validateCurrentPage, unique appelant de ValidationEngine.validatePage, ne transmettait ni numero de ligne ni nombre de lignes. Les valeurs de tableau, stockees sous des clefs suffixees, etaient lues comme absentes : un tableau a colonnes obligatoires passait la soumission entierement vide. Correctif : le hook resout le nombre de lignes de chaque tableau de la page et le transmet, avec l'etat « obligatoire » resolu par les conditions require.
  • Le nombre de lignes d'un tableau n'etait pas conserve. Cause : CollectionRoster tenait son compteur dans un etat local initialise au minimum, jamais ecrit dans les reponses. Changer de page et revenir remettait le tableau au minimum ; rouvrir un brouillon masquait les lignes saisies, qui restaient pourtant dans la soumission — invisibles, non modifiables, et transmises. Correctif : le compteur est persiste dans les reponses sous la clef reservee CODE__rowCount. Une soumission anterieure sans compteur reste lisible : le nombre de lignes est deduit des valeurs presentes.
  • Le bouton de suppression retirait toujours la derniere ligne. Cause : handleRemove decrementait un compteur sans recevoir le numero de ligne, et etait cable a l'identique sur chaque ligne. Supprimer la ligne 2 sur 5 retirait visuellement la ligne 5 ; les valeurs de la ligne 2 restaient, celles de la ligne 5 devenaient orphelines. Meme defaut sur les pages repetables, ou handleRemove(num) recevait le numero sans l'utiliser. Correctif : la suppression retire la ligne designee et remonte les suivantes d'un rang, valeurs comprises.
  • Nouveaux membres optionnels de IFormContextValueisVariableRequired, isVariableReadOnly, getRosterRowCount, addRosterRow, removeRosterRow, removeInstanceValues. Tous optionnels : un hote qui construit son propre contexte de formulaire n'a rien a implementer, les composants retombent sur le comportement anterieur (etat local).
  • Impact : aucune prop de FormRenderer modifiee, aucune signature existante changee. Le test qui verifiait que la suppression retirait « la derniere » ligne decrivait le defaut : il verifie desormais que la ligne designee est bien celle qui part. 11 tests ajoutes, 207 passants.

v1.3.4

  • Correctif — libelles dupliques dans le tableau collection_extend. Cause : chaque cellule du tableau appelait VariableRenderer, qui delegue a FieldWrapper le rendu du libelle ; l'intitule etait donc affiche une fois en en-tete de colonne (<th>) et une fois au-dessus du champ dans chaque ligne. Sur un tableau de six colonnes et dix lignes, soixante libelles redondants rendaient le formulaire illisible.
  • Correctif : nouvelle prop optionnelle hideLabel sur FieldWrapper et VariableRenderer. Lorsqu'elle est active, le libelle n'est pas supprime mais rendu visuellement masque (technique clip-path: inset(50%)), donc toujours present dans l'arbre d'accessibilite. RosterRenderer la positionne uniquement pour la mise en page collection_extend, ou l'en-tete de colonne porte deja l'intitule.
  • Accessibilite : chaque <th> recoit un id stable (roster-<CODE>-col-<VARIABLE>), chaque <td> un attribut headers correspondant, et le contenu de la cellule est enveloppe dans un role="group" avec aria-labelledby pointant sur cet en-tete. Le champ reste donc nommable par les technologies d'assistance, en navigation tableau comme en navigation formulaire.
  • Impact : aucun changement pour les autres types de roster (check, list, collection) ni pour les champs hors tableau — hideLabel est optionnelle et vaut false par defaut. Aucune signature existante modifiee.
  • Nouveaux tests de non-regression (rosterExtendLabels.test.tsx + tests ajoutes a rosterCollection.test.tsx) : un tableau de 6 colonnes x 10 lignes n'expose plus qu'un seul libelle visible par colonne, et 10 libelles accessibles masques. 196 tests passants.

v1.3.3

  • Republication corrigeant le protocole workspace:* dans le tarball publié (les dépendances internes @msbci/* étaient résolues en workspace:*, ce qui cassait l'installation autonome). Publié via pnpm publish qui remplace correctement ces références. Aucun changement de code applicatif.

v1.3.2

  • FileField multi-upload — un seul composant pour type: 'file' et type: 'image'. Mode single intact (maxFiles === 1 ou absent → valeur string legacy), mode multi (maxFiles > 1) → valeur string[]. Upgrade transparent : une valeur string héritée en mode multi est affichée comme [value].
  • UX multi-fichiers : drop-zone native (drag & drop sans dépendance externe), liste cliquable des fichiers avec nom + taille estimée + bouton suppression, masquage automatique de la drop-zone à maxFiles atteint.
  • Galerie image : grille de miniatures (auto-fill, minmax(120px, 1fr)), lightbox au clic (overlay sombre + <img> centré, fermeture par × ou Escape), bouton suppression sur chaque miniature.
  • Camera capture : si imageConfig.allowCamera, un bouton dédié « 📷 Prendre une photo » déclenche un input séparé avec capture="environment" pour l'accès direct à la caméra mobile.
  • Encodage parallèle : Promise.all(files.map(fileToBase64)) — sélectionner 10 fichiers ne séquentialise pas les FileReader.
  • Override d'i18n : nouvelles props labels.file / labels.image sur FormRendererProps, propagées via le FormContext (fieldLabels). Défauts FR + EN conservés.
  • Nouveaux types publics : IFieldLabels, IFileFieldLabels, IImageFieldLabels.
  • 14 nouveaux tests RTL (fileFieldMulti.test.tsx) — 189 tests passants.

v1.3.0

  • Compatible IFormPage.items[] (variables et rosters unifiés)
  • FormGroup / FormSummary lisent désormais via getPageVariables / getPageRosters — tolérant aux schémas legacy
  • Aucun changement cassant — l'API publique de FormRenderer reste identique
  • Bundle inchangé, 169 tests toujours verts

v1.2.0

  • Prop lang + onLangChange sur FormRenderer
  • LangProvider, useLang(), LangSelector (exportés publiquement)
  • Rendu multilingue sur tous les composants (champs, options, pages, rosters)
  • Stratégie de détection : prop / browser / selector / auto
  • Sélecteur de langue intégré (strategy='selector')
  • Defaults UI en français (Soumettre, Suivant, Précédent, ...)
  • 12 nouveaux tests i18n (169 total)

What's new in v1.1.0

  • 4 new field components added to the registry: PanelField (display-only bordered section), RichTextField (interpolated HTML with regex-based sanitization), ListRadioField (Yes/No radios per option) and PhotoCaptureField (mobile rear-camera via native capture="environment").
  • RosterRenderer now supports two dynamic row layouts: rosterType: 'collection' (compact card stack) and 'collection_extend' (extended table with column headers), both with add/remove based on IFormRoster.collectionConfig.
  • Responses passed to onSubmit and onDraftSave are automatically enriched with display labels, page/roster context and interpolated variable labels (via the new @msbci/form-core/enrichResponsesWithContext).
  • FieldWrapper now bypasses label/error chrome for display-only types (panel, richtext).
  • FieldProps type is now exported publicly.
  • Built-in type count: 17 → 21 with the 4 additions above. The Field Registry section was expanded with a complete custom-component example.

License

Copyright (c) 2026 MOSOBI — All rights reserved. Commercial license required. Contact: [email protected]