@sia-ui/cli 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sia-ui/cli
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Aligne les packages publics de SIA UI sur la version 0.7.0 afin que les huit packages restent sur une version commune.
8
+
3
9
  ## 0.6.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -68,6 +68,12 @@ 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.0
72
+
73
+ ### Minor Changes
74
+
75
+ - Aligne les packages publics de SIA UI sur la version 0.7.0 afin que les huit packages restent sur une version commune.
76
+
71
77
  ## 0.6.0
72
78
 
73
79
  ### 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
- () => fourni ?? (syncUrl ? createUrlTableStorage(history) : undefined),
79
- [fourni, history, syncUrl],
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
+ }
@@ -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.4.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.3.0",
1418
+ "version": "0.4.0",
1419
1419
  "status": "experimental",
1420
1420
  "source": "package",
1421
1421
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sia-ui/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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.6.0",
43
- "@sia-ui/react": "0.6.0",
44
- "@sia-ui/utils": "0.6.0",
45
- "@sia-ui/tokens": "0.6.0"
42
+ "@sia-ui/tokens": "0.7.0",
43
+ "@sia-ui/react": "0.7.0",
44
+ "@sia-ui/utils": "0.7.0",
45
+ "@sia-ui/headless": "0.7.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "node scripts/build.mjs",