@hgdc/ui 0.16.1 → 0.17.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/dist/app-shell.js +3 -0
- package/dist/product-switch.d.ts +29 -0
- package/dist/product-switch.js +92 -0
- package/dist/products.d.ts +10 -6
- package/dist/products.js +6 -3
- package/package.json +1 -1
package/dist/app-shell.js
CHANGED
|
@@ -12,6 +12,7 @@ import { isApplePlatform, Kbd, KbdGroup } from "./kbd.js";
|
|
|
12
12
|
import { CONTENT_LOADING_LAYER, ContentLoadingContext, PAGE_PENDING_MIN_MS, PAGE_PENDING_MS, PagePending, } from "./page-pending.js";
|
|
13
13
|
import { ProductBadge } from "./product-badge.js";
|
|
14
14
|
import { ProductRail } from "./product-rail.js";
|
|
15
|
+
import { useProductSwitch } from "./product-switch.js";
|
|
15
16
|
import { HGDC_ACCOUNT, HGDC_PRODUCTS } from "./products.js";
|
|
16
17
|
import { ProductRailShownContext, ShellLeaveContext } from "./shell-placement.js";
|
|
17
18
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
@@ -177,6 +178,8 @@ export function AppShell({ product, organization, productRail = true, accountHre
|
|
|
177
178
|
// Barre des produits élargie : la barre latérale se plie le temps du survol (ADR 0011).
|
|
178
179
|
const [railPeek, setRailPeek] = useState(false);
|
|
179
180
|
useToggleShortcut();
|
|
181
|
+
// Relecture des organisations après un passage depuis la barre, préchargement des autres produits (ADR 0016).
|
|
182
|
+
useProductSwitch(productRail);
|
|
180
183
|
// Chargement de la zone de contenu (ADR 0015) : une navigation en cours (après 50 ms), une page
|
|
181
184
|
// qui attend (`PagePending`, aussitôt : le routeur a déjà attendu), ou le départ vers un autre
|
|
182
185
|
// produit (aussitôt). Une fois montré, au moins 300 ms. Le H est celui du document, en position
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Cookie posé par l'entrée : relire les organisations en arrière-plan. */
|
|
2
|
+
export declare const ORG_SYNC_COOKIE = "hgdc-org-sync";
|
|
3
|
+
/** Cadre caché de la relecture : l'entrée repasse par Account, retour sur une réponse vide. */
|
|
4
|
+
export declare const BACKGROUND_REFRESH_PATH = "/auth/hgdc?refresh=1&frame=1";
|
|
5
|
+
/** Abandon du cadre s'il ne s'est pas chargé : Account injoignable, réseau coupé. */
|
|
6
|
+
export declare const BACKGROUND_REFRESH_TIMEOUT_MS = 20000;
|
|
7
|
+
/**
|
|
8
|
+
* Liens préchargés au survol : l'entrée d'un autre produit en `refresh=background`. Elle ne fait
|
|
9
|
+
* que lire la session et rediriger ; sans session, elle répond 503 à un préchargement (rien n'est
|
|
10
|
+
* créé), et le clic repart normalement. Jamais de `prerender` : la page d'arrivée lit un cookie et
|
|
11
|
+
* ouvre un cadre, ce qu'un prérendu ferait pour un simple survol.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PRODUCT_PREFETCH_RULES: string;
|
|
14
|
+
/** Lit et efface le cookie de relecture. Vrai s'il était posé. */
|
|
15
|
+
export declare function takeOrgSyncCookie(doc?: Document): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Relit les organisations dans un cadre caché, une fois, si l'entrée l'a demandé. Le cadre n'a
|
|
18
|
+
* ni script ni formulaire (`sandbox`), il suit seulement les redirections d'Account ; la nouvelle
|
|
19
|
+
* session du produit recopie les organisations, et les requêtes Convex abonnées se mettent à jour.
|
|
20
|
+
* Si la session d'Account a expiré, Account refuse d'être affiché dans un cadre : rien ne se passe,
|
|
21
|
+
* la session du produit reste celle d'avant.
|
|
22
|
+
*/
|
|
23
|
+
export declare function startBackgroundRefresh(doc?: Document): HTMLIFrameElement | null;
|
|
24
|
+
/**
|
|
25
|
+
* À monter une fois par document dans la coque (`AppShell` le fait) : relecture en arrière-plan
|
|
26
|
+
* après un passage depuis la barre, et préchargement au survol des autres produits. Le cadre vit
|
|
27
|
+
* hors de l'arbre React : un démontage ne l'interrompt pas, il se retire seul.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useProductSwitch(enabled?: boolean): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
/*
|
|
3
|
+
* Passage d'un produit à l'autre (ADR 0016) : la barre des produits pointe vers
|
|
4
|
+
* `/auth/hgdc?refresh=background&next=/`. Avec une session du produit ouverte, l'entrée de
|
|
5
|
+
* `@hgdc/auth` va droit à la page et pose un cookie « relire les organisations » ; la coque le lit
|
|
6
|
+
* ici et relit les organisations dans un cadre caché, sans faire attendre la personne.
|
|
7
|
+
*
|
|
8
|
+
* Les trois noms ci-dessous sont ceux de `@hgdc/auth` (`HGDC_ORG_SYNC_COOKIE`,
|
|
9
|
+
* `HGDC_BACKGROUND_REFRESH_PATH`, `HGDC_ENTRY_REFRESH_BACKGROUND`) : `@hgdc/ui` n'en dépend pas,
|
|
10
|
+
* un test de `@hgdc/auth` vérifie qu'ils restent égaux.
|
|
11
|
+
*/
|
|
12
|
+
/** Cookie posé par l'entrée : relire les organisations en arrière-plan. */
|
|
13
|
+
export const ORG_SYNC_COOKIE = "hgdc-org-sync";
|
|
14
|
+
/** Cadre caché de la relecture : l'entrée repasse par Account, retour sur une réponse vide. */
|
|
15
|
+
export const BACKGROUND_REFRESH_PATH = "/auth/hgdc?refresh=1&frame=1";
|
|
16
|
+
/** Abandon du cadre s'il ne s'est pas chargé : Account injoignable, réseau coupé. */
|
|
17
|
+
export const BACKGROUND_REFRESH_TIMEOUT_MS = 20_000;
|
|
18
|
+
/**
|
|
19
|
+
* Liens préchargés au survol : l'entrée d'un autre produit en `refresh=background`. Elle ne fait
|
|
20
|
+
* que lire la session et rediriger ; sans session, elle répond 503 à un préchargement (rien n'est
|
|
21
|
+
* créé), et le clic repart normalement. Jamais de `prerender` : la page d'arrivée lit un cookie et
|
|
22
|
+
* ouvre un cadre, ce qu'un prérendu ferait pour un simple survol.
|
|
23
|
+
*/
|
|
24
|
+
export const PRODUCT_PREFETCH_RULES = JSON.stringify({
|
|
25
|
+
prefetch: [
|
|
26
|
+
{
|
|
27
|
+
source: "document",
|
|
28
|
+
where: { href_matches: "https://*.hgdc.ch/auth/hgdc?refresh=background*" },
|
|
29
|
+
eagerness: "moderate",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
/** Lit et efface le cookie de relecture. Vrai s'il était posé. */
|
|
34
|
+
export function takeOrgSyncCookie(doc = document) {
|
|
35
|
+
const present = doc.cookie.split(";").some((part) => part.trim() === `${ORG_SYNC_COOKIE}=1`);
|
|
36
|
+
if (present) {
|
|
37
|
+
const secure = doc.location.protocol === "https:" ? "; Secure" : "";
|
|
38
|
+
doc.cookie = `${ORG_SYNC_COOKIE}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
|
|
39
|
+
}
|
|
40
|
+
return present;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Relit les organisations dans un cadre caché, une fois, si l'entrée l'a demandé. Le cadre n'a
|
|
44
|
+
* ni script ni formulaire (`sandbox`), il suit seulement les redirections d'Account ; la nouvelle
|
|
45
|
+
* session du produit recopie les organisations, et les requêtes Convex abonnées se mettent à jour.
|
|
46
|
+
* Si la session d'Account a expiré, Account refuse d'être affiché dans un cadre : rien ne se passe,
|
|
47
|
+
* la session du produit reste celle d'avant.
|
|
48
|
+
*/
|
|
49
|
+
export function startBackgroundRefresh(doc = document) {
|
|
50
|
+
if (!takeOrgSyncCookie(doc))
|
|
51
|
+
return null;
|
|
52
|
+
const frame = doc.createElement("iframe");
|
|
53
|
+
frame.hidden = true;
|
|
54
|
+
frame.tabIndex = -1;
|
|
55
|
+
frame.title = "";
|
|
56
|
+
frame.setAttribute("aria-hidden", "true");
|
|
57
|
+
// Même origine pour les cookies du produit ; ni script, ni formulaire, ni navigation de la page.
|
|
58
|
+
frame.setAttribute("sandbox", "allow-same-origin");
|
|
59
|
+
frame.referrerPolicy = "no-referrer";
|
|
60
|
+
const done = () => {
|
|
61
|
+
window.clearTimeout(timer);
|
|
62
|
+
frame.remove();
|
|
63
|
+
};
|
|
64
|
+
const timer = window.setTimeout(done, BACKGROUND_REFRESH_TIMEOUT_MS);
|
|
65
|
+
frame.addEventListener("load", done, { once: true });
|
|
66
|
+
frame.src = BACKGROUND_REFRESH_PATH;
|
|
67
|
+
doc.body.append(frame);
|
|
68
|
+
return frame;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* À monter une fois par document dans la coque (`AppShell` le fait) : relecture en arrière-plan
|
|
72
|
+
* après un passage depuis la barre, et préchargement au survol des autres produits. Le cadre vit
|
|
73
|
+
* hors de l'arbre React : un démontage ne l'interrompt pas, il se retire seul.
|
|
74
|
+
*/
|
|
75
|
+
export function useProductSwitch(enabled = true) {
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (enabled)
|
|
78
|
+
startBackgroundRefresh();
|
|
79
|
+
}, [enabled]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!enabled)
|
|
82
|
+
return;
|
|
83
|
+
if (typeof HTMLScriptElement === "undefined" || !HTMLScriptElement.supports?.("speculationrules"))
|
|
84
|
+
return;
|
|
85
|
+
// Créé par le DOM, pas par React : un script inséré par `innerHTML` n'est jamais lu.
|
|
86
|
+
const script = document.createElement("script");
|
|
87
|
+
script.type = "speculationrules";
|
|
88
|
+
script.textContent = PRODUCT_PREFETCH_RULES;
|
|
89
|
+
document.head.append(script);
|
|
90
|
+
return () => script.remove();
|
|
91
|
+
}, [enabled]);
|
|
92
|
+
}
|
package/dist/products.d.ts
CHANGED
|
@@ -16,9 +16,10 @@ export type HgdcCatalogProduct = {
|
|
|
16
16
|
href: string;
|
|
17
17
|
/**
|
|
18
18
|
* Entrée dans l'app avec la session HGDC Account (`PRODUCT_ENTRY_PATH`), sans page d'accueil
|
|
19
|
-
* ni bouton de connexion : `https://<mot>.hgdc.ch/auth/hgdc?refresh=
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* ni bouton de connexion : `https://<mot>.hgdc.ch/auth/hgdc?refresh=background&next=/`. Avec une
|
|
20
|
+
* session du produit ouverte, on arrive tout de suite et les organisations se relisent en
|
|
21
|
+
* arrière-plan (une organisation rejointe dans Account apparaît sans se reconnecter, ADR 0013,
|
|
22
|
+
* 0016). Pour Account, sa racine.
|
|
22
23
|
*/
|
|
23
24
|
entryHref: string;
|
|
24
25
|
/** Une phrase, au tutoiement. */
|
|
@@ -40,8 +41,11 @@ export type HgdcCatalogProduct = {
|
|
|
40
41
|
* quand cette session existe.
|
|
41
42
|
*/
|
|
42
43
|
export declare const PRODUCT_ENTRY_PATH = "/auth/hgdc";
|
|
43
|
-
/**
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Requête de l'entrée depuis un autre produit : arriver sur l'accueil, relire les organisations en
|
|
46
|
+
* arrière-plan (`refresh=background`, ADR 0016).
|
|
47
|
+
*/
|
|
48
|
+
export declare const PRODUCT_ENTRY_REFRESH_QUERY = "refresh=background&next=/";
|
|
45
49
|
export declare const HGDC_ACCOUNT: HgdcCatalogProduct;
|
|
46
50
|
/** Les produits de la famille, dans leur ordre d'affichage. Account n'en fait pas partie (`HGDC_ACCOUNT`). */
|
|
47
51
|
export declare const HGDC_PRODUCTS: ReadonlyArray<HgdcCatalogProduct>;
|
|
@@ -83,7 +87,7 @@ export type ProductRailEntry = {
|
|
|
83
87
|
/**
|
|
84
88
|
* Les entrées de la barre des produits : les produits ouverts du catalogue et Account, affiché en
|
|
85
89
|
* tête (le compte commun, pas un produit). Chaque produit pointe vers son entrée
|
|
86
|
-
* `/auth/hgdc?refresh=
|
|
90
|
+
* `/auth/hgdc?refresh=background&next=/` (ADR 0010, 0013, 0016), Account vers sa racine (`accountHref` en
|
|
87
91
|
* développement). Le produit courant pointe vers `/`, sans détour par l'entrée.
|
|
88
92
|
*/
|
|
89
93
|
export declare function productRailEntries(options?: {
|
package/dist/products.js
CHANGED
|
@@ -18,8 +18,11 @@ export const PRODUCT_STAGE_LABEL = {
|
|
|
18
18
|
* quand cette session existe.
|
|
19
19
|
*/
|
|
20
20
|
export const PRODUCT_ENTRY_PATH = "/auth/hgdc";
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Requête de l'entrée depuis un autre produit : arriver sur l'accueil, relire les organisations en
|
|
23
|
+
* arrière-plan (`refresh=background`, ADR 0016).
|
|
24
|
+
*/
|
|
25
|
+
export const PRODUCT_ENTRY_REFRESH_QUERY = "refresh=background&next=/";
|
|
23
26
|
function product(word, pitch, icon, stage) {
|
|
24
27
|
const badge = word.toLowerCase();
|
|
25
28
|
if (!isFamilyProduct(badge))
|
|
@@ -77,7 +80,7 @@ export function productMenuEntries(options = {}) {
|
|
|
77
80
|
/**
|
|
78
81
|
* Les entrées de la barre des produits : les produits ouverts du catalogue et Account, affiché en
|
|
79
82
|
* tête (le compte commun, pas un produit). Chaque produit pointe vers son entrée
|
|
80
|
-
* `/auth/hgdc?refresh=
|
|
83
|
+
* `/auth/hgdc?refresh=background&next=/` (ADR 0010, 0013, 0016), Account vers sa racine (`accountHref` en
|
|
81
84
|
* développement). Le produit courant pointe vers `/`, sans détour par l'entrée.
|
|
82
85
|
*/
|
|
83
86
|
export function productRailEntries(options = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hgdc/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Interface de la H family : 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",
|