@hgdc/ui 0.72.2 → 0.73.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.
@@ -8,6 +8,7 @@ import SecurityLockIcon from "@hugeicons/core-free-icons/SecurityLockIcon";
8
8
  import Tick02Icon from "@hugeicons/core-free-icons/Tick02Icon";
9
9
  import UserIcon from "@hugeicons/core-free-icons/UserIcon";
10
10
  import { HugeiconsIcon } from "@hugeicons/react";
11
+ import { pauseSessionOpening } from "./product-switch.js";
11
12
  import { Menu as MenuPrimitive } from "@base-ui/react/menu";
12
13
  import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
13
14
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu.js";
@@ -47,6 +48,8 @@ export function AccountMenu({ user, onProfile, onSecurity, onSignOut, products:
47
48
  return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: variant === "row" ? (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "flex w-full min-w-0 items-center gap-2.5 rounded-xl p-1.5 text-left outline-none transition-colors duration-(--duration-fast) ease-out hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50 aria-expanded:bg-muted lg:hover:bg-shell-hover lg:aria-expanded:bg-shell-hover lg:rail:justify-center" })) : (_jsx("button", { type: "button", "aria-label": `Compte de ${user.name}`, className: "touch-target grid size-9 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring/50" })), children: [_jsx(AccountAvatar, { user: user }), variant === "row" ? (_jsxs("span", { className: "min-w-0 flex-1 lg:rail:hidden", children: [_jsx("span", { className: "block truncate text-sm font-medium", children: user.name }), _jsx("span", { className: "block truncate text-xs text-muted-foreground lg:text-shell-muted", children: user.email })] })) : null] }), _jsxs(DropdownMenuContent, { align: variant === "row" ? "start" : "end", side: variant === "row" ? "top" : "bottom", className: "w-72", children: [_jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuLabel, { className: "flex items-center gap-2.5 py-2", children: [_jsx(AccountAvatar, { user: user }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-sm font-medium text-foreground", children: user.name }), _jsx("span", { className: "block truncate", children: user.email })] })] }) }), _jsx(DropdownMenuSeparator, {}), onProfile ? (_jsxs(DropdownMenuItem, { onClick: onProfile, children: [_jsx(HugeiconsIcon, { icon: UserIcon, "aria-hidden": true }), "Profil"] })) : null, onSecurity ? (_jsxs(DropdownMenuItem, { onClick: onSecurity, children: [_jsx(HugeiconsIcon, { icon: SecurityLockIcon, "aria-hidden": true }), "S\u00E9curit\u00E9"] })) : null, _jsx(ThemeSwitch, {}), children ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), children] })) : null, _jsx(AccountMenuLinks, { links: menuLinks }), products.length ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuLabel, { children: "Produits" }), products.map((product) => product.current ? (_jsxs(DropdownMenuItem, { disabled: true, className: "data-disabled:opacity-100", children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), _jsx(HugeiconsIcon, { icon: Tick02Icon, "aria-label": "Produit ouvert" })] }, product.name)) : (_jsxs(MenuPrimitive.LinkItem, { href: product.href, onClick: leaveOnClick, className: MENU_LINK_ITEM, children: [_jsx(ProductMenuIcon, { product: product }), _jsx("span", { className: "flex-1", children: product.name }), product.hint ? (_jsx("span", { className: "text-xs font-normal text-muted-foreground", children: product.hint })) : null] }, product.name)))] })] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: () => {
48
49
  // La marque de l'organisation retenue pour la coque ne survit pas à la session.
49
50
  forgetShellOrganization();
51
+ // Aucun module ne se rouvre seul après une déconnexion (ADR 0026).
52
+ pauseSessionOpening();
50
53
  onSignOut();
51
54
  }, children: [_jsx(HugeiconsIcon, { icon: Logout01Icon, "aria-hidden": true }), "Se d\u00E9connecter"] })] })] }));
52
55
  }
@@ -1,8 +1,14 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { FamilyProduct } from "./family-svg.ts";
3
3
  import { type ModuleWord } from "./modules.ts";
4
+ import { type ShellProductEntry } from "./products.ts";
4
5
  /** Ce que montre le milieu de la barre : les onglets du module ouvert, la liste des modules, ou son réglage. */
5
6
  export type ShellNavLevel = "tabs" | "modules" | "edit";
7
+ /**
8
+ * Bases des modules affichés (`shown`), autres que le module ouvert et servis sous la même origine :
9
+ * ceux dont la session s'ouvre en arrière-plan (ADR 0026). Manager n'en fait pas partie.
10
+ */
11
+ export declare function sessionBases(entries: ReadonlyArray<ShellProductEntry>, shown: ReadonlyArray<ModuleWord>, current: FamilyProduct): Array<string>;
6
12
  /**
7
13
  * Le milieu de la barre de Basalte : Manager, un trait, puis l'un des trois niveaux. Dans un module,
8
14
  * ses onglets (`nav`) sous « Retour aux modules » ; un clic y affiche la liste sans rien recharger ;
@@ -14,7 +14,7 @@ import { saveModuleBar, useModuleBar } from "./module-bar-store.js";
14
14
  import { pushModuleBar } from "./product-bar.js";
15
15
  import { hiddenModules, moduleByWord, moveModule, shownModules } from "./modules.js";
16
16
  import { ProductBadge } from "./product-badge.js";
17
- import { PRODUCT_PRERENDER_ATTRIBUTE } from "./product-switch.js";
17
+ import { PRODUCT_PRERENDER_ATTRIBUTE, productEntryBase, useSessionOpening } from "./product-switch.js";
18
18
  import { shellProductEntries } from "./products.js";
19
19
  import { familyProductName } from "./product-name.js";
20
20
  import { lastOrganization } from "./shell-memory.js";
@@ -46,6 +46,19 @@ function ShellEntryLink({ entry, icon, onOpenCurrent, }) {
46
46
  };
47
47
  return (_jsx("li", { children: _jsxs(Tooltip, { disabled: !rail, children: [_jsxs(TooltipTrigger, { render: _jsx("a", { href: entry.href, onClick: onClick, ...(entry.sameOrigin ? { [PRODUCT_PRERENDER_ATTRIBUTE]: "" } : {}), "aria-current": entry.current ? "page" : undefined, className: SIDEBAR_ITEM }), children: [icon ? (_jsx(HugeiconsIcon, { icon: icon, strokeWidth: 1.8, "aria-hidden": true, className: "size-5 shrink-0 lg:size-[1.125rem]" })) : (_jsx(ProductBadge, { product: entry.badge, className: "size-5 shrink-0 lg:size-[1.125rem]" })), _jsx("span", { className: "min-w-0 flex-1 truncate lg:rail:sr-only", children: entry.name }), entry.hint ? _jsx("span", { className: "text-xs font-normal lg:rail:hidden", children: entry.hint }) : null] }), _jsx(TooltipContent, { side: "right", children: entry.name })] }) }));
48
48
  }
49
+ /**
50
+ * Bases des modules affichés (`shown`), autres que le module ouvert et servis sous la même origine :
51
+ * ceux dont la session s'ouvre en arrière-plan (ADR 0026). Manager n'en fait pas partie.
52
+ */
53
+ export function sessionBases(entries, shown, current) {
54
+ return shown.flatMap((word) => {
55
+ const entry = entries.find((item) => item.badge === word);
56
+ if (!entry || entry.badge === current || !entry.sameOrigin)
57
+ return [];
58
+ const base = productEntryBase(entry.href);
59
+ return base ? [base] : [];
60
+ });
61
+ }
49
62
  function ModuleList({ entries, order, onOpenCurrent, onCustomize, }) {
50
63
  const shown = order.flatMap((word) => {
51
64
  const entry = entries.find((item) => item.badge === word);
@@ -91,7 +104,11 @@ export function ShellNavigation({ product, nav, accountHref, level, onLevelChang
91
104
  const organization = useLastOrganization();
92
105
  const order = useModuleBar();
93
106
  const { products, account } = shellProductEntries({ current: product, accountHref, pageOrigin, organization });
94
- return (_jsxs("div", { "data-slot": "shell-navigation", className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { "data-slot": "shell-manager", className: "shrink-0 px-3 pt-1", children: [_jsx("ul", { children: _jsx(ShellEntryLink, { entry: account, icon: UserGroupIcon }) }), _jsx("div", { "aria-hidden": true, className: "mx-2 mt-2 h-px bg-foreground/8" })] }), level === "modules" ? (_jsx(ModuleList, { entries: products, order: shownModules(order, product).filter((word) => !projectModules || word === product || projectModules.includes(word)), onOpenCurrent: () => onLevelChange("tabs"), onCustomize: () => onLevelChange("edit") })) : level === "edit" ? (_jsx(ModuleBarEditor, { order: order, onSave: (next) => {
107
+ const shown = shownModules(order, product).filter((word) => !projectModules || word === product || projectModules.includes(word));
108
+ // Sessions des autres modules affichés, ouvertes en arrière-plan : le premier clic n'attend pas
109
+ // la connexion chez ID (ADR 0026).
110
+ useSessionOpening(sessionBases(products, shown, product));
111
+ return (_jsxs("div", { "data-slot": "shell-navigation", className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { "data-slot": "shell-manager", className: "shrink-0 px-3 pt-1", children: [_jsx("ul", { children: _jsx(ShellEntryLink, { entry: account, icon: UserGroupIcon }) }), _jsx("div", { "aria-hidden": true, className: "mx-2 mt-2 h-px bg-foreground/8" })] }), level === "modules" ? (_jsx(ModuleList, { entries: products, order: shown, onOpenCurrent: () => onLevelChange("tabs"), onCustomize: () => onLevelChange("edit") })) : level === "edit" ? (_jsx(ModuleBarEditor, { order: order, onSave: (next) => {
95
112
  saveModuleBar(next);
96
113
  void pushModuleBar(next);
97
114
  onLevelChange("modules");
@@ -13,6 +13,22 @@ export declare const BACKGROUND_REFRESH_TIMEOUT_MS = 20000;
13
13
  export declare const BACKGROUND_REFRESH_INTERVAL_MS: number;
14
14
  /** Heure de la dernière relecture d'un produit, en `localStorage` : `hgdc-org-sync:<base>` (`/` à la racine). */
15
15
  export declare const ORG_SYNC_LAST_KEY_PREFIX = "hgdc-org-sync:";
16
+ /** Cadre caché qui ouvre la session d'un autre produit, seulement si elle manque (ADR 0026). */
17
+ export declare const SESSION_OPEN_PATH = "/auth/hgdc?frame=open";
18
+ /** Une ouverture au plus par produit pendant ce délai : une session vit 7 jours (`@hgdc/auth`). */
19
+ export declare const SESSION_OPEN_INTERVAL_MS: number;
20
+ /** Attente après l'affichage de la page avant la première ouverture : la page passe d'abord. */
21
+ export declare const SESSION_OPEN_DELAY_MS = 3000;
22
+ /** Heure de la dernière ouverture d'un produit, en `localStorage` : `hgdc-session-open:<base>`. */
23
+ export declare const SESSION_OPEN_KEY_PREFIX = "hgdc-session-open:";
24
+ /**
25
+ * Posée à la déconnexion d'un module (`AccountMenu`), levée à la prochaine connexion par clic
26
+ * (`HgdcSignInButton` de `@hgdc/auth`) : tant qu'elle est là, rien ne s'ouvre en arrière-plan. Une
27
+ * déconnexion (changer de compte, poste partagé) n'est jamais défaite sans la personne.
28
+ */
29
+ export declare const SESSION_OPEN_PAUSED_KEY = "hgdc-session-open:paused";
30
+ /** À la déconnexion d'un module : plus d'ouverture en arrière-plan jusqu'à la prochaine connexion. */
31
+ export declare function pauseSessionOpening(win?: Window | null): void;
16
32
  /** Marque d'un lien de la barre vers un autre produit servi sous la même origine : prérendu au survol. */
17
33
  export declare const PRODUCT_PRERENDER_ATTRIBUTE = "data-hgdc-prerender";
18
34
  /**
@@ -48,6 +64,20 @@ export declare function takeOrgSyncCookie(doc?: Document): boolean;
48
64
  * clic ne relit rien.
49
65
  */
50
66
  export declare function startBackgroundRefresh(doc?: Document): HTMLIFrameElement | null;
67
+ /**
68
+ * La base d'un produit (`/mail`) depuis le lien relatif de sa barre (`/mail/auth/hgdc?…`), ou
69
+ * `null` pour un lien absolu (autre origine) ou une base illisible.
70
+ */
71
+ export declare function productEntryBase(href: string): string | null;
72
+ /**
73
+ * Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
74
+ * fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module. Session déjà
75
+ * ouverte : réponse vide sans appel à ID. Sinon, la connexion HGDC se fait dans le cadre ; si ID
76
+ * demande quelque chose (session expirée, consentement), il refuse d'être affiché dans un cadre et
77
+ * rien ne se passe : le clic fera la connexion habituelle. Pas sur une page prérendue, ni quand
78
+ * l'économie de données est demandée. Rend une fonction qui arrête la file.
79
+ */
80
+ export declare function startSessionOpening(bases: ReadonlyArray<string>, doc?: Document): () => void;
51
81
  /**
52
82
  * Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
53
83
  * (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
@@ -62,3 +92,9 @@ export declare function useBackgroundOrganizationRefresh(): void;
62
92
  * `script-src` doit y autoriser `'inline-speculation-rules'` (README).
63
93
  */
64
94
  export declare function useProductSwitch(enabled?: boolean): void;
95
+ /**
96
+ * Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
97
+ * la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
98
+ * (les bases jointes), pour ne pas relancer la file à chaque rendu.
99
+ */
100
+ export declare function useSessionOpening(bases: ReadonlyArray<string>): void;
@@ -24,6 +24,29 @@ export const BACKGROUND_REFRESH_TIMEOUT_MS = 20_000;
24
24
  export const BACKGROUND_REFRESH_INTERVAL_MS = 10 * 60_000;
25
25
  /** Heure de la dernière relecture d'un produit, en `localStorage` : `hgdc-org-sync:<base>` (`/` à la racine). */
26
26
  export const ORG_SYNC_LAST_KEY_PREFIX = "hgdc-org-sync:";
27
+ /** Cadre caché qui ouvre la session d'un autre produit, seulement si elle manque (ADR 0026). */
28
+ export const SESSION_OPEN_PATH = "/auth/hgdc?frame=open";
29
+ /** Une ouverture au plus par produit pendant ce délai : une session vit 7 jours (`@hgdc/auth`). */
30
+ export const SESSION_OPEN_INTERVAL_MS = 24 * 60 * 60_000;
31
+ /** Attente après l'affichage de la page avant la première ouverture : la page passe d'abord. */
32
+ export const SESSION_OPEN_DELAY_MS = 3000;
33
+ /** Heure de la dernière ouverture d'un produit, en `localStorage` : `hgdc-session-open:<base>`. */
34
+ export const SESSION_OPEN_KEY_PREFIX = "hgdc-session-open:";
35
+ /**
36
+ * Posée à la déconnexion d'un module (`AccountMenu`), levée à la prochaine connexion par clic
37
+ * (`HgdcSignInButton` de `@hgdc/auth`) : tant qu'elle est là, rien ne s'ouvre en arrière-plan. Une
38
+ * déconnexion (changer de compte, poste partagé) n'est jamais défaite sans la personne.
39
+ */
40
+ export const SESSION_OPEN_PAUSED_KEY = "hgdc-session-open:paused";
41
+ /** À la déconnexion d'un module : plus d'ouverture en arrière-plan jusqu'à la prochaine connexion. */
42
+ export function pauseSessionOpening(win = typeof window === "undefined" ? null : window) {
43
+ try {
44
+ win?.localStorage.setItem(SESSION_OPEN_PAUSED_KEY, String(Date.now()));
45
+ }
46
+ catch {
47
+ // Stockage refusé : l'ouverture n'a pas lieu non plus sans lui (`startSessionOpening`).
48
+ }
49
+ }
27
50
  /** Marque d'un lien de la barre vers un autre produit servi sous la même origine : prérendu au survol. */
28
51
  export const PRODUCT_PRERENDER_ATTRIBUTE = "data-hgdc-prerender";
29
52
  /**
@@ -95,19 +118,20 @@ export function takeOrgSyncCookie(doc = document) {
95
118
  * Vrai si la relecture de ce produit est due, et note alors l'heure : au plus une toutes les
96
119
  * `BACKGROUND_REFRESH_INTERVAL_MS`. Sans `localStorage` (navigation privée stricte), toujours due.
97
120
  */
98
- function refreshDue(base, win, now = Date.now()) {
99
- const key = `${ORG_SYNC_LAST_KEY_PREFIX}${base || "/"}`;
121
+ function refreshDue(base, win, now = Date.now(), prefix = ORG_SYNC_LAST_KEY_PREFIX, interval = BACKGROUND_REFRESH_INTERVAL_MS, withoutStorage = true) {
122
+ const key = `${prefix}${base || "/"}`;
100
123
  try {
101
124
  const storage = win?.localStorage;
102
125
  if (!storage)
103
- return true;
126
+ return withoutStorage;
104
127
  const last = Number(storage.getItem(key));
105
- if (Number.isFinite(last) && last > 0 && now - last >= 0 && now - last < BACKGROUND_REFRESH_INTERVAL_MS)
128
+ if (Number.isFinite(last) && last > 0 && now - last >= 0 && now - last < interval)
106
129
  return false;
107
130
  storage.setItem(key, String(now));
108
131
  }
109
132
  catch {
110
- // Stockage refusé : la relecture part, comme avant.
133
+ // Stockage refusé : la relecture part, comme avant ; l'ouverture, elle, ne part pas.
134
+ return withoutStorage;
111
135
  }
112
136
  return true;
113
137
  }
@@ -128,24 +152,82 @@ export function startBackgroundRefresh(doc = document) {
128
152
  const base = takeOrgSync(doc);
129
153
  if (base === null || !refreshDue(base, doc.defaultView))
130
154
  return null;
155
+ return openHiddenFrame(doc, `${base}${BACKGROUND_REFRESH_PATH}`);
156
+ }
157
+ /** Vrai si une déconnexion a suspendu l'ouverture, ou si le stockage manque (rien ne la limiterait). */
158
+ function openingPaused(win) {
159
+ try {
160
+ return !win?.localStorage || win.localStorage.getItem(SESSION_OPEN_PAUSED_KEY) !== null;
161
+ }
162
+ catch {
163
+ return true;
164
+ }
165
+ }
166
+ /**
167
+ * Un cadre caché vers une adresse de l'entrée, retiré à son chargement ou après
168
+ * `BACKGROUND_REFRESH_TIMEOUT_MS`. Ni script, ni formulaire, ni navigation de la page (`sandbox`) ;
169
+ * même origine pour les cookies du produit. `onDone` est appelé une fois, quand il est retiré.
170
+ */
171
+ function openHiddenFrame(doc, src, onDone) {
131
172
  const frame = doc.createElement("iframe");
132
173
  frame.hidden = true;
133
174
  frame.tabIndex = -1;
134
175
  frame.title = "";
135
176
  frame.setAttribute("aria-hidden", "true");
136
- // Même origine pour les cookies du produit ; ni script, ni formulaire, ni navigation de la page.
137
177
  frame.setAttribute("sandbox", "allow-same-origin");
138
178
  frame.referrerPolicy = "no-referrer";
139
179
  const done = () => {
140
180
  window.clearTimeout(timer);
141
181
  frame.remove();
182
+ onDone?.();
142
183
  };
143
184
  const timer = window.setTimeout(done, BACKGROUND_REFRESH_TIMEOUT_MS);
144
185
  frame.addEventListener("load", done, { once: true });
145
- frame.src = `${base}${BACKGROUND_REFRESH_PATH}`;
186
+ frame.src = src;
146
187
  doc.body.append(frame);
147
188
  return frame;
148
189
  }
190
+ /**
191
+ * La base d'un produit (`/mail`) depuis le lien relatif de sa barre (`/mail/auth/hgdc?…`), ou
192
+ * `null` pour un lien absolu (autre origine) ou une base illisible.
193
+ */
194
+ export function productEntryBase(href) {
195
+ if (!href.startsWith("/"))
196
+ return null;
197
+ const index = href.indexOf("/auth/hgdc");
198
+ if (index <= 0)
199
+ return null;
200
+ const base = href.slice(0, index);
201
+ return BASE_PATH.test(base) ? base : null;
202
+ }
203
+ /**
204
+ * Ouvre en arrière-plan la session des autres modules de la barre (ADR 0026) : un cadre caché à la
205
+ * fois vers `<base>/auth/hgdc?frame=open`, au plus une fois par jour et par module. Session déjà
206
+ * ouverte : réponse vide sans appel à ID. Sinon, la connexion HGDC se fait dans le cadre ; si ID
207
+ * demande quelque chose (session expirée, consentement), il refuse d'être affiché dans un cadre et
208
+ * rien ne se passe : le clic fera la connexion habituelle. Pas sur une page prérendue, ni quand
209
+ * l'économie de données est demandée. Rend une fonction qui arrête la file.
210
+ */
211
+ export function startSessionOpening(bases, doc = document) {
212
+ const win = doc.defaultView;
213
+ const saveData = win?.navigator?.connection
214
+ ?.saveData;
215
+ if (saveData || doc.prerendering || openingPaused(win))
216
+ return () => { };
217
+ // Sans stockage, pas de limite par jour : rien ne part (sinon une connexion par module à chaque page).
218
+ const queue = bases.filter((base) => refreshDue(base, win, Date.now(), SESSION_OPEN_KEY_PREFIX, SESSION_OPEN_INTERVAL_MS, false));
219
+ let stopped = false;
220
+ const next = () => {
221
+ const base = queue.shift();
222
+ if (stopped || base === undefined)
223
+ return;
224
+ openHiddenFrame(doc, `${base}${SESSION_OPEN_PATH}`, next);
225
+ };
226
+ next();
227
+ return () => {
228
+ stopped = true;
229
+ };
230
+ }
149
231
  /**
150
232
  * Relecture en arrière-plan si l'entrée l'a demandé. Montée à la racine du document
151
233
  * (`FamilyLoaderRoot`), pour toutes les pages, y compris celles hors de la coque : la page « il te
@@ -181,3 +263,23 @@ export function useProductSwitch(enabled = true) {
181
263
  return () => script.remove();
182
264
  }, [enabled]);
183
265
  }
266
+ /**
267
+ * Dans la barre de Basalte (`ShellNavigation`) : ouvre la session des modules affichés servis sous
268
+ * la même origine, `SESSION_OPEN_DELAY_MS` après l'affichage (ADR 0026). `bases` : clé stable
269
+ * (les bases jointes), pour ne pas relancer la file à chaque rendu.
270
+ */
271
+ export function useSessionOpening(bases) {
272
+ const key = bases.join(" ");
273
+ useEffect(() => {
274
+ if (!key)
275
+ return;
276
+ let stop = () => { };
277
+ const timer = window.setTimeout(() => {
278
+ stop = startSessionOpening(key.split(" "));
279
+ }, SESSION_OPEN_DELAY_MS);
280
+ return () => {
281
+ window.clearTimeout(timer);
282
+ stop();
283
+ };
284
+ }, [key]);
285
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/ui",
3
- "version": "0.72.2",
3
+ "version": "0.73.0",
4
4
  "description": "Interface de la H Collection : jetons de la DA, thèmes, polices et composants.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/ui#readme",