@ghub/identity-chrome 0.2.0 → 0.4.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/README.md +35 -2
- package/dist/elements/account.d.ts +38 -3
- package/dist/elements/account.js +307 -31
- package/dist/elements/apps.js +1 -0
- package/dist/elements/index.d.ts +4 -2
- package/dist/elements/index.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/react.d.ts +13 -3
- package/dist/react.js +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ const account = document.querySelector("gp-account")!;
|
|
|
76
76
|
account.core = chrome;
|
|
77
77
|
account.product = "GAdvisory"; // your own name, from your catalogue entry
|
|
78
78
|
account.person = { name: user.name, email: user.email, id: user.id, avatarUrl: user.picture };
|
|
79
|
-
account.organisations = [{ id: "org_1", name: "Hafen Labs", role: "
|
|
79
|
+
account.organisations = [{ id: "org_1", name: "Hafen Labs", role: "Admin", active: true }];
|
|
80
80
|
account.onSwitch = async (id) => switchOrganisation(id); // reject when it did not switch
|
|
81
81
|
account.onSignOut = async () => {
|
|
82
82
|
await signOut(); // reject when it failed: the popup says they are still signed in
|
|
@@ -89,7 +89,8 @@ set before the element is defined. Neither control renders anything while the co
|
|
|
89
89
|
and the account popup renders nothing without a person.
|
|
90
90
|
|
|
91
91
|
**The account popup shows mostly what the product passes**: the name and address from your
|
|
92
|
-
session, your organisations with
|
|
92
|
+
session, your organisations with the role your product gives them there (its own label, already in the
|
|
93
|
+
person's language and shown as given; none, and the row has no second line) and your two actions.
|
|
93
94
|
Identity adds the picture, the colour of the initial, the links to its own account and set up
|
|
94
95
|
screens and the footer, so the popup still signs somebody out while Identity is down. The
|
|
95
96
|
picture is Identity's if the person has one, else yours, else a coloured initial whose tint
|
|
@@ -99,6 +100,38 @@ Identity, and Sign out says it ends both. Sign out leaves this product only.
|
|
|
99
100
|
|
|
100
101
|
Every word can be replaced through `labels`; the rest stay English.
|
|
101
102
|
|
|
103
|
+
### At the bottom of a sidebar, with your own items
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<div style="display: flex; align-items: center">
|
|
107
|
+
<gp-account
|
|
108
|
+
layout="row"
|
|
109
|
+
density="compact"
|
|
110
|
+
placement="top"
|
|
111
|
+
align="start"
|
|
112
|
+
style="flex: 1"
|
|
113
|
+
></gp-account>
|
|
114
|
+
<gp-apps placement="top" align="start"></gp-apps>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
account.labels = { organisations: "Groups" };
|
|
120
|
+
account.items = [
|
|
121
|
+
{ id: "profile", label: "Profile", href: "/settings/profile", icon: userSvg },
|
|
122
|
+
{ id: "account", label: "Account", href: "/settings/account", icon: settingsSvg },
|
|
123
|
+
{ id: "shortcuts", label: "Keyboard shortcuts", onSelect: () => openShortcuts(), icon: keysSvg },
|
|
124
|
+
];
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`layout="row"` draws the trigger as a full width row, the picture with the name over the
|
|
128
|
+
address; `density="compact"` draws the account popup a step smaller (320 wide, tighter rows),
|
|
129
|
+
so it does not cover the rail and half the page; `placement="top"` opens both popups upward.
|
|
130
|
+
Your items follow the organisations under your product's name, as links or as buttons; on a link, `onSelect` may call
|
|
131
|
+
`preventDefault()` and navigate with your own router. An icon is SVG markup on a 24 grid and
|
|
132
|
+
is kept to its shapes. Until you sign in through Identity, Sign out is the last row of your
|
|
133
|
+
items' group; with `viaIdentity` it ends both sessions and stands on its own.
|
|
134
|
+
|
|
102
135
|
### In React
|
|
103
136
|
|
|
104
137
|
```tsx
|
|
@@ -32,13 +32,24 @@
|
|
|
32
32
|
* sentence that the person is still signed in, and turns the row into Try
|
|
33
33
|
* again. It never claims to have signed out.
|
|
34
34
|
*
|
|
35
|
+
* **In a sidebar** (`layout="row"`, `placement="top"`): the trigger is a full
|
|
36
|
+
* width row, the picture with the name over the address, and the popup opens
|
|
37
|
+
* upward from it. A product with a sidebar puts `<gp-apps>` at the row's end.
|
|
38
|
+
* `density="compact"` draws the popup a step smaller (320 wide, tighter rows),
|
|
39
|
+
* so it does not cover the rail and half the page.
|
|
40
|
+
*
|
|
41
|
+
* **The product's own items** (`items`) sit after the organisations, as a group
|
|
42
|
+
* headed with the product's name. Until the product signs in through Identity,
|
|
43
|
+
* Sign out is the last row of that same group, because it is one of the
|
|
44
|
+
* product's own actions; once it does (`viaIdentity`), signing out ends both
|
|
45
|
+
* sessions and stands on its own again.
|
|
46
|
+
*
|
|
35
47
|
* **Hidden for now (D20):** the other accounts and "Add another account" are
|
|
36
48
|
* not drawn until Identity holds several sessions in one browser and products
|
|
37
49
|
* sign in through it, and no product can switch them on.
|
|
38
50
|
*
|
|
39
51
|
* The words are English by default and every one can be replaced (`labels`).
|
|
40
52
|
*/
|
|
41
|
-
export type OrganisationRole = "owner" | "admin" | "member";
|
|
42
53
|
/** Who is signed in, from the product's own session. */
|
|
43
54
|
export interface AccountPerson {
|
|
44
55
|
name: string;
|
|
@@ -52,9 +63,29 @@ export interface AccountPerson {
|
|
|
52
63
|
export interface AccountOrganisation {
|
|
53
64
|
id: string;
|
|
54
65
|
name: string;
|
|
55
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The product's own role for this person here, already in the person's
|
|
68
|
+
* language and shown as given. Roles differ per product and can be custom,
|
|
69
|
+
* so the element has no list of its own. None, and the row has no second line.
|
|
70
|
+
*/
|
|
71
|
+
role?: string | null;
|
|
56
72
|
active: boolean;
|
|
57
73
|
}
|
|
74
|
+
/** One of the product's own items in the popup, such as its profile or its settings. */
|
|
75
|
+
export interface AccountItem {
|
|
76
|
+
/** Stable across renders, so focus stays on the item when the list is set again. */
|
|
77
|
+
id: string;
|
|
78
|
+
label: string;
|
|
79
|
+
/** A link, so middle click and opening in a new tab work as on any link. Relative to the page. */
|
|
80
|
+
href?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Called when it is chosen. On a link, `event.preventDefault()` hands the
|
|
83
|
+
* navigation to the product, such as a client side router.
|
|
84
|
+
*/
|
|
85
|
+
onSelect?: (event: MouseEvent) => void;
|
|
86
|
+
/** SVG markup on a 24 grid, drawn at 18 in the row's ink. Only shapes are kept. */
|
|
87
|
+
icon?: string;
|
|
88
|
+
}
|
|
58
89
|
/** Signs the person out of this product. Rejecting means they are still signed in. */
|
|
59
90
|
export type SignOutHandler = () => unknown;
|
|
60
91
|
/** Switches the product to this organisation. Rejecting means it did not. */
|
|
@@ -68,7 +99,6 @@ export interface AccountLabels {
|
|
|
68
99
|
manage: string;
|
|
69
100
|
setUp: string;
|
|
70
101
|
organisations: string;
|
|
71
|
-
roles: Record<OrganisationRole, string>;
|
|
72
102
|
active: string;
|
|
73
103
|
showAll: (count: number) => string;
|
|
74
104
|
switched: (organisation: string) => string;
|
|
@@ -86,6 +116,11 @@ export interface AccountLabels {
|
|
|
86
116
|
}
|
|
87
117
|
export declare const ACCOUNT_LABELS: AccountLabels;
|
|
88
118
|
export declare const ACCOUNT_CSS: string;
|
|
119
|
+
/**
|
|
120
|
+
* A product's icon, kept to shapes: no script, no link, no style and no reference
|
|
121
|
+
* to anything outside it, because it is drawn inside every page the element is on.
|
|
122
|
+
*/
|
|
123
|
+
export declare function iconOf(markup: string): SVGSVGElement | null;
|
|
89
124
|
/**
|
|
90
125
|
* Defines `<gp-account>` once, where custom elements exist. Safe to import on a
|
|
91
126
|
* server: nothing touches the DOM until this is called in a browser.
|
package/dist/elements/account.js
CHANGED
|
@@ -33,6 +33,18 @@
|
|
|
33
33
|
* sentence that the person is still signed in, and turns the row into Try
|
|
34
34
|
* again. It never claims to have signed out.
|
|
35
35
|
*
|
|
36
|
+
* **In a sidebar** (`layout="row"`, `placement="top"`): the trigger is a full
|
|
37
|
+
* width row, the picture with the name over the address, and the popup opens
|
|
38
|
+
* upward from it. A product with a sidebar puts `<gp-apps>` at the row's end.
|
|
39
|
+
* `density="compact"` draws the popup a step smaller (320 wide, tighter rows),
|
|
40
|
+
* so it does not cover the rail and half the page.
|
|
41
|
+
*
|
|
42
|
+
* **The product's own items** (`items`) sit after the organisations, as a group
|
|
43
|
+
* headed with the product's name. Until the product signs in through Identity,
|
|
44
|
+
* Sign out is the last row of that same group, because it is one of the
|
|
45
|
+
* product's own actions; once it does (`viaIdentity`), signing out ends both
|
|
46
|
+
* sessions and stands on its own again.
|
|
47
|
+
*
|
|
36
48
|
* **Hidden for now (D20):** the other accounts and "Add another account" are
|
|
37
49
|
* not drawn until Identity holds several sessions in one browser and products
|
|
38
50
|
* sign in through it, and no product can switch them on.
|
|
@@ -41,6 +53,7 @@
|
|
|
41
53
|
*/
|
|
42
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
55
|
exports.ACCOUNT_CSS = exports.ACCOUNT_LABELS = void 0;
|
|
56
|
+
exports.iconOf = iconOf;
|
|
44
57
|
exports.defineAccountElement = defineAccountElement;
|
|
45
58
|
exports.createAccountElementClass = createAccountElementClass;
|
|
46
59
|
const tint_js_1 = require("../tint.js");
|
|
@@ -56,7 +69,6 @@ exports.ACCOUNT_LABELS = {
|
|
|
56
69
|
manage: "Manage your Identity account",
|
|
57
70
|
setUp: "Set up your Identity",
|
|
58
71
|
organisations: "Organisations",
|
|
59
|
-
roles: { owner: "Owner", admin: "Admin", member: "Member" },
|
|
60
72
|
active: "Active",
|
|
61
73
|
showAll: (count) => `Show all ${String(count)}`,
|
|
62
74
|
switched: (organisation) => `Switched to ${organisation}`,
|
|
@@ -78,7 +90,6 @@ const SHOWN = 5;
|
|
|
78
90
|
const TIP_DELAY_MS = 500;
|
|
79
91
|
/** Long enough to cross from the trigger to its tooltip without losing it. */
|
|
80
92
|
const TIP_LINGER_MS = 150;
|
|
81
|
-
const ROLES = ["owner", "admin", "member"];
|
|
82
93
|
const tintRules = tint_js_1.AVATAR_TINTS.map((tint, index) => `.t${String(index)} { background: ${tint}; }`).join("\n ");
|
|
83
94
|
exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
84
95
|
.trigger {
|
|
@@ -89,7 +100,17 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
89
100
|
.trigger:active { background: var(--_active); }
|
|
90
101
|
.trigger[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--_accent); }
|
|
91
102
|
.trigger:focus-visible { outline: 2px solid var(--_ring); outline-offset: 2px; }
|
|
92
|
-
@media (pointer: coarse) { .trigger { box-sizing: content-box; padding: 2px; } }
|
|
103
|
+
@media (pointer: coarse) { :host(:not([layout="row"])) .trigger { box-sizing: content-box; padding: 2px; } }
|
|
104
|
+
:host([layout="row"]) { display: block; }
|
|
105
|
+
:host([layout="row"]) .trigger {
|
|
106
|
+
box-sizing: border-box; width: 100%; height: auto; min-height: 44px; padding: 6px 8px; border-radius: 10px;
|
|
107
|
+
display: flex; align-items: center; gap: 10px; text-align: left; font: inherit;
|
|
108
|
+
}
|
|
109
|
+
:host([layout="row"]) .trigger[aria-expanded="true"] { background: var(--_hover); box-shadow: inset 0 0 0 1px var(--_accent); }
|
|
110
|
+
:host([layout="row"]) .trigger:focus-visible { outline-offset: 0; }
|
|
111
|
+
.who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
112
|
+
.who-name { font-size: 14px; line-height: 19px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
113
|
+
.who-mail { font-size: 12px; line-height: 16px; color: var(--_ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
93
114
|
|
|
94
115
|
.avatar {
|
|
95
116
|
position: relative; display: grid; place-items: center; flex-shrink: 0; overflow: hidden;
|
|
@@ -117,6 +138,8 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
117
138
|
box-shadow: var(--_shadow); color: var(--_ink); animation: rise 120ms ease-out;
|
|
118
139
|
}
|
|
119
140
|
:host([align="start"]) .popup, :host([align="start"]) .tip { right: auto; left: 0; }
|
|
141
|
+
:host([placement="top"]) .popup:not(.sheet) { top: auto; bottom: calc(100% + 8px); }
|
|
142
|
+
:host([placement="top"]) .tip { top: auto; bottom: calc(100% + 6px); }
|
|
120
143
|
.popup.sheet {
|
|
121
144
|
position: fixed; top: auto; right: 0; bottom: 0; left: 0; width: 100%; max-width: none;
|
|
122
145
|
max-height: calc(100dvh - 48px); border-radius: 20px 20px 0 0;
|
|
@@ -175,6 +198,20 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
175
198
|
|
|
176
199
|
.section { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
|
|
177
200
|
.heading { margin: 0; padding: 8px 16px 4px; font-size: 13px; line-height: 18px; font-weight: 500; color: var(--_ink-2); }
|
|
201
|
+
.heading.with-mark { display: flex; align-items: center; gap: 8px; }
|
|
202
|
+
.heading img { display: block; width: 16px; height: 16px; flex-shrink: 0; }
|
|
203
|
+
.stack { display: flex; flex-direction: column; gap: 2px; }
|
|
204
|
+
.stack > * { border-radius: 4px; }
|
|
205
|
+
.stack > :first-child { border-top-left-radius: 18px; border-top-right-radius: 18px; }
|
|
206
|
+
.stack > :last-child { border-bottom-left-radius: 18px; border-bottom-right-radius: 18px; }
|
|
207
|
+
.item {
|
|
208
|
+
display: flex; align-items: center; gap: 12px; box-sizing: border-box; width: 100%; min-height: 48px; padding: 0 16px;
|
|
209
|
+
border: 0; color: var(--_ink); font: inherit; font-size: 14px; line-height: 20px; font-weight: 500;
|
|
210
|
+
text-align: left; text-decoration: none; cursor: pointer;
|
|
211
|
+
}
|
|
212
|
+
.item svg { flex-shrink: 0; color: var(--_ink-2); }
|
|
213
|
+
.item:focus-visible { outline: 2px solid var(--_ring); outline-offset: -2px; }
|
|
214
|
+
a.item:hover, button.item:hover { background-image: linear-gradient(var(--_hover), var(--_hover)); }
|
|
178
215
|
.wrap, .radios { display: flex; flex-direction: column; gap: 2px; }
|
|
179
216
|
.radios.all {
|
|
180
217
|
max-height: 300px; overflow-y: auto; overscroll-behavior: contain;
|
|
@@ -206,7 +243,7 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
206
243
|
.more:focus-visible, .signout:focus-visible { outline: 2px solid var(--_ring); outline-offset: -2px; }
|
|
207
244
|
.signout {
|
|
208
245
|
display: flex; align-items: center; gap: 12px; box-sizing: border-box; width: 100%; min-height: 52px; padding: 0 16px;
|
|
209
|
-
border: 0;
|
|
246
|
+
border: 0; color: var(--_ink); font: inherit; font-size: 14px; font-weight: 500;
|
|
210
247
|
text-align: left; cursor: pointer;
|
|
211
248
|
}
|
|
212
249
|
.signout[aria-busy="true"] { color: var(--_ink-2); cursor: progress; }
|
|
@@ -223,6 +260,40 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
223
260
|
.legal a:hover { text-decoration: underline; }
|
|
224
261
|
.legal a:focus-visible { outline: 2px solid var(--_ring); outline-offset: 2px; }
|
|
225
262
|
|
|
263
|
+
/* density="compact": the same popup a step smaller, for a sidebar, where the
|
|
264
|
+
full one covers the rail and half the page. Below 600 it is the same sheet. */
|
|
265
|
+
:host([density="compact"]) .popup:not(.sheet) { width: 320px; padding: 6px 6px 8px; border-radius: 22px; }
|
|
266
|
+
:host([density="compact"]) .top { min-height: 32px; padding-left: 36px; }
|
|
267
|
+
:host([density="compact"]) .context { font-size: 12px; line-height: 16px; }
|
|
268
|
+
:host([density="compact"]) .close { width: 32px; height: 32px; }
|
|
269
|
+
:host([density="compact"]) .card { padding: 14px 14px 14px; border-radius: 18px; gap: 1px; }
|
|
270
|
+
:host([density="compact"]) .portrait { width: 52px; height: 52px; margin-bottom: 8px; }
|
|
271
|
+
:host([density="compact"]) .s72 { width: 52px; height: 52px; font-size: 22px; }
|
|
272
|
+
:host([density="compact"]) .pencil { width: 24px; height: 24px; right: -5px; bottom: -3px; }
|
|
273
|
+
:host([density="compact"]) .name { font-size: 16px; line-height: 21px; }
|
|
274
|
+
:host([density="compact"]) .email { font-size: 13px; line-height: 18px; }
|
|
275
|
+
:host([density="compact"]) .pill {
|
|
276
|
+
min-height: 34px; margin-top: 10px; padding: 0 14px 0 10px; gap: 8px; font-size: 13px; line-height: 18px;
|
|
277
|
+
}
|
|
278
|
+
:host([density="compact"]) .pill img { width: 18px; height: 18px; }
|
|
279
|
+
:host([density="compact"]) .section { margin-top: 4px; }
|
|
280
|
+
:host([density="compact"]) .heading { padding: 6px 12px 2px; font-size: 12px; line-height: 16px; }
|
|
281
|
+
:host([density="compact"]) .heading img { width: 14px; height: 14px; }
|
|
282
|
+
:host([density="compact"]) .stack > :first-child { border-top-left-radius: 14px; border-top-right-radius: 14px; }
|
|
283
|
+
:host([density="compact"]) .stack > :last-child { border-bottom-left-radius: 14px; border-bottom-right-radius: 14px; }
|
|
284
|
+
:host([density="compact"]) .item { min-height: 38px; padding: 0 12px; gap: 10px; font-size: 13px; line-height: 18px; }
|
|
285
|
+
:host([density="compact"]) .item svg, :host([density="compact"]) .signout svg { width: 16px; height: 16px; }
|
|
286
|
+
:host([density="compact"]) .org { min-height: 44px; padding: 5px 12px; gap: 10px; }
|
|
287
|
+
:host([density="compact"]) .wrap > .radios:last-child > .org:last-child { border-radius: 4px 4px 14px 14px; }
|
|
288
|
+
:host([density="compact"]) .org-mark { width: 28px; height: 28px; border-radius: 8px; font-size: 12.5px; }
|
|
289
|
+
:host([density="compact"]) .org-name { font-size: 13px; line-height: 18px; }
|
|
290
|
+
:host([density="compact"]) .org-role { font-size: 11.5px; line-height: 15px; }
|
|
291
|
+
:host([density="compact"]) .active { font-size: 12px; gap: 5px; }
|
|
292
|
+
:host([density="compact"]) .more { min-height: 38px; padding: 0 12px; font-size: 13px; border-radius: 4px 4px 14px 14px; }
|
|
293
|
+
:host([density="compact"]) .signout { min-height: 40px; padding: 0 12px; gap: 10px; font-size: 13px; }
|
|
294
|
+
:host([density="compact"]) .error { padding: 4px 12px 2px; font-size: 12px; line-height: 17px; }
|
|
295
|
+
:host([density="compact"]) .legal { padding: 8px 0 2px; font-size: 11.5px; line-height: 16px; }
|
|
296
|
+
|
|
226
297
|
@media (prefers-reduced-motion: reduce) {
|
|
227
298
|
.popup, .popup.sheet { animation: none; }
|
|
228
299
|
.spinner { animation-duration: 2400ms; }
|
|
@@ -231,7 +302,7 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
231
302
|
.popup { background: var(--_tint-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
232
303
|
}
|
|
233
304
|
@media (forced-colors: active) {
|
|
234
|
-
.popup, .panel, .org { border: 1px solid CanvasText; background: Canvas; }
|
|
305
|
+
.popup, .panel, .org, .item { border: 1px solid CanvasText; background: Canvas; }
|
|
235
306
|
.org[aria-checked="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
|
|
236
307
|
.avatar, .org-mark { border: 1px solid CanvasText; }
|
|
237
308
|
.pill, .legal a { color: LinkText; }
|
|
@@ -281,8 +352,8 @@ function emailLine(email) {
|
|
|
281
352
|
}
|
|
282
353
|
return line;
|
|
283
354
|
}
|
|
284
|
-
/**
|
|
285
|
-
function
|
|
355
|
+
/** An address the product gave, absolute or relative to the page, and only ever http or https. */
|
|
356
|
+
function productAddress(value) {
|
|
286
357
|
if (typeof value !== "string" || value === "")
|
|
287
358
|
return null;
|
|
288
359
|
try {
|
|
@@ -292,6 +363,108 @@ function productPicture(value) {
|
|
|
292
363
|
return null;
|
|
293
364
|
}
|
|
294
365
|
}
|
|
366
|
+
const ICON_TAGS = new Set([
|
|
367
|
+
"svg",
|
|
368
|
+
"g",
|
|
369
|
+
"path",
|
|
370
|
+
"circle",
|
|
371
|
+
"ellipse",
|
|
372
|
+
"line",
|
|
373
|
+
"polyline",
|
|
374
|
+
"polygon",
|
|
375
|
+
"rect",
|
|
376
|
+
]);
|
|
377
|
+
const ICON_ATTRIBUTES = new Set([
|
|
378
|
+
"viewBox",
|
|
379
|
+
"d",
|
|
380
|
+
"cx",
|
|
381
|
+
"cy",
|
|
382
|
+
"r",
|
|
383
|
+
"rx",
|
|
384
|
+
"ry",
|
|
385
|
+
"x",
|
|
386
|
+
"y",
|
|
387
|
+
"x1",
|
|
388
|
+
"y1",
|
|
389
|
+
"x2",
|
|
390
|
+
"y2",
|
|
391
|
+
"width",
|
|
392
|
+
"height",
|
|
393
|
+
"points",
|
|
394
|
+
"fill",
|
|
395
|
+
"stroke",
|
|
396
|
+
"stroke-width",
|
|
397
|
+
"stroke-linecap",
|
|
398
|
+
"stroke-linejoin",
|
|
399
|
+
"fill-rule",
|
|
400
|
+
"clip-rule",
|
|
401
|
+
"transform",
|
|
402
|
+
"opacity",
|
|
403
|
+
]);
|
|
404
|
+
/**
|
|
405
|
+
* A product's icon, kept to shapes: no script, no link, no style and no reference
|
|
406
|
+
* to anything outside it, because it is drawn inside every page the element is on.
|
|
407
|
+
*/
|
|
408
|
+
function iconOf(markup) {
|
|
409
|
+
let parsed;
|
|
410
|
+
try {
|
|
411
|
+
parsed = new DOMParser().parseFromString(markup, "image/svg+xml");
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const root = parsed.documentElement;
|
|
417
|
+
if (root.localName !== "svg" || parsed.getElementsByTagName("parsererror").length > 0)
|
|
418
|
+
return null;
|
|
419
|
+
const copy = (source) => {
|
|
420
|
+
if (!ICON_TAGS.has(source.localName))
|
|
421
|
+
return null;
|
|
422
|
+
const made = document.createElementNS(dom_js_1.SVG_NS, source.localName);
|
|
423
|
+
for (const attribute of [...source.attributes]) {
|
|
424
|
+
if (ICON_ATTRIBUTES.has(attribute.name) && !/url\(|javascript:|&/iu.test(attribute.value)) {
|
|
425
|
+
made.setAttribute(attribute.name, attribute.value);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const child of [...source.children]) {
|
|
429
|
+
const kept = copy(child);
|
|
430
|
+
if (kept !== null)
|
|
431
|
+
made.append(kept);
|
|
432
|
+
}
|
|
433
|
+
return made;
|
|
434
|
+
};
|
|
435
|
+
const svg = copy(root);
|
|
436
|
+
if (svg === null)
|
|
437
|
+
return null;
|
|
438
|
+
svg.setAttribute("width", "18");
|
|
439
|
+
svg.setAttribute("height", "18");
|
|
440
|
+
svg.setAttribute("aria-hidden", "true");
|
|
441
|
+
svg.setAttribute("focusable", "false");
|
|
442
|
+
return svg;
|
|
443
|
+
}
|
|
444
|
+
function itemsOf(value) {
|
|
445
|
+
if (!Array.isArray(value))
|
|
446
|
+
return [];
|
|
447
|
+
const out = [];
|
|
448
|
+
for (const one of value) {
|
|
449
|
+
if (typeof one !== "object" || one === null)
|
|
450
|
+
continue;
|
|
451
|
+
const { id, label, href, onSelect, icon } = one;
|
|
452
|
+
if (typeof id !== "string" || id === "" || typeof label !== "string" || label.trim() === "")
|
|
453
|
+
continue;
|
|
454
|
+
const link = typeof href === "string" ? productAddress(href) : null;
|
|
455
|
+
const select = typeof onSelect === "function" ? onSelect : null;
|
|
456
|
+
if (link === null && select === null)
|
|
457
|
+
continue;
|
|
458
|
+
out.push({
|
|
459
|
+
id,
|
|
460
|
+
label: label.trim(),
|
|
461
|
+
href: link,
|
|
462
|
+
onSelect: select,
|
|
463
|
+
icon: typeof icon === "string" ? iconOf(icon) : null,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
295
468
|
function personOf(value) {
|
|
296
469
|
if (typeof value !== "object" || value === null)
|
|
297
470
|
return null;
|
|
@@ -316,9 +489,8 @@ function organisationsOf(value) {
|
|
|
316
489
|
const { id, name, role, active } = one;
|
|
317
490
|
if (typeof id !== "string" || id === "" || typeof name !== "string" || name.trim() === "")
|
|
318
491
|
continue;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
out.push({ id, name: name.trim(), role: role, active: active === true });
|
|
492
|
+
const label = typeof role === "string" && role.trim() !== "" ? role.trim() : null;
|
|
493
|
+
out.push({ id, name: name.trim(), role: label, active: active === true });
|
|
322
494
|
}
|
|
323
495
|
// The active one first: the row says which one you are acting in.
|
|
324
496
|
const active = out.findIndex((one) => one.active);
|
|
@@ -347,6 +519,8 @@ function createAccountElementClass() {
|
|
|
347
519
|
#viaIdentity = false;
|
|
348
520
|
#onSignOut = null;
|
|
349
521
|
#onSwitch = null;
|
|
522
|
+
#items = [];
|
|
523
|
+
#given = [];
|
|
350
524
|
#open = false;
|
|
351
525
|
#sheet = false;
|
|
352
526
|
#allShown = false;
|
|
@@ -487,16 +661,28 @@ function createAccountElementClass() {
|
|
|
487
661
|
this.#onSwitch = typeof next === "function" ? next : null;
|
|
488
662
|
this.#render();
|
|
489
663
|
}
|
|
664
|
+
static get observedAttributes() {
|
|
665
|
+
return ["layout"];
|
|
666
|
+
}
|
|
667
|
+
attributeChangedCallback() {
|
|
668
|
+
this.#face = "";
|
|
669
|
+
this.#render();
|
|
670
|
+
}
|
|
671
|
+
get items() {
|
|
672
|
+
return this.#given;
|
|
673
|
+
}
|
|
674
|
+
/** The product's own items. None, and the group is absent. */
|
|
675
|
+
set items(next) {
|
|
676
|
+
this.#given = Array.isArray(next) ? next : [];
|
|
677
|
+
this.#items = itemsOf(next);
|
|
678
|
+
this.#render();
|
|
679
|
+
}
|
|
490
680
|
get labels() {
|
|
491
681
|
return this.#labels;
|
|
492
682
|
}
|
|
493
683
|
/** Any of the words, replaced; the rest stay English. */
|
|
494
684
|
set labels(next) {
|
|
495
|
-
this.#labels = {
|
|
496
|
-
...exports.ACCOUNT_LABELS,
|
|
497
|
-
...next,
|
|
498
|
-
roles: { ...exports.ACCOUNT_LABELS.roles, ...next.roles },
|
|
499
|
-
};
|
|
685
|
+
this.#labels = { ...exports.ACCOUNT_LABELS, ...next };
|
|
500
686
|
this.#render();
|
|
501
687
|
}
|
|
502
688
|
get open() {
|
|
@@ -512,6 +698,7 @@ function createAccountElementClass() {
|
|
|
512
698
|
"viaIdentity",
|
|
513
699
|
"onSignOut",
|
|
514
700
|
"onSwitch",
|
|
701
|
+
"items",
|
|
515
702
|
"labels",
|
|
516
703
|
]) {
|
|
517
704
|
if (Object.prototype.hasOwnProperty.call(this, key)) {
|
|
@@ -725,6 +912,9 @@ function createAccountElementClass() {
|
|
|
725
912
|
void this.#choose(id);
|
|
726
913
|
break;
|
|
727
914
|
}
|
|
915
|
+
case "item":
|
|
916
|
+
this.#chosen(target, event);
|
|
917
|
+
break;
|
|
728
918
|
default:
|
|
729
919
|
break;
|
|
730
920
|
}
|
|
@@ -757,6 +947,25 @@ function createAccountElementClass() {
|
|
|
757
947
|
if (moved !== undefined)
|
|
758
948
|
this.#byKey(`org:${moved.id}`)?.focus();
|
|
759
949
|
}
|
|
950
|
+
/** One of the product's items: its handler runs, a link goes where it goes, and the popup closes. */
|
|
951
|
+
#chosen(target, event) {
|
|
952
|
+
const item = this.#items.find((one) => one.id === target.dataset["id"]);
|
|
953
|
+
if (item === undefined)
|
|
954
|
+
return;
|
|
955
|
+
try {
|
|
956
|
+
item.onSelect?.(event);
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
// The product's fault, and not a reason to leave the popup open over the page.
|
|
960
|
+
}
|
|
961
|
+
this.close(false);
|
|
962
|
+
// Back to the trigger unless the item took focus somewhere, as a dialog it opened would.
|
|
963
|
+
setTimeout(() => {
|
|
964
|
+
if (document.activeElement === document.body || document.activeElement === null) {
|
|
965
|
+
this.#returnFocus();
|
|
966
|
+
}
|
|
967
|
+
}, 0);
|
|
968
|
+
}
|
|
760
969
|
async #choose(id) {
|
|
761
970
|
const handler = this.#onSwitch;
|
|
762
971
|
const organisation = this.#organisations.find((one) => one.id === id);
|
|
@@ -806,7 +1015,7 @@ function createAccountElementClass() {
|
|
|
806
1015
|
#faceOf(view, person) {
|
|
807
1016
|
const own = view?.person.hasIdentity === true ? view.person : null;
|
|
808
1017
|
return {
|
|
809
|
-
picture: own?.avatarUrl ??
|
|
1018
|
+
picture: own?.avatarUrl ?? productAddress(person.avatarUrl),
|
|
810
1019
|
tint: own?.tint ?? (0, tint_js_1.avatarTint)(person.id ?? person.email.toLowerCase()),
|
|
811
1020
|
initial: initialOf(person.name),
|
|
812
1021
|
};
|
|
@@ -823,13 +1032,20 @@ function createAccountElementClass() {
|
|
|
823
1032
|
const labels = this.#labels;
|
|
824
1033
|
const view = state.view;
|
|
825
1034
|
const name = labels.trigger(person.name);
|
|
826
|
-
this
|
|
1035
|
+
const row = this.getAttribute("layout") === "row";
|
|
1036
|
+
// In a row the address is on screen too, so the name says it as well (2.5.3, label in name).
|
|
1037
|
+
this.#trigger.setAttribute("aria-label", row ? `${name}, ${person.email}` : name);
|
|
827
1038
|
this.#tip.textContent = name;
|
|
828
1039
|
const face = this.#faceOf(view, person);
|
|
829
|
-
const signature = `${face.picture ?? ""} ${String(face.tint)} ${face.initial}`;
|
|
1040
|
+
const signature = `${face.picture ?? ""} ${String(face.tint)} ${face.initial} ${row ? `${person.name} ${person.email}` : ""}`;
|
|
830
1041
|
if (signature !== this.#face) {
|
|
831
1042
|
this.#face = signature;
|
|
832
1043
|
this.#trigger.replaceChildren(avatar(face, "s32"));
|
|
1044
|
+
if (row) {
|
|
1045
|
+
const who = (0, dom_js_1.element)("span", { class: "who", "aria-hidden": "true" });
|
|
1046
|
+
who.append((0, dom_js_1.element)("span", { class: "who-name" }, person.name), (0, dom_js_1.element)("span", { class: "who-mail" }, person.email));
|
|
1047
|
+
this.#trigger.append(who);
|
|
1048
|
+
}
|
|
833
1049
|
}
|
|
834
1050
|
if (!this.#open)
|
|
835
1051
|
return;
|
|
@@ -910,8 +1126,7 @@ function createAccountElementClass() {
|
|
|
910
1126
|
out.append(card);
|
|
911
1127
|
if (this.#organisations.length > 0)
|
|
912
1128
|
out.append(this.#organisationsSection());
|
|
913
|
-
|
|
914
|
-
out.append(this.#signOutSection(product));
|
|
1129
|
+
out.append(...this.#actionSections(view, product));
|
|
915
1130
|
if (view !== null && view.legal !== null) {
|
|
916
1131
|
const footer = (0, dom_js_1.element)("div", { class: "legal" });
|
|
917
1132
|
footer.append((0, dom_js_1.element)("a", { href: view.legal.termsUrl, "data-key": "terms" }, labels.terms), (0, dom_js_1.element)("span", { "aria-hidden": "true" }, "·"), (0, dom_js_1.element)("a", { href: view.legal.privacyUrl, "data-key": "privacy" }, labels.privacy));
|
|
@@ -938,19 +1153,20 @@ function createAccountElementClass() {
|
|
|
938
1153
|
if (this.#switching !== null)
|
|
939
1154
|
radios.setAttribute("aria-busy", "true");
|
|
940
1155
|
for (const one of shown) {
|
|
941
|
-
const role = labels.roles[one.role];
|
|
942
1156
|
const row = (0, dom_js_1.element)("div", {
|
|
943
1157
|
class: "org panel",
|
|
944
1158
|
role: "radio",
|
|
945
1159
|
"aria-checked": String(one.active),
|
|
946
|
-
"aria-label": `${one.name}, ${role}
|
|
1160
|
+
"aria-label": one.role ? `${one.name}, ${one.role}` : one.name,
|
|
947
1161
|
tabindex: one === stop ? "0" : "-1",
|
|
948
1162
|
"data-action": "org",
|
|
949
1163
|
"data-id": one.id,
|
|
950
1164
|
"data-key": `org:${one.id}`,
|
|
951
1165
|
});
|
|
952
1166
|
const text = (0, dom_js_1.element)("span", { class: "org-text", "aria-hidden": "true" });
|
|
953
|
-
text.append((0, dom_js_1.element)("span", { class: "org-name", title: one.name }, one.name)
|
|
1167
|
+
text.append((0, dom_js_1.element)("span", { class: "org-name", title: one.name }, one.name));
|
|
1168
|
+
if (one.role)
|
|
1169
|
+
text.append((0, dom_js_1.element)("span", { class: "org-role" }, one.role));
|
|
954
1170
|
row.append((0, dom_js_1.element)("span", { class: `org-mark t${String((0, tint_js_1.avatarTint)(one.id))}`, "aria-hidden": "true" }, initialOf(one.name)), text);
|
|
955
1171
|
if (one.active) {
|
|
956
1172
|
const tag = (0, dom_js_1.element)("span", { class: "active", "aria-hidden": "true" });
|
|
@@ -970,12 +1186,77 @@ function createAccountElementClass() {
|
|
|
970
1186
|
}
|
|
971
1187
|
return section;
|
|
972
1188
|
}
|
|
973
|
-
|
|
1189
|
+
/**
|
|
1190
|
+
* The product's items and Sign out. Until the product signs in through
|
|
1191
|
+
* Identity, Sign out is the last row of the items' group, one of the
|
|
1192
|
+
* product's own actions; once it does, it ends both sessions and stands apart.
|
|
1193
|
+
*/
|
|
1194
|
+
#actionSections(view, product) {
|
|
974
1195
|
const labels = this.#labels;
|
|
975
1196
|
const name = product ?? labels.thisProduct;
|
|
1197
|
+
const sections = [];
|
|
1198
|
+
const joined = !this.#viaIdentity;
|
|
1199
|
+
let stack = null;
|
|
1200
|
+
let section = null;
|
|
1201
|
+
if (this.#items.length > 0) {
|
|
1202
|
+
section = (0, dom_js_1.element)("div", { class: "section" });
|
|
1203
|
+
if (product !== null) {
|
|
1204
|
+
const heading = (0, dom_js_1.element)("p", { class: "heading with-mark" });
|
|
1205
|
+
const mark = view?.yours.find((tile) => tile.current)?.iconUrl ?? null;
|
|
1206
|
+
if (mark !== null) {
|
|
1207
|
+
heading.append((0, dom_js_1.element)("img", {
|
|
1208
|
+
src: mark,
|
|
1209
|
+
alt: "",
|
|
1210
|
+
width: "16",
|
|
1211
|
+
height: "16",
|
|
1212
|
+
referrerpolicy: "no-referrer",
|
|
1213
|
+
}));
|
|
1214
|
+
}
|
|
1215
|
+
heading.append(product);
|
|
1216
|
+
section.append(heading);
|
|
1217
|
+
}
|
|
1218
|
+
stack = (0, dom_js_1.element)("div", { class: "stack" });
|
|
1219
|
+
for (const item of this.#items)
|
|
1220
|
+
stack.append(this.#itemRow(item));
|
|
1221
|
+
section.append(stack);
|
|
1222
|
+
sections.push(section);
|
|
1223
|
+
}
|
|
1224
|
+
if (this.#onSignOut === null)
|
|
1225
|
+
return sections;
|
|
1226
|
+
const button = this.#signOutButton(name);
|
|
1227
|
+
if (joined && stack !== null && section !== null) {
|
|
1228
|
+
stack.append(button);
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
section = (0, dom_js_1.element)("div", { class: "section" });
|
|
1232
|
+
stack = (0, dom_js_1.element)("div", { class: "stack" });
|
|
1233
|
+
stack.append(button);
|
|
1234
|
+
section.append(stack);
|
|
1235
|
+
sections.push(section);
|
|
1236
|
+
}
|
|
1237
|
+
if (this.#signOut === "failed") {
|
|
1238
|
+
button.setAttribute("aria-describedby", "signout-failed");
|
|
1239
|
+
section.append((0, dom_js_1.element)("p", { id: "signout-failed", class: "error", role: "alert" }, labels.signOutFailed(name)));
|
|
1240
|
+
}
|
|
1241
|
+
return sections;
|
|
1242
|
+
}
|
|
1243
|
+
#itemRow(item) {
|
|
1244
|
+
const row = item.href === null
|
|
1245
|
+
? (0, dom_js_1.element)("button", { type: "button" })
|
|
1246
|
+
: (0, dom_js_1.element)("a", { href: item.href });
|
|
1247
|
+
row.className = "item panel";
|
|
1248
|
+
row.dataset["action"] = "item";
|
|
1249
|
+
row.dataset["id"] = item.id;
|
|
1250
|
+
row.dataset["key"] = `item:${item.id}`;
|
|
1251
|
+
if (item.icon !== null)
|
|
1252
|
+
row.append(item.icon.cloneNode(true));
|
|
1253
|
+
row.append((0, dom_js_1.element)("span", {}, item.label));
|
|
1254
|
+
return row;
|
|
1255
|
+
}
|
|
1256
|
+
#signOutButton(name) {
|
|
1257
|
+
const labels = this.#labels;
|
|
976
1258
|
const busy = this.#signOut === "busy";
|
|
977
1259
|
const failed = this.#signOut === "failed";
|
|
978
|
-
const section = (0, dom_js_1.element)("div", { class: "section" });
|
|
979
1260
|
const button = (0, dom_js_1.element)("button", {
|
|
980
1261
|
type: "button",
|
|
981
1262
|
class: "signout panel",
|
|
@@ -996,12 +1277,7 @@ function createAccountElementClass() {
|
|
|
996
1277
|
: this.#viaIdentity
|
|
997
1278
|
? labels.signOutBoth(name)
|
|
998
1279
|
: labels.signOut(name)));
|
|
999
|
-
|
|
1000
|
-
if (failed) {
|
|
1001
|
-
button.setAttribute("aria-describedby", "signout-failed");
|
|
1002
|
-
section.append((0, dom_js_1.element)("p", { id: "signout-failed", class: "error", role: "alert" }, labels.signOutFailed(name)));
|
|
1003
|
-
}
|
|
1004
|
-
return section;
|
|
1280
|
+
return button;
|
|
1005
1281
|
}
|
|
1006
1282
|
};
|
|
1007
1283
|
}
|
package/dist/elements/apps.js
CHANGED
|
@@ -79,6 +79,7 @@ exports.APPS_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
79
79
|
animation: rise 120ms ease-out;
|
|
80
80
|
}
|
|
81
81
|
:host([align="start"]) .popup { right: auto; left: 0; }
|
|
82
|
+
:host([placement="top"]) .popup:not(.sheet) { top: auto; bottom: calc(100% + 8px); }
|
|
82
83
|
.popup[hidden], .scrim[hidden] { display: none; }
|
|
83
84
|
.scroller { overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 4px; }
|
|
84
85
|
.popup.sheet {
|
package/dist/elements/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* in a browser.
|
|
6
6
|
*/
|
|
7
7
|
import type { ChromeCore } from "../core.js";
|
|
8
|
-
import { type AccountLabels, type AccountOrganisation, type AccountPerson, type SignOutHandler, type SwitchHandler } from "./account.js";
|
|
8
|
+
import { type AccountItem, type AccountLabels, type AccountOrganisation, type AccountPerson, type SignOutHandler, type SwitchHandler } from "./account.js";
|
|
9
9
|
import { type AppsLabels } from "./apps.js";
|
|
10
10
|
/** What `<gp-apps>` offers its page. */
|
|
11
11
|
export interface AppsElement extends HTMLElement {
|
|
@@ -29,6 +29,8 @@ export interface AccountElement extends HTMLElement {
|
|
|
29
29
|
viaIdentity: boolean;
|
|
30
30
|
onSignOut: SignOutHandler | null;
|
|
31
31
|
onSwitch: SwitchHandler | null;
|
|
32
|
+
get items(): AccountItem[];
|
|
33
|
+
set items(next: AccountItem[] | null);
|
|
32
34
|
get labels(): AccountLabels;
|
|
33
35
|
set labels(next: Partial<AccountLabels>);
|
|
34
36
|
readonly open: boolean;
|
|
@@ -43,6 +45,6 @@ declare global {
|
|
|
43
45
|
}
|
|
44
46
|
/** Defines `<gp-apps>` and `<gp-account>`, each once. */
|
|
45
47
|
export declare function defineChromeElements(): void;
|
|
46
|
-
export { ACCOUNT_LABELS, createAccountElementClass, defineAccountElement, type
|
|
48
|
+
export { ACCOUNT_LABELS, createAccountElementClass, defineAccountElement, iconOf, type AccountItem, type AccountLabels, type AccountOrganisation, type AccountPerson, type SignOutHandler, type SwitchHandler, } from "./account.js";
|
|
47
49
|
export { APPS_LABELS, createAppsElementClass, defineAppsElement, type AppsLabels } from "./apps.js";
|
|
48
50
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/elements/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* in a browser.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.defineAppsElement = exports.createAppsElementClass = exports.APPS_LABELS = exports.defineAccountElement = exports.createAccountElementClass = exports.ACCOUNT_LABELS = void 0;
|
|
9
|
+
exports.defineAppsElement = exports.createAppsElementClass = exports.APPS_LABELS = exports.iconOf = exports.defineAccountElement = exports.createAccountElementClass = exports.ACCOUNT_LABELS = void 0;
|
|
10
10
|
exports.defineChromeElements = defineChromeElements;
|
|
11
11
|
const account_js_1 = require("./account.js");
|
|
12
12
|
const apps_js_1 = require("./apps.js");
|
|
@@ -19,6 +19,7 @@ var account_js_2 = require("./account.js");
|
|
|
19
19
|
Object.defineProperty(exports, "ACCOUNT_LABELS", { enumerable: true, get: function () { return account_js_2.ACCOUNT_LABELS; } });
|
|
20
20
|
Object.defineProperty(exports, "createAccountElementClass", { enumerable: true, get: function () { return account_js_2.createAccountElementClass; } });
|
|
21
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; } });
|
|
22
23
|
var apps_js_2 = require("./apps.js");
|
|
23
24
|
Object.defineProperty(exports, "APPS_LABELS", { enumerable: true, get: function () { return apps_js_2.APPS_LABELS; } });
|
|
24
25
|
Object.defineProperty(exports, "createAppsElementClass", { enumerable: true, get: function () { return apps_js_2.createAppsElementClass; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
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";
|
|
11
11
|
export { catalogueMarkUrl, createCatalogue, type Catalogue, type CatalogueOptions, } from "./catalogue.js";
|
|
12
|
-
export { ACCOUNT_LABELS, APPS_LABELS, createAccountElementClass, createAppsElementClass, defineAccountElement, defineAppsElement, defineChromeElements, type AccountElement, type AccountLabels, type AccountOrganisation, type AccountPerson, type AppsElement, type AppsLabels, type
|
|
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
13
|
export { AVATAR_INK, AVATAR_TINTS, avatarTint } from "./tint.js";
|
|
14
14
|
export type { CatalogueEntry, ChromeIdentityLinks, ChromeLegal, ChromePerson, ChromePublicView, ChromeTile, ChromeView, } from "./types.js";
|
|
15
15
|
export { safeUrl } from "./validate.js";
|
package/dist/react.d.ts
CHANGED
|
@@ -9,17 +9,20 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type ReactElement } from "react";
|
|
11
11
|
import type { ChromeCore } from "./core.js";
|
|
12
|
-
import type { AccountLabels, AccountOrganisation, AccountPerson, AppsLabels, SignOutHandler, SwitchHandler } from "./elements/index.js";
|
|
12
|
+
import type { AccountItem, AccountLabels, AccountOrganisation, AccountPerson, AppsLabels, SignOutHandler, SwitchHandler } from "./elements/index.js";
|
|
13
13
|
/** Which edge of the trigger the popup lines up with. The end, by default. */
|
|
14
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";
|
|
15
17
|
export interface GpAppsProps {
|
|
16
18
|
core: ChromeCore | null;
|
|
17
19
|
labels?: Partial<AppsLabels>;
|
|
18
20
|
align?: Align;
|
|
21
|
+
placement?: Placement;
|
|
19
22
|
className?: string;
|
|
20
23
|
}
|
|
21
24
|
/** `<gp-apps>`, the apps picker. */
|
|
22
|
-
export declare function GpApps({ core, labels, align, className }: GpAppsProps): ReactElement;
|
|
25
|
+
export declare function GpApps({ core, labels, align, placement, className }: GpAppsProps): ReactElement;
|
|
23
26
|
export interface GpAccountProps {
|
|
24
27
|
core: ChromeCore | null;
|
|
25
28
|
/** Who is signed in, from the product's own session. Nothing renders without one. */
|
|
@@ -31,11 +34,18 @@ export interface GpAccountProps {
|
|
|
31
34
|
viaIdentity?: boolean;
|
|
32
35
|
onSignOut?: SignOutHandler | null;
|
|
33
36
|
onSwitch?: SwitchHandler | null;
|
|
37
|
+
/** The product's own items, after the organisations. */
|
|
38
|
+
items?: AccountItem[] | null;
|
|
34
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
|
+
/** `compact` for a sidebar: the popup a step smaller. */
|
|
43
|
+
density?: "regular" | "compact";
|
|
35
44
|
align?: Align;
|
|
45
|
+
placement?: Placement;
|
|
36
46
|
className?: string;
|
|
37
47
|
}
|
|
38
48
|
/** `<gp-account>`, the account popup. */
|
|
39
|
-
export declare function GpAccount({ core, person, organisations, product, viaIdentity, onSignOut, onSwitch, labels, align, className, }: GpAccountProps): ReactElement;
|
|
49
|
+
export declare function GpAccount({ core, person, organisations, product, viaIdentity, onSignOut, onSwitch, items, labels, layout, density, align, placement, className, }: GpAccountProps): ReactElement;
|
|
40
50
|
export {};
|
|
41
51
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.js
CHANGED
|
@@ -15,7 +15,7 @@ exports.GpAccount = GpAccount;
|
|
|
15
15
|
const react_1 = require("react");
|
|
16
16
|
const index_js_1 = require("./elements/index.js");
|
|
17
17
|
/** `<gp-apps>`, the apps picker. */
|
|
18
|
-
function GpApps({ core, labels, align, className }) {
|
|
18
|
+
function GpApps({ core, labels, align, placement, className }) {
|
|
19
19
|
const ref = (0, react_1.useRef)(null);
|
|
20
20
|
(0, react_1.useEffect)(() => {
|
|
21
21
|
(0, index_js_1.defineChromeElements)();
|
|
@@ -28,10 +28,10 @@ function GpApps({ core, labels, align, className }) {
|
|
|
28
28
|
if (ref.current !== null && labels !== undefined)
|
|
29
29
|
ref.current.labels = labels;
|
|
30
30
|
}, [labels]);
|
|
31
|
-
return (0, react_1.createElement)("gp-apps", { ref, className, align });
|
|
31
|
+
return (0, react_1.createElement)("gp-apps", { ref, className, align, placement });
|
|
32
32
|
}
|
|
33
33
|
/** `<gp-account>`, the account popup. */
|
|
34
|
-
function GpAccount({ core, person, organisations = null, product = null, viaIdentity = false, onSignOut = null, onSwitch = null, labels, align, className, }) {
|
|
34
|
+
function GpAccount({ core, person, organisations = null, product = null, viaIdentity = false, onSignOut = null, onSwitch = null, items = null, labels, layout, density, align, placement, className, }) {
|
|
35
35
|
const ref = (0, react_1.useRef)(null);
|
|
36
36
|
(0, react_1.useEffect)(() => {
|
|
37
37
|
(0, index_js_1.defineChromeElements)();
|
|
@@ -64,10 +64,14 @@ function GpAccount({ core, person, organisations = null, product = null, viaIden
|
|
|
64
64
|
if (ref.current !== null)
|
|
65
65
|
ref.current.onSwitch = onSwitch;
|
|
66
66
|
}, [onSwitch]);
|
|
67
|
+
(0, react_1.useEffect)(() => {
|
|
68
|
+
if (ref.current !== null)
|
|
69
|
+
ref.current.items = items;
|
|
70
|
+
}, [items]);
|
|
67
71
|
(0, react_1.useEffect)(() => {
|
|
68
72
|
if (ref.current !== null && labels !== undefined)
|
|
69
73
|
ref.current.labels = labels;
|
|
70
74
|
}, [labels]);
|
|
71
|
-
return (0, react_1.createElement)("gp-account", { ref, className, align });
|
|
75
|
+
return (0, react_1.createElement)("gp-account", { ref, className, layout, density, align, placement });
|
|
72
76
|
}
|
|
73
77
|
//# sourceMappingURL=react.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghub/identity-chrome",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
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",
|