Releases: klee-contrib/focus4
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.
v11.17
Typage de FormNode
Cette version traite enfin l'issue #190 et propose de nouveaux types (Patch
et consorts) pour décrire le type de formulaires patchés avec useFormNode
pour utilisation dans des fonctions ou composants.
La documentation sur le sujet est complète 😉
Autres mises à jour
- Focus vient avec React 18.3, qui est une version préliminaire à la future version 19 qui vient de sortir en bêta. Cette version n'a pour seul but que de mettre des warnings sur des choses qui ne marcheront plus en 19. A priori c'est pas grand chose. Il y a eu de tous petits fixes dans Focus vis-à-vis de ça (j'ai enlevé le seul
findDOMNode
qu'il me restait quoi) - Focus nécessite maintenant Node 20 pour s'installer (suite à une mise à jour de mon outil de test unitaire, Vitest c'est bien si vous en cherchez un 😉)
v11.16
Cette release adresse essentiellement l'issue #191.
errorDisplay
La nouvelle propriété errorDisplay
, disponible dans les options de champ (fieldFor
/selectFor
/autocompleteFor
), le <Form>
et les actions de formulaires (a.errorDisplay()
dans useFormActions
), permet de renseigner le mode d'affichage des erreurs des champs. Elle remplace forceErrorDisplay
(sur actions
et <Form>
) et noError
sur les champs. Elle peut avoir 3 valeurs :
always
=> les erreurs sont toujours affichéesafter-focus
=> les erreurs sont affichées après avoir focus le champ en question au moins une foisnever
=> les erreurs ne sont jamais affichées
(Les erreurs ne sont quand même jamais affichées sur un champ quand il a le focus)
Dans les actions de formulaires :
after-focus
passera le mode àalways
après l'appel desave()
, et reviendra àafter-focus
une fois la sauvegarde effectuée (ou un cancel)- Si le mode n'est pas renseigné, il sera égal à
after-focus
si le formulaire est initialement en édition, etalways
sinon
Ce fonctionnement correspond presque exactement au fonctionnement précédent, mais implémenté de manière plus simple, claire, et flexible (en particulier parce qu'on peut surcharger l'affichage champ par champ).
breaking change: forceErrorDisplay
n'existe plus, vous pouvez remplacer vos éventuelles assignations manuelles à false
par errorDisplay: "after-focus"
et à true
par errorDisplay: "always"
.
a.successMessage()
Vous pouvez désormais surcharger le message de succès d'un formulaire directement via successMessage()
, au lieu de passer i18nPrefix()
pour que le message pointe sur {i18nPrefix}.detail.saved
. Cela implique aussi que vous pouvez appeler successMessage("")
pour désactiver le message. C'est un breaking change évidemment... que vous pouvez résoudre soit en changeant i18nPrefix(prefix)
par successMessage(prefix + ".detail.saved")
, ou en simplifiant.
Réécriture useLoad
/useFormActions
Les deux hooks ont été réécrits pour qu'ils soient effectivement conçus de la même façon. Cela a permis de :
- Autoriser le changement de
node
suruseLoad
etuseFormActions
- Autoriser un array de dépendances sur
useFormActions
Toute modification de node
ou des dépendances va mettre à jour toutes les fonctions définies dans useLoad
et useFormActions
(params
, load
, save
, on
...), et relancer la fonction de chargement. Si c'est probablement toujours souhaitable pour useLoad
(et c'était déjà le cas de toute façon), ça l'est peut être moins pour useFormActions
si les dépendances sont utilisées dans le save
ou un on
, mais pas dans le load
. Dans ce cas, il vaut mieux séparer le load
dans un useLoad
sans dépendances. Il y a un exemple dans la doc.
De plus, useFormActions
ne supportent plus d'avoir plusieurs services de sauvagarde, une fonctionnalité qui n'a jamais été utilisée... (théoriquement un breaking change aussi mais bon...)
Réécriture Input
(gestion des masques)
Le composant Input
a été réécrit, en internalisant la gestion des masques de saisie (au lieu de dépendre d'une librairie externe inchangée depuis 7 ans et difficile à utiliser). S'il est fonctionnellement identique au composant précédent, certains bugs ont été corrigés (en particulier liés aux couper/copier/coller), et il est entièrement documenté dans la doc Focus 😉.
v11.15
Améliorations tableFor
En plus d'avoir revu et amélioré le CSS des tableaux, il est désormais possible dans un tableau :
- D'afficher des actions sur chaque ligne comme sur
listFor
aveclineOperationList
- D'afficher une checkbox dans le header du tableau et d'y associer des actions globales comme
ActionBar
avechasSelectAll
etoperationList
(sioperationList
est renseigné sanshasSelectAll
, alors les actions globales seront tout le temps affichées et s'appliqueront sur tous les éléments du tableau) - D'afficher un
LoadingComponent
après le tableau comme la liste (Le "Chargement..." par défaut).
Les checkbox et les actions des tableaux et des listes sont désormais tout le temps affichées, au lieu de ne l'être qu'au survol ou dès qu'au moins un élément est sélectionné (une nouvelle classe CSS .list--selected
/.table--selected
est ajoutée sur la liste/tableau dès qu'un élément est sélectionné dedans si jamais vous voudriez reproduire l'ancien comportement en surchargeant le CSS...)
Les actions de listes et de tableau peuvent désormais prendre les props color
, variant
et disabled
, qui seront répercutées sur les composants de boutons qu'elles posent. Pour les actions secondaires, les propriétés color
et variant
seront répercutées sur le bouton du menu, qui peut aussi être muni d'une tooltip via secondaryLabel
.
Par ailleurs, il n'est plus possible de passer un isLoading
aux composants de listes lorsqu'ils sont associés à un store, puisque le store porte déjà un isLoading
. En mode local
, vous pouvez désormais l'assigner manuellement du coup.
ThemeProvider
Le nouveau composant ThemeProvider
permet de reproduire ce que fait le Layout
pour pouvoir passer un appTheme
et surcharger tous les classes CSS des composants qui sont posés dedans. Ainsi, vous avez désormais une option de plus pour surcharger du CSS entre "surcharger toutes les classes via le Layout
" et "surcharger toutes les props theme
dans tous les composants". Les classes posées par un ThemeProvider
s'ajouteront à celles posées par un ThemeProvider
parent (et donc celles du Layout
).
Icônes dans les Panels
Vous pouvez désormais poser une icône devant le titre dans un Panel :)
Documentation
La refonte de la documentation est enfin terminée ! Vous pouvez désormais y trouver en plus :
- Une documentation sur le modèle métier et le CSS entièrement refaite
- Une documentation sur les composants de liste et les stores de collection qui inclue des exemples interactifs comme les composants de base
- La documentation de toutes les variables CSS de tous les composants avec showcase (
toolbox
,forms
etcollections
) avec prise en compte du thème sombre et surcharges locales.
Tout information dans la doc doit désormais être à jour, et la doc a vocation à être à peu près exhaustive. N'hésitez pas à remonter toute info manquante ou imprécise ;)
v11.14
Refonte de la gestion des icônes
Focus ne met plus "material-icons"
en dur pour toute icône renseignée avec simplement un nom, ni "icon-{name}"
pour une icône dite "personnalisée". Désormais, toutes les icônes sont définies comme ayant un name
et une className
, et un className
par défaut est défini dans config.defaultIconClassName
(qui est toujours "material-icons"
en revanche).
Vous pouvez donc passer "home"
, ou {name: "home", className: "material-icons-outlined"}
, ou encore {i18nKey: "focus.icons.list.showAll"}
à toute propriété de composant qui prend une icône. Si vous utilisez une clé i18n, library
a été remplacé par className
, mais la clé doit toujours pointer sur un objet {name, className?}
.
L'API de <FontIcon>
a évolué pour suivre ce changement :
<FontIcon>home</FontIcon>
<FontIcon iconClassName="material-icons-outlined">home</FontIcon>
<FontIcon iconI18nKey="focus.icons.list.showAll" />
// ou vous pouvez utiliser la prop combinée `icon` qui prend directement un des 3 types de définition d'icône :
<FontIcon icon={{className: "material-icons-round"}}>home</FontIcon>
La classe CSS de l'icône peut également être un template (si elle utilise {name}
), dans le cas où vous avez besoin d'avoir une classe CSS différente par icône (si votre police ne convertit pas directement le nom comme Material Icons ou si vous n'avez pas de police).
Breaking changes
getIcon()
n'existe plus, à remplacer par les nouvelles façons de décrire des icônes- Une icône custom ne pose plus 2 spans (celui du
FontIcon
+ celui de l'icône custom en"icon-{name}"
, mais plus qu'un seul.
referenceStore.get
Vous pouvez enfin forcer la récupération d'une liste de référence en faisant await referenceStore.get("droit")
, qui appellera le serveur si besoin. Il n'y a plus besoin de tricher avec des await when(() => referenceStore.droit.length > 0)
!
useReferenceTracking
(referenceStore.track
)
Le tracking automatique de toutes les références dans les formulaires (withReferenceTracking
/ config.trackReferenceLoading
) s'est révélé être une mauvaise idée (ça ne marche pas bien sur les pages qui ont plusieurs formulaires), il a donc été retiré (RIP janvier 2024 - janvier 2024).
A la place, vous pouvez désormais le faire explicitement via useReferenceTracking
:
const actions = useFormActions(entity, a => / * */):
useReferenceTracking(actions.trackingId, referenceStore, "droit");
useReferenceTracking
utilise une nouvelle API referenceStore.track
qui permet d'ajouter un ou plusieurs IDs de suivi sur des listes de référence. Vous n'aurez à priori jamais besoin d'utiliser cette API directement.
Refonte de la documentation
Ce n'est pas encore tout à fait #195 en entier (il y a encore pas mal de boulot à faire dessus), mais cette release introduit une refonte de la documentation, qui contient en particulier un showcase de tous les composants de base, à peu près entièrement fonctionnel.
Les évolutions de la documentation se sont pour l'instant concentrées sur les évolutions récentes de Focus, donc attendez vous à voir de la documentation à jour sur tous les composants de la refonte toolbox
/forms
et sur les fonctionnalités de base (messages, requêtes, store de références). Et une page d'accueil à jour aussi 😄
La partie sur les formulaires, les listes, le CSS et la mise en page seront mises à jour plus tard, et incluront évidemment un bout de showcase 😉
Elle est toujours au même endroit
loading
sur Button
et IconButton
Vous pouvez désormais afficher un spinner à la place de l'icône dans un bouton. Vous pouvez regarder à quoi ça ressemble dans la doc 😉
v11.13
Refonte du RequestStore
, et suivi de requêtes
Le RequestStore
ne suit désormais que les requêtes en cours (dans requestStore.pending
) et expose (enfin !) requestStore.isLoading
pour savoir s'il y a au moins une requête en cours (il n'y a plus besoin de le calculer vous même en faisant requestStore.pending.size > 0
)
Vous pouvez aussi maintenant utiliser le RequestStore
pour gérer vos propres isLoading
avec requestStore.track
:
const id = useId(); // ou v4() du package "uuid" si vous n'est pas dans un composant
/* ---- */
const user = await requestStore.track(id, () => getUser(id)) // Enregistre le service sur cet ID et l'appelle
/* ---- */
requestStore.isLoading(id) // La requête est-elle en cours ?
En plus de proposer une façon standardisée de gérer un isLoading
, vous pouvez passer plusieurs IDs à requestStore.track
et surtout utiliser le même ID pour plusieurs requêtes. De cette façon, vous pouvez obtenir simplement un état isLoading
pour plusieurs requêtes en même temps.
requestStore.track
est intégré à useLoad
et useFormActions
. Les deux méthodes utilisent leur propre ID de suivi, et il est possible d'en passer d'autres avec .trackingId(id)
, par exemple :
const actions = useFormActions(form, f => f.params().load().save());
useLoad(otherStore, f => f.params().load().trackingId(actions.trackingId));
// => actions.isLoading sera donc `true` si l'un des 3 services est en cours de chargement !
De plus, par défaut, le isLoading
de useLoad
et useFormActions
prennent également en compte le chargement des listes de références (sans distinction). Vous pouvez changer ce comportement par défaut en changeant la configuration globale config.trackReferenceLoading
à false
, ou bien appeler withReferenceTracking(active)
dans useLoad
ou useFormActions
pour activer/désactiver localement.
CollectionStore.criteriaMode
Vous pouvez enfin finalement renseigner criteriaMode
à la création de votre CollectionStore
pour choisir entre "direct"
(lancement automatique immédiat de la recherche après modification, comportement par défaut), "debounced"
(lancement de la recherche après un petit délai, précédemment configuré avec debounceCriteria: true
), et "manual"
, où les critères ne seront appliqués qu'après avoir appelé search()
explicitement.
Vous n'avez donc plus besoin de créer un formulaire distinct pour reproduire ce comportement et appliquer les critères à la sauvegarde. De plus, il est également possible de renseigner un criteriaBuilder
dans la définition du CollectionStore
pour personnaliser le critère, comme un formulaire classique.
const searchStore = new CollectionStore(search, CriteriaEntity, {
sortBy: "date",
top: 20,
criteriaMode: "manual",
criteriaBuilder: x => x.patch("dateDebut", g => g.metadata({label: "Date de début"}))
});
Breaking changes
- Dans tout ce qu'exposait le
RequestStore
avant, il ne reste plus quepending
, qui est désormais une liste au lieu d'un set useLoad
ne renvoie plusisLoading
mais[isLoading, trackingId]
. Il faudra donc ajouter des[]
autour de vosisLoading
existantsuseLoad
etuseFormActions
incluent désormais l'état de chargement de toutes les listes de référence dans leursisLoading
. Si cela ne convient pas, cela peut se désactiver globalement avecconfig.trackReferenceLoading = false
(de@focus4/core
)debounceCriteria: true
dans leCollectionStore
a été remplacé parcriteriaMode: "debounced"