Releases: klee-contrib/focus4
v12.2
CollectionStore : tri multiple
Le store de collection gère maintenant du tri par plusieurs colonnes, à la fois en mode local
qu'en mode server
.
Cela implique les breaking changes suivants :
CollectionStore
:sortBy?: string
etsortAsc: boolean
ont été remplacés parsort: {fieldName: string; sortDesc?: boolean}[]
(L'inversion desortAsc
/sortDesc
ne change pas la valeur du tri par défaut, qui étaitsortAsc: true
avant et qui estsortDesc: false
maintenant).sort: {fieldName: string; sortDesc?: boolean}[]
est aussi utilisé dans leQueryInput
du service de recherche, à la place desortFieldName
etsortDesc
.
ActionBar
/Summary
/AdvancedSearch
orderableColumnList
:{label, key, order}
a été remplacé par{label, sort: {fieldName, sortDesc}[]}
.
Le tri est donc maintenant au moins toujours défini de la même manière 🙂
Impacts sur les composants
- Via les évols sur
orderableColumnList
, vous pouvez définir des tri par plusieurs colonnes - Dans
tableFor
, vous pouvez activer le tri multiple via la propmaxSort
, qui définit le nombre maximal de colonnes sur lesquelles il est possible de trier via les headers du tableau. Cette prop vaut1
par défaut pour conserver le comportement actuel.
Ces deux évolutions sont utilisées dans le showcase des composants dans la documentation 🙂
v12.1
Evolutions de useLoad
- Vous pouvez désormais utiliser
useLoad
sur unCollectionStore
en modelocal
(sans service de chargement dédié côté serveur). Cela fonctionne de la même façon qu'avec un store d'entité classique, à la différence près qu'il faut appelercollectionStore.search()
et nonstoreNode.load()
pour appeler manuellement le service de chargement (puisque c'était déjà la méthode pour le store avec service de chargement dédié).
Cela entraîne un petit breaking change, sur le fait qu'il n'est plus possible de renseignerstore.isLoading
manuellement sur unCollectionStore
local. Vous devriez de toute façon utiliseruseLoad
à la place maintenant, et sinon vous avez accès autrackingId
du store pour faire unrequestStore.track
à la main. - Les requêtes en cours lors d'un changement de paramètres ou si le composant contenant le
useLoad
est démonté sont maintenant correctement annulées, directement au niveau du navigateur. Cela veut dire que le résultat de cette requête ne sera même pas interprété par le navigateur, ce qui permet d'éviter tout une catégorie de problèmes si le serveur répond trop lentement.
Autres évolutions
- Les traductions de messages d'erreurs sur les champs peuvent désormais utiliser la définition du champ dans leur template. Par exemple, vous pouvez désormais avoir
Le champ '{{label}}' est obligatoire
comme message. - Le composant
InputFile
peut désormais afficher des icônes différentes en fonction du type du fichier téléchargé.
v12
Montées de version
Cette version de Focus réalise les montées de versions suivantes :
react
19mobx-react
9eslint
9lodash
=>es-toolkit
numeral
=>Intl.NumberFormat
(qui est une API native du navigateur)
Les breaking changes associés sont :
- Pour React, certaines APIs dépréciées depuis longtemps (mais pas toutes) ont enfin été retirées. A priori, si votre application à été commencée en React 16 vous ne devriez être concernés par aucune de ces dépréciations
- Pour MobX React, les breaking changes concernent les composants classes, sur lesquels il n'y a plus de "hack" fait pour rendre les props (et le state) observable. C'est potentiellement très dérangeant pour les applications qui utilisent (encore) des classes
- Pour ESLint, c'est juste le format de la configuration
- Pour les autres libs, elles ont été retirées de Focus, mais cela ne vous force pas nécessairement à en faire de même (comme
moment
à l'époque)
Retrait du module legacy
et support des composants classes
La montée de version de mobx-react
a aussi l'occasion de passer aux décorateurs standards stage-3, qui ne sont pas compatibles avec les anciens. Toutes ces évolutions combinées vont rentre une éventuelle montée de version pour les projets qui utilisent encore des composants classes très difficile, sans compter le temps qu'il faudrait pour mettre à jour le module legacy
lui même.
Le plus simple (pour l'instant en tout cas, il n'est pas exclus de revenir sur la décision plus tard, au moins partiellement) sera donc de supprimer le module legacy
(AutoForm
, @classAutorun
et l'ancien routeur à base de ViewStore
essentiellement), ainsi que les APIs makeFormNode
/makeFormActions
pour être cohérent. Si jamais un ancien projet souhaite faire la mise à jour vers la v12 dans le futur, son temps sera bien mieux utilisé à migrer vers des composants fonctions plutôt que de repasser sur tous ses composants classes pour essayer de continuer à les faire fonctionner...
Pour utiliser les nouveaux décorateurs, il faudra enlever l'option experimentalDecorators: true
de la configuration Typescript.
Redécoupage des modules
- Le module
forms
a été divisé en deux :forms
etform-toolbox
, pour séparer les composants pour créer les formulaires (fieldFor
,useFormNode
...) qui sont dans le premier, et les composants de formulaire, utilisés commeInputComponent
/SelectComponent
... (Input
,Select
...). Cela permet de retirer la dépendance au moduletoolbox
deforms
, pour permettre l'usage des formulaires Focus sans embarquer le moduletoolbox
qui contient les composants de base, et ouvrir la porte à un interface "natif" avec d'autres librairies de composants. Il y aura donc des imports à modifier. - Le module
focus4
a été supprimé. Vous devez maintenant explicitement référencer les modules dans votre package.json (un nouvel outil présenté plus loin vous aidera à le faire). Le modulecore
expose désormais une fonctionbaseI18nextConfig
qui permet de simplifier l'initialisation des traductions (le modulefocus4
réexportait toutes les traductions dans un seul objet avant). - L'objet de configuration (
config
) a été divisé entre le modulecore
(coreConfig
) etstyling
(uiConfig
), afin de pouvoir avoir des séparations franches entre les modules de base de Focus. En particulier,styling
ettoolbox
ne dépendent plus decore
et pourraient être utilisés dans une application qui ne référence pas le reste de Focus (ni même MobX). En passant,uiConfig
utilise désormais Material Symbols à la place de Material Icons par défaut.
Traductions
Focus comporte désormais des traductions en anglais, et utilise désormais react-i18next
dans les composants pour s'interfacer avec i18next
. Cela permet en particulier d'utiliser la fonction i18next.changeLanguage()
et d'avoir l'intégralité des composants Focus qui se mettent à jour par défaut, sans avoir à recharger la page. Pour que cela fonctionne avec vos propres composants, il faudra utiliser le hook useTraduction()
de react-i18next
vous aussi.
De plus, une nouvelle option de configuration coreConfig.useI18nextAcceptHeader
peut être renseignée à true
pour renseigner le header Accept-Language
par défaut sur tous vos appels serveur avec la langue sélectionnée dans i18next
. Vous pouvez aussi laisser le header par défaut posé par le navigateur et utiliser le module d'i18next
qui permet la détection automatique de la langue de l'utilisateur si vous préférez automatiser la chose.
Le starter-kit est donc désormais entièrement traduit et propose une dropdown dans son header pour changer entre français et anglais. La documentation dispose également d'une dropdown, mais elle ne concerne que les traductions Focus (vous pourrez encore attendre longtemps la doc Focus en anglais hein 😅).
Refonte de l'installation
Les modules de Focus déclarent désormais explicitement des peer dependencies vers react
, react-dom
, mobx
et mobx-react
, et toutes les autres dépendances (comme es-toolkit
qui remplace lodash
par exemple), ainsi que tous les modules Focus, doivent être définis explicitement dans les dépendances du projet, au lieu de dépendre d'un ajout implicite de tout ça via le méta package focus4
. Cela permet d'arrêter de définir des "fausses" peerDependencies
pour faire marcher les auto-imports.
Le module tooling
expose désormais une nouvelle commande install
ou update
(qui sont rigoureusement identiques), pour gérer l'installation ou la mise à jour des modules Focus. Au lancement, elle va parcourir votre package.json
et récupérer la dernière version de chaque module Focus défini dedans (ou ajouter tous les modules s'il n'y en a aucun), ainsi que celles de leurs peer dependencies. Vous pourrez donc utiliser cette commande pour gérer les mise à jour Focus automatiquement, au lieu de faire des mises à jour du package.json à la main et de bidouiller les peer dependencies pour que ça continue à marcher.
Exemple de montée de version
La mise à jour a été réalisée sur le starter kit ici, mais il y a aussi beaucoup d'évolutions liée aux traductions qui ne sont pas vraiment obligatoires à faire 🙂
v11.24
Une petite release contenant quelques évolutions et de fixes de stabilités.
disabled
Tous les composants de Select et d'Autocomplete supportent maintenant une liste de valeurs dans leur propriété disabled
, au lieu d'un simple booléen, afin de pouvoir désactiver certaines options uniquement au lieu de tout le champ.
InputFile
Focus contient désormais (enfin !) un composant de upload de fichier, il est présenté dans la documentation.
v11.23
Evolutions sur les formulaires
Fix #193
-
useFormNode
peut désormais être appelé avec une définition d'entité à la place d'un noeud existant. Vous n'êtes donc plus obligés d'utiliser un store externe global, ou de faire lebuildNode
vous même dans le composant -
useFormActions
:-
Prend une nouvelle méthode
init(init?: () => Promise<EntityToType<E0>>)
, qui sera appelée à la place deload
au premier rendu s'il n'y pas deload
à appeler. Elle effectue unset
des données retournées par la méthode en paramètre sur leformNode
(et non unreplace
sur lestoreNode
commeload
), puis écrase les données du noeud source par les données du formulaire (avec unreplace
cette fois-ci).Elle permettra de faire des initialisations de données pour la création sans utiliser
load
(ou un effet externe) quand on a besoin d'un appel serveur et de s'assurer que le valeur dehasChanged
du formulaire sera toujours calculée à partir des données initiales. Les données complèteront celles déjà définies dansuseFormNode
(d'où leset
), etinit
pourra être appelé sans paramètre si on veut juste reset le noeud source.Idéalement, tout formulaire utilisé en création devrait appeler
init
, ce qui ne correspond malheureusement pas forcément à tous les formulaires sansload
(il peut être défini dans unuseLoad
séparé par exemple, et ici on ne voudra pas du tout reset le noeud source). -
Prend deux nouvelles méthodes
create
etupdate
, mutuellement exclusives avecsave
, qui seront appelées paractions.save()
selon si on a desparams
(pourupdate
) ou non (pourcreate
).update
sera appelée avec lesparams
puis avec le contenu du formulaire, pour correspondre aux API PUT classiques. Pour le reste, elles sont identiques àsave
. -
On ajoute
on("init")
,.on("create")
eton("update")
, pour faire comme les autres méthodes -
On autorise les fonctions de sauvegarde (donc
create
,update
etsave
) à retourner une primitive, en plus devoid
ou le type du noeud. -
Les différents
on
seront appelés avec la valeur retournée par la fonction correspondante (pourinit
,load
,save
,create
etupdate
). Cela permettra par exemple de faireon("create", id => router.to(x => x(id)))
si votre service de création renvoie un ID. -
On appelle aussi
on("error")
pourload
, et il sera appelé avec le nom du service en erreur, ainsi que l'erreur elle-même. -
On expose
actions.params
, pour pouvoir l'utiliser dans le rendu par exemple (pour distinguer le cas de création)
-
La documentation a évidemment été mise à jour, et le starter kit utilise également ces nouvelles fonctionnalités, si vous voulez un exemple 🙂
Les petits breaking changes à noter :
- Les stores (source et de formulaire) sont désormais entièrement vidés en cas d'erreur pendant le chargement.
params()
n'accepte plus de surcharge (qui n'était pas documentée d'ailleurs) qui prend plus d'un seul paramètre. Il faut explicitement passer un array dans ce cas.- Le type de
FormActions
a évolué pour inclure aussi les types de retours decreate
,update
etsave
, ce qui peut poser quelques soucis si vous l'avez utilisé en props à certains endroits.
ProblemDetails
Focus accepte désormais nativement des réponses en erreur sous le format ProblemDetails
, et utilisera les champs detail
ou title
en plus de errors
pour y récupérer le ou les messages d'erreurs à afficher à l'utilisateur.
On supporte désormais des paires clés/valeurs dans errors
(au lieu d'une simple liste plate). Chaque message sera préfixé par sa clé quand il sera ajouté au messageStore
, ce qui s'interfacera à priori nativement avec les retours en erreurs de vos APIs de validation côté serveur.
Le seul breaking change la dessus concerne ce qui sera renvoyé à un catch
d'un appel d'API :
- avant, on renvoyait la réponse, avec
$status
et$parsedErrors
avec les errors lues du retour serveur. - maintenant, on renvoie toujours la réponse, avec
status
s'il n'y était pas (status
fait partie de la specProblemDetails
), et$messages
, qui contient la liste des messages ajoutés dans lemessageStore
.
C'est presque la même chose qu'avant donc 😁
v11.22
Remarque importante : attention lors de la mise à jour à vos peerDependencies
, on n'est plus sur la dernière version de React ni d'i18next, il faudra bien avoir un 18
en face des deux packages react
et react-dom
et un 23
en face d'i18next
. Une version (majeure !) ultérieure de Focus prendra en compte ces montées de versions.
hasChanged
Vous pouvez désormais utiliser node.form.hasChanged
ou field.hasChanged
dans un noeud de formulaire, pour savoir si une (ou plusieurs) valeur(s) a (ont) été modifiée(s) par rapport à leur(s) valeur(s) correspondantes dans le noeud source.
Remarque : un champ ajouté sera toujours identifié avec hasChanged = false
puisqu'il n'existe pas dans le noeud source
router.confirmation
De nouvelles APIs ont été ajoutées sur le routeur afin de pouvoir entrer dans un mode de "confirmation", qui permet de bloquer toute navigation avant que l'utilisateur confirme son intention.
Si la méthode router.confirmation.toggle()
permet d'activer/désactiver le mode manuellement, il peut se brancher directement à useFormActions
via la méthode .withConfirmation(router)
dans son configurateur. Cela aura pour effet d'activer le mode de confirmation si le formulaire est en édition et qu'au moins un champ a été modifié (via node.form.hasChanged
), et de le désactiver sinon.
Vous pouvez interagir avec ce mode via :
router.confirmation.active
=> Pour savoir si le mode est actif ou nonrouter.confirmation.pending
=> Pour savoir si une confirmation est attendue de la part de l'utilisateur : vous pouvez l'utiliser pour afficher unDialog
par exemplerouter.confirmation.commit(save?: boolean)
=> Pour confirmer la navigation en attente. Si vous l'appelez avectrue
, alors la sauvegarde du formulaire sera effectuée avec la confirmation (le service de sauvegarde est passé au routeur viawithConfirmation(router)
router.confirmation.cancel()
=> Pour annuler la navigation en attente.
Vous pouvez utiliser ces APIs pour créer votre propre Dialog
pour gérer la fonctionnalité. Le starter kit en implémente un exemple.
De plus, la documentation est évidemment à jour 🙂.
Remarques
- Si vous déclenchez une navigation en dehors de l'application, alors vous aurez le popup natif du navigateur (au lieu de votre
Dialog
), qui n'est pas personnalisable. Il ne permettra en particulier pas de déclencher la sauvegarde. - Vous pouvez tout à fait avoir plusieurs formulaires sur la page qui sont branchés au mode de confirmation. Il sera actif dès lors qu'au moins un formulaire est en édition, et la confirmation avec sauvegarde appellera tous les services de sauvegarde des formulaires en édition.
- Le routeur a été partiellement réimplémenté, en internalisant la dépendance à
yester
(une librairie développé par un mec dans son coin il y a 8 ans), qui n'était une légère surcouche àhistory
, la librairie de référence pour gérer la navigation en JS.
v11.21
Refonte du Layout
Cette verrsion réimplémente le Layout/Scrollable (et les choses qui y sont liées comme le Header, le ScrollspyContainer et l'AdvancedSearch) pour implémenter tous les comportement "sticky" avec des position: sticky
en CSS au lieu de calculer des positions à la main en Javascript pour tout faire.
En particulier :
-
Le Header n'est plus posé en double, c'est le même Header qui reste entre le header déplié (s'il y en a un) et le Header sticky. Si vous posez un HeaderContent, il scrollera maintenant avec le reste de la page et il passera sous le HeaderTopRow. Les HeaderActions seront posées sous le HeaderContent comme aujourd'hui et scrolleront pour rester "sticky" à leur place sur le header sticky. Il n'y a donc plus de notion de
canDeploy
/header plié ou déplié. -
Le menu de gauche du Scrollspy (et de l'AdvancedSearch pour les facettes) est un composant dédié appelé
LateralMenu
qui se pose directement à côté (avec undisplay: flex
à priori), au lieu d'avoir un portal qui remonte le poser dans leScrollable
(Layout
), et il est entièrement géré en CSS. Cela vous laisse donc par exemple la possibilité de changer complètement la mise en page du Scrollspy (par exemple pour mettre le menu au dessus au lieu d'à gauche).Le
MenuComponent
du Scrollspy par défaut contient désormais leLateralMenu
, donc si vous voulez le surcharger et garder son positionnement à gauche vous devrez aussi poser unLateralMenu
Les facettes de l'AdvancedSearch sont maintenant toujours posées ensticky
à gauche avec unLateralMenu
(enfin sauf si avecfacetBoxPosition
ànone
ouaction-bar
. D'ailleurs, vous pouvez maintenant facilement le mettre à droite en inversant le sens duflex
qui le contient. -
Le "top row" (donc
Summary
+ActionBar
) de la recherche avancée est désormais "sticky" par défaut, et un nouveau hookuseStickyClip
, utilisé par la recherche avancée du coup, permet de faire en sorte que les résultats soient bien "coupés" lorsqu'ils passent sous la "top row" (au lieu d'apparaitre derrière).tableFor
peut aussi le faire avec les headers de colonnes, mais ce n'est pas le cas par défaut (à cause dubox-shadow
qui est posé par défaut en CSS qui fait que c'est moche 😄 ) -
Tous les calculs en Javascript n'ont pas été retirés, car on a toujours besoin de déterminer le
top
des diversposition: sticky
. Par défaut, pour les composants qui ont des comportements sticky, il sera calculé comme avant avec la hauteur du header +--content-padding-top
, mais vous pouvez le surcharger avec le propoverrideOffsetTop
dans leScrollspy
,AdvancedSearch
ettableFor
. -
Ah, et le
Panel
est désormais dans le modulelayout
au lieu du moduleforms
, désolé ça va faire beaucoup d'imports à changer... 🥺
Cette version intègre également la documentation complète du module layout
, maintenant qu'il a été refait tout beau ✨
Vous pourrez donc y voir ce que donnent les évolutions ainsi présentées dans cette release note 🙂
Résumé des breaking changes
- Les imports de
Panel
viennent désormais de@focus4/layout
au lieu de@focus4/forms
- Le
HeaderScrolling
n'a plus de propcanDeploy
(elle ne sert plus à rien) - Le
HeaderContent
est affiché différemment - Les menus latéraux du
ScrollspyContainer
et de l'AdvancedSearch
ont un nouveau design (etAdvancedSearch
n'a plus defacetBoxPosition: "sticky"
car elle est toujours sticky maintenant) - Vous ne pouvez plus utiliser le
ScrollableContext
(qui est désormais exposé depuis@focus4/layout
) pour poser un menu latéral, il faut le faire avec le composantLateralMenu
. Si vous l'utilisiez pour faire une surcharge bizarre de la taille du header, vous pouvez maintenant la passer directement auScrollspyContainer
.
Fil d'Ariane
Pour répondre à la demande populaire, un composant de fil d'Ariane a été ajouté dans le module layout
, qui s'intègre nativement avec le routeur et les traductions i18n.
Il est utilisé dans le starter kit et possède évidemment sa propre page de documentation 😉
v11.20
Cette release contient un grand nombre de petites évolutions suite à des demandes utilisateurs diverses :
Composants par défaut sur les domaines
Les composants par défaut (InputComponent
, SelectComponent
, DisplayComponent
...) sont désormais portés par le domaine au lieu d'être définis dans le composant de champ (Field
, posé par fieldFor
/selectFor
/autocompleteFor
). Cette évolution est accompagnée d'un renforcement des types de composants que l'on peut passer aux domaines, afin de pouvoir être sûr que le composant passé corresponde bien au type de domaine (exemple : utiliser une Checkbox
sur un domaine de type "boolean"
uniquement).
Cela implique les changements (potentiellement breaking changes) suivants :
-
Si vous utilisez
makeField
ou.add()
pour créer un champ sans domaine, alors vous n'aurez plus de composants d'affichage ou de saisie par défaut. Il est donc impératif de renseigner un domaine ou les composants d'affichage dont vous avez besoin. -
Les composants génériques, ceux qui fonctionnent pour plusieurs types comme
SelectAutocomplete
par exemple, doivent spécifier le type dans la définition, car il ne peut pas être inféré automatiquement 🥺Cela veut dire qu'écrire
domain({type: "number", SelectComponent: SelectAutocomplete})
est désormais une erreur et que vous devez écriredomain({type: "number", SelectComponent: SelectAutocomplete<"number">})
pour que ça fonctionne. En contrepartie, vous aurez bien le bon type partout dansselectProps
, ce qui n'était pas forcément le cas avant. -
Les domaines multiples (
"boolean-array"
,"number-array"
et"string-array"
) utilisent désormaisSelectChips
etAutocompleteChips
comme composants par défaut (et aucunInputComponent
), puisque les composants doivent désormais correspondre au type du domaine. Les domaines de type"object"
n'ont plus aucun composant de saisie par défaut. Cela ne devrait casser personne puisqu'il fallait de toute façon impérativement renseigner d'autres composants pour utiliser ces domaines, vous pouvez en revanche maintenant vous en abstenir si les composants par défaut vous conviennent. -
Vous devez désormais impérativement utiliser la fonction
domain()
pour créer un domaine. Si vous utilisiez simplement{type: "string"}
à certains endroits par exemple, vous devrez le wrapper, ou bien spécifier manuellement les 5 composants de champs.
Autres évolutions
-
b4768c7 -
showIfNoData
sur les operationList deActionBar
/tableFor
Vous pouvez renseigner cette propriété (àtrue
) dans une définition d'action dans uneActionBar
ou dans untableFor
pour qu'elle soit affichée en permanence, au lieu d'être conditionnée au fait d'avoir sélectionné au moins un élément. A noter que dans l'action est toujours appelée avec les éléments sélectionnés, donc ici ça serait une liste vide. Cette propriété doit être renseignée même s'il n'y a pas de sélection du tout (ce qui est un breaking change pourtableFor
). -
6743d7a -
noSuggestionsOnEmptyQuery
surAutocomplete
Le composant d'Autocomplete
(et doncSelectAutocomplete
également) peuvent désormais ne pas afficher les suggestions tant que la query est vide, à la manière deAutocompleteSearch
(dont le comportement inverse pouvait déjà être activé avecsearchOnEmptyQuery
) -
34827f3 -
noBlurOnClick
=>noFocusOnClick
Dans la définition d'un "trailing button" dans un dérivé deTextField
, la propriéténoBlurOnClick
a été renommée ennoFocusOnClick
puisqu'elle n'effectue plus de "blur" (l'implémentation précédente le faisait à tort, ce qui pouvait entraîner des effets secondaires indésirables, comme dans l'InputDate
par exemple) -
0775e83 - Conservation des modifications de
criteriaBuilder
dans le type deCollectionStore
Le type d'unCollectionStore
qui a patché son critère (comme avecuseFormNode
, viacriteriaBuilder
), prend désormais en compte le type résultant de ces modifications, au lieu de l'ignorer silencieusement. -
c39d60e - Fix critères "vides" affichés quand même dans le
Summary
En particulier pour les listes vides. -
d05d66c - Ajout mouseEvent sur
onLineClick
surtableFor
Il est passé en deuxième paramètre. -
ab3eddd - Extraction de
useInput
deInput
Le comportement du composantInput
qui gère le masque de saisie et la saisie de nombres (décimaux) a été extrait dans un hook, si vous voulez l'utiliser dans un autre composant que leTextField
.
v11.19
(Pour une fois, cette release n'apporte aucun breaking change 😄)
Compositions isRequired: false
Les compositions ("object"
, "list"
, et "recursive-list"
) peuvent désormais renseigner leur caractère obligatoire via isRequired
, comme les champs. Si une composition est marquée comme étant non obligatoire, alors tous ses champs seront également non obligatoires si la composition est entièrement vide (tous les champs à undefined
et toutes les listes vides). toFlatValues
retirera la composition de son résultat (au lieu de mettre {}
) dans ce cas.
isRequired
reste facultatif dans les définitions d'entité et il sera true
pour coller à l'existant sinon. Si vous utilisez TopModel, vous pouvez le générer avec l'option extendedCompositions
du générateur JS.
required()
Vous pouvez désormais utiliser la fonction required()
sur un builder de noeud de formulaire, qui fonctionne de la même manière que edit()
:
required(false)
rendra le noeud non obligatoire (comme décrit dans le paragraphe précédent)required(() => /* truc calculé */, "champ1", "champ2")
patchera les deux champs pour modifier leurisRequired
(cela appelerametadata()
).
Cette évolution a nécessité une petite refonte interne de la gestion des surcharges de metadata dans Focus, qui permet en plus de pouvoir spécifier plusieurs patch
sur le même champ sans perdre leur caractère calculé (un second patch
après un metadata calculé sortait d'ailleurs une erreur, ce qui n'est plus le cas).
Champs retirés sur Patch
Vous pouvez maintenant spécifier les champs retirés dans un formulaire avec Patch
via son nouveau troisième paramètre optionnel : Patch<MyEntity, {}, "champRetiré1" | "champRetiré2">
.
v11.18
Retrait des styles inline sur les champs
(fix partiel de #194)
Les champs ne posent plus de style inline à base de labelRatio
/valueRatio
(et disableInlineSizing
pour le désactiver). A la place, deux variables CSS sont définies globalement, --field-label-width
et --field-value-width
, initialisées à 33%
et 67%
pour conserver l'existant par défaut, qui sont utilisées dans le CSS pour définir les largeurs dans les champs. Par conséquent, toute la mise en page des champs est désormais gérée en CSS, ce qui devrait largement faciliter sa personnalisation (à défaut de proposer d'autres mises en page par défaut, pour l'instant).
Il reste possible de spécifier de manière "inline" labelWidth
et valueWidth
sur Form
et Field
, à la place de labelRatio
et valueRatio
(ce sont donc plus des ratios mais simplement la valeur des deux variables CSS, donc vous pouvez y mettre ce que vous voulez). Ce n'est donc plus la manière recommandée de gérer la mise en page (puisqu'il est possible de tout faire en CSS maintenant), mais cela permet une mise à jour simple de l'existant. Pour émuler le comportement de disableInlineSizing
, vous pouvez simplement affecter auto
aux deux variables par exemple.
(Ces propriétés vont simplement poser les variables en style inline sur le <form>
ou le <div>
principal du Field
)
TL:DR breaking changes :
labelRatio: x
=>labelWidth: "x%"
, etvalueWidth: "(100 - x)%"
sivalueRatio
n'était pas renseigné (valueRatio
était calculé automatiquement avant, mais on ne peut plus le faire puisqu'on n'utilise plus nécessairement un pourcentage)valueRatio: x
=>valueWidth: "x%"
disableInlineSizing: true
=>labelWidth: "auto", valueWidth: "auto"
react-transition-group
=> react-transition-state
Focus n'utilise plus react-transition-group
qui n'est plus maintenu et ne sera jamais mis à jour pour React 19 (la lib est incompatible), mais une autre librairie plus flexible qui elle sera compatible. Cela ne change rien à l'utilisation (c'est utilisé pour les dialogs et les popins), mais cela devrait retirer un warning en console.