@design.estate/dees-catalog 3.97.1 → 3.98.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/dist_bundle/bundle.js +4606 -3404
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-button/dees-button/dees-button.d.ts +35 -0
- package/dist_ts_web/elements/00group-button/dees-button/dees-button.js +128 -5
- package/dist_ts_web/elements/00group-feedback/dees-spinner/dees-spinner.js +12 -3
- package/dist_ts_web/elements/00group-form/dees-form-submit/dees-form-submit.d.ts +9 -0
- package/dist_ts_web/elements/00group-form/dees-form-submit/dees-form-submit.js +25 -2
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +15 -0
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +36 -2
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.d.ts +246 -0
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.demo.js +439 -21
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.js +857 -23
- package/package.json +3 -3
- package/readme.hints.md +35 -0
- package/readme.md +80 -11
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-button/dees-button/dees-button.ts +128 -2
- package/ts_web/elements/00group-feedback/dees-spinner/dees-spinner.ts +11 -2
- package/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts +18 -0
- package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +29 -0
- package/ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.demo.ts +418 -19
- package/ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.ts +969 -20
|
@@ -1,17 +1,263 @@
|
|
|
1
1
|
import { DeesElement, type TemplateResult } from '@design.estate/dees-element';
|
|
2
2
|
import '../../00group-layout/dees-tile/dees-tile.js';
|
|
3
|
+
import '../../00group-button/dees-button/dees-button.js';
|
|
4
|
+
import '../../00group-form/dees-form/dees-form.js';
|
|
5
|
+
import '../../00group-form/dees-form-submit/dees-form-submit.js';
|
|
6
|
+
import '../../00group-input/dees-input-text/dees-input-text.js';
|
|
7
|
+
import '../../00group-utility/dees-icon/dees-icon.js';
|
|
3
8
|
declare global {
|
|
4
9
|
interface HTMLElementTagNameMap {
|
|
5
10
|
'dees-simple-login': DeesSimpleLogin;
|
|
6
11
|
}
|
|
7
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The authentication methods this component can offer.
|
|
15
|
+
* A method is offered when it is configured — see `passkey`, `providers` and `password`.
|
|
16
|
+
*/
|
|
17
|
+
export type TDeesLoginMethod = 'passkey' | 'provider' | 'password';
|
|
18
|
+
/**
|
|
19
|
+
* Passkey ceremonies are two different things and need two different affordances:
|
|
20
|
+
* `authenticate` signs in with an existing credential, `register` enrolls a new one.
|
|
21
|
+
*/
|
|
22
|
+
export type TDeesLoginPasskeyIntent = 'authenticate' | 'register';
|
|
23
|
+
/**
|
|
24
|
+
* `optional` is a user-initiated ceremony (an explicit button press).
|
|
25
|
+
* `conditional` is the browser's passkey autofill, started silently on mount.
|
|
26
|
+
*/
|
|
27
|
+
export type TDeesLoginMediation = 'optional' | 'conditional';
|
|
28
|
+
/**
|
|
29
|
+
* One "sign in with …" button.
|
|
30
|
+
* Icons are consumer-supplied on purpose: the catalog carries no third-party brand assets.
|
|
31
|
+
*/
|
|
32
|
+
export interface IDeesLoginProvider {
|
|
33
|
+
/** stable identifier handed back to the consumer, e.g. an OIDC provider id */
|
|
34
|
+
id: string;
|
|
35
|
+
/** display name, rendered as "<providerPrefix> <label>" */
|
|
36
|
+
label: string;
|
|
37
|
+
/** dees-icon name — Lucide only, e.g. 'lucide:keyRound'. The `fa:` prefix is unsupported. */
|
|
38
|
+
icon?: string;
|
|
39
|
+
/** optional second line under the label */
|
|
40
|
+
description?: string;
|
|
41
|
+
/** disables this single provider without removing it */
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Addresses one interactive affordance for busy/error purposes.
|
|
46
|
+
* A bare `TDeesLoginMethod` is accepted wherever a target is expected.
|
|
47
|
+
*/
|
|
48
|
+
export interface IDeesLoginTarget {
|
|
49
|
+
method: TDeesLoginMethod;
|
|
50
|
+
/** passkey only — defaults to 'authenticate' */
|
|
51
|
+
intent?: TDeesLoginPasskeyIntent;
|
|
52
|
+
/** provider only — omit to address the provider group as a whole */
|
|
53
|
+
providerId?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Copy overrides. Every field falls back to the built-in default. */
|
|
56
|
+
export interface IDeesLoginLabels {
|
|
57
|
+
heading?: string;
|
|
58
|
+
subheading?: string;
|
|
59
|
+
passkeyAuthenticate?: string;
|
|
60
|
+
passkeyRegister?: string;
|
|
61
|
+
/** prefixed to every provider label; set to '' to render bare provider labels */
|
|
62
|
+
providerPrefix?: string;
|
|
63
|
+
credentialsHeading?: string;
|
|
64
|
+
passwordSubmit?: string;
|
|
65
|
+
usernameLabel?: string;
|
|
66
|
+
passwordLabel?: string;
|
|
67
|
+
divider?: string;
|
|
68
|
+
unavailable?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Resolved browser capability for passkeys. */
|
|
71
|
+
export interface IDeesLoginPasskeySupport {
|
|
72
|
+
/** WebAuthn is usable at all — secure context plus PublicKeyCredential */
|
|
73
|
+
available: boolean;
|
|
74
|
+
/** browser can run `navigator.credentials.get({ mediation: 'conditional' })` */
|
|
75
|
+
conditionalMediation: boolean;
|
|
76
|
+
/** a user-verifying platform authenticator exists (informational — never a gate) */
|
|
77
|
+
platformAuthenticator: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface IDeesLoginPasskeyContext {
|
|
80
|
+
intent: TDeesLoginPasskeyIntent;
|
|
81
|
+
/** current value of the username field, when the password method renders one */
|
|
82
|
+
username: string | undefined;
|
|
83
|
+
mediation: TDeesLoginMediation;
|
|
84
|
+
/** aborted when the element disconnects or another ceremony supersedes this one */
|
|
85
|
+
signal: AbortSignal;
|
|
86
|
+
}
|
|
87
|
+
export interface IDeesLoginProviderContext {
|
|
88
|
+
providerId: string;
|
|
89
|
+
provider: IDeesLoginProvider;
|
|
90
|
+
}
|
|
91
|
+
export interface IDeesLoginPasswordContext {
|
|
92
|
+
data: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
export type TDeesLoginPasskeyHandler = (contextArg: IDeesLoginPasskeyContext) => unknown | Promise<unknown>;
|
|
95
|
+
export type TDeesLoginProviderHandler = (contextArg: IDeesLoginProviderContext) => unknown | Promise<unknown>;
|
|
96
|
+
export type TDeesLoginPasswordHandler = (contextArg: IDeesLoginPasswordContext) => unknown | Promise<unknown>;
|
|
97
|
+
/**
|
|
98
|
+
* dees-simple-login — the login surface for an application shell.
|
|
99
|
+
*
|
|
100
|
+
* Offers up to three authentication methods in one card: passkeys, identity providers
|
|
101
|
+
* ("sign in with …") and a username/password form. A method is offered only when it is
|
|
102
|
+
* configured, so the zero-configuration default is the password form alone.
|
|
103
|
+
*
|
|
104
|
+
* ## Who owns the WebAuthn ceremony
|
|
105
|
+
*
|
|
106
|
+
* Not this component. A passkey ceremony needs server-issued options and server-side
|
|
107
|
+
* verification, so the catalog deliberately carries no WebAuthn dependency and no
|
|
108
|
+
* knowledge of any wire protocol. There are two ways to plug a ceremony in, and exactly
|
|
109
|
+
* one of them is active per interaction:
|
|
110
|
+
*
|
|
111
|
+
* 1. **Event mode** (no handler set) — the component dispatches `passkey-login`,
|
|
112
|
+
* `passkey-register` or `provider-login` and stops. The consumer drives everything and
|
|
113
|
+
* reports progress back through `setBusy()` / `reportError()`.
|
|
114
|
+
* 2. **Handler mode** (handler property set) — the component awaits the handler, owns the
|
|
115
|
+
* per-method busy state and turns a rejection into that method's error message. The
|
|
116
|
+
* request event is *not* dispatched, so a ceremony can never be started twice.
|
|
117
|
+
*
|
|
118
|
+
* The legacy `login` event is a notification rather than a request and always fires, even
|
|
119
|
+
* when `passwordLoginHandler` is set. Do not both listen to `login` and set that handler.
|
|
120
|
+
*
|
|
121
|
+
* ## Shadow DOM contract
|
|
122
|
+
*
|
|
123
|
+
* Consumers reach into this shadow root: `.loginContainer`, `.login` and `.slotContainer`
|
|
124
|
+
* carry the post-login transition, and `shadowRoot.querySelector('dees-form')` is expected
|
|
125
|
+
* to be the password form. Those are load-bearing and must not be renamed, and no second
|
|
126
|
+
* `dees-form` may precede the password form.
|
|
127
|
+
*/
|
|
8
128
|
export declare class DeesSimpleLogin extends DeesElement {
|
|
9
129
|
static demo: () => TemplateResult<1>;
|
|
10
130
|
static demoGroups: string[];
|
|
131
|
+
/** application name, interpolated into the default subheading */
|
|
11
132
|
accessor name: string;
|
|
133
|
+
/**
|
|
134
|
+
* offer a passkey affordance. Silently drops out when the browser cannot do WebAuthn,
|
|
135
|
+
* unless it is the only configured method — then the card explains itself instead of
|
|
136
|
+
* rendering empty.
|
|
137
|
+
*/
|
|
138
|
+
accessor passkey: boolean;
|
|
139
|
+
/** which passkey affordances to render, in this order */
|
|
140
|
+
accessor passkeyIntents: TDeesLoginPasskeyIntent[];
|
|
141
|
+
/**
|
|
142
|
+
* overrides browser capability detection. `undefined` detects automatically; set it when
|
|
143
|
+
* the server already knows whether passkeys are configured for this deployment.
|
|
144
|
+
*/
|
|
145
|
+
accessor passkeyAvailable: boolean | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* opt into WebAuthn conditional mediation (passkey autofill). Requires
|
|
148
|
+
* `passkeyLoginHandler` and a rendered password method — the username field is what the
|
|
149
|
+
* browser attaches its passkey picker to. Silently inert when either is missing.
|
|
150
|
+
*/
|
|
151
|
+
accessor passkeyAutofill: boolean;
|
|
152
|
+
/** identity providers to offer. Empty means the provider method is not configured. */
|
|
153
|
+
accessor providers: IDeesLoginProvider[];
|
|
154
|
+
/** offer the username/password form. On by default — set `.password=${false}` to drop it. */
|
|
155
|
+
accessor password: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* reorders the configured methods. Configured methods missing from this list are appended
|
|
158
|
+
* in canonical order, so an incomplete list can never silently hide a method.
|
|
159
|
+
*/
|
|
160
|
+
accessor methodOrder: TDeesLoginMethod[];
|
|
161
|
+
/** copy overrides */
|
|
162
|
+
accessor labels: IDeesLoginLabels;
|
|
163
|
+
/** drives the passkey sign-in ceremony — see "Who owns the WebAuthn ceremony" */
|
|
164
|
+
accessor passkeyLoginHandler: TDeesLoginPasskeyHandler | undefined;
|
|
165
|
+
/** drives the passkey enrollment ceremony */
|
|
166
|
+
accessor passkeyRegisterHandler: TDeesLoginPasskeyHandler | undefined;
|
|
167
|
+
/** drives a provider sign-in (redirect or popup) */
|
|
168
|
+
accessor providerLoginHandler: TDeesLoginProviderHandler | undefined;
|
|
169
|
+
/** drives a credential sign-in. The legacy `login` event fires regardless. */
|
|
170
|
+
accessor passwordLoginHandler: TDeesLoginPasswordHandler | undefined;
|
|
171
|
+
private accessor detectedPasskeySupport;
|
|
172
|
+
private accessor busyKeys;
|
|
173
|
+
private accessor errorMap;
|
|
174
|
+
private ceremonyControllers;
|
|
175
|
+
private autofillController;
|
|
176
|
+
private autofillStarted;
|
|
177
|
+
private interactivityObserver;
|
|
12
178
|
static styles: import("@design.estate/dees-element").CSSResult[];
|
|
179
|
+
/** resolved browser capability, with `passkeyAvailable` applied */
|
|
180
|
+
get passkeySupport(): IDeesLoginPasskeySupport;
|
|
181
|
+
/** the methods that are configured, in render order */
|
|
182
|
+
get resolvedMethods(): TDeesLoginMethod[];
|
|
183
|
+
private isConfigured;
|
|
184
|
+
private get resolvedPasskeyIntents();
|
|
185
|
+
/**
|
|
186
|
+
* true when nothing at all is offered — typically a passkey-only card in a browser that
|
|
187
|
+
* cannot do WebAuthn. Silent degradation would leave an empty card, so this case gets a
|
|
188
|
+
* message instead.
|
|
189
|
+
*/
|
|
190
|
+
private get isUnavailable();
|
|
191
|
+
private get shouldOfferPasskeyAutofill();
|
|
192
|
+
private copy;
|
|
193
|
+
private static targetKey;
|
|
194
|
+
/** true while a ceremony for this target is running */
|
|
195
|
+
isBusy(targetArg: IDeesLoginTarget | TDeesLoginMethod): boolean;
|
|
196
|
+
/** marks a target busy — for consumers driving a ceremony from an intent event */
|
|
197
|
+
setBusy(targetArg: IDeesLoginTarget | TDeesLoginMethod, busyArg: boolean): void;
|
|
198
|
+
/** the error currently shown for a target, if any */
|
|
199
|
+
getError(targetArg: IDeesLoginTarget | TDeesLoginMethod): string | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* shows an error next to one method without touching the others — a failed passkey
|
|
202
|
+
* attempt must never blank the password form.
|
|
203
|
+
*/
|
|
204
|
+
reportError(targetArg: IDeesLoginTarget | TDeesLoginMethod, messageArg: string): void;
|
|
205
|
+
/** clears one target's error, or every error when called without a target */
|
|
206
|
+
clearError(targetArg?: IDeesLoginTarget | TDeesLoginMethod): void;
|
|
207
|
+
/** clears every busy and error state and resets the password form */
|
|
208
|
+
reset(): void;
|
|
13
209
|
render(): TemplateResult;
|
|
210
|
+
private renderMethods;
|
|
211
|
+
private renderMethod;
|
|
212
|
+
private renderPasskeyMethod;
|
|
213
|
+
/**
|
|
214
|
+
* Past three providers the buttons pair up into two columns, and the "Continue with"
|
|
215
|
+
* prefix is dropped so a bare provider name still fits its half-width button.
|
|
216
|
+
*/
|
|
217
|
+
private get denseProviders();
|
|
218
|
+
private renderProviderMethod;
|
|
219
|
+
private renderPasswordMethod;
|
|
220
|
+
private renderError;
|
|
221
|
+
connectedCallback(): Promise<void>;
|
|
14
222
|
firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): Promise<void>;
|
|
223
|
+
updated(_changedProperties: Map<string | number | symbol, unknown>): void;
|
|
224
|
+
disconnectedCallback(): Promise<void>;
|
|
225
|
+
private detectPasskeySupportSync;
|
|
226
|
+
private detectPasskeySupportAsync;
|
|
227
|
+
private handleFormData;
|
|
228
|
+
private handlePasskey;
|
|
229
|
+
private handleProvider;
|
|
230
|
+
private runCeremony;
|
|
231
|
+
/**
|
|
232
|
+
* Starts the browser's passkey autofill. Conditional mediation has to be requested
|
|
233
|
+
* before the user touches anything, and it must stay silent — a background ceremony that
|
|
234
|
+
* finds no credential is not an error the user should see.
|
|
235
|
+
*/
|
|
236
|
+
private maybeStartPasskeyAutofill;
|
|
237
|
+
private abortAutofill;
|
|
238
|
+
private abortCeremonies;
|
|
239
|
+
private readUsername;
|
|
240
|
+
private static isAbortError;
|
|
241
|
+
private static toErrorMessage;
|
|
242
|
+
/**
|
|
243
|
+
* The login card and the slotted app are both permanently in the DOM, stacked and
|
|
244
|
+
* cross-faded with opacity plus `pointer-events`. That was enough while nothing inside
|
|
245
|
+
* either half could hold focus — but `dees-button` faces are focusable now, and
|
|
246
|
+
* `pointer-events: none` does not remove anything from the tab order. Without this, a
|
|
247
|
+
* keyboard user could Tab out of the password form straight into the invisible app shell,
|
|
248
|
+
* and after signing in could Tab back into the invisible login card and start a passkey
|
|
249
|
+
* ceremony there. `inert` is what actually takes a hidden subtree out of focus and the
|
|
250
|
+
* accessibility tree.
|
|
251
|
+
*
|
|
252
|
+
* Inertness is derived from each container's effective `pointer-events` rather than from
|
|
253
|
+
* an internal flag, because consumers reverse this transition by hand — cloudly's
|
|
254
|
+
* `switchToLoginContent()` writes these inline styles directly and knows nothing about
|
|
255
|
+
* `inert`. Keying off a flag they cannot reset would leave their login card permanently
|
|
256
|
+
* inert. A MutationObserver on the inline styles keeps both directions correct no matter
|
|
257
|
+
* who wrote them.
|
|
258
|
+
*/
|
|
259
|
+
private syncContainerInertness;
|
|
260
|
+
private observeContainerInteractivity;
|
|
15
261
|
/**
|
|
16
262
|
* allows switching to slotted content
|
|
17
263
|
*/
|