@substrat-run/elements 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +58 -2
- package/dist/avatar.d.ts +56 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +107 -0
- package/dist/avatar.js.map +1 -0
- package/dist/controls.d.ts +21 -0
- package/dist/controls.d.ts.map +1 -0
- package/dist/controls.js +21 -0
- package/dist/controls.js.map +1 -0
- package/dist/entity-table-model.d.ts +269 -0
- package/dist/entity-table-model.d.ts.map +1 -0
- package/dist/entity-table-model.js +490 -0
- package/dist/entity-table-model.js.map +1 -0
- package/dist/entity-table.d.ts +187 -0
- package/dist/entity-table.d.ts.map +1 -0
- package/dist/entity-table.js +1004 -0
- package/dist/entity-table.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/session-controller.d.ts +25 -0
- package/dist/internal/session-controller.d.ts.map +1 -0
- package/dist/internal/session-controller.js +46 -0
- package/dist/internal/session-controller.js.map +1 -0
- package/dist/internal/substrat-element.d.ts +36 -0
- package/dist/internal/substrat-element.d.ts.map +1 -0
- package/dist/internal/substrat-element.js +195 -0
- package/dist/internal/substrat-element.js.map +1 -0
- package/dist/jsx.d.ts +8 -0
- package/dist/jsx.d.ts.map +1 -0
- package/dist/jsx.js +2 -0
- package/dist/jsx.js.map +1 -0
- package/dist/session.d.ts +84 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +158 -0
- package/dist/session.js.map +1 -0
- package/dist/themes/atelier.css +59 -0
- package/dist/themes/base.css +7 -0
- package/dist/themes/ledger.css +59 -0
- package/dist/themes/native.css +11 -0
- package/dist/user-menu.d.ts +95 -0
- package/dist/user-menu.d.ts.map +1 -0
- package/dist/user-menu.js +212 -0
- package/dist/user-menu.js.map +1 -0
- package/package.json +47 -3
package/dist/session.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is signed in, for `<sub-avatar>` and `<sub-user-menu>` — with no DOM in it, so a
|
|
3
|
+
* second renderer gets the same answers from the same code (as `EntityTableModel` is
|
|
4
|
+
* for the table).
|
|
5
|
+
*
|
|
6
|
+
* One `SessionStore` per URL, shared: an avatar in the corner and the menu beside it
|
|
7
|
+
* read the session once between them, and a refresh of one is a refresh of both.
|
|
8
|
+
*
|
|
9
|
+
* A session read answers 200 with `{ principal, display, … }`, or anything else for
|
|
10
|
+
* "nobody". `email`, `picture` and `team` are read when present and never required —
|
|
11
|
+
* the todo demo's `/api/me` has only the first two.
|
|
12
|
+
*/
|
|
13
|
+
const text = (v) => (typeof v === 'string' && v.trim() !== '' ? v : undefined);
|
|
14
|
+
/** A read's body as a session, or null when it is not one. Extra fields are ignored. */
|
|
15
|
+
export function parseSession(body) {
|
|
16
|
+
if (typeof body !== 'object' || body === null)
|
|
17
|
+
return null;
|
|
18
|
+
const b = body;
|
|
19
|
+
const principal = text(b.principal);
|
|
20
|
+
if (!principal)
|
|
21
|
+
return null;
|
|
22
|
+
const session = { principal, display: text(b.display) ?? text(b.email) ?? principal };
|
|
23
|
+
const email = text(b.email);
|
|
24
|
+
const picture = text(b.picture);
|
|
25
|
+
const team = text(b.team);
|
|
26
|
+
if (email)
|
|
27
|
+
session.email = email;
|
|
28
|
+
if (picture)
|
|
29
|
+
session.picture = picture;
|
|
30
|
+
if (team)
|
|
31
|
+
session.team = team;
|
|
32
|
+
return session;
|
|
33
|
+
}
|
|
34
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
35
|
+
const firstGrapheme = (word) => segmenter.segment(word)[Symbol.iterator]().next().value?.segment ?? '';
|
|
36
|
+
/**
|
|
37
|
+
* One or two letters for a name, by grapheme rather than code unit, so an accented or
|
|
38
|
+
* non-Latin first letter, or an emoji, is never cut in half. A single name gives one
|
|
39
|
+
* letter; "Ada Lovelace" gives "AL", "María José García" gives "MG" (first and last).
|
|
40
|
+
* A name with no letter-like start (a bare email local part, say) still gives its
|
|
41
|
+
* first grapheme.
|
|
42
|
+
*/
|
|
43
|
+
export function initialsOf(display) {
|
|
44
|
+
const name = display.includes('@') ? display.slice(0, display.indexOf('@')) : display;
|
|
45
|
+
const words = name.split(/[\s._-]+/u).filter(Boolean);
|
|
46
|
+
if (words.length === 0)
|
|
47
|
+
return '?';
|
|
48
|
+
const first = firstGrapheme(words[0]);
|
|
49
|
+
const last = words.length > 1 ? firstGrapheme(words[words.length - 1]) : '';
|
|
50
|
+
return (first + last).toLocaleUpperCase();
|
|
51
|
+
}
|
|
52
|
+
/** The Web Awesome palette hues an avatar may take; gray is left out for "nobody". */
|
|
53
|
+
export const HUES = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink'];
|
|
54
|
+
/**
|
|
55
|
+
* A hue chosen by the principal, not the name, so a person keeps their colour when they
|
|
56
|
+
* change how they are called. FNV-1a over the UTF-16 units: stable and cheap, not a
|
|
57
|
+
* security property.
|
|
58
|
+
*/
|
|
59
|
+
export function hueOf(principal) {
|
|
60
|
+
let h = 0x811c9dc5;
|
|
61
|
+
for (let i = 0; i < principal.length; i++) {
|
|
62
|
+
h ^= principal.charCodeAt(i);
|
|
63
|
+
h = Math.imul(h, 0x01000193);
|
|
64
|
+
}
|
|
65
|
+
return HUES[(h >>> 0) % HUES.length];
|
|
66
|
+
}
|
|
67
|
+
export class SessionStore {
|
|
68
|
+
url;
|
|
69
|
+
fetchImpl;
|
|
70
|
+
#state = { phase: 'loading' };
|
|
71
|
+
#listeners = new Set();
|
|
72
|
+
#inFlight = null;
|
|
73
|
+
constructor(url, fetchImpl = (u, init) => globalThis.fetch(u, init)) {
|
|
74
|
+
this.url = url;
|
|
75
|
+
this.fetchImpl = fetchImpl;
|
|
76
|
+
}
|
|
77
|
+
get state() {
|
|
78
|
+
return this.#state;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Called with the current state now and on every change; returns the unsubscribe.
|
|
82
|
+
* The first subscriber starts the first read.
|
|
83
|
+
*/
|
|
84
|
+
subscribe(listener) {
|
|
85
|
+
this.#listeners.add(listener);
|
|
86
|
+
listener(this.#state);
|
|
87
|
+
if (this.#state.phase === 'loading' && !this.#inFlight)
|
|
88
|
+
void this.refresh();
|
|
89
|
+
return () => this.#listeners.delete(listener);
|
|
90
|
+
}
|
|
91
|
+
/** Read again. Calls made while a read is in flight share it. */
|
|
92
|
+
refresh() {
|
|
93
|
+
this.#inFlight ??= this.#read().finally(() => (this.#inFlight = null));
|
|
94
|
+
return this.#inFlight;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A failed read (network down, a 5xx) is "signed out" only on the first read; after
|
|
98
|
+
* that it keeps the last answer, because a blip must not sign somebody out on screen.
|
|
99
|
+
*/
|
|
100
|
+
async #read() {
|
|
101
|
+
let next;
|
|
102
|
+
try {
|
|
103
|
+
const res = await this.fetchImpl(this.url, { credentials: 'same-origin', headers: { accept: 'application/json' } });
|
|
104
|
+
const session = res.ok ? parseSession(await res.json()) : null;
|
|
105
|
+
next = session ? { phase: 'signed-in', session } : { phase: 'signed-out' };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
if (this.#state.phase !== 'loading')
|
|
109
|
+
return;
|
|
110
|
+
next = { phase: 'signed-out' };
|
|
111
|
+
}
|
|
112
|
+
if (sameState(this.#state, next))
|
|
113
|
+
return;
|
|
114
|
+
this.#state = next;
|
|
115
|
+
for (const l of this.#listeners)
|
|
116
|
+
l(next);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function sameState(a, b) {
|
|
120
|
+
if (a.phase !== b.phase)
|
|
121
|
+
return false;
|
|
122
|
+
if (a.phase !== 'signed-in' || b.phase !== 'signed-in')
|
|
123
|
+
return true;
|
|
124
|
+
return JSON.stringify(a.session) === JSON.stringify(b.session);
|
|
125
|
+
}
|
|
126
|
+
const stores = new Map();
|
|
127
|
+
/**
|
|
128
|
+
* The shared store for a session URL, resolved against the page. In a browser it reads
|
|
129
|
+
* again whenever the tab comes back into view, so signing out in another tab shows here
|
|
130
|
+
* the next time somebody looks — without polling.
|
|
131
|
+
*/
|
|
132
|
+
export function sessionAt(url) {
|
|
133
|
+
const key = typeof location === 'undefined' ? url : new URL(url, location.href).href;
|
|
134
|
+
let store = stores.get(key);
|
|
135
|
+
if (!store) {
|
|
136
|
+
const created = (store = new SessionStore(key));
|
|
137
|
+
stores.set(key, created);
|
|
138
|
+
if (typeof document !== 'undefined') {
|
|
139
|
+
document.addEventListener('visibilitychange', () => {
|
|
140
|
+
if (document.visibilityState === 'visible' && created.state.phase !== 'loading')
|
|
141
|
+
void created.refresh();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return store;
|
|
146
|
+
}
|
|
147
|
+
/** Where "Sign in" goes: back to this page once the issuer is done. */
|
|
148
|
+
export const signInHref = (urls, returnTo) => withParams(urls.login, { returnTo });
|
|
149
|
+
/** Where "Switch user" goes: `prompt=select_account` gets past the issuer's SSO session. */
|
|
150
|
+
export const switchUserHref = (urls, returnTo) => withParams(urls.login, { prompt: 'select_account', returnTo });
|
|
151
|
+
function withParams(base, params) {
|
|
152
|
+
const [path, query = ''] = base.split('?', 2);
|
|
153
|
+
const q = new URLSearchParams(query);
|
|
154
|
+
for (const [k, v] of Object.entries(params))
|
|
155
|
+
q.set(k, v);
|
|
156
|
+
return `${path}?${q}`;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Atelier — the warm one. Paper-toned neutrals, a serif for headings, roomier rows and a
|
|
3
|
+
* terracotta accent. For a vertical whose users are not at a desk all day.
|
|
4
|
+
*
|
|
5
|
+
* A Substrat theme is values for Web Awesome's own tokens and nothing else: no selectors
|
|
6
|
+
* into components, no raw CSS. Put the class on <html> beside Web Awesome's default
|
|
7
|
+
* theme (and `wa-dark` for dark mode). The layer is Web Awesome's last, so these win over
|
|
8
|
+
* its theme without `!important` and still lose to an app's unlayered styles.
|
|
9
|
+
*/
|
|
10
|
+
@layer wa-theme-overrides {
|
|
11
|
+
.sub-theme-atelier {
|
|
12
|
+
--wa-color-brand-95: oklch(96.8% 0.026 38);
|
|
13
|
+
--wa-color-brand-90: oklch(93.0% 0.037 38);
|
|
14
|
+
--wa-color-brand-80: oklch(86.0% 0.059 38);
|
|
15
|
+
--wa-color-brand-70: oklch(77.5% 0.086 38);
|
|
16
|
+
--wa-color-brand-60: oklch(66.5% 0.112 38);
|
|
17
|
+
--wa-color-brand-50: oklch(53.0% 0.140 38);
|
|
18
|
+
--wa-color-brand-40: oklch(45.0% 0.109 38);
|
|
19
|
+
--wa-color-brand-30: oklch(38.0% 0.081 38);
|
|
20
|
+
--wa-color-brand-20: oklch(32.0% 0.053 38);
|
|
21
|
+
--wa-color-brand-10: oklch(23.5% 0.026 38);
|
|
22
|
+
--wa-color-brand-05: oklch(17.0% 0.017 38);
|
|
23
|
+
--wa-color-brand: var(--wa-color-brand-50);
|
|
24
|
+
--wa-color-brand-on: white;
|
|
25
|
+
--wa-color-neutral-95: oklch(96.8% 0.006 70);
|
|
26
|
+
--wa-color-neutral-90: oklch(93.0% 0.006 70);
|
|
27
|
+
--wa-color-neutral-80: oklch(86.0% 0.007 70);
|
|
28
|
+
--wa-color-neutral-70: oklch(77.5% 0.010 70);
|
|
29
|
+
--wa-color-neutral-60: oklch(66.5% 0.013 70);
|
|
30
|
+
--wa-color-neutral-50: oklch(53.0% 0.016 70);
|
|
31
|
+
--wa-color-neutral-40: oklch(45.0% 0.012 70);
|
|
32
|
+
--wa-color-neutral-30: oklch(38.0% 0.009 70);
|
|
33
|
+
--wa-color-neutral-20: oklch(32.0% 0.006 70);
|
|
34
|
+
--wa-color-neutral-10: oklch(23.5% 0.006 70);
|
|
35
|
+
--wa-color-neutral-05: oklch(17.0% 0.006 70);
|
|
36
|
+
--wa-color-neutral: var(--wa-color-neutral-50);
|
|
37
|
+
--wa-font-family-body: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
38
|
+
--wa-font-family-heading: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, ui-serif, serif;
|
|
39
|
+
--wa-font-size-scale: 1.02;
|
|
40
|
+
--wa-space-scale: 1.12;
|
|
41
|
+
--wa-border-radius-scale: 1.6;
|
|
42
|
+
--wa-shadow-blur-scale: 0.6;
|
|
43
|
+
--wa-panel-border-radius: var(--wa-border-radius-l);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sub-theme-atelier:not(.wa-dark) {
|
|
47
|
+
--wa-color-surface-default: oklch(99.2% 0.006 80);
|
|
48
|
+
--wa-color-surface-raised: oklch(99.6% 0.004 80);
|
|
49
|
+
--wa-color-surface-lowered: oklch(96.4% 0.012 75);
|
|
50
|
+
--wa-color-surface-border: oklch(90.5% 0.016 70);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sub-theme-atelier.wa-dark {
|
|
54
|
+
--wa-color-surface-default: oklch(20% 0.012 60);
|
|
55
|
+
--wa-color-surface-raised: oklch(23% 0.013 60);
|
|
56
|
+
--wa-color-surface-lowered: oklch(17% 0.011 60);
|
|
57
|
+
--wa-color-surface-border: oklch(31% 0.014 60);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Web Awesome's default theme: every --wa-* token the elements read, in light and dark
|
|
3
|
+
* (`wa-dark` on <html>). A Substrat theme (ledger.css, atelier.css) is a class layered
|
|
4
|
+
* over this, so import this first and a theme after it. Only the theme — not Web
|
|
5
|
+
* Awesome's native-element styles — so an app's own <button> and <input> keep its look.
|
|
6
|
+
*/
|
|
7
|
+
@import '@awesome.me/webawesome/dist/styles/themes/default.css';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Ledger — the dense one. For screens people work in all day: compact rows, flat
|
|
3
|
+
* surfaces with hairline borders instead of shadows, small radii, one cool teal accent.
|
|
4
|
+
*
|
|
5
|
+
* A Substrat theme is values for Web Awesome's own tokens and nothing else: no selectors
|
|
6
|
+
* into components, no raw CSS. Put the class on <html> beside Web Awesome's default
|
|
7
|
+
* theme (and `wa-dark` for dark mode). The layer is Web Awesome's last, so these win over
|
|
8
|
+
* its theme without `!important` and still lose to an app's unlayered styles.
|
|
9
|
+
*/
|
|
10
|
+
@layer wa-theme-overrides {
|
|
11
|
+
.sub-theme-ledger {
|
|
12
|
+
--wa-color-brand-95: oklch(96.8% 0.019 200);
|
|
13
|
+
--wa-color-brand-90: oklch(93.0% 0.028 200);
|
|
14
|
+
--wa-color-brand-80: oklch(86.0% 0.044 200);
|
|
15
|
+
--wa-color-brand-70: oklch(77.5% 0.064 200);
|
|
16
|
+
--wa-color-brand-60: oklch(66.5% 0.084 200);
|
|
17
|
+
--wa-color-brand-50: oklch(53.0% 0.105 200);
|
|
18
|
+
--wa-color-brand-40: oklch(45.0% 0.082 200);
|
|
19
|
+
--wa-color-brand-30: oklch(38.0% 0.061 200);
|
|
20
|
+
--wa-color-brand-20: oklch(32.0% 0.040 200);
|
|
21
|
+
--wa-color-brand-10: oklch(23.5% 0.020 200);
|
|
22
|
+
--wa-color-brand-05: oklch(17.0% 0.013 200);
|
|
23
|
+
--wa-color-brand: var(--wa-color-brand-50);
|
|
24
|
+
--wa-color-brand-on: white;
|
|
25
|
+
--wa-color-neutral-95: oklch(96.8% 0.004 250);
|
|
26
|
+
--wa-color-neutral-90: oklch(93.0% 0.004 250);
|
|
27
|
+
--wa-color-neutral-80: oklch(86.0% 0.005 250);
|
|
28
|
+
--wa-color-neutral-70: oklch(77.5% 0.007 250);
|
|
29
|
+
--wa-color-neutral-60: oklch(66.5% 0.010 250);
|
|
30
|
+
--wa-color-neutral-50: oklch(53.0% 0.012 250);
|
|
31
|
+
--wa-color-neutral-40: oklch(45.0% 0.009 250);
|
|
32
|
+
--wa-color-neutral-30: oklch(38.0% 0.007 250);
|
|
33
|
+
--wa-color-neutral-20: oklch(32.0% 0.005 250);
|
|
34
|
+
--wa-color-neutral-10: oklch(23.5% 0.004 250);
|
|
35
|
+
--wa-color-neutral-05: oklch(17.0% 0.004 250);
|
|
36
|
+
--wa-color-neutral: var(--wa-color-neutral-50);
|
|
37
|
+
--wa-font-family-body: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
38
|
+
--wa-font-family-heading: var(--wa-font-family-body);
|
|
39
|
+
--wa-font-size-scale: 0.94;
|
|
40
|
+
--wa-space-scale: 0.82;
|
|
41
|
+
--wa-border-radius-scale: 0.45;
|
|
42
|
+
--wa-shadow-offset-y-scale: 0;
|
|
43
|
+
--wa-shadow-blur-scale: 0;
|
|
44
|
+
--wa-form-control-height: 2.1rem;
|
|
45
|
+
--wa-panel-border-radius: var(--wa-border-radius-s);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sub-theme-ledger:not(.wa-dark) {
|
|
49
|
+
--wa-color-surface-lowered: var(--wa-color-neutral-95);
|
|
50
|
+
--wa-color-surface-border: var(--wa-color-neutral-90);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sub-theme-ledger.wa-dark {
|
|
54
|
+
--wa-color-surface-default: var(--wa-color-neutral-05);
|
|
55
|
+
--wa-color-surface-raised: var(--wa-color-neutral-10);
|
|
56
|
+
--wa-color-surface-lowered: oklch(14% 0.008 250);
|
|
57
|
+
--wa-color-surface-border: var(--wa-color-neutral-20);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Web Awesome's styles for plain HTML — <button>, <input>, <table>, headings, links —
|
|
3
|
+
* from the same --wa-* tokens, so markup that is not a component still matches the
|
|
4
|
+
* theme. In Web Awesome's `wa-native` layer: an app's unlayered CSS still wins.
|
|
5
|
+
*/
|
|
6
|
+
@import '@awesome.me/webawesome/dist/styles/native.css';
|
|
7
|
+
|
|
8
|
+
/* `wa-visually-hidden-label`: a control whose label is read but not shown. A `wa-*`
|
|
9
|
+
control is named by its `label` attribute — `aria-label` on the host does not reach
|
|
10
|
+
the input inside its shadow root. */
|
|
11
|
+
@import '@awesome.me/webawesome/dist/styles/utilities/visually-hidden.css';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<sub-user-menu>` — the account menu in a vertical's header: who is signed in, the
|
|
3
|
+
* vertical's own account items, and switching user and signing out, which it owns
|
|
4
|
+
* because it is the one place that knows the auth endpoints.
|
|
5
|
+
*
|
|
6
|
+
* A `wa-dropdown` around a `<sub-avatar>`. Signed out, it draws "Sign in" instead; while
|
|
7
|
+
* the first session read is in flight it draws the avatar's empty circle, never "Sign in".
|
|
8
|
+
*
|
|
9
|
+
* Slots (each has a default; slot something in to replace it):
|
|
10
|
+
* (default) — your items, `<wa-dropdown-item value="…">`; their `wa-select` reaches
|
|
11
|
+
* you as usual
|
|
12
|
+
* `trigger` — what is clicked (default: the avatar, with `show` beside it). It is
|
|
13
|
+
* put inside the menu's own `<button>`, so put no button in it
|
|
14
|
+
* `header` — the top of the menu (default: picture, name, then email and team)
|
|
15
|
+
* `teams` — between the header and your items; empty until team switching exists
|
|
16
|
+
* `switch-user` — default "Switch user" (`hide-switch-user` drops it)
|
|
17
|
+
* `sign-out` — default "Sign out"; a replacement is yours to handle
|
|
18
|
+
* `signed-out` — what shows when nobody is signed in (default: a "Sign in" button);
|
|
19
|
+
* an empty `<span slot="signed-out">` shows nothing
|
|
20
|
+
*
|
|
21
|
+
* Attributes: `show` (`avatar` | `name` | `email`, default `avatar`), `size`,
|
|
22
|
+
* `session-url` (`/api/me`), `login-url` (`/api/auth/login`), `logout-url`
|
|
23
|
+
* (`/api/auth/logout`), `account-url` (adds "Manage account" when set),
|
|
24
|
+
* `hide-switch-user`, `placement` (default `bottom-end`).
|
|
25
|
+
* Property: `session` — set it to draw a session the host already read.
|
|
26
|
+
* Events, both cancelable — `preventDefault()` keeps the navigation from happening:
|
|
27
|
+
* `sub-sign-out`, `sub-switch-user`. The built-in items send no `wa-select`.
|
|
28
|
+
* Method: `refresh()` — read the session again.
|
|
29
|
+
*
|
|
30
|
+
* Sign-out is a top-level navigation, never a `fetch`: the logout route sends the
|
|
31
|
+
* browser on to the issuer, and the cookie carrying the logout hint is scoped to that
|
|
32
|
+
* one path.
|
|
33
|
+
*/
|
|
34
|
+
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
35
|
+
import '@awesome.me/webawesome/dist/components/divider/divider.js';
|
|
36
|
+
import '@awesome.me/webawesome/dist/components/dropdown/dropdown.js';
|
|
37
|
+
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
|
38
|
+
import './avatar.js';
|
|
39
|
+
import type { AvatarShow } from './avatar.js';
|
|
40
|
+
import SubstratElement from './internal/substrat-element.js';
|
|
41
|
+
import { type Session } from './session.js';
|
|
42
|
+
export declare class SubUserMenu extends SubstratElement {
|
|
43
|
+
#private;
|
|
44
|
+
static properties: {
|
|
45
|
+
show: {
|
|
46
|
+
reflect: boolean;
|
|
47
|
+
};
|
|
48
|
+
size: {};
|
|
49
|
+
sessionUrl: {
|
|
50
|
+
attribute: string;
|
|
51
|
+
};
|
|
52
|
+
loginUrl: {
|
|
53
|
+
attribute: string;
|
|
54
|
+
};
|
|
55
|
+
logoutUrl: {
|
|
56
|
+
attribute: string;
|
|
57
|
+
};
|
|
58
|
+
accountUrl: {
|
|
59
|
+
attribute: string;
|
|
60
|
+
};
|
|
61
|
+
hideSwitchUser: {
|
|
62
|
+
attribute: string;
|
|
63
|
+
type: BooleanConstructor;
|
|
64
|
+
};
|
|
65
|
+
placement: {};
|
|
66
|
+
session: {
|
|
67
|
+
attribute: boolean;
|
|
68
|
+
};
|
|
69
|
+
customTrigger: {
|
|
70
|
+
state: boolean;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
show: AvatarShow;
|
|
74
|
+
size: string;
|
|
75
|
+
sessionUrl: string;
|
|
76
|
+
loginUrl: string;
|
|
77
|
+
logoutUrl: string;
|
|
78
|
+
accountUrl: string | undefined;
|
|
79
|
+
hideSwitchUser: boolean;
|
|
80
|
+
placement: 'bottom-end' | 'bottom-start' | 'top-end' | 'top-start';
|
|
81
|
+
/** `undefined`: read it. `null`: nobody. */
|
|
82
|
+
session: Session | null | undefined;
|
|
83
|
+
customTrigger: boolean;
|
|
84
|
+
constructor();
|
|
85
|
+
/** Read the session again (every element on that URL redraws). */
|
|
86
|
+
refresh(): Promise<void>;
|
|
87
|
+
static css: import("lit").CSSResult;
|
|
88
|
+
render(): import("lit").TemplateResult<1>;
|
|
89
|
+
}
|
|
90
|
+
declare global {
|
|
91
|
+
interface HTMLElementTagNameMap {
|
|
92
|
+
'sub-user-menu': SubUserMenu;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=user-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-menu.d.ts","sourceRoot":"","sources":["../src/user-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,yDAAyD,CAAC;AACjE,OAAO,2DAA2D,CAAC;AACnE,OAAO,6DAA6D,CAAC;AACrE,OAAO,uEAAuE,CAAC;AAC/E,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,eAAe,MAAM,gCAAgC,CAAC;AAE7D,OAAO,EAA8B,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAQxE,qBAAa,WAAY,SAAQ,eAAe;;IAC9C,MAAM,CAAC,UAAU;QACf,IAAI;YAAI,OAAO;;QACf,IAAI;QACJ,UAAU;YAAI,SAAS;;QACvB,QAAQ;YAAI,SAAS;;QACrB,SAAS;YAAI,SAAS;;QACtB,UAAU;YAAI,SAAS;;QACvB,cAAc;YAAI,SAAS;YAAsB,IAAI;;QACrD,SAAS;QACT,OAAO;YAAI,SAAS;;QACpB,aAAa;YAAI,KAAK;;MACtB;IAEM,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,YAAY,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;IAC3E,4CAA4C;IACpC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,aAAa,EAAE,OAAO,CAAC;IAI/B,cAUC;IAED,kEAAkE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAEvB;IAED,MAAM,CAAC,GAAG,0BA6DR;IAEF,MAAM,oCA8CL;CA2BF;AAOD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,WAAW,CAAC;KAC9B;CACF"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<sub-user-menu>` — the account menu in a vertical's header: who is signed in, the
|
|
3
|
+
* vertical's own account items, and switching user and signing out, which it owns
|
|
4
|
+
* because it is the one place that knows the auth endpoints.
|
|
5
|
+
*
|
|
6
|
+
* A `wa-dropdown` around a `<sub-avatar>`. Signed out, it draws "Sign in" instead; while
|
|
7
|
+
* the first session read is in flight it draws the avatar's empty circle, never "Sign in".
|
|
8
|
+
*
|
|
9
|
+
* Slots (each has a default; slot something in to replace it):
|
|
10
|
+
* (default) — your items, `<wa-dropdown-item value="…">`; their `wa-select` reaches
|
|
11
|
+
* you as usual
|
|
12
|
+
* `trigger` — what is clicked (default: the avatar, with `show` beside it). It is
|
|
13
|
+
* put inside the menu's own `<button>`, so put no button in it
|
|
14
|
+
* `header` — the top of the menu (default: picture, name, then email and team)
|
|
15
|
+
* `teams` — between the header and your items; empty until team switching exists
|
|
16
|
+
* `switch-user` — default "Switch user" (`hide-switch-user` drops it)
|
|
17
|
+
* `sign-out` — default "Sign out"; a replacement is yours to handle
|
|
18
|
+
* `signed-out` — what shows when nobody is signed in (default: a "Sign in" button);
|
|
19
|
+
* an empty `<span slot="signed-out">` shows nothing
|
|
20
|
+
*
|
|
21
|
+
* Attributes: `show` (`avatar` | `name` | `email`, default `avatar`), `size`,
|
|
22
|
+
* `session-url` (`/api/me`), `login-url` (`/api/auth/login`), `logout-url`
|
|
23
|
+
* (`/api/auth/logout`), `account-url` (adds "Manage account" when set),
|
|
24
|
+
* `hide-switch-user`, `placement` (default `bottom-end`).
|
|
25
|
+
* Property: `session` — set it to draw a session the host already read.
|
|
26
|
+
* Events, both cancelable — `preventDefault()` keeps the navigation from happening:
|
|
27
|
+
* `sub-sign-out`, `sub-switch-user`. The built-in items send no `wa-select`.
|
|
28
|
+
* Method: `refresh()` — read the session again.
|
|
29
|
+
*
|
|
30
|
+
* Sign-out is a top-level navigation, never a `fetch`: the logout route sends the
|
|
31
|
+
* browser on to the issuer, and the cookie carrying the logout hint is scoped to that
|
|
32
|
+
* one path.
|
|
33
|
+
*/
|
|
34
|
+
import '@awesome.me/webawesome/dist/components/button/button.js';
|
|
35
|
+
import '@awesome.me/webawesome/dist/components/divider/divider.js';
|
|
36
|
+
import '@awesome.me/webawesome/dist/components/dropdown/dropdown.js';
|
|
37
|
+
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
|
38
|
+
import './avatar.js';
|
|
39
|
+
import { css, html, nothing } from 'lit';
|
|
40
|
+
import SubstratElement from './internal/substrat-element.js';
|
|
41
|
+
import { SessionController } from './internal/session-controller.js';
|
|
42
|
+
import { signInHref, switchUserHref } from './session.js';
|
|
43
|
+
export class SubUserMenu extends SubstratElement {
|
|
44
|
+
static properties = {
|
|
45
|
+
show: { reflect: true },
|
|
46
|
+
size: {},
|
|
47
|
+
sessionUrl: { attribute: 'session-url' },
|
|
48
|
+
loginUrl: { attribute: 'login-url' },
|
|
49
|
+
logoutUrl: { attribute: 'logout-url' },
|
|
50
|
+
accountUrl: { attribute: 'account-url' },
|
|
51
|
+
hideSwitchUser: { attribute: 'hide-switch-user', type: Boolean },
|
|
52
|
+
placement: {},
|
|
53
|
+
session: { attribute: false },
|
|
54
|
+
customTrigger: { state: true },
|
|
55
|
+
};
|
|
56
|
+
#session = new SessionController(this);
|
|
57
|
+
constructor() {
|
|
58
|
+
super();
|
|
59
|
+
this.show = 'avatar';
|
|
60
|
+
this.size = '2rem';
|
|
61
|
+
this.sessionUrl = '/api/me';
|
|
62
|
+
this.loginUrl = '/api/auth/login';
|
|
63
|
+
this.logoutUrl = '/api/auth/logout';
|
|
64
|
+
this.hideSwitchUser = false;
|
|
65
|
+
this.placement = 'bottom-end';
|
|
66
|
+
this.customTrigger = false;
|
|
67
|
+
}
|
|
68
|
+
/** Read the session again (every element on that URL redraws). */
|
|
69
|
+
refresh() {
|
|
70
|
+
return this.#session.refresh();
|
|
71
|
+
}
|
|
72
|
+
static css = css `
|
|
73
|
+
:host {
|
|
74
|
+
display: inline-block;
|
|
75
|
+
font-family: var(--wa-font-family-body);
|
|
76
|
+
}
|
|
77
|
+
.trigger {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: var(--wa-space-2xs);
|
|
81
|
+
max-width: 100%;
|
|
82
|
+
padding: var(--wa-space-3xs);
|
|
83
|
+
margin: 0;
|
|
84
|
+
border: 0;
|
|
85
|
+
border-radius: var(--wa-border-radius-pill);
|
|
86
|
+
background: none;
|
|
87
|
+
color: inherit;
|
|
88
|
+
font: inherit;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
:host(:not([show='avatar'])) .trigger {
|
|
92
|
+
padding-inline-end: var(--wa-space-xs);
|
|
93
|
+
}
|
|
94
|
+
.trigger:hover {
|
|
95
|
+
background: var(--wa-color-neutral-fill-quiet);
|
|
96
|
+
}
|
|
97
|
+
.trigger:focus-visible {
|
|
98
|
+
outline: var(--wa-focus-ring);
|
|
99
|
+
outline-offset: var(--wa-focus-ring-offset);
|
|
100
|
+
}
|
|
101
|
+
.placeholder {
|
|
102
|
+
display: inline-block;
|
|
103
|
+
border-radius: 50%;
|
|
104
|
+
background: var(--wa-color-neutral-fill-quiet);
|
|
105
|
+
}
|
|
106
|
+
.header {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: var(--wa-space-s);
|
|
110
|
+
padding: var(--wa-space-xs) var(--wa-space-m) var(--wa-space-s);
|
|
111
|
+
max-width: 18rem;
|
|
112
|
+
}
|
|
113
|
+
.who {
|
|
114
|
+
min-width: 0;
|
|
115
|
+
line-height: var(--wa-line-height-condensed);
|
|
116
|
+
}
|
|
117
|
+
.who > * {
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
text-overflow: ellipsis;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
}
|
|
122
|
+
.name {
|
|
123
|
+
font-weight: var(--wa-font-weight-semibold);
|
|
124
|
+
color: var(--wa-color-text-normal);
|
|
125
|
+
}
|
|
126
|
+
.detail {
|
|
127
|
+
font-size: var(--wa-font-size-xs);
|
|
128
|
+
color: var(--wa-color-text-quiet);
|
|
129
|
+
}
|
|
130
|
+
wa-divider {
|
|
131
|
+
--spacing: var(--wa-space-2xs);
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
render() {
|
|
135
|
+
const state = this.#session.state;
|
|
136
|
+
if (state.phase === 'loading') {
|
|
137
|
+
return html `<span class="placeholder" style="width: ${this.size}; height: ${this.size}" aria-hidden="true"></span>`;
|
|
138
|
+
}
|
|
139
|
+
if (state.phase === 'signed-out') {
|
|
140
|
+
return html `<slot name="signed-out"
|
|
141
|
+
><wa-button size="s" variant="brand" href=${signInHref(this.#urls, here())}>Sign in</wa-button></slot
|
|
142
|
+
>`;
|
|
143
|
+
}
|
|
144
|
+
const s = state.session;
|
|
145
|
+
return html `<wa-dropdown placement=${this.placement} @wa-select=${this.#onSelect}>
|
|
146
|
+
<button
|
|
147
|
+
slot="trigger"
|
|
148
|
+
class="trigger"
|
|
149
|
+
part="trigger"
|
|
150
|
+
aria-label=${this.show === 'avatar' && !this.customTrigger ? `Account: ${s.display}` : nothing}
|
|
151
|
+
>
|
|
152
|
+
<slot name="trigger" @slotchange=${this.#onTriggerSlot}
|
|
153
|
+
><sub-avatar .session=${s} show=${this.show} size=${this.size}></sub-avatar
|
|
154
|
+
></slot>
|
|
155
|
+
</button>
|
|
156
|
+
<slot name="header">
|
|
157
|
+
<div class="header" part="header">
|
|
158
|
+
<sub-avatar .session=${s} size="2.5rem" aria-hidden="true"></sub-avatar>
|
|
159
|
+
<div class="who">
|
|
160
|
+
<div class="name">${s.display}</div>
|
|
161
|
+
${s.email && s.email !== s.display ? html `<div class="detail">${s.email}</div>` : nothing}
|
|
162
|
+
${s.team ? html `<div class="detail">${s.team}</div>` : nothing}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</slot>
|
|
166
|
+
<wa-divider></wa-divider>
|
|
167
|
+
<slot name="teams"></slot>
|
|
168
|
+
<slot></slot>
|
|
169
|
+
${this.accountUrl
|
|
170
|
+
? html `<wa-dropdown-item data-builtin="account" href=${this.accountUrl}>Manage account</wa-dropdown-item>`
|
|
171
|
+
: nothing}
|
|
172
|
+
<slot name="switch-user"
|
|
173
|
+
>${this.hideSwitchUser
|
|
174
|
+
? nothing
|
|
175
|
+
: html `<wa-dropdown-item data-builtin="switch-user">Switch user</wa-dropdown-item>`}</slot
|
|
176
|
+
>
|
|
177
|
+
<wa-divider></wa-divider>
|
|
178
|
+
<slot name="sign-out"><wa-dropdown-item data-builtin="sign-out">Sign out</wa-dropdown-item></slot>
|
|
179
|
+
</wa-dropdown>`;
|
|
180
|
+
}
|
|
181
|
+
get #urls() {
|
|
182
|
+
return { login: this.loginUrl, logout: this.logoutUrl };
|
|
183
|
+
}
|
|
184
|
+
#onTriggerSlot(e) {
|
|
185
|
+
this.customTrigger = e.target.assignedNodes().length > 0;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* A built-in item is recognised by being OURS — in this shadow root — never by its
|
|
189
|
+
* value, so a slotted `<wa-dropdown-item value="sign-out">` of the host's is the
|
|
190
|
+
* host's. Ours stop here: the host hears `sub-sign-out`, not a `wa-select` it would
|
|
191
|
+
* have to tell apart from its own.
|
|
192
|
+
*/
|
|
193
|
+
#onSelect(e) {
|
|
194
|
+
const item = e.detail.item;
|
|
195
|
+
const builtin = item.getRootNode() === this.renderRoot ? item.dataset.builtin : undefined;
|
|
196
|
+
if (!builtin)
|
|
197
|
+
return;
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
if (builtin === 'account')
|
|
200
|
+
return; // its href navigates
|
|
201
|
+
const name = builtin === 'sign-out' ? 'sub-sign-out' : 'sub-switch-user';
|
|
202
|
+
const go = new CustomEvent(name, { bubbles: true, composed: true, cancelable: true });
|
|
203
|
+
if (!this.dispatchEvent(go))
|
|
204
|
+
return;
|
|
205
|
+
location.assign(builtin === 'sign-out' ? this.logoutUrl : switchUserHref(this.#urls, here()));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** This page, as the place to come back to. */
|
|
209
|
+
const here = () => location.pathname + location.search + location.hash;
|
|
210
|
+
if (!customElements.get('sub-user-menu'))
|
|
211
|
+
customElements.define('sub-user-menu', SubUserMenu);
|
|
212
|
+
//# sourceMappingURL=user-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-menu.js","sourceRoot":"","sources":["../src/user-menu.ts"],"names":[],"mappings":"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"}
|