@ghub/identity-chrome 0.1.0 → 0.3.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.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The host contract: what the two elements read from the page they sit in.
3
+ *
4
+ * **The published `@ghub/tokens` names, and no family of their own.** The
5
+ * design drew a `--gc-*` set; the operator chose (2026-10-17) to read the
6
+ * names a host on `@ghub/tokens` already declares on its root, so such a host
7
+ * is styled with no setup at all. Custom properties inherit into a shadow
8
+ * root, which is the one thing that crosses it. A host on something else sets
9
+ * the same names.
10
+ *
11
+ * | Read | From | Notes |
12
+ * | --- | --- | --- |
13
+ * | the popup's surface | `--ga-menu`, else `--gm-overlay` | Aurora's frosted menu where the host is Aurora |
14
+ * | its frost | `--ga-frost-menu`, else none | |
15
+ * | its shadow | `--ga-shadow-menu`, else `--gm-shadow-overlay` | |
16
+ * | the solid surface | `--gm-overlay` | reduced transparency, the badge, the fade |
17
+ * | the account popup's tinted ground | `--ga-menu`, else `--gm-sunken` | the operator's pick (2026-10-17): exact to sheet 32 on a light host; on a dark one a tray a step below the page |
18
+ * | its solid ground | `--gm-sunken` | reduced transparency |
19
+ * | the account popup's panels | `--ga-track`, else `--gm-overlay` | Aurora's six per cent ink film, the white wash sheet 32 draws on glass |
20
+ * | everything else | `--gm-plate`, `--gm-ink`, `--gm-ink-2`, `--gm-ink-3`, `--gm-hairline`, `--gm-control`, `--gm-hover`, `--gm-active`, `--gm-selected`, `--gm-accent`, `--gm-accent-text`, `--gm-ring`, `--gm-crit-ink` | |
21
+ * | the face | `--font-sans` | |
22
+ *
23
+ * A host that declares none of them gets modern light, or modern dark under
24
+ * `prefers-color-scheme: dark`, the values sheet 31 draws its light and dark
25
+ * hosts with. No second accent: the popups take the host's.
26
+ */
27
+ /** The `:host` rules both elements start their sheet with. */
28
+ export declare const CONTRACT_CSS: string;
29
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * The host contract: what the two elements read from the page they sit in.
4
+ *
5
+ * **The published `@ghub/tokens` names, and no family of their own.** The
6
+ * design drew a `--gc-*` set; the operator chose (2026-10-17) to read the
7
+ * names a host on `@ghub/tokens` already declares on its root, so such a host
8
+ * is styled with no setup at all. Custom properties inherit into a shadow
9
+ * root, which is the one thing that crosses it. A host on something else sets
10
+ * the same names.
11
+ *
12
+ * | Read | From | Notes |
13
+ * | --- | --- | --- |
14
+ * | the popup's surface | `--ga-menu`, else `--gm-overlay` | Aurora's frosted menu where the host is Aurora |
15
+ * | its frost | `--ga-frost-menu`, else none | |
16
+ * | its shadow | `--ga-shadow-menu`, else `--gm-shadow-overlay` | |
17
+ * | the solid surface | `--gm-overlay` | reduced transparency, the badge, the fade |
18
+ * | the account popup's tinted ground | `--ga-menu`, else `--gm-sunken` | the operator's pick (2026-10-17): exact to sheet 32 on a light host; on a dark one a tray a step below the page |
19
+ * | its solid ground | `--gm-sunken` | reduced transparency |
20
+ * | the account popup's panels | `--ga-track`, else `--gm-overlay` | Aurora's six per cent ink film, the white wash sheet 32 draws on glass |
21
+ * | everything else | `--gm-plate`, `--gm-ink`, `--gm-ink-2`, `--gm-ink-3`, `--gm-hairline`, `--gm-control`, `--gm-hover`, `--gm-active`, `--gm-selected`, `--gm-accent`, `--gm-accent-text`, `--gm-ring`, `--gm-crit-ink` | |
22
+ * | the face | `--font-sans` | |
23
+ *
24
+ * A host that declares none of them gets modern light, or modern dark under
25
+ * `prefers-color-scheme: dark`, the values sheet 31 draws its light and dark
26
+ * hosts with. No second accent: the popups take the host's.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.CONTRACT_CSS = void 0;
30
+ const LIGHT = {
31
+ overlay: "#ffffff",
32
+ sunken: "oklch(0.955 0.004 258)",
33
+ plate: "oklch(0.97 0.003 258)",
34
+ ink: "oklch(0.19 0.008 258)",
35
+ ink2: "oklch(0.42 0.012 258)",
36
+ ink3: "oklch(0.53 0.014 258)",
37
+ hairline: "oklch(0 0 0 / 0.09)",
38
+ control: "oklch(0 0 0 / 0.48)",
39
+ hover: "oklch(0 0 0 / 0.045)",
40
+ active: "oklch(0 0 0 / 0.08)",
41
+ selected: "oklch(0.52 0.11 205 / 0.1)",
42
+ accent: "oklch(0.52 0.11 205)",
43
+ accentText: "oklch(0.5 0.11 205)",
44
+ ring: "oklch(0.52 0.11 205)",
45
+ critInk: "oklch(0.5 0.19 22)",
46
+ shadow: "0 0 0 1px oklch(0 0 0 / 0.07), 0 2px 4px oklch(0 0 0 / 0.04), 0 18px 40px -10px oklch(0 0 0 / 0.2)",
47
+ };
48
+ const DARK = {
49
+ overlay: "oklch(0.265 0.01 258)",
50
+ sunken: "oklch(0.155 0.007 258)",
51
+ plate: "oklch(0.235 0.009 258)",
52
+ ink: "oklch(0.975 0.003 255)",
53
+ ink2: "oklch(0.8 0.009 255)",
54
+ ink3: "oklch(0.645 0.012 258)",
55
+ hairline: "oklch(1 0 0 / 0.11)",
56
+ control: "oklch(1 0 0 / 0.36)",
57
+ hover: "oklch(1 0 0 / 0.06)",
58
+ active: "oklch(1 0 0 / 0.1)",
59
+ selected: "oklch(0.72 0.12 200 / 0.14)",
60
+ accent: "oklch(0.72 0.12 200)",
61
+ accentText: "oklch(0.78 0.11 200)",
62
+ ring: "oklch(0.82 0.11 196)",
63
+ critInk: "oklch(0.72 0.18 25)",
64
+ shadow: "inset 0 1px 0 oklch(1 0 0 / 0.08), 0 0 0 1px oklch(1 0 0 / 0.06), 0 24px 48px -12px oklch(0 0 0 / 0.6)",
65
+ };
66
+ /** The element's private names, each read from the host's published one with a fallback. */
67
+ function declarations(f) {
68
+ return `
69
+ --_overlay: var(--ga-menu, var(--gm-overlay, ${f.overlay}));
70
+ --_overlay-solid: var(--gm-overlay, ${f.overlay});
71
+ --_backdrop: var(--ga-frost-menu, none);
72
+ --_shadow: var(--ga-shadow-menu, var(--gm-shadow-overlay, ${f.shadow}));
73
+ --_tint: var(--ga-menu, var(--gm-sunken, ${f.sunken}));
74
+ --_tint-solid: var(--gm-sunken, ${f.sunken});
75
+ --_panel: var(--ga-track, var(--gm-overlay, ${f.overlay}));
76
+ --_plate: var(--gm-plate, ${f.plate});
77
+ --_ink: var(--gm-ink, ${f.ink});
78
+ --_ink-2: var(--gm-ink-2, ${f.ink2});
79
+ --_ink-3: var(--gm-ink-3, ${f.ink3});
80
+ --_hairline: var(--gm-hairline, ${f.hairline});
81
+ --_control: var(--gm-control, ${f.control});
82
+ --_hover: var(--gm-hover, ${f.hover});
83
+ --_active: var(--gm-active, ${f.active});
84
+ --_selected: var(--gm-selected, ${f.selected});
85
+ --_accent: var(--gm-accent, ${f.accent});
86
+ --_accent-text: var(--gm-accent-text, ${f.accentText});
87
+ --_ring: var(--gm-ring, ${f.ring});
88
+ --_crit-ink: var(--gm-crit-ink, ${f.critInk});
89
+ --_font: var(--font-sans, Geist, ui-sans-serif, system-ui, sans-serif);`;
90
+ }
91
+ /** The `:host` rules both elements start their sheet with. */
92
+ exports.CONTRACT_CSS = `
93
+ :host {${declarations(LIGHT)}
94
+ position: relative;
95
+ display: inline-block;
96
+ font-family: var(--_font);
97
+ -webkit-font-smoothing: antialiased;
98
+ }
99
+ @media (prefers-color-scheme: dark) {
100
+ :host {${declarations(DARK)}
101
+ }
102
+ }
103
+ :host([hidden]) { display: none; }
104
+ .sr-only {
105
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
106
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
107
+ }
108
+ `;
109
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The few DOM helpers both elements build their shadow trees with. Nothing here
3
+ * runs until an element is constructed, so importing it on a server is safe.
4
+ */
5
+ export declare const SVG_NS = "http://www.w3.org/2000/svg";
6
+ export declare function element<K extends keyof HTMLElementTagNameMap>(tag: K, attributes?: Record<string, string>, text?: string): HTMLElementTagNameMap[K];
7
+ /** An icon of one stroked or filled path, hidden from assistive technology. */
8
+ export declare function pathIcon(size: number, viewBox: string, d: string, stroke?: {
9
+ width: number;
10
+ } | null): SVGSVGElement;
11
+ /** The close cross both popups draw. */
12
+ export declare function crossMark(): SVGSVGElement;
13
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * The few DOM helpers both elements build their shadow trees with. Nothing here
4
+ * runs until an element is constructed, so importing it on a server is safe.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SVG_NS = void 0;
8
+ exports.element = element;
9
+ exports.pathIcon = pathIcon;
10
+ exports.crossMark = crossMark;
11
+ exports.SVG_NS = "http://www.w3.org/2000/svg";
12
+ function element(tag, attributes = {}, text) {
13
+ const made = document.createElement(tag);
14
+ for (const [name, value] of Object.entries(attributes))
15
+ made.setAttribute(name, value);
16
+ if (text !== undefined)
17
+ made.textContent = text;
18
+ return made;
19
+ }
20
+ /** An icon of one stroked or filled path, hidden from assistive technology. */
21
+ function pathIcon(size, viewBox, d, stroke = null) {
22
+ const svg = document.createElementNS(exports.SVG_NS, "svg");
23
+ svg.setAttribute("width", String(size));
24
+ svg.setAttribute("height", String(size));
25
+ svg.setAttribute("viewBox", viewBox);
26
+ svg.setAttribute("aria-hidden", "true");
27
+ const path = document.createElementNS(exports.SVG_NS, "path");
28
+ path.setAttribute("d", d);
29
+ if (stroke === null) {
30
+ path.setAttribute("fill", "currentColor");
31
+ }
32
+ else {
33
+ path.setAttribute("fill", "none");
34
+ path.setAttribute("stroke", "currentColor");
35
+ path.setAttribute("stroke-width", String(stroke.width));
36
+ path.setAttribute("stroke-linecap", "round");
37
+ }
38
+ svg.append(path);
39
+ return svg;
40
+ }
41
+ /** The close cross both popups draw. */
42
+ function crossMark() {
43
+ return pathIcon(14, "0 0 14 14", "M2 2l10 10M12 2L2 12", { width: 1.6 });
44
+ }
45
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The two header controls as custom elements, and the one call that defines both.
3
+ *
4
+ * Safe to import on a server: nothing touches the DOM until a define call runs
5
+ * in a browser.
6
+ */
7
+ import type { ChromeCore } from "../core.js";
8
+ import { type AccountItem, type AccountLabels, type AccountOrganisation, type AccountPerson, type SignOutHandler, type SwitchHandler } from "./account.js";
9
+ import { type AppsLabels } from "./apps.js";
10
+ /** What `<gp-apps>` offers its page. */
11
+ export interface AppsElement extends HTMLElement {
12
+ get core(): ChromeCore | null;
13
+ set core(next: ChromeCore | null);
14
+ get labels(): AppsLabels;
15
+ set labels(next: Partial<AppsLabels>);
16
+ readonly open: boolean;
17
+ show(): void;
18
+ close(returnFocus: boolean): void;
19
+ }
20
+ /** What `<gp-account>` offers its page. */
21
+ export interface AccountElement extends HTMLElement {
22
+ get core(): ChromeCore | null;
23
+ set core(next: ChromeCore | null);
24
+ get person(): AccountPerson | null;
25
+ set person(next: AccountPerson | null);
26
+ get organisations(): AccountOrganisation[];
27
+ set organisations(next: AccountOrganisation[] | null);
28
+ product: string | null;
29
+ viaIdentity: boolean;
30
+ onSignOut: SignOutHandler | null;
31
+ onSwitch: SwitchHandler | null;
32
+ get items(): AccountItem[];
33
+ set items(next: AccountItem[] | null);
34
+ get labels(): AccountLabels;
35
+ set labels(next: Partial<AccountLabels>);
36
+ readonly open: boolean;
37
+ show(): void;
38
+ close(returnFocus: boolean): void;
39
+ }
40
+ declare global {
41
+ interface HTMLElementTagNameMap {
42
+ "gp-apps": AppsElement;
43
+ "gp-account": AccountElement;
44
+ }
45
+ }
46
+ /** Defines `<gp-apps>` and `<gp-account>`, each once. */
47
+ export declare function defineChromeElements(): void;
48
+ export { ACCOUNT_LABELS, createAccountElementClass, defineAccountElement, iconOf, type AccountItem, type AccountLabels, type AccountOrganisation, type AccountPerson, type SignOutHandler, type SwitchHandler, } from "./account.js";
49
+ export { APPS_LABELS, createAppsElementClass, defineAppsElement, type AppsLabels } from "./apps.js";
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * The two header controls as custom elements, and the one call that defines both.
4
+ *
5
+ * Safe to import on a server: nothing touches the DOM until a define call runs
6
+ * in a browser.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.defineAppsElement = exports.createAppsElementClass = exports.APPS_LABELS = exports.iconOf = exports.defineAccountElement = exports.createAccountElementClass = exports.ACCOUNT_LABELS = void 0;
10
+ exports.defineChromeElements = defineChromeElements;
11
+ const account_js_1 = require("./account.js");
12
+ const apps_js_1 = require("./apps.js");
13
+ /** Defines `<gp-apps>` and `<gp-account>`, each once. */
14
+ function defineChromeElements() {
15
+ (0, apps_js_1.defineAppsElement)();
16
+ (0, account_js_1.defineAccountElement)();
17
+ }
18
+ var account_js_2 = require("./account.js");
19
+ Object.defineProperty(exports, "ACCOUNT_LABELS", { enumerable: true, get: function () { return account_js_2.ACCOUNT_LABELS; } });
20
+ Object.defineProperty(exports, "createAccountElementClass", { enumerable: true, get: function () { return account_js_2.createAccountElementClass; } });
21
+ Object.defineProperty(exports, "defineAccountElement", { enumerable: true, get: function () { return account_js_2.defineAccountElement; } });
22
+ Object.defineProperty(exports, "iconOf", { enumerable: true, get: function () { return account_js_2.iconOf; } });
23
+ var apps_js_2 = require("./apps.js");
24
+ Object.defineProperty(exports, "APPS_LABELS", { enumerable: true, get: function () { return apps_js_2.APPS_LABELS; } });
25
+ Object.defineProperty(exports, "createAppsElementClass", { enumerable: true, get: function () { return apps_js_2.createAppsElementClass; } });
26
+ Object.defineProperty(exports, "defineAppsElement", { enumerable: true, get: function () { return apps_js_2.defineAppsElement; } });
27
+ //# sourceMappingURL=index.js.map
package/dist/index.d.ts CHANGED
@@ -2,11 +2,15 @@
2
2
  * @ghub/identity-chrome
3
3
  *
4
4
  * The shared header controls of the Gelhaus Solutions products. This entry is the
5
- * browser half: state, cache and the last list held for an outage. The server
6
- * half, with the token, is `@ghub/identity-chrome/server`.
5
+ * browser half: state, cache and the last list held for an outage, and the two
6
+ * controls as custom elements (`<gp-apps>`, `<gp-account>`). The server half,
7
+ * with the token, is `@ghub/identity-chrome/server`; React wrappers are
8
+ * `@ghub/identity-chrome/react`.
7
9
  */
8
10
  export { CACHE_KEY, CACHE_MAX_AGE_MS, DEFAULT_TIMEOUT_MS, createChromeCore, type ChromeCore, type ChromeCoreOptions, type ChromeState, type ChromeStatus, type StorageLike, } from "./core.js";
9
11
  export { catalogueMarkUrl, createCatalogue, type Catalogue, type CatalogueOptions, } from "./catalogue.js";
10
- export type { CatalogueEntry, ChromeLegal, ChromePerson, ChromePublicView, ChromeTile, ChromeView, } from "./types.js";
12
+ export { ACCOUNT_LABELS, APPS_LABELS, createAccountElementClass, createAppsElementClass, defineAccountElement, defineAppsElement, defineChromeElements, type AccountElement, type AccountItem, type AccountLabels, type AccountOrganisation, type AccountPerson, type AppsElement, type AppsLabels, type SignOutHandler, type SwitchHandler, } from "./elements/index.js";
13
+ export { AVATAR_INK, AVATAR_TINTS, avatarTint } from "./tint.js";
14
+ export type { CatalogueEntry, ChromeIdentityLinks, ChromeLegal, ChromePerson, ChromePublicView, ChromeTile, ChromeView, } from "./types.js";
11
15
  export { safeUrl } from "./validate.js";
12
16
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -3,11 +3,13 @@
3
3
  * @ghub/identity-chrome
4
4
  *
5
5
  * The shared header controls of the Gelhaus Solutions products. This entry is the
6
- * browser half: state, cache and the last list held for an outage. The server
7
- * half, with the token, is `@ghub/identity-chrome/server`.
6
+ * browser half: state, cache and the last list held for an outage, and the two
7
+ * controls as custom elements (`<gp-apps>`, `<gp-account>`). The server half,
8
+ * with the token, is `@ghub/identity-chrome/server`; React wrappers are
9
+ * `@ghub/identity-chrome/react`.
8
10
  */
9
11
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.safeUrl = exports.createCatalogue = exports.catalogueMarkUrl = exports.createChromeCore = exports.DEFAULT_TIMEOUT_MS = exports.CACHE_MAX_AGE_MS = exports.CACHE_KEY = void 0;
12
+ exports.safeUrl = exports.avatarTint = exports.AVATAR_TINTS = exports.AVATAR_INK = exports.defineChromeElements = exports.defineAppsElement = exports.defineAccountElement = exports.createAppsElementClass = exports.createAccountElementClass = exports.APPS_LABELS = exports.ACCOUNT_LABELS = exports.createCatalogue = exports.catalogueMarkUrl = exports.createChromeCore = exports.DEFAULT_TIMEOUT_MS = exports.CACHE_MAX_AGE_MS = exports.CACHE_KEY = void 0;
11
13
  var core_js_1 = require("./core.js");
12
14
  Object.defineProperty(exports, "CACHE_KEY", { enumerable: true, get: function () { return core_js_1.CACHE_KEY; } });
13
15
  Object.defineProperty(exports, "CACHE_MAX_AGE_MS", { enumerable: true, get: function () { return core_js_1.CACHE_MAX_AGE_MS; } });
@@ -16,6 +18,18 @@ Object.defineProperty(exports, "createChromeCore", { enumerable: true, get: func
16
18
  var catalogue_js_1 = require("./catalogue.js");
17
19
  Object.defineProperty(exports, "catalogueMarkUrl", { enumerable: true, get: function () { return catalogue_js_1.catalogueMarkUrl; } });
18
20
  Object.defineProperty(exports, "createCatalogue", { enumerable: true, get: function () { return catalogue_js_1.createCatalogue; } });
21
+ var index_js_1 = require("./elements/index.js");
22
+ Object.defineProperty(exports, "ACCOUNT_LABELS", { enumerable: true, get: function () { return index_js_1.ACCOUNT_LABELS; } });
23
+ Object.defineProperty(exports, "APPS_LABELS", { enumerable: true, get: function () { return index_js_1.APPS_LABELS; } });
24
+ Object.defineProperty(exports, "createAccountElementClass", { enumerable: true, get: function () { return index_js_1.createAccountElementClass; } });
25
+ Object.defineProperty(exports, "createAppsElementClass", { enumerable: true, get: function () { return index_js_1.createAppsElementClass; } });
26
+ Object.defineProperty(exports, "defineAccountElement", { enumerable: true, get: function () { return index_js_1.defineAccountElement; } });
27
+ Object.defineProperty(exports, "defineAppsElement", { enumerable: true, get: function () { return index_js_1.defineAppsElement; } });
28
+ Object.defineProperty(exports, "defineChromeElements", { enumerable: true, get: function () { return index_js_1.defineChromeElements; } });
29
+ var tint_js_1 = require("./tint.js");
30
+ Object.defineProperty(exports, "AVATAR_INK", { enumerable: true, get: function () { return tint_js_1.AVATAR_INK; } });
31
+ Object.defineProperty(exports, "AVATAR_TINTS", { enumerable: true, get: function () { return tint_js_1.AVATAR_TINTS; } });
32
+ Object.defineProperty(exports, "avatarTint", { enumerable: true, get: function () { return tint_js_1.avatarTint; } });
19
33
  var validate_js_1 = require("./validate.js");
20
34
  Object.defineProperty(exports, "safeUrl", { enumerable: true, get: function () { return validate_js_1.safeUrl; } });
21
35
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @ghub/identity-chrome/react
3
+ *
4
+ * The two header controls for a React page: each renders its custom element and
5
+ * hands it what it needs as properties after mounting, because a core, a person
6
+ * and a callback are objects and an attribute can carry only a string. The
7
+ * elements do the drawing, the keyboard and the accessibility; these wrappers
8
+ * only connect them.
9
+ */
10
+ import { type ReactElement } from "react";
11
+ import type { ChromeCore } from "./core.js";
12
+ import type { AccountItem, AccountLabels, AccountOrganisation, AccountPerson, AppsLabels, SignOutHandler, SwitchHandler } from "./elements/index.js";
13
+ /** Which edge of the trigger the popup lines up with. The end, by default. */
14
+ type Align = "start" | "end";
15
+ /** Whether the popup opens below the trigger (a top bar) or above it (the bottom of a sidebar). Below, by default. */
16
+ type Placement = "bottom" | "top";
17
+ export interface GpAppsProps {
18
+ core: ChromeCore | null;
19
+ labels?: Partial<AppsLabels>;
20
+ align?: Align;
21
+ placement?: Placement;
22
+ className?: string;
23
+ }
24
+ /** `<gp-apps>`, the apps picker. */
25
+ export declare function GpApps({ core, labels, align, placement, className }: GpAppsProps): ReactElement;
26
+ export interface GpAccountProps {
27
+ core: ChromeCore | null;
28
+ /** Who is signed in, from the product's own session. Nothing renders without one. */
29
+ person: AccountPerson | null;
30
+ organisations?: AccountOrganisation[] | null;
31
+ /** The product's own name, from its catalogue entry. */
32
+ product?: string | null;
33
+ /** The product signs in through Identity, so signing out ends both. */
34
+ viaIdentity?: boolean;
35
+ onSignOut?: SignOutHandler | null;
36
+ onSwitch?: SwitchHandler | null;
37
+ /** The product's own items, after the organisations. */
38
+ items?: AccountItem[] | null;
39
+ labels?: Partial<AccountLabels>;
40
+ /** `row` for the bottom of a sidebar: the picture with the name over the address, full width. */
41
+ layout?: "button" | "row";
42
+ align?: Align;
43
+ placement?: Placement;
44
+ className?: string;
45
+ }
46
+ /** `<gp-account>`, the account popup. */
47
+ export declare function GpAccount({ core, person, organisations, product, viaIdentity, onSignOut, onSwitch, items, labels, layout, align, placement, className, }: GpAccountProps): ReactElement;
48
+ export {};
49
+ //# sourceMappingURL=react.d.ts.map
package/dist/react.js ADDED
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.GpApps = GpApps;
5
+ exports.GpAccount = GpAccount;
6
+ /**
7
+ * @ghub/identity-chrome/react
8
+ *
9
+ * The two header controls for a React page: each renders its custom element and
10
+ * hands it what it needs as properties after mounting, because a core, a person
11
+ * and a callback are objects and an attribute can carry only a string. The
12
+ * elements do the drawing, the keyboard and the accessibility; these wrappers
13
+ * only connect them.
14
+ */
15
+ const react_1 = require("react");
16
+ const index_js_1 = require("./elements/index.js");
17
+ /** `<gp-apps>`, the apps picker. */
18
+ function GpApps({ core, labels, align, placement, className }) {
19
+ const ref = (0, react_1.useRef)(null);
20
+ (0, react_1.useEffect)(() => {
21
+ (0, index_js_1.defineChromeElements)();
22
+ }, []);
23
+ (0, react_1.useEffect)(() => {
24
+ if (ref.current !== null)
25
+ ref.current.core = core;
26
+ }, [core]);
27
+ (0, react_1.useEffect)(() => {
28
+ if (ref.current !== null && labels !== undefined)
29
+ ref.current.labels = labels;
30
+ }, [labels]);
31
+ return (0, react_1.createElement)("gp-apps", { ref, className, align, placement });
32
+ }
33
+ /** `<gp-account>`, the account popup. */
34
+ function GpAccount({ core, person, organisations = null, product = null, viaIdentity = false, onSignOut = null, onSwitch = null, items = null, labels, layout, align, placement, className, }) {
35
+ const ref = (0, react_1.useRef)(null);
36
+ (0, react_1.useEffect)(() => {
37
+ (0, index_js_1.defineChromeElements)();
38
+ }, []);
39
+ (0, react_1.useEffect)(() => {
40
+ if (ref.current !== null)
41
+ ref.current.core = core;
42
+ }, [core]);
43
+ (0, react_1.useEffect)(() => {
44
+ if (ref.current !== null)
45
+ ref.current.person = person;
46
+ }, [person]);
47
+ (0, react_1.useEffect)(() => {
48
+ if (ref.current !== null)
49
+ ref.current.organisations = organisations;
50
+ }, [organisations]);
51
+ (0, react_1.useEffect)(() => {
52
+ if (ref.current !== null)
53
+ ref.current.product = product;
54
+ }, [product]);
55
+ (0, react_1.useEffect)(() => {
56
+ if (ref.current !== null)
57
+ ref.current.viaIdentity = viaIdentity;
58
+ }, [viaIdentity]);
59
+ (0, react_1.useEffect)(() => {
60
+ if (ref.current !== null)
61
+ ref.current.onSignOut = onSignOut;
62
+ }, [onSignOut]);
63
+ (0, react_1.useEffect)(() => {
64
+ if (ref.current !== null)
65
+ ref.current.onSwitch = onSwitch;
66
+ }, [onSwitch]);
67
+ (0, react_1.useEffect)(() => {
68
+ if (ref.current !== null)
69
+ ref.current.items = items;
70
+ }, [items]);
71
+ (0, react_1.useEffect)(() => {
72
+ if (ref.current !== null && labels !== undefined)
73
+ ref.current.labels = labels;
74
+ }, [labels]);
75
+ return (0, react_1.createElement)("gp-account", { ref, className, layout, align, placement });
76
+ }
77
+ //# sourceMappingURL=react.js.map
package/dist/tint.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The coloured initial's seven tints (sheet 32), and how one is picked.
3
+ *
4
+ * Tints of the seven product mark hues at L 0.84, C 0.07, with the initial in
5
+ * mark ink at 600: ink on tint measures 9.8 to 11.2, so the initial passes 4.5
6
+ * at any size on every host.
7
+ *
8
+ * **Picked by a stable id, never by the name**, so a person keeps their colour
9
+ * when they change their name and two people of the same name do not share one.
10
+ * Identity picks it from the Identity's id and sends only the result; a product
11
+ * hashes its own user id the same way for somebody with no Identity. Identity
12
+ * keeps a copy of `avatarTint` in step by hand, because this package is public
13
+ * and Identity does not depend on it: the vectors in `tint.test.ts` are the
14
+ * contract between the two.
15
+ */
16
+ /** Tide, Harbour, Iris, Rose, Ember, Amber, Moss: the order the index counts in. */
17
+ export declare const AVATAR_TINTS: readonly ["oklch(0.84 0.06 200)", "oklch(0.84 0.06 258)", "oklch(0.84 0.07 296)", "oklch(0.85 0.07 355)", "oklch(0.85 0.07 33)", "oklch(0.87 0.08 70)", "oklch(0.85 0.07 148)"];
18
+ /** Mark ink: the initial on every tint. */
19
+ export declare const AVATAR_INK = "#141c1e";
20
+ /** Which of the seven tints a stable id gets: FNV-1a over its UTF-16 code units, modulo seven. */
21
+ export declare function avatarTint(id: string): number;
22
+ //# sourceMappingURL=tint.d.ts.map
package/dist/tint.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * The coloured initial's seven tints (sheet 32), and how one is picked.
4
+ *
5
+ * Tints of the seven product mark hues at L 0.84, C 0.07, with the initial in
6
+ * mark ink at 600: ink on tint measures 9.8 to 11.2, so the initial passes 4.5
7
+ * at any size on every host.
8
+ *
9
+ * **Picked by a stable id, never by the name**, so a person keeps their colour
10
+ * when they change their name and two people of the same name do not share one.
11
+ * Identity picks it from the Identity's id and sends only the result; a product
12
+ * hashes its own user id the same way for somebody with no Identity. Identity
13
+ * keeps a copy of `avatarTint` in step by hand, because this package is public
14
+ * and Identity does not depend on it: the vectors in `tint.test.ts` are the
15
+ * contract between the two.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.AVATAR_INK = exports.AVATAR_TINTS = void 0;
19
+ exports.avatarTint = avatarTint;
20
+ /** Tide, Harbour, Iris, Rose, Ember, Amber, Moss: the order the index counts in. */
21
+ exports.AVATAR_TINTS = [
22
+ "oklch(0.84 0.06 200)",
23
+ "oklch(0.84 0.06 258)",
24
+ "oklch(0.84 0.07 296)",
25
+ "oklch(0.85 0.07 355)",
26
+ "oklch(0.85 0.07 33)",
27
+ "oklch(0.87 0.08 70)",
28
+ "oklch(0.85 0.07 148)",
29
+ ];
30
+ /** Mark ink: the initial on every tint. */
31
+ exports.AVATAR_INK = "#141c1e";
32
+ /** Which of the seven tints a stable id gets: FNV-1a over its UTF-16 code units, modulo seven. */
33
+ function avatarTint(id) {
34
+ let hash = 0x811c9dc5;
35
+ for (let i = 0; i < id.length; i += 1) {
36
+ hash ^= id.charCodeAt(i);
37
+ hash = Math.imul(hash, 0x01000193);
38
+ }
39
+ return (hash >>> 0) % exports.AVATAR_TINTS.length;
40
+ }
41
+ //# sourceMappingURL=tint.js.map
package/dist/types.d.ts CHANGED
@@ -19,6 +19,28 @@ export interface ChromeTile {
19
19
  export interface ChromePerson {
20
20
  hasIdentity: boolean;
21
21
  avatarUrl: string | null;
22
+ /**
23
+ * Which of the seven avatar tints the Identity's id picks (0 to 6, `avatarTint`),
24
+ * so the coloured initial is the same in every product. Null without an Identity,
25
+ * and from an Identity too old to send it.
26
+ */
27
+ tint: number | null;
28
+ }
29
+ /**
30
+ * Where the account popup's own links go on Identity: the pill and the pencil.
31
+ * Identity sends them, like the footer, so its address can move without a
32
+ * release here. Null when they were not sent or any is not a web address, and
33
+ * then neither the pill nor the pencil is drawn.
34
+ */
35
+ export interface ChromeIdentityLinks {
36
+ /** "Manage your Identity account". */
37
+ accountUrl: string;
38
+ /** The pencil on the picture: the profile screen, where the picture is set. */
39
+ pictureUrl: string;
40
+ /** "Set up your Identity": registration, saying which product sent them and never carrying an address. */
41
+ setupUrl: string;
42
+ /** Identity's own mark for the pill, at an address that always leads to the current file. */
43
+ iconUrl: string | null;
22
44
  }
23
45
  /**
24
46
  * The account popup's two footer links, the same in every product. Identity sends
@@ -29,11 +51,12 @@ export interface ChromeLegal {
29
51
  termsUrl: string;
30
52
  privacyUrl: string;
31
53
  }
32
- /** The person, the products they have, the public ones they do not, and the footer. */
54
+ /** The person, the products they have, the public ones they do not, Identity's own links and the footer. */
33
55
  export interface ChromeView {
34
56
  person: ChromePerson;
35
57
  yours: ChromeTile[];
36
58
  more: ChromeTile[];
59
+ identity: ChromeIdentityLinks | null;
37
60
  legal: ChromeLegal | null;
38
61
  }
39
62
  /** The public half alone, for painting the lower group before the personal one arrives. */
package/dist/validate.js CHANGED
@@ -69,6 +69,29 @@ function legalOf(value) {
69
69
  const privacyUrl = safeUrl(value["privacyUrl"]);
70
70
  return termsUrl === null || privacyUrl === null ? null : { termsUrl, privacyUrl };
71
71
  }
72
+ /** All three of Identity's links, or null, the same way as the footer. */
73
+ function identityOf(value) {
74
+ if (!isRecord(value))
75
+ return null;
76
+ const accountUrl = safeUrl(value["accountUrl"]);
77
+ const pictureUrl = safeUrl(value["pictureUrl"]);
78
+ const setupUrl = safeUrl(value["setupUrl"]);
79
+ if (accountUrl === null || pictureUrl === null || setupUrl === null)
80
+ return null;
81
+ const icon = value["iconUrl"];
82
+ return {
83
+ accountUrl,
84
+ pictureUrl,
85
+ setupUrl,
86
+ iconUrl: icon === null || icon === undefined ? null : safeUrl(icon),
87
+ };
88
+ }
89
+ /** One of the seven tints, or null for anything else. */
90
+ function tintOf(value) {
91
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < 7
92
+ ? value
93
+ : null;
94
+ }
72
95
  /** A view that can be drawn, or null where the answer is not one. */
73
96
  function parseView(value) {
74
97
  if (!isRecord(value) || !isRecord(value["person"]))
@@ -85,9 +108,11 @@ function parseView(value) {
85
108
  person: {
86
109
  hasIdentity: person["hasIdentity"],
87
110
  avatarUrl: avatar === null || avatar === undefined ? null : safeUrl(avatar),
111
+ tint: tintOf(person["tint"]),
88
112
  },
89
113
  yours,
90
114
  more,
115
+ identity: identityOf(value["identity"]),
91
116
  legal: legalOf(value["legal"]),
92
117
  };
93
118
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ghub/identity-chrome",
3
- "version": "0.1.0",
4
- "description": "The shared header controls of the Gelhaus Solutions products: the apps picker and the account popup. The browser half (state, cache, the last list held for an outage) and the server half (the token calls and a route handler). No dependencies.",
3
+ "version": "0.3.0",
4
+ "description": "The shared header controls of the Gelhaus Solutions products: the apps picker and the account popup, as custom elements with shadow DOM and as React wrappers. The browser half (state, cache, the last list held for an outage) and the server half (the token calls and a route handler). No dependencies.",
5
5
  "author": "Gelhaus Solutions (Enno Gelhaus) <egelhaus@ennogelhaus.de> (https://ennogelhaus.de)",
6
6
  "homepage": "https://ennogelhaus.de",
7
7
  "license": "SEE LICENSE IN LICENSE",
@@ -20,6 +20,10 @@
20
20
  "./server": {
21
21
  "types": "./dist/server.d.ts",
22
22
  "default": "./dist/server.js"
23
+ },
24
+ "./react": {
25
+ "types": "./dist/react.d.ts",
26
+ "default": "./dist/react.js"
23
27
  }
24
28
  },
25
29
  "files": [
@@ -34,8 +38,19 @@
34
38
  "publishConfig": {
35
39
  "access": "public"
36
40
  },
41
+ "peerDependencies": {
42
+ "react": ">=19"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "react": {
46
+ "optional": true
47
+ }
48
+ },
37
49
  "devDependencies": {
38
50
  "@types/node": "^22.19.7",
51
+ "@types/react": "^19.3.0",
52
+ "jsdom": "^30.1.1",
53
+ "react": "19.2.8",
39
54
  "typescript": "5.9.3",
40
55
  "vitest": "^4.1.8"
41
56
  },