@flung.dev/connect 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -12
- package/dist/core/client.d.ts +9 -2
- package/dist/core/client.js +29 -10
- package/dist/core/controller.d.ts +214 -0
- package/dist/core/controller.js +407 -0
- package/dist/core/events.d.ts +25 -0
- package/dist/core/events.js +1 -0
- package/dist/core/index.d.ts +10 -4
- package/dist/core/index.js +6 -3
- package/dist/core/labels.d.ts +78 -0
- package/dist/core/labels.js +41 -0
- package/dist/core/platforms.d.ts +7 -1
- package/dist/core/platforms.js +11 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +1 -0
- package/dist/element/copy.d.ts +3 -33
- package/dist/element/copy.js +2 -29
- package/dist/element/dom.d.ts +25 -5
- package/dist/element/dom.js +49 -5
- package/dist/element/events.d.ts +14 -18
- package/dist/element/flung-connect.d.ts +21 -1
- package/dist/element/flung-connect.js +221 -190
- package/dist/element/marks.d.ts +17 -0
- package/dist/element/marks.js +35 -0
- package/dist/element/styles.d.ts +6 -4
- package/dist/element/styles.js +195 -52
- package/dist/element/tooltip.d.ts +26 -0
- package/dist/element/tooltip.js +135 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +0 -1
- package/dist/react/index.d.ts +53 -3
- package/dist/react/index.js +40 -2
- package/dist/react/use-flung-connect.d.ts +13 -0
- package/dist/react/use-flung-connect.js +65 -0
- package/package.json +1 -1
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { STYLES } from './styles.js';
|
|
7
|
-
|
|
8
|
-
const
|
|
1
|
+
import { createConnectController, initialConnectState } from '../core/controller.js';
|
|
2
|
+
import { DEFAULT_LABELS, formatLabel as format } from '../core/labels.js';
|
|
3
|
+
import { parsePlatformList } from '../core/platforms.js';
|
|
4
|
+
import { avatar, button, html, mark } from './dom.js';
|
|
5
|
+
import { CHOICE_MARKS, FLUNG_MARK, PLATFORM_MARKS } from './marks.js';
|
|
6
|
+
import { BASE_STYLES, STYLES } from './styles.js';
|
|
7
|
+
import { panelTooltip, TOOLTIP_BASE_STYLES, TOOLTIP_STYLES } from './tooltip.js';
|
|
8
|
+
const LAYOUTS = ['list', 'grid', 'compact'];
|
|
9
|
+
/**
|
|
10
|
+
* Which of a platform's tiles its step shows under: a reconnect or a disconnect under the account's tile, a connect under
|
|
11
|
+
* the tile of the variant it is for, anything else under the first. -1 when none matches.
|
|
12
|
+
*/
|
|
13
|
+
function stepTile(item, tiles) {
|
|
14
|
+
const { step } = item;
|
|
15
|
+
const accountId = step.kind === 'confirm' ? step.account.account.id : step.kind === 'waiting' ? step.accountId : null;
|
|
16
|
+
if (accountId !== null)
|
|
17
|
+
return tiles.findIndex((tile) => tile.accounts.some((account) => account.account.id === accountId));
|
|
18
|
+
if (step.kind === 'waiting')
|
|
19
|
+
return tiles.findIndex((tile) => tile.variant === step.variant);
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
/** The key of a tile's Connect button, so focus can come back to it. */
|
|
23
|
+
const connectKey = (platform, variant) => (variant === null ? `connect-${platform}` : `connect-${platform}-${variant}`);
|
|
9
24
|
/** Builds the class on first use, so importing the package on a server, where there is no `HTMLElement`, is safe. */
|
|
10
25
|
export function createFlungConnectClass() {
|
|
11
26
|
return class FlungConnect extends HTMLElement {
|
|
12
|
-
static observedAttributes = ['token', 'base-url', 'platforms'];
|
|
27
|
+
static observedAttributes = ['token', 'base-url', 'platforms', 'branding', 'mode', 'layout', 'unstyled', 'variants'];
|
|
13
28
|
#root = this.attachShadow({ mode: 'open' });
|
|
29
|
+
#tooltip = panelTooltip(this.#root);
|
|
14
30
|
#source = null;
|
|
15
|
-
#client = null;
|
|
16
31
|
#labels = DEFAULT_LABELS;
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
#focus = null;
|
|
32
|
+
#controller = null;
|
|
33
|
+
#unsubscribe = null;
|
|
34
|
+
/** Each platform's step when the panel was last drawn, and the variant it was for, to move focus when one changes. */
|
|
35
|
+
#steps = new Map();
|
|
22
36
|
#started = false;
|
|
23
37
|
get token() {
|
|
24
38
|
return this.#source;
|
|
@@ -32,10 +46,22 @@ export function createFlungConnectClass() {
|
|
|
32
46
|
}
|
|
33
47
|
set labels(value) {
|
|
34
48
|
this.#labels = { ...DEFAULT_LABELS, ...value };
|
|
35
|
-
this.#
|
|
49
|
+
if (this.#controller === null)
|
|
50
|
+
this.#render();
|
|
51
|
+
else
|
|
52
|
+
this.#controller.configure({ labels: this.#labels });
|
|
53
|
+
}
|
|
54
|
+
get state() {
|
|
55
|
+
return this.#controller?.getState() ?? initialConnectState(this.#source !== null);
|
|
56
|
+
}
|
|
57
|
+
get controller() {
|
|
58
|
+
return this.#controller;
|
|
59
|
+
}
|
|
60
|
+
connect(platform, options) {
|
|
61
|
+
this.#controller?.connect(platform, options);
|
|
36
62
|
}
|
|
37
63
|
refresh() {
|
|
38
|
-
return this.#
|
|
64
|
+
return this.#controller?.refresh() ?? Promise.resolve();
|
|
39
65
|
}
|
|
40
66
|
connectedCallback() {
|
|
41
67
|
if (this.#started)
|
|
@@ -48,204 +74,209 @@ export function createFlungConnectClass() {
|
|
|
48
74
|
attributeChangedCallback(name, previous, next) {
|
|
49
75
|
if (previous === next || !this.#started)
|
|
50
76
|
return;
|
|
51
|
-
if (name === 'token')
|
|
77
|
+
if (name === 'token') {
|
|
52
78
|
this.#source = next;
|
|
53
|
-
if (name === 'platforms')
|
|
54
|
-
void this.#load();
|
|
55
|
-
else
|
|
56
79
|
this.#reset();
|
|
80
|
+
}
|
|
81
|
+
else if (name === 'base-url')
|
|
82
|
+
this.#reset();
|
|
83
|
+
else if (name === 'platforms')
|
|
84
|
+
this.#controller?.configure({ platforms: parsePlatformList(next) });
|
|
85
|
+
else if (name === 'mode')
|
|
86
|
+
this.#controller?.configure({ mode: next === 'redirect' ? 'redirect' : 'popup' });
|
|
87
|
+
else
|
|
88
|
+
this.#render();
|
|
57
89
|
}
|
|
90
|
+
/** A new token or API: a new controller, which reads everything again and reports a redirect that came back. */
|
|
58
91
|
#reset() {
|
|
59
92
|
if (!this.#started)
|
|
60
93
|
return;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.#platforms = platforms.map((info) => info.platform).filter((platform) => platforms.find((info) => info.platform === platform)?.connectable === true && (asked ?? [platform]).includes(platform) && (granted ?? [platform]).includes(platform));
|
|
79
|
-
this.#accounts = accounts.filter((account) => account.status !== 'disconnected' && this.#platforms.includes(account.platform));
|
|
80
|
-
if (this.#message.text === this.#labels.loading || this.#message.text === this.#labels.noToken)
|
|
81
|
-
this.#message = { text: this.#platforms.length === 0 ? this.#labels.empty : '', error: false };
|
|
82
|
-
this.#render();
|
|
83
|
-
this.#emit('flung-load', { accounts: this.#accounts });
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
this.#fail(error, (message) => format(this.#labels.loadFailed, { message }));
|
|
87
|
-
}
|
|
94
|
+
this.#unsubscribe?.();
|
|
95
|
+
this.#controller?.destroy();
|
|
96
|
+
this.#steps.clear();
|
|
97
|
+
const controller = createConnectController({
|
|
98
|
+
token: this.#source,
|
|
99
|
+
baseUrl: this.getAttribute('base-url') ?? undefined,
|
|
100
|
+
platforms: parsePlatformList(this.getAttribute('platforms')),
|
|
101
|
+
mode: this.getAttribute('mode') === 'redirect' ? 'redirect' : 'popup',
|
|
102
|
+
labels: this.#labels,
|
|
103
|
+
onLoad: (detail) => this.#emit('flung-load', detail),
|
|
104
|
+
onConnect: (detail) => this.#emit('flung-connect', detail),
|
|
105
|
+
onDisconnect: (detail) => this.#emit('flung-disconnect', detail),
|
|
106
|
+
onError: (detail) => this.#emit('flung-error', detail),
|
|
107
|
+
});
|
|
108
|
+
this.#controller = controller;
|
|
109
|
+
this.#unsubscribe = controller.subscribe(() => this.#render());
|
|
110
|
+
this.#render();
|
|
88
111
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (typeof location === 'undefined')
|
|
92
|
-
return;
|
|
93
|
-
const result = readRedirectResult(location.href);
|
|
94
|
-
if (result === null)
|
|
95
|
-
return;
|
|
96
|
-
history.replaceState(history.state, '', withoutRedirectResult(location.href));
|
|
97
|
-
if (result.status === 'connected') {
|
|
98
|
-
const accounts = this.#accounts.filter((account) => result.accountIds.includes(account.id));
|
|
99
|
-
const platform = accounts[0]?.platform;
|
|
100
|
-
this.#say(format(this.#labels.connected, { platform: platform === undefined ? 'The account' : PLATFORM_NAMES[platform] }));
|
|
101
|
-
this.#emit('flung-connect', { session: null, accounts });
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
this.#say(result.error?.message ?? '', true);
|
|
105
|
-
this.#emit('flung-error', { code: result.error?.code ?? 'platform_error', message: result.error?.message ?? '' });
|
|
106
|
-
}
|
|
112
|
+
#emit(type, detail) {
|
|
113
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
107
114
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
this
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
void connectInPopup(client, begin, { signal: abort.signal })
|
|
125
|
-
.then(async (outcome) => {
|
|
126
|
-
if (outcome.kind === 'finished')
|
|
127
|
-
return outcome.session;
|
|
128
|
-
row.blockedUrl = outcome.session.authorizationUrl;
|
|
129
|
-
this.#render();
|
|
130
|
-
return client.waitForSession(outcome.session.id, { signal: abort.signal });
|
|
131
|
-
})
|
|
132
|
-
.then((session) => this.#finish(platform, session), (error) => this.#settle(platform, error));
|
|
133
|
-
}
|
|
134
|
-
async #finish(platform, session) {
|
|
135
|
-
this.#rows.set(platform, { kind: 'idle' });
|
|
136
|
-
const name = PLATFORM_NAMES[platform];
|
|
137
|
-
if (session.status === 'connected') {
|
|
138
|
-
await this.#load();
|
|
139
|
-
this.#say(format(this.#labels.connected, { platform: name }));
|
|
140
|
-
this.#emit('flung-connect', { session, accounts: this.#accounts.filter((account) => session.accountIds.includes(account.id)) });
|
|
141
|
-
}
|
|
142
|
-
else if (session.status === 'expired') {
|
|
143
|
-
this.#say(format(this.#labels.expired, { platform: name }), true);
|
|
144
|
-
this.#emit('flung-error', { code: 'expired', message: this.#message.text });
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
const message = session.error?.message ?? '';
|
|
148
|
-
this.#say(format(this.#labels.failed, { platform: name, message }), true);
|
|
149
|
-
this.#emit('flung-error', { code: session.error?.code ?? 'platform_error', message });
|
|
150
|
-
}
|
|
115
|
+
#render() {
|
|
116
|
+
const state = this.state;
|
|
117
|
+
const labels = state.labels;
|
|
118
|
+
const layout = LAYOUTS.find((value) => value === this.getAttribute('layout')) ?? 'list';
|
|
119
|
+
const active = this.#root.activeElement instanceof HTMLElement ? (this.#root.activeElement.dataset['key'] ?? null) : null;
|
|
120
|
+
const tiles = this.getAttribute('variants') === 'tiles';
|
|
121
|
+
const list = html('ul', { class: 'list', part: 'list' }, ...state.platforms.flatMap((item) => this.#platformRows(item, labels, tiles)));
|
|
122
|
+
const empty = state.message.text === '';
|
|
123
|
+
const message = html('p', { class: state.message.error ? 'message error' : 'message', part: state.message.error ? 'message message-error' : 'message', role: 'status', 'aria-live': 'polite', hidden: empty }, state.message.text);
|
|
124
|
+
const panel = html('section', { class: 'panel', part: 'panel', 'data-layout': layout, 'aria-label': labels.panel }, list, message, this.#footer(labels));
|
|
125
|
+
const styles = this.hasAttribute('unstyled') ? BASE_STYLES + TOOLTIP_BASE_STYLES : STYLES + TOOLTIP_BASE_STYLES + TOOLTIP_STYLES;
|
|
126
|
+
// The element a tooltip points at is about to be replaced, so the tooltip goes with it.
|
|
127
|
+
this.#tooltip.hide();
|
|
128
|
+
this.#root.replaceChildren(html('style', {}, styles), panel, this.#tooltip.element);
|
|
129
|
+
this.#placeFocus(state, active);
|
|
130
|
+
this.dispatchEvent(new CustomEvent('flung-change', { detail: { state } }));
|
|
151
131
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Keeps focus where the person is: on the field when a question opens, on the first choice when the platform asks
|
|
134
|
+
* what to connect, on Keep when a disconnect asks to confirm, and back on Connect when a question or a wait closes.
|
|
135
|
+
* Otherwise on whatever had it before the panel was drawn again.
|
|
136
|
+
*/
|
|
137
|
+
#placeFocus(state, active) {
|
|
138
|
+
let wanted = active;
|
|
139
|
+
const fallback = [];
|
|
140
|
+
for (const item of state.platforms) {
|
|
141
|
+
const before = this.#steps.get(item.platform) ?? { kind: 'idle', variant: null };
|
|
142
|
+
const now = item.step.kind;
|
|
143
|
+
if (now === before.kind)
|
|
144
|
+
continue;
|
|
145
|
+
if (now === 'handle')
|
|
146
|
+
wanted = `handle-${item.platform}`;
|
|
147
|
+
else if (now === 'choose')
|
|
148
|
+
wanted = `choose-${item.platform}-personal`;
|
|
149
|
+
else if (now === 'confirm')
|
|
150
|
+
wanted = `keep-${item.platform}`;
|
|
151
|
+
// Back to the Connect that started it: a variant's own tile, else the platform's.
|
|
152
|
+
else if (now === 'idle')
|
|
153
|
+
fallback.push(connectKey(item.platform, before.variant), connectKey(item.platform, null));
|
|
166
154
|
}
|
|
167
|
-
|
|
168
|
-
|
|
155
|
+
this.#steps = new Map(state.platforms.map((item) => [item.platform, { kind: item.step.kind, variant: item.step.kind === 'waiting' ? item.step.variant : null }]));
|
|
156
|
+
const find = (key) => (key === null ? undefined : [...this.#root.querySelectorAll('[data-key]')].find((node) => node.dataset['key'] === key));
|
|
157
|
+
(find(wanted) ?? (active !== null ? fallback.map(find).find((node) => node !== undefined) : undefined))?.focus();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A platform's rows: one, or with `variants="tiles"` one per way it connects (LinkedIn, then LinkedIn Page), each with
|
|
161
|
+
* its own accounts. A platform that connects only through a variant is drawn as that variant's tile.
|
|
162
|
+
*/
|
|
163
|
+
#platformRows(item, labels, tiles) {
|
|
164
|
+
const slot = `platform-${item.platform}`;
|
|
165
|
+
if (item.variants.length === 0)
|
|
166
|
+
return [this.#platformRow(item, { variant: null, accounts: item.accounts }, labels, { chooser: false, extra: true, slot })];
|
|
167
|
+
if (!tiles) {
|
|
168
|
+
const variant = item.connectable ? null : (item.variants[0] ?? null);
|
|
169
|
+
return [this.#platformRow(item, { variant, accounts: item.accounts }, labels, { chooser: item.connectable, extra: true, slot })];
|
|
169
170
|
}
|
|
171
|
+
const own = item.connectable ? [{ variant: null, accounts: item.accounts.filter((account) => !account.page) }] : [];
|
|
172
|
+
const split = [...own, ...item.variants.map((variant) => ({ variant, accounts: item.accounts.filter((account) => account.page) }))];
|
|
173
|
+
const at = Math.max(0, stepTile(item, split));
|
|
174
|
+
return split.map((tile, index) => this.#platformRow(item, tile, labels, { chooser: false, extra: index === at, slot: index === 0 ? `platform-${item.platform}` : `platform-${item.platform}-${tile.variant ?? ''}` }));
|
|
170
175
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
/**
|
|
177
|
+
* One tile: mark, name, Connect, and its accounts. `chooser` makes Connect ask what to connect; `extra` puts the
|
|
178
|
+
* platform's question, wait or confirm under it; `slot` names the slot a page's own row goes in.
|
|
179
|
+
*/
|
|
180
|
+
#platformRow(item, tile, labels, options) {
|
|
181
|
+
const { chooser } = options;
|
|
182
|
+
const { platform, busy, step } = item;
|
|
183
|
+
const { variant } = tile;
|
|
184
|
+
const name = variant === null ? item.name : format(labels.pagesName, { platform: item.name });
|
|
185
|
+
const first = tile.accounts.length === 0;
|
|
186
|
+
const text = first ? labels.connect : labels.connectAnother;
|
|
187
|
+
const choosing = chooser && step.kind === 'choose';
|
|
188
|
+
// With a chooser, Connect opens the question and closes it again; a tile connects its own way straight away.
|
|
189
|
+
const onConnect = () => (choosing ? this.#controller?.cancel(platform) : this.#controller?.connect(platform, chooser ? {} : { variant }));
|
|
190
|
+
const connect = button(text, connectKey(platform, variant), 'connect', onConnect, {
|
|
191
|
+
variant: first ? 'primary' : 'secondary',
|
|
192
|
+
disabled: busy,
|
|
193
|
+
label: `${text} ${name}`,
|
|
194
|
+
...(chooser ? { expanded: choosing, controls: `choose-${platform}` } : {}),
|
|
195
|
+
});
|
|
196
|
+
const count = first ? labels.notConnected : format(labels.connectedCount, { count: String(tile.accounts.length) });
|
|
197
|
+
const title = html('span', { class: 'title', part: 'title' }, html('span', { class: 'name', part: 'name' }, name), html('span', { class: first ? 'count' : 'count on', part: first ? 'count' : 'count count-connected' }, count));
|
|
198
|
+
const badge = html('span', { class: 'badge', part: 'badge', 'aria-hidden': 'true' }, mark(PLATFORM_MARKS[platform].path));
|
|
199
|
+
const row = html('div', { class: 'row', part: 'row' }, html('slot', { name: options.slot }, badge, title, connect));
|
|
200
|
+
const items = tile.accounts.map((account) => this.#accountRow(account, busy, labels));
|
|
201
|
+
const parts = variant === null ? `platform platform-${platform}` : `platform platform-${platform} platform-${platform}-${variant}`;
|
|
202
|
+
return html('li', { class: 'platform', part: parts, 'data-platform': platform, 'data-variant': variant ?? undefined }, row, items.length === 0 ? null : html('ul', { class: 'accounts', part: 'accounts' }, ...items), options.extra ? this.#rowExtra(item, labels) : null);
|
|
175
203
|
}
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
#accountRow(item, busy, labels) {
|
|
205
|
+
const { account } = item;
|
|
206
|
+
const status = item.reconnect === null ? null : item.reconnect.reason === 'needs_reconnect' ? labels.needsReconnect : format(labels.reconnectBy, { date: new Date(item.reconnect.by).toLocaleDateString(undefined, { day: 'numeric', month: 'short' }) });
|
|
207
|
+
const reconnect = status === null ? null : button(labels.reconnect, `reconnect-${account.id}`, 'reconnect', () => this.#controller?.reconnect(account.id), { disabled: busy, label: `${labels.reconnect} ${item.name}` });
|
|
208
|
+
const disconnect = button(labels.disconnect, `disconnect-${account.id}`, 'disconnect', () => this.#controller?.requestDisconnect(account.id), { variant: 'quiet', disabled: busy, label: `${labels.disconnect} ${item.name}` });
|
|
209
|
+
const names = html('span', { class: 'names', part: 'account-names' }, html('span', { class: 'handle', part: 'account-name', 'data-tooltip': item.displayName, 'data-tooltip-truncated': true }, item.displayName), item.username === null ? null : html('span', { class: 'username', part: 'account-username', 'data-tooltip': `@${item.username}`, 'data-tooltip-truncated': true }, `@${item.username}`));
|
|
210
|
+
const tag = item.page ? html('span', { class: 'tag', part: 'tag tag-page' }, labels.pageTag) : null;
|
|
211
|
+
const who = html('span', { class: 'who', part: 'account-details' }, names, tag, status === null ? null : html('span', { class: 'status', part: 'status' }, status));
|
|
212
|
+
return html('li', { class: 'account', part: 'account', 'data-account': account.id }, html('slot', { name: `account-${account.id}` }, avatar(item), who, html('span', { class: 'actions', part: 'actions' }, reconnect, disconnect)));
|
|
179
213
|
}
|
|
180
|
-
|
|
181
|
-
|
|
214
|
+
/** Flung's mark and name, linking to its docs, unless the page set `branding="none"`. */
|
|
215
|
+
#footer(labels) {
|
|
216
|
+
if (this.getAttribute('branding') === 'none')
|
|
217
|
+
return null;
|
|
218
|
+
const link = html('a', { href: 'https://docs.flung.dev', target: '_blank', rel: 'noopener', part: 'footer-link' }, mark(FLUNG_MARK.path, { viewBox: FLUNG_MARK.viewBox, className: 'flung', part: 'footer-mark', size: 14 }), 'Flung');
|
|
219
|
+
return html('p', { class: 'footer', part: 'footer' }, labels.securedBy, link);
|
|
182
220
|
}
|
|
183
|
-
#
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const message = html('p', { class: this.#message.error ? 'message error' : 'message', part: 'message', role: 'status', 'aria-live': 'polite' }, this.#message.text);
|
|
187
|
-
const panel = html('section', { class: 'panel', part: 'panel', 'aria-label': this.#labels.panel }, list, message);
|
|
188
|
-
this.#root.replaceChildren(html('style', {}, STYLES), panel);
|
|
189
|
-
const key = this.#focus ?? active;
|
|
190
|
-
this.#focus = null;
|
|
191
|
-
if (key !== null)
|
|
192
|
-
this.#root.querySelector(`[data-key="${key}"]`)?.focus();
|
|
193
|
-
}
|
|
194
|
-
#platformRow(platform) {
|
|
195
|
-
const name = PLATFORM_NAMES[platform];
|
|
196
|
-
const state = this.#rows.get(platform) ?? { kind: 'idle' };
|
|
197
|
-
const busy = state.kind === 'waiting';
|
|
198
|
-
const accounts = this.#accounts.filter((account) => account.platform === platform);
|
|
199
|
-
const connect = button(accounts.length === 0 ? this.#labels.connect : this.#labels.connectAnother, `connect-${platform}`, () => this.#askOrStart(platform), { primary: accounts.length === 0, disabled: busy, label: `${accounts.length === 0 ? this.#labels.connect : this.#labels.connectAnother} ${name}` });
|
|
200
|
-
const row = html('div', { class: 'row' }, html('span', { class: 'badge', part: 'badge', 'aria-hidden': 'true' }, name.slice(0, 2)), html('span', { class: 'name', part: 'name' }, name), connect);
|
|
201
|
-
const items = accounts.map((account) => this.#accountRow(account, busy));
|
|
202
|
-
return html('li', { class: 'platform', part: 'platform', 'data-platform': platform }, row, items.length === 0 ? null : html('ul', { class: 'accounts' }, ...items), this.#rowExtra(platform, state));
|
|
203
|
-
}
|
|
204
|
-
#accountRow(account, busy) {
|
|
205
|
-
const due = account.reconnectBy === null ? null : new Date(account.reconnectBy);
|
|
206
|
-
const status = account.status === 'needs_reconnect' ? this.#labels.needsReconnect : due !== null && due.getTime() - Date.now() < SOON_MS ? format(this.#labels.reconnectBy, { date: due.toLocaleDateString() }) : null;
|
|
207
|
-
return html('li', { class: 'account', part: 'account', 'data-account': account.id }, html('span', { class: 'who' }, displayName(account)), status === null ? null : html('span', { class: 'status', part: 'status' }, status), status === null ? null : button(this.#labels.reconnect, `reconnect-${account.id}`, () => this.#start(account.platform, account, undefined), { disabled: busy }), button(this.#labels.disconnect, `disconnect-${account.id}`, () => this.#confirm(account), { danger: true, disabled: busy, label: `${this.#labels.disconnect} ${displayName(account)}` }));
|
|
208
|
-
}
|
|
209
|
-
#rowExtra(platform, state) {
|
|
210
|
-
const name = PLATFORM_NAMES[platform];
|
|
211
|
-
if (state.kind === 'handle') {
|
|
221
|
+
#rowExtra(item, labels) {
|
|
222
|
+
const { platform, name, step } = item;
|
|
223
|
+
if (step.kind === 'handle') {
|
|
212
224
|
const id = `handle-${platform}`;
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
const form = html('form', { class: 'form', part: 'form' }, html('label', { for: id }, platform === 'mastodon' ?
|
|
225
|
+
const input = html('input', { id, name: 'handle', part: 'input', 'data-key': id, autocomplete: 'username', spellcheck: 'false', required: step.required });
|
|
226
|
+
const submit = html('button', { type: 'submit', class: 'primary', part: 'button button-primary button-continue', 'data-key': `continue-${platform}` }, labels.continue);
|
|
227
|
+
const form = html('form', { class: 'form', part: 'form' }, html('label', { for: id, part: 'label' }, platform === 'mastodon' ? labels.handleMastodon : labels.handleBluesky), input, submit, button(labels.cancel, `cancel-${platform}`, 'cancel', () => this.#controller?.cancel(platform)));
|
|
216
228
|
form.addEventListener('submit', (event) => {
|
|
217
229
|
event.preventDefault();
|
|
218
|
-
|
|
219
|
-
this.#start(platform, null, handle === '' ? undefined : handle);
|
|
230
|
+
this.#controller?.submitHandle(platform, input.value);
|
|
220
231
|
});
|
|
221
232
|
return form;
|
|
222
233
|
}
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
if (step.kind === 'choose')
|
|
235
|
+
return this.#chooser(item, step.variants, labels);
|
|
236
|
+
if (step.kind === 'waiting') {
|
|
237
|
+
const link = step.blockedUrl === null ? null : html('a', { href: step.blockedUrl, target: '_blank', rel: 'noopener', part: 'link' }, format(labels.openSignIn, { platform: name }));
|
|
238
|
+
const text = step.blockedUrl === null ? format(labels.waiting, { platform: name }) : `${format(labels.blocked, { platform: name })} `;
|
|
239
|
+
return html('p', { class: 'note', part: 'note' }, html('span', { class: 'spinner', part: 'spinner', 'aria-hidden': 'true' }), html('span', { class: 'note-text', part: 'note-text' }, text, link), button(labels.cancel, `cancel-${platform}`, 'cancel', () => this.#controller?.cancel(platform)));
|
|
227
240
|
}
|
|
228
|
-
if (
|
|
229
|
-
return html('div', { class: 'confirm', part: 'confirm' }, html('p', {}, format(
|
|
241
|
+
if (step.kind === 'confirm') {
|
|
242
|
+
return html('div', { class: 'confirm', part: 'confirm' }, html('p', { part: 'confirm-text' }, format(labels.confirmDisconnect, { account: step.account.name })), button(labels.disconnect, `confirm-${platform}`, 'confirm', () => void this.#controller?.confirmDisconnect(platform), { variant: 'danger' }), button(labels.keep, `keep-${platform}`, 'keep', () => this.#controller?.cancel(platform)));
|
|
230
243
|
}
|
|
231
244
|
return null;
|
|
232
245
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
/**
|
|
247
|
+
* What to connect, when a platform offers its own connect and a variant: a group of two buttons, each with an icon,
|
|
248
|
+
* a name and a line under it (read as the button's description). Arrow keys move between them, Escape closes the
|
|
249
|
+
* question, and focus starts on the first.
|
|
250
|
+
*/
|
|
251
|
+
#chooser(item, variants, labels) {
|
|
252
|
+
const { platform } = item;
|
|
253
|
+
const titleId = `choose-title-${platform}`;
|
|
254
|
+
const option = (variant, key, icon, name, description) => {
|
|
255
|
+
const nameId = `${key}-name`;
|
|
256
|
+
const descriptionId = `${key}-description`;
|
|
257
|
+
const element = html('button', { type: 'button', class: 'option', part: `button button-option button-option-${variant ?? 'personal'}`, 'data-key': key, 'aria-labelledby': nameId, 'aria-describedby': descriptionId }, html('span', { class: 'option-icon', part: 'option-icon', 'aria-hidden': 'true' }, mark(icon, { className: 'option-mark', part: 'option-mark', size: 18 })), html('span', { class: 'option-text', part: 'option-text' }, html('span', { class: 'option-name', part: 'option-name', id: nameId }, name), html('span', { class: 'option-description', part: 'option-description', id: descriptionId }, description)));
|
|
258
|
+
element.addEventListener('click', () => this.#controller?.connect(platform, { variant }));
|
|
259
|
+
return element;
|
|
260
|
+
};
|
|
261
|
+
const options = [
|
|
262
|
+
option(null, `choose-${platform}-personal`, CHOICE_MARKS.personal, labels.variantPersonal, labels.variantPersonalDescription),
|
|
263
|
+
...variants.map((variant) => option(variant, `choose-${platform}-${variant}`, CHOICE_MARKS[variant], labels.variantPages, labels.variantPagesDescription)),
|
|
264
|
+
];
|
|
265
|
+
const group = html('div', { class: 'choose', part: 'choose', id: `choose-${platform}`, role: 'group', 'aria-labelledby': titleId }, html('p', { class: 'choose-title', part: 'choose-title', id: titleId }, format(labels.chooseVariant, { platform: item.name })), html('div', { class: 'options', part: 'choose-options' }, ...options), button(labels.cancel, `cancel-${platform}`, 'cancel', () => this.#controller?.cancel(platform)));
|
|
266
|
+
group.addEventListener('keydown', (event) => {
|
|
267
|
+
if (event.key === 'Escape') {
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
this.#controller?.cancel(platform);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
273
|
+
const at = options.indexOf(event.target);
|
|
274
|
+
if (step === undefined || at === -1)
|
|
275
|
+
return;
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
options[(at + step + options.length) % options.length]?.focus();
|
|
278
|
+
});
|
|
279
|
+
return group;
|
|
249
280
|
}
|
|
250
281
|
};
|
|
251
282
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Platform } from '../core/types.js';
|
|
2
|
+
/** A platform's mark on a 24 by 24 grid, and its brand colour as six hex digits. */
|
|
3
|
+
export interface PlatformMark {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly hex: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const PLATFORM_MARKS: Readonly<Record<Platform, PlatformMark>>;
|
|
8
|
+
/** The icons of the choice between a personal profile and a company page, on a 24 by 24 grid (Boxicons, MIT). */
|
|
9
|
+
export declare const CHOICE_MARKS: {
|
|
10
|
+
readonly personal: 'M12 2a5 5 0 1 0 5 5 5 5 0 0 0-5-5zm0 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3zm9 11v-1a7 7 0 0 0-7-7h-4a7 7 0 0 0-7 7v1h2v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1z';
|
|
11
|
+
readonly pages: 'M19 2H9c-1.103 0-2 .897-2 2v6H5c-1.103 0-2 .897-2 2v9a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4c0-1.103-.897-2-2-2zM5 12h6v8H5v-8zm14 8h-6v-8c0-1.103-.897-2-2-2H9V4h10v16zM11 6h2v2h-2zm4 0h2v2h-2zm0 4.031h2V12h-2zM15 14h2v2h-2zm-8 .001h2v2H7z';
|
|
12
|
+
};
|
|
13
|
+
/** Flung's mark, from https://assets.flung.dev/brand/logo/flung-mark-on-light.svg. */
|
|
14
|
+
export declare const FLUNG_MARK: {
|
|
15
|
+
readonly viewBox: '-2 -2 36 36';
|
|
16
|
+
readonly path: 'M12.942 6.235L21.674 4.058A6.0 6.0 0 0 1 28.947 8.428L29.915 12.309A6.0 6.0 0 0 1 25.545 19.582L16.812 21.76A6.0 6.0 0 0 1 9.539 17.39L8.571 13.508A6.0 6.0 0 0 1 12.942 6.235ZM13.758 19.429L20.55 17.736L14.528 29.131Z M3 12.2H7.4 M2.2 18H5.4';
|
|
17
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const PLATFORM_MARKS = {
|
|
2
|
+
// Simple Icons x (CC0 1.0)
|
|
3
|
+
x: { hex: '000000', path: 'M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z' },
|
|
4
|
+
// Simple Icons instagram (CC0 1.0)
|
|
5
|
+
instagram: { hex: 'FF0069', path: 'M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077' },
|
|
6
|
+
// Simple Icons facebook (CC0 1.0)
|
|
7
|
+
facebook: { hex: '0866FF', path: 'M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z' },
|
|
8
|
+
// Simple Icons threads (CC0 1.0)
|
|
9
|
+
threads: { hex: '000000', path: 'M18.263 11.097c-.03-3.486-1.92-5.586-5.111-5.586-2.13 0-3.922.963-4.863 2.499l2.062 1.438c.535-.843 1.272-1.543 2.628-1.543 1.528 0 2.318.85 2.544 2.431a15 15 0 0 0-2.236-.173c-4.125 0-6.068 1.867-6.068 4.336s1.943 3.99 4.804 3.99c3.139 0 5.013-2.115 5.781-4.735.798.361 1.348 1.204 1.348 2.47 0 3.387-3.907 5.232-7.22 5.232-4.885 0-8.077-3.207-8.077-8.424 0-6.392 4.223-10.487 9.9-10.487 3.808 0 5.69 1.671 6.97 3.914l2.108-1.475C21.44 2.078 18.331 0 13.663 0 6.227 0 1.168 5.277 1.168 12.934c0 7 4.953 11.066 10.856 11.066 4.878 0 9.809-2.846 9.809-7.716 0-2.545-1.46-4.231-3.569-5.187m-6.33 4.855c-1.077 0-2.026-.512-2.026-1.453 0-1.483 1.822-1.934 3.606-1.934.678 0 1.34.045 1.927.173-.422 1.927-1.671 3.215-3.508 3.214Z' },
|
|
10
|
+
// Boxicons bxl-linkedin (MIT)
|
|
11
|
+
linkedin: { hex: '0A66C2', path: 'M2.7949999999999995 5.009a2.188 2.188 0 1 0 4.376 0a2.188 2.188 0 1 0 -4.376 0zM9.237 8.855v12.139h3.769v-6.003c0-1.584.298-3.118 2.262-3.118 1.937 0 1.961 1.811 1.961 3.218v5.904H21v-6.657c0-3.27-.704-5.783-4.526-5.783-1.835 0-3.065 1.007-3.568 1.96h-.051v-1.66H9.237zm-6.142 0H6.87v12.139H3.095z' },
|
|
12
|
+
// Simple Icons tiktok (CC0 1.0)
|
|
13
|
+
tiktok: { hex: '000000', path: 'M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z' },
|
|
14
|
+
// Simple Icons youtube (CC0 1.0)
|
|
15
|
+
youtube: { hex: 'FF0000', path: 'M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z' },
|
|
16
|
+
// Simple Icons pinterest (CC0 1.0)
|
|
17
|
+
pinterest: { hex: 'BD081C', path: 'M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z' },
|
|
18
|
+
// Simple Icons bluesky (CC0 1.0)
|
|
19
|
+
bluesky: { hex: '1185FE', path: 'M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026' },
|
|
20
|
+
// Simple Icons mastodon (CC0 1.0)
|
|
21
|
+
mastodon: { hex: '6364FF', path: 'M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z' },
|
|
22
|
+
// Simple Icons discord (CC0 1.0)
|
|
23
|
+
discord: { hex: '5865F2', path: 'M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z' },
|
|
24
|
+
// Simple Icons farcaster (CC0 1.0)
|
|
25
|
+
farcaster: { hex: '855DCD', path: 'M18.24.24H5.76C2.5789.24 0 2.8188 0 6v12c0 3.1811 2.5789 5.76 5.76 5.76h12.48c3.1812 0 5.76-2.5789 5.76-5.76V6C24 2.8188 21.4212.24 18.24.24m.8155 17.1662v.504c.2868-.0256.5458.1905.5439.479v.5688h-5.1437v-.5688c-.0019-.2885.2576-.5047.5443-.479v-.504c0-.22.1525-.402.358-.458l-.0095-4.3645c-.1589-1.7366-1.6402-3.0979-3.4435-3.0979-1.8038 0-3.2846 1.3613-3.4435 3.0979l-.0096 4.3578c.2276.0424.5318.2083.5395.4648v.504c.2863-.0256.5457.1905.5438.479v.5688H4.3915v-.5688c-.0019-.2885.2575-.5047.5438-.479v-.504c0-.2529.2011-.4548.4536-.4724v-7.895h-.4905L4.2898 7.008l2.6405-.0005V5.0419h9.9495v1.9656h2.8219l-.6091 2.0314h-.4901v7.8949c.2519.0177.453.2195.453.4724' },
|
|
26
|
+
};
|
|
27
|
+
/** The icons of the choice between a personal profile and a company page, on a 24 by 24 grid (Boxicons, MIT). */
|
|
28
|
+
export const CHOICE_MARKS = {
|
|
29
|
+
// Boxicons bx-user
|
|
30
|
+
personal: 'M12 2a5 5 0 1 0 5 5 5 5 0 0 0-5-5zm0 8a3 3 0 1 1 3-3 3 3 0 0 1-3 3zm9 11v-1a7 7 0 0 0-7-7h-4a7 7 0 0 0-7 7v1h2v-1a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v1z',
|
|
31
|
+
// Boxicons bx-buildings
|
|
32
|
+
pages: 'M19 2H9c-1.103 0-2 .897-2 2v6H5c-1.103 0-2 .897-2 2v9a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4c0-1.103-.897-2-2-2zM5 12h6v8H5v-8zm14 8h-6v-8c0-1.103-.897-2-2-2H9V4h10v16zM11 6h2v2h-2zm4 0h2v2h-2zm0 4.031h2V12h-2zM15 14h2v2h-2zm-8 .001h2v2H7z',
|
|
33
|
+
};
|
|
34
|
+
/** Flung's mark, from https://assets.flung.dev/brand/logo/flung-mark-on-light.svg. */
|
|
35
|
+
export const FLUNG_MARK = { viewBox: '-2 -2 36 36', path: 'M12.942 6.235L21.674 4.058A6.0 6.0 0 0 1 28.947 8.428L29.915 12.309A6.0 6.0 0 0 1 25.545 19.582L16.812 21.76A6.0 6.0 0 0 1 9.539 17.39L8.571 13.508A6.0 6.0 0 0 1 12.942 6.235ZM13.758 19.429L20.55 17.736L14.528 29.131Z M3 12.2H7.4 M2.2 18H5.4' };
|
package/dist/element/styles.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
/** What `unstyled` keeps: the element is a block, and `hidden` hides it. Everything else is the page's, through `::part()`. */
|
|
2
|
+
export declare const BASE_STYLES = "\n:host { display: block; }\n:host([hidden]) { display: none; }\n[hidden] { display: none !important; }\n";
|
|
1
3
|
/**
|
|
2
|
-
* The panel's look, inside its shadow root.
|
|
3
|
-
* page can set on the element; each
|
|
4
|
-
* unless `theme` says which.
|
|
4
|
+
* The panel's look, inside its shadow root. Colours, type, spacing, sizes, radii, borders, shadows and the focus ring
|
|
5
|
+
* are `--flung-*` properties a page can set on the element; each piece has a `part` name for `::part()`. Follows the
|
|
6
|
+
* page's light or dark scheme unless `theme` says which. `layout` picks `list`, `grid` or `compact`.
|
|
5
7
|
*/
|
|
6
|
-
export declare const STYLES
|
|
8
|
+
export declare const STYLES: string;
|