@zevra/ui 0.43.0 → 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 +49 -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 +33 -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 +93 -8
- package/dist/grille.d.ts.map +1 -1
- package/dist/grille.js +251 -112
- package/dist/grille.js.map +1 -1
- package/package.json +1 -1
- package/react/css-inline.ts +1 -1
- package/react/data-table.tsx +87 -6
- package/react/grille.tsx +627 -268
- package/src/data.css +101 -10
package/dist/grille.js
CHANGED
|
@@ -22,10 +22,12 @@ import { DataTable, colonnesAffichees, epinglage } from './data-table.js';
|
|
|
22
22
|
import { usePersistedState } from './use-persisted-state.js';
|
|
23
23
|
import { Button } from './actions.js';
|
|
24
24
|
import { Icon, IconButton } from './icon.js';
|
|
25
|
-
import { Checkbox, Field, Input, Search,
|
|
25
|
+
import { Checkbox, Field, Input, Search, SelectAnchor } from './forms.js';
|
|
26
|
+
import { SelectMenu } from './select-menu.js';
|
|
26
27
|
import { Menu, MenuItem, Pager, PagerArrow, Popover } from './navigation.js';
|
|
27
28
|
import { Empty } from './feedback.js';
|
|
28
29
|
import { Meta } from './data.js';
|
|
30
|
+
import { Mono } from './typography.js';
|
|
29
31
|
import { cx } from './cx.js';
|
|
30
32
|
export { usePersistedState };
|
|
31
33
|
/** Identifiant réservé de la colonne de cases. */
|
|
@@ -45,6 +47,11 @@ export const TEXTES_GRILLE = {
|
|
|
45
47
|
min: 'Min.',
|
|
46
48
|
max: 'Max.',
|
|
47
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.',
|
|
48
55
|
periodes: 'Périodes',
|
|
49
56
|
aujourdhui: "Aujourd'hui",
|
|
50
57
|
septJours: '7 derniers jours',
|
|
@@ -191,15 +198,22 @@ const nombre = (s) => {
|
|
|
191
198
|
const n = Number(s.replace(',', '.'));
|
|
192
199
|
return Number.isFinite(n) ? n : null;
|
|
193
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
|
+
}
|
|
194
208
|
/** Le filtre de toutes les colonnes de la grille : il lit la FORME de la
|
|
195
209
|
* valeur, pas la colonne. Bornes comprises. */
|
|
196
210
|
export const filtreGrille = (ligne, colonne, filtre) => {
|
|
197
211
|
const brut = ligne.getValue(colonne);
|
|
198
212
|
switch (filtre?.type) {
|
|
199
213
|
case 'texte':
|
|
200
|
-
return
|
|
214
|
+
return normaliser(brut).includes(normaliser(filtre.valeur.trim()));
|
|
201
215
|
case 'choix':
|
|
202
|
-
return String(brut ?? '')
|
|
216
|
+
return filtre.valeurs.includes(String(brut ?? ''));
|
|
203
217
|
case 'nombre': {
|
|
204
218
|
const v = typeof brut === 'number' ? brut : nombre(String(brut ?? ''));
|
|
205
219
|
const min = nombre(filtre.min);
|
|
@@ -220,7 +234,7 @@ export const filtreGrille = (ligne, colonne, filtre) => {
|
|
|
220
234
|
};
|
|
221
235
|
filtreGrille.autoRemove = (f) => !f ||
|
|
222
236
|
(f.type === 'texte' && f.valeur.trim() === '') ||
|
|
223
|
-
(f.type === 'choix' && f.
|
|
237
|
+
(f.type === 'choix' && f.valeurs.length === 0) ||
|
|
224
238
|
(f.type === 'nombre' && f.min === '' && f.max === '') ||
|
|
225
239
|
(f.type === 'date' && f.du === '' && f.au === '');
|
|
226
240
|
/** Ne garde d'une saisie que ce qui peut faire un nombre : chiffres, un
|
|
@@ -393,21 +407,22 @@ function PoigneeLargeur({ colonne, table, nom, t, }) {
|
|
|
393
407
|
// une valeur, et les flèches pour la changer.
|
|
394
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() }));
|
|
395
409
|
}
|
|
396
|
-
|
|
397
|
-
function CaseTout({ table, t }) {
|
|
410
|
+
function CaseTout({ table, t, options }) {
|
|
398
411
|
// Une enveloppe plutôt qu'une ref sur la case : `Checkbox` ne transmet
|
|
399
412
|
// pas de ref sous React 18, que le paquet accepte encore.
|
|
400
413
|
const ref = useRef(null);
|
|
401
414
|
const partiel = table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected();
|
|
402
415
|
// `indeterminate` n'a pas d'attribut HTML : seule la propriété DOM existe.
|
|
416
|
+
// Sans elle, « 3 sur 40 cochées » ressemble à « aucune ».
|
|
403
417
|
useEffect(() => {
|
|
404
418
|
const caseTout = ref.current?.querySelector('input');
|
|
405
419
|
if (caseTout)
|
|
406
420
|
caseTout.indeterminate = partiel;
|
|
407
421
|
}, [partiel]);
|
|
408
|
-
|
|
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 }) }));
|
|
409
424
|
}
|
|
410
|
-
function colonneSelection(t) {
|
|
425
|
+
function colonneSelection(t, options) {
|
|
411
426
|
return {
|
|
412
427
|
id: COLONNE_SELECTION,
|
|
413
428
|
size: 40,
|
|
@@ -418,11 +433,13 @@ function colonneSelection(t) {
|
|
|
418
433
|
enableColumnFilter: false,
|
|
419
434
|
enableGlobalFilter: false,
|
|
420
435
|
enablePinning: false,
|
|
421
|
-
header: ({ table }) => _jsx(CaseTout, { table: table, t: t }),
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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,
|
|
426
443
|
meta: { classe: 'zv-cell--case' },
|
|
427
444
|
};
|
|
428
445
|
}
|
|
@@ -454,74 +471,51 @@ function MenuColonnes({ table, t }) {
|
|
|
454
471
|
};
|
|
455
472
|
}, [ouvert]);
|
|
456
473
|
const colonnes = table.getAllLeafColumns().filter((c) => c.id !== COLONNE_SELECTION);
|
|
457
|
-
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 && (
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
+
})] }))] }) }));
|
|
467
484
|
}
|
|
468
485
|
/* ─── Filtres de colonne ────────────────────────────────────────────── */
|
|
469
486
|
function FiltreColonne({ colonne, type, t, }) {
|
|
470
487
|
const nom = libelleColonne(colonne);
|
|
471
488
|
const filtre = colonne.getFilterValue();
|
|
472
|
-
if (type === 'choix')
|
|
473
|
-
|
|
474
|
-
// encore passer. Une option qui ne rendrait aucune ligne n'est pas offerte.
|
|
475
|
-
const options = [...colonne.getFacetedUniqueValues().keys()]
|
|
476
|
-
.filter((v) => v != null && v !== '')
|
|
477
|
-
.map(String)
|
|
478
|
-
.sort((a, b) => a.localeCompare(b, 'fr'));
|
|
479
|
-
const valeur = filtre?.type === 'choix' ? filtre.valeur : '';
|
|
480
|
-
// L'option choisie reste dans la liste même si un autre filtre l'a
|
|
481
|
-
// fait disparaître : sinon le <select> afficherait « Tous » en mentant.
|
|
482
|
-
if (valeur && !options.includes(valeur))
|
|
483
|
-
options.unshift(valeur);
|
|
484
|
-
return (_jsxs(Select, { className: "zv-table__filtre", value: valeur, onChange: (e) => colonne.setFilterValue({ type: 'choix', valeur: e.target.value }), "aria-label": t.filtrerColonne(nom), children: [_jsx("option", { value: "", children: t.tous }), options.map((o) => (_jsx("option", { value: o, children: o }, o)))] }));
|
|
485
|
-
}
|
|
489
|
+
if (type === 'choix')
|
|
490
|
+
return _jsx(FiltreChoix, { colonne: colonne, nom: nom, t: t });
|
|
486
491
|
if (type === 'nombre' || type === 'date') {
|
|
487
492
|
return _jsx(FiltrePlage, { colonne: colonne, type: type, nom: nom, t: t });
|
|
488
493
|
}
|
|
489
494
|
return (_jsx(Input, { className: "zv-table__filtre", value: filtre?.type === 'texte' ? filtre.valeur : '', onChange: (e) => colonne.setFilterValue({ type: 'texte', valeur: e.target.value }), placeholder: t.filtrer, "aria-label": t.filtrerColonne(nom) }));
|
|
490
495
|
}
|
|
491
496
|
/**
|
|
492
|
-
*
|
|
493
|
-
*
|
|
497
|
+
* Un filtre qui s'ouvre : le champ de la rangée RÉSUME la valeur, et ouvre
|
|
498
|
+
* un panneau (une liste de choix, une plage) sous lui.
|
|
494
499
|
*
|
|
495
|
-
* ⚠️ LE
|
|
496
|
-
* horizontalement, et `overflow-x: auto` force `overflow-y` : un
|
|
500
|
+
* ⚠️ LE PANNEAU SORT DU CADRE, par un portail. `.zv-table` défile
|
|
501
|
+
* horizontalement, et `overflow-x: auto` force `overflow-y` : un panneau
|
|
497
502
|
* posé dans la cellule serait rogné par le bas du tableau, invisible sur un
|
|
498
|
-
* tableau de trois lignes. Il est
|
|
499
|
-
*
|
|
503
|
+
* tableau de trois lignes. Il est rendu dans le <body> et placé sous son
|
|
504
|
+
* déclencheur, en coordonnées d'écran.
|
|
505
|
+
*
|
|
506
|
+
* ⚠️ AUCUN <select> NATIF ICI : sa liste est peinte par le SYSTÈME, hors
|
|
507
|
+
* charte (menu noir sous macOS sombre), et certaines apps l'interdisent.
|
|
500
508
|
*/
|
|
501
|
-
function
|
|
509
|
+
function FiltreFlottant({ libelle, resume, vide, icone, role, panneau: contenu, }) {
|
|
502
510
|
const [ouvert, setOuvert] = useState(false);
|
|
503
511
|
const [position, setPosition] = useState(null);
|
|
504
512
|
const declencheur = useRef(null);
|
|
505
513
|
const panneau = useRef(null);
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
//
|
|
511
|
-
// saisie s'affiche dans le champ sans filtrer le tableau. Resynchronisé
|
|
512
|
-
// quand le filtre change d'ailleurs (« Réinitialiser la vue »).
|
|
513
|
-
const [brouillon, setBrouillon] = useState([a, b]);
|
|
514
|
-
useEffect(() => {
|
|
515
|
-
setBrouillon([a, b]);
|
|
516
|
-
}, [a, b]);
|
|
517
|
-
const saisir = (bas, haut) => {
|
|
518
|
-
setBrouillon([bas, haut]);
|
|
519
|
-
if (type === 'nombre' || (dateComplete(bas) && dateComplete(haut)))
|
|
520
|
-
ecrire(bas, haut);
|
|
521
|
-
};
|
|
522
|
-
const resume = type === 'nombre' ? t.resumeNombres(a, b) : t.resumeDates(a, b);
|
|
523
|
-
const vide = !a && !b;
|
|
524
|
-
// Placé AVANT l'affichage (layout effect) : sinon le popover apparaît un
|
|
514
|
+
const fermer = useCallback(() => {
|
|
515
|
+
setOuvert(false);
|
|
516
|
+
declencheur.current?.focus();
|
|
517
|
+
}, []);
|
|
518
|
+
// Placé AVANT l'affichage (layout effect) : sinon le panneau apparaît un
|
|
525
519
|
// instant en haut à gauche de la page.
|
|
526
520
|
useLayoutEffect(() => {
|
|
527
521
|
if (!ouvert) {
|
|
@@ -537,8 +531,8 @@ function FiltrePlage({ colonne, type, nom, t, }) {
|
|
|
537
531
|
const largeur = panneau.current?.firstElementChild?.offsetWidth ?? 0;
|
|
538
532
|
const vue = declencheur.current?.ownerDocument.defaultView;
|
|
539
533
|
const droite = vue ? vue.innerWidth : r.right + largeur;
|
|
540
|
-
// Le décalage de 5 px sous le déclencheur est celui de .zv-popover
|
|
541
|
-
// (top: calc(100% + 5px)) : l'enveloppe se pose au ras
|
|
534
|
+
// Le décalage de 5 px sous le déclencheur est celui de .zv-popover et
|
|
535
|
+
// de .zv-menu (top: calc(100% + 5px)) : l'enveloppe se pose au ras.
|
|
542
536
|
setPosition({ top: r.bottom, left: Math.max(0, Math.min(r.left, droite - largeur - 8)) });
|
|
543
537
|
};
|
|
544
538
|
placer();
|
|
@@ -561,10 +555,8 @@ function FiltrePlage({ colonne, type, nom, t, }) {
|
|
|
561
555
|
setOuvert(false);
|
|
562
556
|
};
|
|
563
557
|
const echap = (e) => {
|
|
564
|
-
if (e.key === 'Escape')
|
|
565
|
-
|
|
566
|
-
declencheur.current?.focus();
|
|
567
|
-
}
|
|
558
|
+
if (e.key === 'Escape')
|
|
559
|
+
fermer();
|
|
568
560
|
};
|
|
569
561
|
doc.addEventListener('mousedown', dehors);
|
|
570
562
|
doc.addEventListener('keydown', echap);
|
|
@@ -572,15 +564,70 @@ function FiltrePlage({ colonne, type, nom, t, }) {
|
|
|
572
564
|
doc.removeEventListener('mousedown', dehors);
|
|
573
565
|
doc.removeEventListener('keydown', echap);
|
|
574
566
|
};
|
|
575
|
-
}, [ouvert]);
|
|
576
|
-
// Le focus entre dans le
|
|
577
|
-
//
|
|
578
|
-
//
|
|
567
|
+
}, [ouvert, fermer]);
|
|
568
|
+
// Le focus entre dans le panneau une fois PLACÉ : avant, il est en
|
|
569
|
+
// `visibility: hidden`, et le navigateur refuse le focus à un élément
|
|
570
|
+
// invisible. La frappe partait alors dans le vide. L'option choisie
|
|
571
|
+
// d'abord, sinon le premier champ ou la première option.
|
|
579
572
|
const place = position !== null;
|
|
580
573
|
useEffect(() => {
|
|
581
|
-
if (ouvert
|
|
582
|
-
|
|
574
|
+
if (!ouvert || !place)
|
|
575
|
+
return;
|
|
576
|
+
const p = panneau.current;
|
|
577
|
+
(p?.querySelector('[aria-selected="true"]') ?? p?.querySelector('input, button'))?.focus();
|
|
583
578
|
}, [ouvert, place]);
|
|
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 &&
|
|
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)] }));
|
|
581
|
+
}
|
|
582
|
+
function FiltreChoix({ colonne, nom, t }) {
|
|
583
|
+
const filtre = colonne.getFilterValue();
|
|
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()]
|
|
591
|
+
.filter((v) => v != null && v !== '')
|
|
592
|
+
.map(String)
|
|
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 }))] }));
|
|
611
|
+
}
|
|
612
|
+
function FiltrePlage({ colonne, type, nom, t, }) {
|
|
613
|
+
const idChamp = useId();
|
|
614
|
+
const filtre = colonne.getFilterValue();
|
|
615
|
+
const [a, b] = filtre?.type === 'nombre' ? [filtre.min, filtre.max] : filtre?.type === 'date' ? [filtre.du, filtre.au] : ['', ''];
|
|
616
|
+
const ecrire = (bas, haut) => colonne.setFilterValue(type === 'nombre' ? { type, min: bas, max: haut } : { type, du: bas, au: haut });
|
|
617
|
+
// Ce qui est TAPÉ, distinct de ce qui est APPLIQUÉ : une date en cours de
|
|
618
|
+
// saisie s'affiche dans le champ sans filtrer le tableau. Resynchronisé
|
|
619
|
+
// quand le filtre change d'ailleurs (« Réinitialiser la vue »).
|
|
620
|
+
const [brouillon, setBrouillon] = useState([a, b]);
|
|
621
|
+
useEffect(() => {
|
|
622
|
+
setBrouillon([a, b]);
|
|
623
|
+
}, [a, b]);
|
|
624
|
+
const saisir = (bas, haut) => {
|
|
625
|
+
setBrouillon([bas, haut]);
|
|
626
|
+
if (type === 'nombre' || (dateComplete(bas) && dateComplete(haut)))
|
|
627
|
+
ecrire(bas, haut);
|
|
628
|
+
};
|
|
629
|
+
const resume = type === 'nombre' ? t.resumeNombres(a, b) : t.resumeDates(a, b);
|
|
630
|
+
const vide = !a && !b;
|
|
584
631
|
const champ = (valeur, changer, suffixe, bornes) => type === 'date' ? (_jsx(Input, { id: idChamp + suffixe, type: "date", value: valeur,
|
|
585
632
|
// Le calendrier natif grise ce qui rendrait la plage incohérente :
|
|
586
633
|
// « Au » ne peut pas précéder « Du ».
|
|
@@ -589,15 +636,13 @@ function FiltrePlage({ colonne, type, nom, t, }) {
|
|
|
589
636
|
// française selon la langue du système. Le clavier numérique vient
|
|
590
637
|
// d'`inputMode`, le refus des lettres de `saisieNombre`.
|
|
591
638
|
inputMode: "decimal", autoComplete: "off", value: valeur, onChange: (e) => changer(saisieNombre(e.target.value)) }));
|
|
592
|
-
return (
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
return (_jsx(MenuItem, { actif: actif, onClick: () => saisir(du, au), children: t[cle] }, cle));
|
|
597
|
-
}) })), _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 }))] }) }), declencheur.current?.ownerDocument.body ?? document.body)] }));
|
|
639
|
+
return (_jsx(FiltreFlottant, { libelle: t.filtrerColonne(nom), resume: resume, vide: vide, icone: type === 'date' ? 'calendrier' : 'filtre', role: "dialog", panneau: () => (_jsxs(Popover, { className: "zv-grille__plage", role: "dialog", "aria-label": t.filtrerColonne(nom), children: [type === 'date' && (_jsx("div", { className: "zv-grille__periodes", role: "group", "aria-label": t.periodes, children: ['aujourdhui', 'septJours', 'trenteJours', 'ceMois', 'cetteAnnee'].map((cle) => {
|
|
640
|
+
const [du, au] = periodes(new Date())[cle];
|
|
641
|
+
return (_jsx(MenuItem, { role: "button", actif: a === du && b === au, "aria-selected": undefined, "aria-pressed": a === du && b === au, onClick: () => saisir(du, au), children: t[cle] }, cle));
|
|
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 }))] })) }));
|
|
598
643
|
}
|
|
599
644
|
/* ─── La grille ──────────────────────────────────────────────────────── */
|
|
600
|
-
export function DataGrid({ tableId, data, columns, onRowClick, 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, }) {
|
|
601
646
|
const t = { ...TEXTES_GRILLE, ...textes };
|
|
602
647
|
const ids = useMemo(() => columns.map((c) => idDeColonne(c)).filter((id) => id != null), [columns]);
|
|
603
648
|
const cleIds = ids.join('\u0000');
|
|
@@ -622,34 +667,73 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
622
667
|
const [vue, setVue, reinitialiserVue] = usePersistedState(`table:${tableId}:v1`, defaut, reconcilier);
|
|
623
668
|
const [columnFilters, setColumnFilters] = useState([]);
|
|
624
669
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
625
|
-
const [
|
|
670
|
+
const [selectionInterne, setSelectionInterne] = useState({});
|
|
626
671
|
const [pageIndex, setPageIndex] = useState(0);
|
|
627
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
|
+
};
|
|
628
707
|
// La colonne de cases est épinglée à gauche, toujours, et n'entre pas dans
|
|
629
708
|
// la vue enregistrée : elle dépend de la prop, pas de l'utilisateur.
|
|
630
709
|
const pinning = {
|
|
631
|
-
left:
|
|
710
|
+
left: avecCases ? [COLONNE_SELECTION, ...(vue.columnPinning.left ?? [])] : vue.columnPinning.left,
|
|
632
711
|
right: vue.columnPinning.right,
|
|
633
712
|
};
|
|
634
713
|
const pagination = {
|
|
635
714
|
pageIndex,
|
|
636
715
|
pageSize: parPage === false ? Math.max(data.length, 1) : vue.pageSize,
|
|
637
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;
|
|
638
721
|
const colonnes = useMemo(() => {
|
|
639
722
|
const avecFiltre = columns.map((c) => (c.filterFn ? c : { ...c, filterFn: filtreGrille }));
|
|
640
|
-
return
|
|
723
|
+
return avecCases ? [colonneSelection(t, optionsCases), ...avecFiltre] : avecFiltre;
|
|
641
724
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
642
|
-
}, [columns,
|
|
725
|
+
}, [columns, avecCases, t.toutSelectionner, t.selectionnerLigne]);
|
|
726
|
+
const visibiliteCourante = visibilite ?? vue.columnVisibility;
|
|
643
727
|
const table = useReactTable({
|
|
644
728
|
data,
|
|
645
729
|
columns: colonnes,
|
|
646
730
|
getRowId,
|
|
647
731
|
state: {
|
|
648
732
|
columnOrder: vue.columnOrder,
|
|
649
|
-
columnVisibility:
|
|
733
|
+
columnVisibility: visibiliteCourante,
|
|
650
734
|
columnSizing: vue.columnSizing,
|
|
651
735
|
columnPinning: pinning,
|
|
652
|
-
sorting: vue.sorting,
|
|
736
|
+
sorting: triServeur ? triServeur.tri : vue.sorting,
|
|
653
737
|
columnFilters,
|
|
654
738
|
globalFilter,
|
|
655
739
|
rowSelection,
|
|
@@ -657,12 +741,17 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
657
741
|
},
|
|
658
742
|
defaultColumn: { size: 150, minSize: 60 },
|
|
659
743
|
onColumnOrderChange: champ('columnOrder'),
|
|
660
|
-
onColumnVisibilityChange:
|
|
744
|
+
onColumnVisibilityChange: onVisibilite
|
|
745
|
+
? (u) => onVisibilite((v) => appliquer(u, v))
|
|
746
|
+
: champ('columnVisibility'),
|
|
661
747
|
onColumnSizingChange: champ('columnSizing'),
|
|
662
|
-
onSortingChange:
|
|
748
|
+
onSortingChange: triServeur
|
|
749
|
+
? (u) => triServeur.onTri(appliquer(u, triServeur.tri))
|
|
750
|
+
: champ('sorting'),
|
|
751
|
+
manualSorting: Boolean(triServeur),
|
|
663
752
|
onColumnPinningChange: (u) => setVue((v) => {
|
|
664
753
|
const actuel = {
|
|
665
|
-
left:
|
|
754
|
+
left: avecCases ? [COLONNE_SELECTION, ...(v.columnPinning.left ?? [])] : v.columnPinning.left,
|
|
666
755
|
right: v.columnPinning.right,
|
|
667
756
|
};
|
|
668
757
|
const n = appliquer(u, actuel);
|
|
@@ -676,7 +765,7 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
676
765
|
}),
|
|
677
766
|
onColumnFiltersChange: setColumnFilters,
|
|
678
767
|
onGlobalFilterChange: setGlobalFilter,
|
|
679
|
-
onRowSelectionChange:
|
|
768
|
+
onRowSelectionChange: surSelectionLignes,
|
|
680
769
|
onPaginationChange: (u) => {
|
|
681
770
|
const n = appliquer(u, pagination);
|
|
682
771
|
setPageIndex(n.pageIndex);
|
|
@@ -684,14 +773,17 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
684
773
|
setVue((v) => ({ ...v, pageSize: n.pageSize }));
|
|
685
774
|
}
|
|
686
775
|
},
|
|
687
|
-
enableRowSelection:
|
|
776
|
+
enableRowSelection: externe?.selectionnable
|
|
777
|
+
? (r) => externe.selectionnable(r.original)
|
|
778
|
+
: avecCases,
|
|
688
779
|
enableMultiSort: true,
|
|
689
780
|
enableColumnResizing: true,
|
|
690
781
|
columnResizeMode: 'onChange',
|
|
691
782
|
enableColumnPinning: true,
|
|
692
|
-
|
|
783
|
+
// Sans casse ni accents, comme les filtres de colonne.
|
|
784
|
+
globalFilterFn: (ligne, colonne, q) => normaliser(ligne.getValue(colonne)).includes(normaliser(String(q).trim())),
|
|
693
785
|
getCoreRowModel: getCoreRowModel(),
|
|
694
|
-
getSortedRowModel: getSortedRowModel(),
|
|
786
|
+
getSortedRowModel: triServeur ? undefined : getSortedRowModel(),
|
|
695
787
|
getFilteredRowModel: getFilteredRowModel(),
|
|
696
788
|
getPaginationRowModel: getPaginationRowModel(),
|
|
697
789
|
getFacetedRowModel: getFacetedRowModel(),
|
|
@@ -702,9 +794,11 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
702
794
|
const surSelection = useRef(onSelectionChange);
|
|
703
795
|
surSelection.current = onSelectionChange;
|
|
704
796
|
useEffect(() => {
|
|
797
|
+
if (externe)
|
|
798
|
+
return;
|
|
705
799
|
surSelection.current?.(table.getSelectedRowModel().rows.map((r) => r.original));
|
|
706
800
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
707
|
-
}, [
|
|
801
|
+
}, [selectionInterne]);
|
|
708
802
|
// Une page au-delà de la dernière (filtre resserré, données rechargées)
|
|
709
803
|
// afficherait la ligne d'absence alors que des lignes existent.
|
|
710
804
|
const nbPages = table.getPageCount();
|
|
@@ -712,17 +806,22 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
712
806
|
if (pageIndex > 0 && pageIndex >= nbPages)
|
|
713
807
|
setPageIndex(Math.max(0, nbPages - 1));
|
|
714
808
|
}, [pageIndex, nbPages]);
|
|
715
|
-
const
|
|
716
|
-
reinitialiserVue();
|
|
809
|
+
const effacerFiltres = () => {
|
|
717
810
|
setColumnFilters([]);
|
|
718
811
|
setGlobalFilter('');
|
|
812
|
+
};
|
|
813
|
+
const reinitialiser = () => {
|
|
814
|
+
reinitialiserVue();
|
|
815
|
+
if (onVisibilite)
|
|
816
|
+
onVisibilite(() => defaut.columnVisibility);
|
|
817
|
+
effacerFiltres();
|
|
719
818
|
setPageIndex(0);
|
|
720
819
|
};
|
|
721
820
|
/* Glisser-déposer des en-têtes. */
|
|
722
821
|
const idDnd = useId();
|
|
723
822
|
const capteurs = useSensors(
|
|
724
|
-
// 4 px avant de saisir : sans ce seuil, un simple clic sur
|
|
725
|
-
// 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.
|
|
726
825
|
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
727
826
|
const deplacables = table.getCenterVisibleLeafColumns().map((c) => c.id);
|
|
728
827
|
const nomDe = (id) => {
|
|
@@ -759,22 +858,62 @@ export function DataGrid({ tableId, data, columns, onRowClick, getRowId, selecti
|
|
|
759
858
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
760
859
|
}, [lignesBase, colonnes]);
|
|
761
860
|
const filtrables = table.getVisibleLeafColumns().some((c) => c.getCanFilter() && types[c.id]);
|
|
762
|
-
|
|
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) => {
|
|
763
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) => {
|
|
764
870
|
const type = c.getCanFilter() ? types[c.id] : false;
|
|
765
|
-
return
|
|
766
|
-
})
|
|
767
|
-
|
|
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;
|
|
768
906
|
const { pageIndex: page, pageSize } = table.getState().pagination;
|
|
769
907
|
const debut = total === 0 ? 0 : page * pageSize + 1;
|
|
770
908
|
const fin = Math.min(total, (page + 1) * pageSize);
|
|
771
|
-
const nbChoisies = Object.keys(
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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" })] })] }))] }));
|
|
779
918
|
}
|
|
780
919
|
//# sourceMappingURL=grille.js.map
|