@sia-ui/cli 0.6.0 → 0.7.1
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/CHANGELOG.md +12 -0
- package/README.md +12 -0
- package/dist/registry/package/components/DataTable/index.tsx +2 -0
- package/dist/registry/package/components/DataTable/query.ts +82 -2
- package/dist/registry/package/components/SecretFields/index.tsx +53 -0
- package/dist/registry/package/components/Select/index.tsx +10 -0
- package/dist/registry/package/components/Select/styles.css +6 -3
- package/dist/registry/registry.json +3 -3
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sia-ui/cli
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Aligne les packages publics de SIA UI sur la version 0.7.1 afin que les huit packages restent sur une version commune.
|
|
8
|
+
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Aligne les packages publics de SIA UI sur la version 0.7.0 afin que les huit packages restent sur une version commune.
|
|
14
|
+
|
|
3
15
|
## 0.6.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -68,6 +68,18 @@ Le contenu ci-dessous reprend intégralement `CHANGELOG.md` pour rester visible
|
|
|
68
68
|
<!-- sia:changelog:start -->
|
|
69
69
|
# @sia-ui/cli
|
|
70
70
|
|
|
71
|
+
## 0.7.1
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- Aligne les packages publics de SIA UI sur la version 0.7.1 afin que les huit packages restent sur une version commune.
|
|
76
|
+
|
|
77
|
+
## 0.7.0
|
|
78
|
+
|
|
79
|
+
### Minor Changes
|
|
80
|
+
|
|
81
|
+
- Aligne les packages publics de SIA UI sur la version 0.7.0 afin que les huit packages restent sur une version commune.
|
|
82
|
+
|
|
71
83
|
## 0.6.0
|
|
72
84
|
|
|
73
85
|
### Minor Changes
|
|
@@ -19,8 +19,10 @@ import { renduCellule, valeurDe } from "./values";
|
|
|
19
19
|
import "./styles.css";
|
|
20
20
|
|
|
21
21
|
export {
|
|
22
|
+
createTanStackRouterStorage,
|
|
22
23
|
createUrlTableStorage,
|
|
23
24
|
useDataTableQuery,
|
|
25
|
+
type TanStackRouterLike,
|
|
24
26
|
type UseDataTableQueryOptions,
|
|
25
27
|
} from "./query";
|
|
26
28
|
|
|
@@ -33,6 +33,74 @@ export function createUrlTableStorage(
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Ce que l'adaptateur demande d'un routeur : la forme du `router` de
|
|
38
|
+
* TanStack Router, sans en dépendre. Méthodes écrites en syntaxe de méthode :
|
|
39
|
+
* leurs paramètres se comparent alors de façon bivariante, et le routeur typé
|
|
40
|
+
* de l'application s'y passe sans transtypage.
|
|
41
|
+
*/
|
|
42
|
+
export interface TanStackRouterLike {
|
|
43
|
+
state: { location: { search: object } };
|
|
44
|
+
navigate(options: {
|
|
45
|
+
to: string;
|
|
46
|
+
search: Record<string, unknown>;
|
|
47
|
+
replace: boolean;
|
|
48
|
+
}): unknown;
|
|
49
|
+
subscribe(event: "onResolved", listener: () => void): () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** `"2"` → `2`, mais jamais `"007"`, `"1e5"` ni `""` : la conversion est sans perte. */
|
|
53
|
+
function nombreCanonique(valeur: string): string | number {
|
|
54
|
+
const nombre = Number(valeur);
|
|
55
|
+
return valeur !== "" && Number.isFinite(nombre) && String(nombre) === valeur
|
|
56
|
+
? nombre
|
|
57
|
+
: valeur;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* L'état du tableau dans les paramètres de TanStack Router.
|
|
62
|
+
*
|
|
63
|
+
* Deux pièges de la recette qu'on écrivait à la main :
|
|
64
|
+
*
|
|
65
|
+
* - TanStack Router sérialise les paramètres en JSON. Une chaîne qui a l'air
|
|
66
|
+
* d'un nombre y est mise entre guillemets — `page=2` devenait
|
|
67
|
+
* `page=%222%22`. L'adaptateur passe donc de vrais nombres ; les autres
|
|
68
|
+
* valeurs restent des chaînes, que le routeur relit à l'identique.
|
|
69
|
+
* - La lecture part de l'objet déjà décodé par le routeur, jamais de la chaîne
|
|
70
|
+
* brute, qui porte ces guillemets.
|
|
71
|
+
*
|
|
72
|
+
* Les paramètres étrangers au tableau sont gardés par `useTableQuery`, qui
|
|
73
|
+
* n'écrit que ses propres clés.
|
|
74
|
+
*/
|
|
75
|
+
export function createTanStackRouterStorage(
|
|
76
|
+
router: TanStackRouterLike,
|
|
77
|
+
mode: "replace" | "push" = "replace",
|
|
78
|
+
): TableQueryStorage {
|
|
79
|
+
return {
|
|
80
|
+
read: () => {
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
for (const [cle, valeur] of Object.entries(router.state.location.search)) {
|
|
83
|
+
if (valeur === undefined || valeur === null) continue;
|
|
84
|
+
for (const v of Array.isArray(valeur) ? valeur : [valeur]) {
|
|
85
|
+
params.append(cle, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return params.toString();
|
|
89
|
+
},
|
|
90
|
+
write: (value) => {
|
|
91
|
+
const search: Record<string, unknown> = {};
|
|
92
|
+
for (const [cle, brut] of new URLSearchParams(value)) {
|
|
93
|
+
const valeur = nombreCanonique(brut);
|
|
94
|
+
const deja = search[cle];
|
|
95
|
+
search[cle] =
|
|
96
|
+
deja === undefined ? valeur : [...(Array.isArray(deja) ? deja : [deja]), valeur];
|
|
97
|
+
}
|
|
98
|
+
router.navigate({ to: ".", search, replace: mode === "replace" });
|
|
99
|
+
},
|
|
100
|
+
subscribe: (listener) => router.subscribe("onResolved", listener),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
36
104
|
export interface UseDataTableQueryOptions {
|
|
37
105
|
defaultState?: Partial<TableQueryState>;
|
|
38
106
|
/** `false` garde l'état en mémoire — utile dans une modale. */
|
|
@@ -50,6 +118,11 @@ export interface UseDataTableQueryOptions {
|
|
|
50
118
|
* remplace celui de l'URL, et `syncUrl` / `history` sont ignorés.
|
|
51
119
|
*/
|
|
52
120
|
storage?: TableQueryStorage;
|
|
121
|
+
/**
|
|
122
|
+
* Le routeur TanStack Router de l'application. Raccourci de
|
|
123
|
+
* `storage: createTanStackRouterStorage(router)` ; `history` s'applique.
|
|
124
|
+
*/
|
|
125
|
+
router?: TanStackRouterLike;
|
|
53
126
|
/** Les noms des paramètres serveur — `limit`, `sort` + `order`… */
|
|
54
127
|
serverParams?: TableParamsMapping;
|
|
55
128
|
}
|
|
@@ -69,14 +142,21 @@ export function useDataTableQuery(
|
|
|
69
142
|
history = "replace",
|
|
70
143
|
prefix,
|
|
71
144
|
storage: fourni,
|
|
145
|
+
router,
|
|
72
146
|
serverParams,
|
|
73
147
|
} = options;
|
|
74
148
|
|
|
75
149
|
// Créé une fois : un objet reconstruit à chaque rendu réabonnerait
|
|
76
150
|
// l'écouteur `popstate` en boucle.
|
|
77
151
|
const storage = useMemo(
|
|
78
|
-
() =>
|
|
79
|
-
|
|
152
|
+
() =>
|
|
153
|
+
fourni ??
|
|
154
|
+
(router
|
|
155
|
+
? createTanStackRouterStorage(router, history)
|
|
156
|
+
: syncUrl
|
|
157
|
+
? createUrlTableStorage(history)
|
|
158
|
+
: undefined),
|
|
159
|
+
[fourni, history, router, syncUrl],
|
|
80
160
|
);
|
|
81
161
|
|
|
82
162
|
return useTableQuery({
|
|
@@ -191,3 +191,56 @@ export function SecretFields({
|
|
|
191
191
|
</div>
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** Ce qu'il faut envoyer au serveur après une saisie dans `SecretFields`. */
|
|
196
|
+
export interface SecretChanges {
|
|
197
|
+
/** À créer ou à remplacer (rotation) : nom → nouvelle valeur. */
|
|
198
|
+
set: Record<string, string>;
|
|
199
|
+
/** Noms enregistrés retirés de la liste : à supprimer. */
|
|
200
|
+
remove: string[];
|
|
201
|
+
/**
|
|
202
|
+
* Noms nouveaux restés sans valeur. Rien à envoyer pour eux — mais les
|
|
203
|
+
* ignorer sans le dire perdrait une saisie : à signaler avant l'envoi.
|
|
204
|
+
*/
|
|
205
|
+
missing: string[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Ce qui change, calculé une fois pour tous les projets.
|
|
210
|
+
*
|
|
211
|
+
* Un nom enregistré laissé vide est inchangé ; saisi, il est remplacé. Un nom
|
|
212
|
+
* enregistré absent de la liste est à supprimer — y compris quand il a été
|
|
213
|
+
* renommé : l'ancien part, le nouveau arrive avec sa valeur. Les lignes sans
|
|
214
|
+
* nom sont ignorées ; un nom en double garde sa dernière valeur saisie.
|
|
215
|
+
*
|
|
216
|
+
* ```ts
|
|
217
|
+
* const { set, remove, missing } = secretChanges(secrets, enregistres);
|
|
218
|
+
* if (missing.length) return; // à compléter
|
|
219
|
+
* await api.patch(`/projets/${id}/secrets`, { body: { set, remove } });
|
|
220
|
+
* setSecrets(clearSecretValues(secrets));
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export function secretChanges(
|
|
224
|
+
entries: SecretEntry[],
|
|
225
|
+
storedKeys: string[] = [],
|
|
226
|
+
): SecretChanges {
|
|
227
|
+
const enregistres = new Set(storedKeys);
|
|
228
|
+
const presents = new Set<string>();
|
|
229
|
+
const set: Record<string, string> = {};
|
|
230
|
+
const missing: string[] = [];
|
|
231
|
+
|
|
232
|
+
for (const { key, value } of entries) {
|
|
233
|
+
const nom = key.trim();
|
|
234
|
+
if (!nom) continue;
|
|
235
|
+
presents.add(nom);
|
|
236
|
+
if (value !== "") set[nom] = value;
|
|
237
|
+
else if (!enregistres.has(nom) && !missing.includes(nom)) missing.push(nom);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Un nom saisi plus loin avec une valeur n'est plus manquant.
|
|
241
|
+
return {
|
|
242
|
+
set,
|
|
243
|
+
remove: storedKeys.filter((nom) => !presents.has(nom)),
|
|
244
|
+
missing: missing.filter((nom) => !(nom in set)),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -191,6 +191,16 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select
|
|
|
191
191
|
if (searchable || fetcher) window.setTimeout(() => searchRef.current?.focus(), 0);
|
|
192
192
|
}, [currentValue, fetcher, open, searchable, visibleOptions]);
|
|
193
193
|
|
|
194
|
+
// L'option active reste visible : au clavier, descendre au-delà du bas de
|
|
195
|
+
// la liste la laissait hors champ, et l'on choisissait à l'aveugle.
|
|
196
|
+
// `nearest` ne fait défiler que si elle sort de la zone visible.
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (!open || activeIndex < 0) return;
|
|
199
|
+
document
|
|
200
|
+
.getElementById(`${listboxId}-${activeIndex}`)
|
|
201
|
+
?.scrollIntoView?.({ block: "nearest" });
|
|
202
|
+
}, [activeIndex, listboxId, open]);
|
|
203
|
+
|
|
194
204
|
const setTriggerRef = (node: HTMLButtonElement | null) => {
|
|
195
205
|
triggerRef.current = node;
|
|
196
206
|
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
.sia-select__clear:hover { color:var(--sia-foreground); background:var(--sia-surface); }
|
|
13
13
|
.sia-select__chevron { transform:translateY(-.5rem); font-size:1.1rem; }
|
|
14
14
|
.sia-select__overlay { overflow:hidden; border:1px solid var(--sia-border); border-radius:var(--sia-radius); color:var(--sia-foreground); background:var(--sia-background); box-shadow:0 14px 36px rgb(15 23 42 / .16); }
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/* Colonne flex, pas grille : un enfant de grille garde une hauteur minimale
|
|
16
|
+
égale à son contenu, la liste débordait alors du panneau au lieu de
|
|
17
|
+
défiler — et l'overlay, en overflow:hidden, la coupait. */
|
|
18
|
+
.sia-select__panel { display:flex; flex-direction:column; min-width:12rem; max-height:min(22rem,calc(100vh - 1rem)); }
|
|
19
|
+
.sia-select__search-wrap { flex:none; display:flex; align-items:center; gap:.5rem; padding:.55rem .65rem; color:var(--sia-muted); border-bottom:1px solid var(--sia-border); }
|
|
17
20
|
.sia-select__search { min-width:0; width:100%; padding:.2rem 0; border:0; outline:0; color:var(--sia-foreground); background:transparent; font:inherit; }
|
|
18
|
-
.sia-select__options { overflow-y:auto; padding:.35rem; }
|
|
21
|
+
.sia-select__options { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding:.35rem; }
|
|
19
22
|
.sia-select__option { display:flex; align-items:center; justify-content:space-between; gap:.75rem; width:100%; padding:.55rem .65rem; border:0; border-radius:calc(var(--sia-radius) * .7); color:inherit; background:transparent; font:inherit; text-align:left; cursor:pointer; }
|
|
20
23
|
.sia-select__option:hover:not(:disabled),.sia-select__option--active:not(:disabled) { background:var(--sia-surface); }
|
|
21
24
|
.sia-select__option[aria-selected="true"] { color:var(--sia-primary); background:color-mix(in srgb,var(--sia-primary) 10%,transparent); }
|
|
@@ -452,7 +452,7 @@
|
|
|
452
452
|
"name": "data-table",
|
|
453
453
|
"description": "Data table: row actions with permissions and confirmation, selection, card view, density and sorting.",
|
|
454
454
|
"type": "component",
|
|
455
|
-
"version": "0.
|
|
455
|
+
"version": "0.5.0",
|
|
456
456
|
"status": "stable",
|
|
457
457
|
"source": "package",
|
|
458
458
|
"files": [
|
|
@@ -1415,7 +1415,7 @@
|
|
|
1415
1415
|
"name": "secret-fields",
|
|
1416
1416
|
"description": "Write-only key/value secrets editor: stored names shown, values never read back.",
|
|
1417
1417
|
"type": "component",
|
|
1418
|
-
"version": "0.
|
|
1418
|
+
"version": "0.4.0",
|
|
1419
1419
|
"status": "experimental",
|
|
1420
1420
|
"source": "package",
|
|
1421
1421
|
"files": [
|
|
@@ -1437,7 +1437,7 @@
|
|
|
1437
1437
|
"name": "select",
|
|
1438
1438
|
"description": "Accessible searchable select with positioned overlay and async option loading.",
|
|
1439
1439
|
"type": "component",
|
|
1440
|
-
"version": "0.3.
|
|
1440
|
+
"version": "0.3.1",
|
|
1441
1441
|
"status": "stable",
|
|
1442
1442
|
"source": "package",
|
|
1443
1443
|
"files": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sia-ui/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Install SIA UI components progressively into an application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sia-ui",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"react-dom": "^19.2.3",
|
|
40
40
|
"typescript": "^5.9.3",
|
|
41
41
|
"vitest": "^4.0.16",
|
|
42
|
-
"@sia-ui/headless": "0.
|
|
43
|
-
"@sia-ui/react": "0.
|
|
44
|
-
"@sia-ui/
|
|
45
|
-
"@sia-ui/
|
|
42
|
+
"@sia-ui/headless": "0.7.1",
|
|
43
|
+
"@sia-ui/react": "0.7.1",
|
|
44
|
+
"@sia-ui/tokens": "0.7.1",
|
|
45
|
+
"@sia-ui/utils": "0.7.1"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "node scripts/build.mjs",
|