@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.
Files changed (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +58 -2
  3. package/dist/avatar.d.ts +56 -0
  4. package/dist/avatar.d.ts.map +1 -0
  5. package/dist/avatar.js +107 -0
  6. package/dist/avatar.js.map +1 -0
  7. package/dist/controls.d.ts +21 -0
  8. package/dist/controls.d.ts.map +1 -0
  9. package/dist/controls.js +21 -0
  10. package/dist/controls.js.map +1 -0
  11. package/dist/entity-table-model.d.ts +269 -0
  12. package/dist/entity-table-model.d.ts.map +1 -0
  13. package/dist/entity-table-model.js +490 -0
  14. package/dist/entity-table-model.js.map +1 -0
  15. package/dist/entity-table.d.ts +187 -0
  16. package/dist/entity-table.d.ts.map +1 -0
  17. package/dist/entity-table.js +1004 -0
  18. package/dist/entity-table.js.map +1 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/internal/session-controller.d.ts +25 -0
  24. package/dist/internal/session-controller.d.ts.map +1 -0
  25. package/dist/internal/session-controller.js +46 -0
  26. package/dist/internal/session-controller.js.map +1 -0
  27. package/dist/internal/substrat-element.d.ts +36 -0
  28. package/dist/internal/substrat-element.d.ts.map +1 -0
  29. package/dist/internal/substrat-element.js +195 -0
  30. package/dist/internal/substrat-element.js.map +1 -0
  31. package/dist/jsx.d.ts +8 -0
  32. package/dist/jsx.d.ts.map +1 -0
  33. package/dist/jsx.js +2 -0
  34. package/dist/jsx.js.map +1 -0
  35. package/dist/session.d.ts +84 -0
  36. package/dist/session.d.ts.map +1 -0
  37. package/dist/session.js +158 -0
  38. package/dist/session.js.map +1 -0
  39. package/dist/themes/atelier.css +59 -0
  40. package/dist/themes/base.css +7 -0
  41. package/dist/themes/ledger.css +59 -0
  42. package/dist/themes/native.css +11 -0
  43. package/dist/user-menu.d.ts +95 -0
  44. package/dist/user-menu.d.ts.map +1 -0
  45. package/dist/user-menu.js +212 -0
  46. package/dist/user-menu.js.map +1 -0
  47. package/package.json +47 -3
@@ -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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"}