@zevra/ui 0.43.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DECISIONS.md +32 -0
- package/dist/css-inline.d.ts.map +1 -1
- package/dist/css-inline.js +1 -1
- package/dist/css-inline.js.map +1 -1
- package/dist/data-table.d.ts +29 -3
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +58 -40
- package/dist/data-table.js.map +1 -1
- package/dist/grille.d.ts +91 -8
- package/dist/grille.d.ts.map +1 -1
- package/dist/grille.js +188 -78
- package/dist/grille.js.map +1 -1
- package/package.json +1 -1
- package/react/css-inline.ts +1 -1
- package/react/data-table.tsx +76 -5
- package/react/grille.tsx +454 -164
- package/src/data.css +71 -3
package/dist/grille.js
CHANGED
|
@@ -27,6 +27,7 @@ import { SelectMenu } from './select-menu.js';
|
|
|
27
27
|
import { Menu, MenuItem, Pager, PagerArrow, Popover } from './navigation.js';
|
|
28
28
|
import { Empty } from './feedback.js';
|
|
29
29
|
import { Meta } from './data.js';
|
|
30
|
+
import { Mono } from './typography.js';
|
|
30
31
|
import { cx } from './cx.js';
|
|
31
32
|
export { usePersistedState };
|
|
32
33
|
/** Identifiant réservé de la colonne de cases. */
|
|
@@ -46,6 +47,11 @@ export const TEXTES_GRILLE = {
|
|
|
46
47
|
min: 'Min.',
|
|
47
48
|
max: 'Max.',
|
|
48
49
|
effacer: 'Effacer le filtre',
|
|
50
|
+
effacerFiltres: 'Effacer les filtres',
|
|
51
|
+
toutAfficher: 'Tout afficher',
|
|
52
|
+
aucun: 'Aucun',
|
|
53
|
+
rechercherValeur: 'Rechercher une valeur',
|
|
54
|
+
aucuneValeur: 'Aucune valeur.',
|
|
49
55
|
periodes: 'Périodes',
|
|
50
56
|
aujourdhui: "Aujourd'hui",
|
|
51
57
|
septJours: '7 derniers jours',
|
|
@@ -192,15 +198,22 @@ const nombre = (s) => {
|
|
|
192
198
|
const n = Number(s.replace(',', '.'));
|
|
193
199
|
return Number.isFinite(n) ? n : null;
|
|
194
200
|
};
|
|
201
|
+
/** Sans casse ni accents : « decision » trouve « Décision ». */
|
|
202
|
+
export function normaliser(v) {
|
|
203
|
+
return String(v ?? '')
|
|
204
|
+
.toLowerCase()
|
|
205
|
+
.normalize('NFD')
|
|
206
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
207
|
+
}
|
|
195
208
|
/** Le filtre de toutes les colonnes de la grille : il lit la FORME de la
|
|
196
209
|
* valeur, pas la colonne. Bornes comprises. */
|
|
197
210
|
export const filtreGrille = (ligne, colonne, filtre) => {
|
|
198
211
|
const brut = ligne.getValue(colonne);
|
|
199
212
|
switch (filtre?.type) {
|
|
200
213
|
case 'texte':
|
|
201
|
-
return
|
|
214
|
+
return normaliser(brut).includes(normaliser(filtre.valeur.trim()));
|
|
202
215
|
case 'choix':
|
|
203
|
-
return String(brut ?? '')
|
|
216
|
+
return filtre.valeurs.includes(String(brut ?? ''));
|
|
204
217
|
case 'nombre': {
|
|
205
218
|
const v = typeof brut === 'number' ? brut : nombre(String(brut ?? ''));
|
|
206
219
|
const min = nombre(filtre.min);
|
|
@@ -221,7 +234,7 @@ export const filtreGrille = (ligne, colonne, filtre) => {
|
|
|
221
234
|
};
|
|
222
235
|
filtreGrille.autoRemove = (f) => !f ||
|
|
223
236
|
(f.type === 'texte' && f.valeur.trim() === '') ||
|
|
224
|
-
(f.type === 'choix' && f.
|
|
237
|
+
(f.type === 'choix' && f.valeurs.length === 0) ||
|
|
225
238
|
(f.type === 'nombre' && f.min === '' && f.max === '') ||
|
|
226
239
|
(f.type === 'date' && f.du === '' && f.au === '');
|
|
227
240
|
/** Ne garde d'une saisie que ce qui peut faire un nombre : chiffres, un
|
|
@@ -394,21 +407,22 @@ function PoigneeLargeur({ colonne, table, nom, t, }) {
|
|
|
394
407
|
// une valeur, et les flèches pour la changer.
|
|
395
408
|
role: "separator", "aria-orientation": "vertical", "aria-label": t.largeur(nom), "aria-valuenow": Math.round(reglee ?? colonne.getSize()), "aria-valuemin": min, tabIndex: 0, title: t.largeurAide, className: cx('zv-colhead__resize', actif && 'zv-colhead__resize--actif'), onPointerDown: appui, onPointerMove: mouvement, onPointerUp: fin, onPointerCancel: fin, onDoubleClick: rendre, onKeyDown: clavier, onClick: (e) => e.stopPropagation() }));
|
|
396
409
|
}
|
|
397
|
-
|
|
398
|
-
function CaseTout({ table, t }) {
|
|
410
|
+
function CaseTout({ table, t, options }) {
|
|
399
411
|
// Une enveloppe plutôt qu'une ref sur la case : `Checkbox` ne transmet
|
|
400
412
|
// pas de ref sous React 18, que le paquet accepte encore.
|
|
401
413
|
const ref = useRef(null);
|
|
402
414
|
const partiel = table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected();
|
|
403
415
|
// `indeterminate` n'a pas d'attribut HTML : seule la propriété DOM existe.
|
|
416
|
+
// Sans elle, « 3 sur 40 cochées » ressemble à « aucune ».
|
|
404
417
|
useEffect(() => {
|
|
405
418
|
const caseTout = ref.current?.querySelector('input');
|
|
406
419
|
if (caseTout)
|
|
407
420
|
caseTout.indeterminate = partiel;
|
|
408
421
|
}, [partiel]);
|
|
409
|
-
|
|
422
|
+
const aucune = !table.getPrePaginationRowModel().rows.some((r) => r.getCanSelect());
|
|
423
|
+
return (_jsx("span", { ref: ref, className: "zv-cell--case", children: _jsx(Checkbox, { checked: table.getIsAllRowsSelected(), disabled: aucune || options.current?.desactivee, onChange: table.getToggleAllRowsSelectedHandler(), "aria-label": t.toutSelectionner, title: t.toutSelectionner }) }));
|
|
410
424
|
}
|
|
411
|
-
function colonneSelection(t) {
|
|
425
|
+
function colonneSelection(t, options) {
|
|
412
426
|
return {
|
|
413
427
|
id: COLONNE_SELECTION,
|
|
414
428
|
size: 40,
|
|
@@ -419,11 +433,13 @@ function colonneSelection(t) {
|
|
|
419
433
|
enableColumnFilter: false,
|
|
420
434
|
enableGlobalFilter: false,
|
|
421
435
|
enablePinning: false,
|
|
422
|
-
header: ({ table }) => _jsx(CaseTout, { table: table, t: t }),
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
436
|
+
header: ({ table }) => _jsx(CaseTout, { table: table, t: t, options: options }),
|
|
437
|
+
// Une ligne qui ne se sélectionne pas n'a PAS de case : une case grisée
|
|
438
|
+
// promettrait une action qui n'existe pas pour elle.
|
|
439
|
+
cell: ({ row }) => row.getCanSelect() ? (_jsx(Checkbox, { checked: row.getIsSelected(), disabled: options.current?.desactivee, onChange: row.getToggleSelectedHandler(),
|
|
440
|
+
// La case vit dans une ligne cliquable : sans ces deux arrêts,
|
|
441
|
+
// cocher ouvrirait aussi la ligne, et Espace l'ouvrirait au clavier.
|
|
442
|
+
onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), "aria-label": options.current?.label?.(row.original) ?? t.selectionnerLigne })) : null,
|
|
427
443
|
meta: { classe: 'zv-cell--case' },
|
|
428
444
|
};
|
|
429
445
|
}
|
|
@@ -455,16 +471,16 @@ function MenuColonnes({ table, t }) {
|
|
|
455
471
|
};
|
|
456
472
|
}, [ouvert]);
|
|
457
473
|
const colonnes = table.getAllLeafColumns().filter((c) => c.id !== COLONNE_SELECTION);
|
|
458
|
-
return (_jsx("div", { ref: ancre, children: _jsxs(SelectAnchor, { children: [_jsx(Button, { variante: "fantome", compact: true, icone: "reglages", "aria-expanded": ouvert, "aria-haspopup": "true", onClick: () => setOuvert((o) => !o), children: t.colonnes }), ouvert && (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
474
|
+
return (_jsx("div", { ref: ancre, children: _jsxs(SelectAnchor, { children: [_jsx(Button, { variante: "fantome", compact: true, icone: "reglages", "aria-expanded": ouvert, "aria-haspopup": "true", onClick: () => setOuvert((o) => !o), children: t.colonnes }), ouvert && (_jsxs(Menu, { role: "group", "aria-label": t.colonnesAffichees, className: "zv-grille__menu", children: [colonnes.some((c) => !c.getIsVisible()) && (_jsx(Button, { variante: "fantome", compact: true, onClick: () => table.setColumnVisibility((v) => Object.fromEntries(Object.keys(v).map((k) => [k, true]))), children: t.toutAfficher })), colonnes.map((c) => {
|
|
475
|
+
const nom = libelleColonne(c);
|
|
476
|
+
const cote = c.getIsPinned();
|
|
477
|
+
const suivant = cote === false ? 'left' : cote === 'left' ? 'right' : false;
|
|
478
|
+
return (_jsxs("div", { className: "zv-grille__menu-ligne", children: [_jsxs("label", { className: "zv-menu__item zv-grille__menu-case", children: [_jsx(Checkbox, { checked: c.getIsVisible(), disabled: !c.getCanHide(), onChange: c.getToggleVisibilityHandler() }), _jsx("span", { children: nom }), cote && (_jsx(Meta, { taille: "sm", children: cote === 'left' ? t.aGauche : t.aDroite }))] }), c.getCanPin() && (_jsx(IconButton, { icone: "epingle", taille: "sm", className: "zv-grille__epingle", "aria-pressed": cote !== false, label: suivant === 'left'
|
|
479
|
+
? t.epinglerGauche(nom)
|
|
480
|
+
: suivant === 'right'
|
|
481
|
+
? t.epinglerDroite(nom)
|
|
482
|
+
: t.detacher(nom), onClick: () => c.pin(suivant) }))] }, c.id));
|
|
483
|
+
})] }))] }) }));
|
|
468
484
|
}
|
|
469
485
|
/* ─── Filtres de colonne ────────────────────────────────────────────── */
|
|
470
486
|
function FiltreColonne({ colonne, type, t, }) {
|
|
@@ -563,35 +579,35 @@ function FiltreFlottant({ libelle, resume, vide, icone, role, panneau: contenu,
|
|
|
563
579
|
return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: declencheur, type: "button", className: "zv-input zv-input--trigger zv-table__filtre", "data-vide": vide ? 'oui' : undefined, "aria-expanded": ouvert, "aria-haspopup": role, "aria-label": `${libelle} : ${resume}`, onClick: () => setOuvert((o) => !o), children: [_jsx("span", { className: "zv-colhead__label", children: resume }), _jsx(Icon, { nom: icone, taille: "sm", discret: true })] }), ouvert &&
|
|
564
580
|
createPortal(_jsx("div", { ref: panneau, className: "zv-grille__flottant", style: position ? { top: position.top, left: position.left } : { visibility: 'hidden' }, children: contenu(fermer) }), declencheur.current?.ownerDocument.body ?? document.body)] }));
|
|
565
581
|
}
|
|
566
|
-
/** Les flèches promènent le focus d'une option à l'autre, Origine et Fin
|
|
567
|
-
* vont aux extrémités : le parcours clavier d'une liste, que le <select>
|
|
568
|
-
* natif donnait et qu'une liste dessinée doit rendre. */
|
|
569
|
-
function parcourirListe(e) {
|
|
570
|
-
const options = [...e.currentTarget.querySelectorAll('[role="option"]')];
|
|
571
|
-
const i = options.indexOf(e.target);
|
|
572
|
-
const vers = e.key === 'ArrowDown' ? i + 1 : e.key === 'ArrowUp' ? i - 1 : e.key === 'Home' ? 0 : e.key === 'End' ? options.length - 1 : null;
|
|
573
|
-
if (vers === null)
|
|
574
|
-
return;
|
|
575
|
-
e.preventDefault();
|
|
576
|
-
options[Math.max(0, Math.min(options.length - 1, vers))]?.focus();
|
|
577
|
-
}
|
|
578
582
|
function FiltreChoix({ colonne, nom, t }) {
|
|
579
583
|
const filtre = colonne.getFilterValue();
|
|
580
|
-
|
|
581
|
-
//
|
|
582
|
-
|
|
584
|
+
const choisies = filtre?.type === 'choix' ? filtre.valeurs : [];
|
|
585
|
+
// Les valeurs du modèle FACETTÉ, avec leur nombre de lignes : celles que
|
|
586
|
+
// les autres filtres laissent encore passer. Une option qui ne rendrait
|
|
587
|
+
// aucune ligne n'est pas offerte, sauf si elle est déjà cochée : sinon
|
|
588
|
+
// la liste mentirait sur ce qui filtre.
|
|
589
|
+
const comptes = colonne.getFacetedUniqueValues();
|
|
590
|
+
const options = [...comptes.keys()]
|
|
583
591
|
.filter((v) => v != null && v !== '')
|
|
584
592
|
.map(String)
|
|
585
|
-
.sort((a, b) => a.localeCompare(b, 'fr'));
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return (_jsx(FiltreFlottant, { libelle: t.filtrerColonne(nom), resume:
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
593
|
+
.sort((a, b) => a.localeCompare(b, 'fr', { numeric: true }));
|
|
594
|
+
for (const c of choisies)
|
|
595
|
+
if (!options.includes(c))
|
|
596
|
+
options.unshift(c);
|
|
597
|
+
const ecrire = (valeurs) => colonne.setFilterValue({ type: 'choix', valeurs });
|
|
598
|
+
const resume = choisies.length === 0 ? t.tous : choisies.length === 1 ? (choisies[0] ?? t.tous) : `${choisies.length} / ${options.length}`;
|
|
599
|
+
return (_jsx(FiltreFlottant, { libelle: t.filtrerColonne(nom), resume: resume, vide: choisies.length === 0, icone: "chevron-bas", role: "dialog", panneau: () => _jsx(ListeChoix, { options: options, comptes: comptes, choisies: choisies, ecrire: ecrire, nom: nom, t: t }) }));
|
|
600
|
+
}
|
|
601
|
+
/** Plusieurs valeurs à la fois (« Acte » OU « Contrat ») : des cases, avec
|
|
602
|
+
* le nombre de lignes de chacune, une recherche au-delà de huit options,
|
|
603
|
+
* et « Tout » / « Aucun » sur ce que la recherche montre. */
|
|
604
|
+
function ListeChoix({ options, comptes, choisies, ecrire, nom, t, }) {
|
|
605
|
+
const [q, setQ] = useState('');
|
|
606
|
+
const vues = q.trim() ? options.filter((o) => normaliser(o).includes(normaliser(q.trim()))) : options;
|
|
607
|
+
return (_jsxs(Popover, { className: "zv-grille__plage", role: "dialog", "aria-label": t.filtrerColonne(nom), children: [options.length > 8 && (_jsx(Search, { value: q, onChange: (e) => setQ(e.target.value), placeholder: t.rechercherValeur, "aria-label": t.rechercherValeur })), _jsxs("div", { className: "zv-grille__menu-ligne", children: [_jsx(Button, { variante: "fantome", compact: true, onClick: () => ecrire([...new Set([...choisies, ...vues])]), children: t.tous }), _jsx(Button, { variante: "fantome", compact: true, onClick: () => ecrire(choisies.filter((c) => !vues.includes(c))), children: t.aucun })] }), _jsxs("div", { className: "zv-grille__liste", role: "group", "aria-label": t.filtrerColonne(nom), children: [vues.map((o) => {
|
|
608
|
+
const pris = choisies.includes(o);
|
|
609
|
+
return (_jsxs("label", { className: "zv-menu__item zv-grille__menu-case", children: [_jsx(Checkbox, { checked: pris, onChange: () => ecrire(pris ? choisies.filter((c) => c !== o) : [...choisies, o]) }), _jsx("span", { className: "zv-colhead__label", children: o }), _jsx(Meta, { taille: "sm", discret: true, className: "zv-grille__compte", children: comptes.get(o) ?? 0 })] }, o));
|
|
610
|
+
}), vues.length === 0 && _jsx(Meta, { taille: "sm", children: t.aucuneValeur })] }), choisies.length > 0 && (_jsx(Button, { variante: "fantome", compact: true, icone: "fermer", onClick: () => ecrire([]), children: t.effacer }))] }));
|
|
595
611
|
}
|
|
596
612
|
function FiltrePlage({ colonne, type, nom, t, }) {
|
|
597
613
|
const idChamp = useId();
|
|
@@ -626,7 +642,7 @@ function FiltrePlage({ colonne, type, nom, t, }) {
|
|
|
626
642
|
}) })), _jsxs("div", { className: "zv-grille__bornes", children: [_jsx(Field, { label: type === 'date' ? t.du : t.min, htmlFor: idChamp + 'a', children: champ(brouillon[0], (v) => saisir(v, brouillon[1]), 'a', { max: brouillon[1] }) }), _jsx(Field, { label: type === 'date' ? t.au : t.max, htmlFor: idChamp + 'b', children: champ(brouillon[1], (v) => saisir(brouillon[0], v), 'b', { min: brouillon[0] }) })] }), !vide && (_jsx(Button, { variante: "fantome", compact: true, icone: "fermer", onClick: () => saisir('', ''), children: t.effacer }))] })) }));
|
|
627
643
|
}
|
|
628
644
|
/* ─── La grille ──────────────────────────────────────────────────────── */
|
|
629
|
-
export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getRowId, selection = false, onSelectionChange, parPage = 25, recherche = true, filtres = true, exportCsv = true, vueInitiale, vide, outils, textes, className, }) {
|
|
645
|
+
export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getRowId, selection = false, onSelectionChange, triServeur, onRowContextMenu, onRowDoubleClick, onRowDragStart, ligneSaisie, groupe, rang, pied, dense, remplir, apresLignes, envelopperTableau, barre = true, champRecherche, visibilite, onVisibilite, onLignesAffichees, parPage = 25, recherche = true, filtres = true, exportCsv = true, vueInitiale, vide, outils, textes, className, }) {
|
|
630
646
|
const t = { ...TEXTES_GRILLE, ...textes };
|
|
631
647
|
const ids = useMemo(() => columns.map((c) => idDeColonne(c)).filter((id) => id != null), [columns]);
|
|
632
648
|
const cleIds = ids.join('\u0000');
|
|
@@ -651,34 +667,73 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
651
667
|
const [vue, setVue, reinitialiserVue] = usePersistedState(`table:${tableId}:v1`, defaut, reconcilier);
|
|
652
668
|
const [columnFilters, setColumnFilters] = useState([]);
|
|
653
669
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
654
|
-
const [
|
|
670
|
+
const [selectionInterne, setSelectionInterne] = useState({});
|
|
655
671
|
const [pageIndex, setPageIndex] = useState(0);
|
|
656
672
|
const champ = (k) => (u) => setVue((v) => ({ ...v, [k]: appliquer(u, v[k]) }));
|
|
673
|
+
/* Sélection. Tenue par la grille (`true`) ou par l'app (un objet) : dans le
|
|
674
|
+
* second cas, l'état de TanStack se DÉDUIT des clés de l'app à chaque rendu,
|
|
675
|
+
* et chaque changement lui est rendu en clés. */
|
|
676
|
+
const avecCases = selection !== false;
|
|
677
|
+
const externe = typeof selection === 'object' ? selection : null;
|
|
678
|
+
const idLigne = (l, i) => getRowId?.(l, i) ?? String(i);
|
|
679
|
+
const cleDe = (l, i) => externe?.cle?.(l) ?? idLigne(l, i);
|
|
680
|
+
const rowSelection = externe
|
|
681
|
+
? Object.fromEntries(
|
|
682
|
+
// Une ligne sans case ne se peint pas cochée, même si sa clé est
|
|
683
|
+
// dans l'ensemble de l'app : on la verrait choisie sans pouvoir
|
|
684
|
+
// la décocher.
|
|
685
|
+
data.flatMap((l, i) => externe.choisies.has(cleDe(l, i)) && externe.selectionnable?.(l) !== false
|
|
686
|
+
? [[idLigne(l, i), true]]
|
|
687
|
+
: []))
|
|
688
|
+
: selectionInterne;
|
|
689
|
+
const surSelectionLignes = (u) => {
|
|
690
|
+
if (!externe)
|
|
691
|
+
return setSelectionInterne(u);
|
|
692
|
+
const n = appliquer(u, rowSelection);
|
|
693
|
+
const suite = new Set(externe.choisies);
|
|
694
|
+
// Seules les clés des lignes PRÉSENTES changent : une ligne cochée puis
|
|
695
|
+
// filtrée hors de l'écran, ou sur une autre page, reste cochée.
|
|
696
|
+
data.forEach((l, i) => {
|
|
697
|
+
if (externe.selectionnable?.(l) === false)
|
|
698
|
+
return;
|
|
699
|
+
const cle = cleDe(l, i);
|
|
700
|
+
if (n[idLigne(l, i)])
|
|
701
|
+
suite.add(cle);
|
|
702
|
+
else
|
|
703
|
+
suite.delete(cle);
|
|
704
|
+
});
|
|
705
|
+
externe.onChoisir(suite);
|
|
706
|
+
};
|
|
657
707
|
// La colonne de cases est épinglée à gauche, toujours, et n'entre pas dans
|
|
658
708
|
// la vue enregistrée : elle dépend de la prop, pas de l'utilisateur.
|
|
659
709
|
const pinning = {
|
|
660
|
-
left:
|
|
710
|
+
left: avecCases ? [COLONNE_SELECTION, ...(vue.columnPinning.left ?? [])] : vue.columnPinning.left,
|
|
661
711
|
right: vue.columnPinning.right,
|
|
662
712
|
};
|
|
663
713
|
const pagination = {
|
|
664
714
|
pageIndex,
|
|
665
715
|
pageSize: parPage === false ? Math.max(data.length, 1) : vue.pageSize,
|
|
666
716
|
};
|
|
717
|
+
// L'option de sélection est lue AU RENDU par les cases : les colonnes n'ont
|
|
718
|
+
// pas à se recalculer à chaque case cochée.
|
|
719
|
+
const optionsCases = useRef(externe);
|
|
720
|
+
optionsCases.current = externe;
|
|
667
721
|
const colonnes = useMemo(() => {
|
|
668
722
|
const avecFiltre = columns.map((c) => (c.filterFn ? c : { ...c, filterFn: filtreGrille }));
|
|
669
|
-
return
|
|
723
|
+
return avecCases ? [colonneSelection(t, optionsCases), ...avecFiltre] : avecFiltre;
|
|
670
724
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
671
|
-
}, [columns,
|
|
725
|
+
}, [columns, avecCases, t.toutSelectionner, t.selectionnerLigne]);
|
|
726
|
+
const visibiliteCourante = visibilite ?? vue.columnVisibility;
|
|
672
727
|
const table = useReactTable({
|
|
673
728
|
data,
|
|
674
729
|
columns: colonnes,
|
|
675
730
|
getRowId,
|
|
676
731
|
state: {
|
|
677
732
|
columnOrder: vue.columnOrder,
|
|
678
|
-
columnVisibility:
|
|
733
|
+
columnVisibility: visibiliteCourante,
|
|
679
734
|
columnSizing: vue.columnSizing,
|
|
680
735
|
columnPinning: pinning,
|
|
681
|
-
sorting: vue.sorting,
|
|
736
|
+
sorting: triServeur ? triServeur.tri : vue.sorting,
|
|
682
737
|
columnFilters,
|
|
683
738
|
globalFilter,
|
|
684
739
|
rowSelection,
|
|
@@ -686,12 +741,17 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
686
741
|
},
|
|
687
742
|
defaultColumn: { size: 150, minSize: 60 },
|
|
688
743
|
onColumnOrderChange: champ('columnOrder'),
|
|
689
|
-
onColumnVisibilityChange:
|
|
744
|
+
onColumnVisibilityChange: onVisibilite
|
|
745
|
+
? (u) => onVisibilite((v) => appliquer(u, v))
|
|
746
|
+
: champ('columnVisibility'),
|
|
690
747
|
onColumnSizingChange: champ('columnSizing'),
|
|
691
|
-
onSortingChange:
|
|
748
|
+
onSortingChange: triServeur
|
|
749
|
+
? (u) => triServeur.onTri(appliquer(u, triServeur.tri))
|
|
750
|
+
: champ('sorting'),
|
|
751
|
+
manualSorting: Boolean(triServeur),
|
|
692
752
|
onColumnPinningChange: (u) => setVue((v) => {
|
|
693
753
|
const actuel = {
|
|
694
|
-
left:
|
|
754
|
+
left: avecCases ? [COLONNE_SELECTION, ...(v.columnPinning.left ?? [])] : v.columnPinning.left,
|
|
695
755
|
right: v.columnPinning.right,
|
|
696
756
|
};
|
|
697
757
|
const n = appliquer(u, actuel);
|
|
@@ -705,7 +765,7 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
705
765
|
}),
|
|
706
766
|
onColumnFiltersChange: setColumnFilters,
|
|
707
767
|
onGlobalFilterChange: setGlobalFilter,
|
|
708
|
-
onRowSelectionChange:
|
|
768
|
+
onRowSelectionChange: surSelectionLignes,
|
|
709
769
|
onPaginationChange: (u) => {
|
|
710
770
|
const n = appliquer(u, pagination);
|
|
711
771
|
setPageIndex(n.pageIndex);
|
|
@@ -713,14 +773,17 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
713
773
|
setVue((v) => ({ ...v, pageSize: n.pageSize }));
|
|
714
774
|
}
|
|
715
775
|
},
|
|
716
|
-
enableRowSelection:
|
|
776
|
+
enableRowSelection: externe?.selectionnable
|
|
777
|
+
? (r) => externe.selectionnable(r.original)
|
|
778
|
+
: avecCases,
|
|
717
779
|
enableMultiSort: true,
|
|
718
780
|
enableColumnResizing: true,
|
|
719
781
|
columnResizeMode: 'onChange',
|
|
720
782
|
enableColumnPinning: true,
|
|
721
|
-
|
|
783
|
+
// Sans casse ni accents, comme les filtres de colonne.
|
|
784
|
+
globalFilterFn: (ligne, colonne, q) => normaliser(ligne.getValue(colonne)).includes(normaliser(String(q).trim())),
|
|
722
785
|
getCoreRowModel: getCoreRowModel(),
|
|
723
|
-
getSortedRowModel: getSortedRowModel(),
|
|
786
|
+
getSortedRowModel: triServeur ? undefined : getSortedRowModel(),
|
|
724
787
|
getFilteredRowModel: getFilteredRowModel(),
|
|
725
788
|
getPaginationRowModel: getPaginationRowModel(),
|
|
726
789
|
getFacetedRowModel: getFacetedRowModel(),
|
|
@@ -731,9 +794,11 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
731
794
|
const surSelection = useRef(onSelectionChange);
|
|
732
795
|
surSelection.current = onSelectionChange;
|
|
733
796
|
useEffect(() => {
|
|
797
|
+
if (externe)
|
|
798
|
+
return;
|
|
734
799
|
surSelection.current?.(table.getSelectedRowModel().rows.map((r) => r.original));
|
|
735
800
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
736
|
-
}, [
|
|
801
|
+
}, [selectionInterne]);
|
|
737
802
|
// Une page au-delà de la dernière (filtre resserré, données rechargées)
|
|
738
803
|
// afficherait la ligne d'absence alors que des lignes existent.
|
|
739
804
|
const nbPages = table.getPageCount();
|
|
@@ -741,17 +806,22 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
741
806
|
if (pageIndex > 0 && pageIndex >= nbPages)
|
|
742
807
|
setPageIndex(Math.max(0, nbPages - 1));
|
|
743
808
|
}, [pageIndex, nbPages]);
|
|
744
|
-
const
|
|
745
|
-
reinitialiserVue();
|
|
809
|
+
const effacerFiltres = () => {
|
|
746
810
|
setColumnFilters([]);
|
|
747
811
|
setGlobalFilter('');
|
|
812
|
+
};
|
|
813
|
+
const reinitialiser = () => {
|
|
814
|
+
reinitialiserVue();
|
|
815
|
+
if (onVisibilite)
|
|
816
|
+
onVisibilite(() => defaut.columnVisibility);
|
|
817
|
+
effacerFiltres();
|
|
748
818
|
setPageIndex(0);
|
|
749
819
|
};
|
|
750
820
|
/* Glisser-déposer des en-têtes. */
|
|
751
821
|
const idDnd = useId();
|
|
752
822
|
const capteurs = useSensors(
|
|
753
|
-
// 4 px avant de saisir : sans ce seuil, un simple clic sur
|
|
754
|
-
// démarre un déplacement.
|
|
823
|
+
// 4 px avant de saisir : sans ce seuil, un simple clic sur l'en-tête
|
|
824
|
+
// démarre un déplacement au lieu de trier.
|
|
755
825
|
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
756
826
|
const deplacables = table.getCenterVisibleLeafColumns().map((c) => c.id);
|
|
757
827
|
const nomDe = (id) => {
|
|
@@ -788,22 +858,62 @@ export function DataGrid({ tableId, data, columns, onRowClick, ligneActive, getR
|
|
|
788
858
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
789
859
|
}, [lignesBase, colonnes]);
|
|
790
860
|
const filtrables = table.getVisibleLeafColumns().some((c) => c.getCanFilter() && types[c.id]);
|
|
791
|
-
|
|
861
|
+
/** Une rangée qui suit les colonnes affichées (ordre, épinglage) : les
|
|
862
|
+
* filtres, la ligne de saisie, le pied. */
|
|
863
|
+
const rangee = (classe, contenu) => (_jsx("div", { className: classe, children: colonnesAffichees(table).map((c) => {
|
|
792
864
|
const pin = epinglage(c, table);
|
|
865
|
+
const registre = c.columnDef.meta?.registre;
|
|
866
|
+
return (_jsx("div", { className: cx('zv-cell', registre === 'num' && 'zv-cell--num', registre === 'nom' && 'zv-cell--name', pin.classes), style: pin.style, children: contenu(c) }, c.id));
|
|
867
|
+
}) }));
|
|
868
|
+
const rangeeFiltres = filtres && filtrables
|
|
869
|
+
? rangee('zv-table__head zv-table__head--filtres', (c) => {
|
|
793
870
|
const type = c.getCanFilter() ? types[c.id] : false;
|
|
794
|
-
return
|
|
795
|
-
})
|
|
796
|
-
|
|
871
|
+
return type ? _jsx(FiltreColonne, { colonne: c, type: type, t: t }) : null;
|
|
872
|
+
})
|
|
873
|
+
: null;
|
|
874
|
+
/* Lignes : le rang (des lignes gardées en tête), puis les intitulés de
|
|
875
|
+
* groupe et le pied, calculés sur ce qui est AFFICHÉ. */
|
|
876
|
+
const lignesTriees = table.getRowModel().rows;
|
|
877
|
+
const lignesRendues = useMemo(() => {
|
|
878
|
+
if (!rang)
|
|
879
|
+
return lignesTriees;
|
|
880
|
+
return lignesTriees
|
|
881
|
+
.map((r, i) => ({ r, i, k: rang(r.original) }))
|
|
882
|
+
.sort((a, b) => a.k - b.k || a.i - b.i)
|
|
883
|
+
.map((x) => x.r);
|
|
884
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
885
|
+
}, [lignesTriees, rang]);
|
|
886
|
+
const affichees = table.getPrePaginationRowModel().rows.map((r) => r.original);
|
|
887
|
+
const filtre = columnFilters.length > 0 || globalFilter.trim() !== '';
|
|
888
|
+
const surAffichees = useRef(onLignesAffichees);
|
|
889
|
+
surAffichees.current = onLignesAffichees;
|
|
890
|
+
const signatureAffichees = `${filtre}|${lignesRendues.map((r) => r.id).join('\u0001')}`;
|
|
891
|
+
useEffect(() => {
|
|
892
|
+
surAffichees.current?.(lignesRendues.map((r) => r.original), filtre);
|
|
893
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
894
|
+
}, [signatureAffichees]);
|
|
895
|
+
const intercalaire = groupe
|
|
896
|
+
? (ligne, precedente) => {
|
|
897
|
+
const cle = groupe.de(ligne.original);
|
|
898
|
+
if (precedente && groupe.de(precedente.original) === cle)
|
|
899
|
+
return null;
|
|
900
|
+
const membres = lignesRendues.filter((r) => groupe.de(r.original) === cle).map((r) => r.original);
|
|
901
|
+
return (_jsx("div", { className: "zv-row zv-row--groupe", children: groupe.rendu ? groupe.rendu(cle, membres) : _jsx(Mono, { ton: "label", children: cle }) }));
|
|
902
|
+
}
|
|
903
|
+
: undefined;
|
|
904
|
+
const casesPied = pied && lignesRendues.length > 0 ? pied(affichees) : null;
|
|
905
|
+
const total = affichees.length;
|
|
797
906
|
const { pageIndex: page, pageSize } = table.getState().pagination;
|
|
798
907
|
const debut = total === 0 ? 0 : page * pageSize + 1;
|
|
799
908
|
const fin = Math.min(total, (page + 1) * pageSize);
|
|
800
|
-
const nbChoisies = Object.keys(
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
909
|
+
const nbChoisies = externe ? externe.choisies.size : Object.keys(selectionInterne).length;
|
|
910
|
+
const tableau = (_jsx(DndContext, { id: idDnd, sensors: capteurs, collisionDetection: closestCenter, modifiers: [axeHorizontal], onDragEnd: finDeplacement, accessibility: {
|
|
911
|
+
announcements: annonces,
|
|
912
|
+
screenReaderInstructions: {
|
|
913
|
+
draggable: t.dndInstructions,
|
|
914
|
+
},
|
|
915
|
+
}, children: _jsx(SortableContext, { items: deplacables, strategy: horizontalListSortingStrategy, children: _jsx(DataTable, { table: table, className: "zv-table--grille", celluleEntete: (p) => _jsx(EnteteColonne, { ...p, t: t }), sousEntete: rangeeFiltres, onRowClick: onRowClick, ligneActive: ligneActive, onRowContextMenu: onRowContextMenu, onRowDoubleClick: onRowDoubleClick, onRowDragStart: onRowDragStart, lignes: rang ? lignesRendues : undefined, intercalaire: intercalaire, avantLignes: ligneSaisie ? rangee('zv-row zv-row--saisie', (c) => ligneSaisie[c.id] ?? null) : undefined, apresLignes: _jsxs(_Fragment, { children: [casesPied && rangee('zv-row zv-row--pied', (c) => casesPied[c.id] ?? null), apresLignes] }), dense: dense, remplir: remplir, vide: vide && !filtre ? (vide) : (_jsxs(Empty, { children: [data.length === 0 ? t.aucuneDonnee : filtre ? t.aucunResultat : t.aucuneLigne, ' ', filtre && (_jsx(Button, { variante: "fantome", compact: true, onClick: effacerFiltres, children: t.effacerFiltres }))] })) }) }) }));
|
|
916
|
+
return (_jsxs("div", { className: cx('zv-grille', remplir && 'zv-grille--remplir', className), children: [barre && (_jsxs("div", { className: "zv-grille__outils", children: [champRecherche ??
|
|
917
|
+
(recherche !== false ? (_jsx(Search, { value: globalFilter, onChange: (e) => setGlobalFilter(e.target.value), placeholder: typeof recherche === 'string' ? recherche : t.rechercher, "aria-label": t.rechercher })) : (_jsx("span", {}))), _jsxs("div", { className: "zv-grille__actions", children: [avecCases && nbChoisies > 0 && _jsx(Meta, { children: t.choisies(nbChoisies) }), outils, !onVisibilite && _jsx(MenuColonnes, { table: table, t: t }), exportCsv !== false && (_jsx(Button, { variante: "fantome", compact: true, icone: "telecharger", onClick: () => telecharger(versCsv(table), typeof exportCsv === 'string' ? exportCsv : tableId), children: t.exporter })), _jsx(Button, { variante: "fantome", compact: true, icone: "rembobiner", onClick: reinitialiser, children: t.reinitialiser })] })] })), envelopperTableau ? envelopperTableau(tableau) : tableau, parPage !== false && (_jsxs(Pager, { pied: true, "aria-label": t.pagination, children: [_jsx(Meta, { children: t.compte(debut, fin, total) }), _jsxs("div", { className: "zv-grille__pied", children: [_jsx(SelectMenu, { className: "zv-grille__taille", label: t.lignesParPage, valeur: String(pageSize), options: TAILLES_PAGE.map((taille) => ({ valeur: String(taille), label: t.parPage(taille) })), onChange: (v) => table.setPageSize(Number(v)) }), _jsx(PagerArrow, { "aria-label": t.pagePrecedente, desactive: !table.getCanPreviousPage(), onClick: () => table.getCanPreviousPage() && table.previousPage(), children: "\u2039" }), _jsx(Meta, { children: t.page(nbPages === 0 ? 0 : page + 1, nbPages) }), _jsx(PagerArrow, { "aria-label": t.pageSuivante, desactive: !table.getCanNextPage(), onClick: () => table.getCanNextPage() && table.nextPage(), children: "\u203A" })] })] }))] }));
|
|
808
918
|
}
|
|
809
919
|
//# sourceMappingURL=grille.js.map
|