@singlebase/elements 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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/authui.cjs +2 -0
  4. package/dist/authui.cjs.map +1 -0
  5. package/dist/authui.d.ts +22 -0
  6. package/dist/authui.js +24 -0
  7. package/dist/authui.js.map +1 -0
  8. package/dist/controllers/auth-controller.d.ts +17 -0
  9. package/dist/controllers/event-bridge.d.ts +27 -0
  10. package/dist/controllers/settings-controller.d.ts +18 -0
  11. package/dist/display-8HEcKYR4.js +3444 -0
  12. package/dist/display-8HEcKYR4.js.map +1 -0
  13. package/dist/display-CLDNNZQy.cjs +976 -0
  14. package/dist/display-CLDNNZQy.cjs.map +1 -0
  15. package/dist/elements/account-screen.d.ts +99 -0
  16. package/dist/elements/auth-screen.d.ts +215 -0
  17. package/dist/elements/base.d.ts +69 -0
  18. package/dist/elements/buttons.d.ts +45 -0
  19. package/dist/elements/display.d.ts +51 -0
  20. package/dist/elements/form-base.d.ts +23 -0
  21. package/dist/elements/guard.d.ts +65 -0
  22. package/dist/elements/uploader.d.ts +225 -0
  23. package/dist/index.cjs +2 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +30 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/messages.d.ts +147 -0
  29. package/dist/singlebase-elements.min.js +7616 -0
  30. package/dist/singlebase-elements.min.js.map +1 -0
  31. package/dist/standalone.d.ts +2 -0
  32. package/dist/styles/shared.d.ts +8 -0
  33. package/dist/styles/tokens.d.ts +44 -0
  34. package/dist/upload-messages.d.ts +45 -0
  35. package/dist/uploader-D-Da1trc.js +2290 -0
  36. package/dist/uploader-D-Da1trc.js.map +1 -0
  37. package/dist/uploader-eFYKpk8b.cjs +1390 -0
  38. package/dist/uploader-eFYKpk8b.cjs.map +1 -0
  39. package/dist/uploader.cjs +2 -0
  40. package/dist/uploader.cjs.map +1 -0
  41. package/dist/uploader.d.ts +10 -0
  42. package/dist/uploader.js +9 -0
  43. package/dist/uploader.js.map +1 -0
  44. package/dist/utils/files.d.ts +38 -0
  45. package/dist/utils/inspect.d.ts +23 -0
  46. package/dist/utils/profile.d.ts +9 -0
  47. package/dist/utils/redirect.d.ts +15 -0
  48. package/package.json +76 -0
@@ -0,0 +1,99 @@
1
+ import { type PropertyValues } from "lit";
2
+ import { type AuthSettings } from "@singlebase/singlebase-sdk";
3
+ import { SinglebaseFormBase } from "./form-base.js";
4
+ import "./uploader.js";
5
+ /**
6
+ * The authenticated account view, laid out exactly as the design mock's
7
+ * isAuthed branch: identity header, profile summary with grouped edit
8
+ * cards, password section, two-factor, connected accounts and danger zone.
9
+ *
10
+ * Two-factor setup and account deletion have no backing operation yet, so
11
+ * they are hidden (SHOW_TWO_FACTOR, SHOW_DELETE_ACCOUNT). Listing and
12
+ * unlinking providers isn't supported either, and says so; connecting
13
+ * a *new* provider is supported, so those tiles are live.
14
+ */
15
+ export declare class SinglebaseAccountScreen extends SinglebaseFormBase {
16
+ accessor logoText: string;
17
+ /** A logo image, used instead of `logo-text` when set. */
18
+ accessor logoUrl: string;
19
+ accessor settings: AuthSettings | null;
20
+ accessor nonceStorageKey: string;
21
+ private accessor editMode;
22
+ private accessor toast;
23
+ private accessor photoBusy;
24
+ /** A photo URL that failed to load, so the initials show instead of a broken image. */
25
+ private accessor photoFailed;
26
+ private accessor profileSaved;
27
+ private accessor passSaved;
28
+ private accessor profileError;
29
+ private accessor newPassError;
30
+ private accessor deleteStage;
31
+ private accessor confirmText;
32
+ private accessor dFirst;
33
+ private accessor dLast;
34
+ private accessor dPhone;
35
+ private accessor dEmail;
36
+ private accessor emailStep;
37
+ private accessor dNewEmail;
38
+ private accessor emailCode;
39
+ private accessor emailError;
40
+ private accessor emailSaved;
41
+ private accessor curPass;
42
+ private accessor newPass;
43
+ private accessor confirmPass;
44
+ private settingsCtl;
45
+ private flashTimer?;
46
+ private uidBase;
47
+ private uid;
48
+ willUpdate(changed: PropertyValues): void;
49
+ disconnectedCallback(): void;
50
+ private flash;
51
+ /** Matches the guest screens: image when given one, else the text label. */
52
+ private renderLogo;
53
+ private get user();
54
+ private get fullName();
55
+ private get initials();
56
+ private uploadClientFor;
57
+ private uploadClientCache;
58
+ /**
59
+ * The upload API bound to the same dispatcher as this widget's auth client,
60
+ * so the photo goes to the same project with the same bearer — including on
61
+ * a page where this widget was given its own `.client`.
62
+ */
63
+ private get uploadClient();
64
+ private onChoosePhoto;
65
+ private onPhotoSelected;
66
+ /** The server has set the photo; read the account back to get its URL. */
67
+ private onPhotoUploaded;
68
+ private onPhotoProblem;
69
+ private startProfileEdit;
70
+ private onSaveProfile;
71
+ private startPassEdit;
72
+ private onChangePass;
73
+ private startEmailEdit;
74
+ /**
75
+ * Step one: ask for a code. The code goes to the address on file, not to
76
+ * the new one — that is what proves the person asking already controls the
77
+ * account rather than merely knowing an address.
78
+ */
79
+ private onRequestEmailCode;
80
+ /** Step two: confirm the code together with the new address. */
81
+ private onConfirmEmailChange;
82
+ private renderEmailSection;
83
+ private onSignOut;
84
+ private onConnectProvider;
85
+ private get availableProviders();
86
+ private renderIdentity;
87
+ private renderProfileSection;
88
+ private renderPasswordSection;
89
+ /** Layout preserved from the mock; no enrolment operation exists yet. */
90
+ private renderTwoFactorSection;
91
+ private renderConnectedSection;
92
+ private renderDangerSection;
93
+ protected render(): import("lit").TemplateResult<1>;
94
+ }
95
+ declare global {
96
+ interface HTMLElementTagNameMap {
97
+ "singlebase-authui-account": SinglebaseAccountScreen;
98
+ }
99
+ }
@@ -0,0 +1,215 @@
1
+ import { nothing, type PropertyValues } from "lit";
2
+ import { type AuthSettings } from "@singlebase/singlebase-sdk";
3
+ import { SinglebaseElementBase } from "./base.js";
4
+ import "./account-screen.js";
5
+ import "./buttons.js";
6
+ /** Screens a signed-out visitor may reach. */
7
+ export type GuestScreen = "signin" | "signup" | "forgot" | "verify" | "otp" | "newpass" | "invite";
8
+ /** Screens that require a live session. */
9
+ export type ProtectedScreen = "account";
10
+ /** Every value `screen` accepts. "oauth-callback" is entered by URL, not by hand. */
11
+ export type Screen = GuestScreen | ProtectedScreen | "oauth-callback";
12
+ export declare function isGuestScreen(value: string): value is GuestScreen;
13
+ export declare function isProtectedScreen(value: string): value is ProtectedScreen;
14
+ export declare function isScreen(value: string): value is Screen;
15
+ /**
16
+ * The complete auth widget: routes among signin, signup, recovery, one-time
17
+ * code, invite and OAuth-callback views (spec: AuthScreen), and renders the
18
+ * design's full card chrome — brand panel, logo, method accordion, step bar,
19
+ * banner/notice, actions and footer.
20
+ *
21
+ * Structure and styling follow project/AuthWidget.dc.html; the individual
22
+ * sb-* form elements remain available for hosts that want to compose their
23
+ * own screen instead of using this one.
24
+ */
25
+ export declare class SinglebaseAuthScreen extends SinglebaseElementBase {
26
+ static styles: import("lit").CSSResult[];
27
+ accessor initialScreen: Screen;
28
+ /**
29
+ * Two-way screen binding for SPA routers: set it to deep-link a screen,
30
+ * read it (or listen for `singlebase-screen-change`) to mirror the widget
31
+ * into your URL. `goto()` is the imperative form.
32
+ */
33
+ accessor screen: Screen | "";
34
+ /**
35
+ * By default the widget swaps to the account view once authenticated.
36
+ * SPAs usually want to route away and unmount instead — set this and the
37
+ * widget renders nothing when signed in, leaving navigation to the host.
38
+ */
39
+ accessor noAccountView: boolean;
40
+ accessor settings: AuthSettings | null;
41
+ accessor authEnabled: boolean;
42
+ accessor allowEmailSignin: boolean;
43
+ accessor allowEmailSignup: boolean;
44
+ accessor allowEmailOtp: boolean;
45
+ accessor allowOauth: boolean;
46
+ accessor allowAccountCreation: boolean;
47
+ accessor layout: "card" | "split";
48
+ accessor oauthPlacement: "top" | "bottom";
49
+ accessor stepped: boolean;
50
+ /**
51
+ * Branding is the host's, so these default to empty and the corresponding
52
+ * slots simply do not render. ("KEYRING" and friends were the fictional
53
+ * brand in the design mock — they must never ship as defaults.)
54
+ */
55
+ accessor logoText: string;
56
+ /**
57
+ * A logo image, used instead of `logo-text` when set. `logo-text` is still
58
+ * worth supplying alongside it — it becomes the image's alt text.
59
+ */
60
+ accessor logoUrl: string;
61
+ accessor brandLine: string;
62
+ accessor brandFoot: string;
63
+ accessor signInTitle: string;
64
+ /**
65
+ * The "Auth by Singlebase" credit under the card. On by default; turn it
66
+ * off with branding="false", exactly like the allow-* flags.
67
+ *
68
+ * It is a plain link — no pixel, no beacon, no request of any kind. A
69
+ * third-party network call originating from a sign-in form is the sort of
70
+ * thing that fails a security review, and deservedly.
71
+ */
72
+ accessor branding: boolean;
73
+ /**
74
+ * Where to send the user once a session exists. Same-origin only — an
75
+ * absolute URL pointing anywhere else is refused, because a login widget
76
+ * that forwards to an attacker-supplied host is an open redirect.
77
+ *
78
+ * When unset, a `?redirect=` (or `?next=`) parameter on the current URL is
79
+ * used instead, so "you were sent to the login page, now go back where you
80
+ * were headed" works without the host wiring anything.
81
+ */
82
+ accessor redirectUrl: string;
83
+ /** Shown as "Terms" in the consent line under the primary action. */
84
+ accessor tosUrl: string;
85
+ /** Shown as "Privacy" in the consent line under the primary action. */
86
+ accessor privacyUrl: string;
87
+ /** Where the OAuth nonce is stashed between starting a flow and returning. */
88
+ accessor nonceStorageKey: string;
89
+ accessor inviteEmail: string;
90
+ accessor inviteOrg: string;
91
+ /** Invite code carried by the invite link; when absent the widget asks for it. */
92
+ accessor inviteCode: string;
93
+ private accessor step;
94
+ private accessor method;
95
+ private accessor email;
96
+ private accessor password;
97
+ private accessor name;
98
+ private accessor firstName;
99
+ private accessor lastName;
100
+ private accessor phone;
101
+ private accessor newPass;
102
+ private accessor confirmPass;
103
+ private accessor code;
104
+ private accessor verifyNext;
105
+ private accessor loading;
106
+ private accessor banner;
107
+ private accessor notice;
108
+ private accessor emailError;
109
+ private accessor passError;
110
+ private accessor newPassError;
111
+ private accessor attempts;
112
+ private accessor needsMfaCode;
113
+ private settingsCtl;
114
+ private bridge;
115
+ private uidBase;
116
+ private uid;
117
+ willUpdate(changed: PropertyValues): void;
118
+ disconnectedCallback(): void;
119
+ private gotoUnsubscribe?;
120
+ private gotoClient;
121
+ /**
122
+ * Subscribes to auth.goto() so a host can drive every widget on the page
123
+ * with one call. Bound here rather than in connectedCallback because the
124
+ * app usually creates its SinglebaseAuth() instance *after* the widget
125
+ * upgrades — at connect time there is often no client to subscribe to yet.
126
+ */
127
+ private bindGoto;
128
+ /** Shows a screen. The SPA-facing counterpart of the `screen` property. */
129
+ goto(screen: Screen): void;
130
+ private get cfg();
131
+ /** The screen the host asked for, before any guarding is applied. */
132
+ private get requestedScreen();
133
+ /**
134
+ * Resolves the screen actually rendered.
135
+ *
136
+ * This is the single place navigation is decided, so a protected screen
137
+ * cannot be reached by setting an attribute, editing the URL, or calling
138
+ * goto() — every route runs through the same auth check. The guest
139
+ * fallbacks below additionally reflect what the project has enabled.
140
+ */
141
+ private get activeScreen();
142
+ /** True when a signed-in user is sitting on a guest screen (spec: offer continue/sign out). */
143
+ private get showsSignedInInterstitial();
144
+ /**
145
+ * The post-login destination, or null when there is none.
146
+ *
147
+ * Only same-origin targets are accepted. A login widget that forwards to
148
+ * an attacker-chosen host is a textbook open redirect, and the `redirect`
149
+ * parameter it reads comes straight off the URL bar, so it is untrusted
150
+ * input by definition.
151
+ */
152
+ private get redirectTarget();
153
+ private get redirectParam();
154
+ /** Navigates to the post-login destination if there is a safe one. */
155
+ private followRedirect;
156
+ private get isOAuthCallback();
157
+ private accessor callbackDone;
158
+ /** Strips the provider's parameters so a reload cannot replay the exchange. */
159
+ private cleanCallbackUrl;
160
+ /**
161
+ * Completes the provider round trip. Runs once per landing: the URL is
162
+ * cleaned immediately so a refresh cannot resend a single-use access code.
163
+ */
164
+ private handleOAuthCallback;
165
+ /** The small logo above a screen title: image when given one, else text. */
166
+ private renderLogo;
167
+ /** The larger logo in the split layout's brand panel. */
168
+ private renderBrandMark;
169
+ private renderBranding;
170
+ /** Every screen renders inside this, so the credit appears once and only once. */
171
+ private panel;
172
+ private renderOAuthCallback;
173
+ private setScreen;
174
+ private withLoading;
175
+ private describeError;
176
+ private get codeValue();
177
+ private onSubmit;
178
+ private abortController;
179
+ private signal;
180
+ private onCodeInput;
181
+ private onCodePaste;
182
+ private fillCode;
183
+ private onResend;
184
+ private goBack;
185
+ private titleFor;
186
+ private subFor;
187
+ private ctaFor;
188
+ private stepPct;
189
+ private renderPrimary;
190
+ private renderEmailField;
191
+ private renderPasswordField;
192
+ private renderCodeRow;
193
+ /** The email/password method accordion body for signin, signup and otp. */
194
+ private renderEmailMethodBody;
195
+ private renderAccordion;
196
+ private renderScreenBody;
197
+ private renderGuest;
198
+ private renderBlocked;
199
+ /**
200
+ * Shown when a signed-in visitor lands on a guest screen. Rather than
201
+ * presenting a sign-in form to someone already signed in, offer the two
202
+ * things they could actually want: carry on, or switch accounts.
203
+ */
204
+ private renderSignedIn;
205
+ private onContinue;
206
+ private onInterstitialSignOut;
207
+ /** The Terms/Privacy consent line, rendered only when the host supplies a link. */
208
+ private renderConsent;
209
+ protected render(): import("lit").TemplateResult<1> | typeof nothing;
210
+ }
211
+ declare global {
212
+ interface HTMLElementTagNameMap {
213
+ "singlebase-authui": SinglebaseAuthScreen;
214
+ }
215
+ }
@@ -0,0 +1,69 @@
1
+ import { LitElement, type PropertyValues } from "lit";
2
+ import { type AuthUIConfig } from "@singlebase/core";
3
+ import type { AuthClient, UserProfile } from "@singlebase/singlebase-sdk";
4
+ import { AuthController } from "../controllers/auth-controller.js";
5
+ import { defaultMessages, type SinglebaseAuthMessages } from "../messages.js";
6
+ /**
7
+ * Base class for every singlebase-* element: resolves an AuthClient, exposes
8
+ * .messages overrides, and gives subclasses a live AuthController bound to
9
+ * whichever client won.
10
+ */
11
+ export declare abstract class SinglebaseElementBase extends LitElement {
12
+ static styles: import("lit").CSSResult[];
13
+ accessor client: AuthClient | null;
14
+ accessor messages: Partial<SinglebaseAuthMessages>;
15
+ /**
16
+ * theme/density/field-style are plain reflected attributes so a composing
17
+ * parent can forward them verbatim to the singlebase-* children it renders.
18
+ * CSS custom properties inherit through shadow boundaries on their own,
19
+ * but an *attribute* selector like :host([theme="dark"]) only matches
20
+ * the element that actually carries the attribute, so it must be
21
+ * explicitly propagated down the composition tree.
22
+ */
23
+ accessor theme: "light" | "dark" | undefined;
24
+ accessor density: "comfortable" | "compact" | undefined;
25
+ /**
26
+ * Input chrome: boxed (default) or a single bottom rule. Reflected for the
27
+ * same reason as theme/density — the CSS is an attribute selector.
28
+ */
29
+ accessor fieldStyle: "outline" | "underline" | undefined;
30
+ protected auth: AuthController;
31
+ /**
32
+ * Resolution order: an explicit `.client`, then the page's default
33
+ * SinglebaseClient()'s auth.
34
+ *
35
+ * That fallback is what lets several widgets sit on one page — a header
36
+ * sign-in, a modal, an account panel — and share one session with no
37
+ * wiring, since SinglebaseClient() is cached per project. A page talking to
38
+ * two projects sets `.client` on the widgets belonging to the second.
39
+ *
40
+ * Reading `.auth` is also what *constructs* auth: a page that only drops
41
+ * in a widget initializes it with no bootstrap code at all.
42
+ */
43
+ get resolvedClient(): AuthClient | null;
44
+ /** Page-wide UI config from SinglebaseClient({ authui }), if there is one. */
45
+ get uiConfig(): AuthUIConfig | null;
46
+ /**
47
+ * Config messages sit *under* the element's own, so a page-wide translation
48
+ * can be overridden per widget without repeating the whole bag.
49
+ */
50
+ get msg(): SinglebaseAuthMessages;
51
+ /** Config keys already applied, so a later render does not fight the host. */
52
+ private appliedConfig;
53
+ /**
54
+ * Copies the page-wide config onto this element.
55
+ *
56
+ * An attribute present in markup always wins — that is the whole precedence
57
+ * rule, and it is checked per key rather than wholesale, so a widget can
58
+ * override one thing and inherit the rest. Keys the element does not have
59
+ * are skipped, which is how one config can serve every singlebase-authui-*
60
+ * element without any of them knowing about the others.
61
+ */
62
+ protected applyUIConfig(): void;
63
+ isAuthenticated(): boolean;
64
+ getUser(): UserProfile | null;
65
+ refreshSession(): Promise<import("@singlebase/singlebase-sdk").AuthSession | undefined>;
66
+ logout(): Promise<void>;
67
+ willUpdate(changed: PropertyValues): void;
68
+ }
69
+ export { defaultMessages };
@@ -0,0 +1,45 @@
1
+ import { nothing, type PropertyValues } from "lit";
2
+ import type { AuthSettings, OAuthIntent } from "@singlebase/singlebase-sdk";
3
+ import { SinglebaseFormBase } from "./form-base.js";
4
+ export type ButtonsType = "signout" | "oauth" | "link";
5
+ /**
6
+ * The widget's action buttons, selected with `type`:
7
+ *
8
+ * <singlebase-authui-buttons type="signout"></singlebase-authui-buttons>
9
+ * <singlebase-authui-buttons type="oauth" intent="signup"></singlebase-authui-buttons>
10
+ * <singlebase-authui-buttons type="link" provider="github"></singlebase-authui-buttons>
11
+ *
12
+ * They live under one tag rather than three because they are the same thing
13
+ * from a host's point of view — a button that performs one auth action — and
14
+ * every custom element name is a global, unversioned claim on any page this
15
+ * widget is dropped into.
16
+ */
17
+ export declare class SinglebaseAuthButtons extends SinglebaseFormBase {
18
+ accessor type: ButtonsType;
19
+ /** oauth: which flow the provider buttons start. */
20
+ accessor intent: OAuthIntent;
21
+ /** link: the provider to connect to the signed-in account. */
22
+ accessor provider: string;
23
+ /** link: display name, when it differs from the provider id. */
24
+ accessor providerName: string;
25
+ /** Inside a card that already supplies chrome, drop this element's own. */
26
+ accessor embedded: boolean;
27
+ accessor settings: AuthSettings | null;
28
+ accessor nonceStorageKey: string;
29
+ private settingsCtl;
30
+ willUpdate(changed: PropertyValues): void;
31
+ private get effectiveSettings();
32
+ private get providers();
33
+ /** Shared by the oauth and link flows: nonce, start, redirect. */
34
+ private startOAuth;
35
+ private renderOAuth;
36
+ private renderLink;
37
+ private onSignOut;
38
+ private renderSignOut;
39
+ protected render(): import("lit").TemplateResult<1> | typeof nothing;
40
+ }
41
+ declare global {
42
+ interface HTMLElementTagNameMap {
43
+ "singlebase-authui-buttons": SinglebaseAuthButtons;
44
+ }
45
+ }
@@ -0,0 +1,51 @@
1
+ import { nothing, type PropertyValues } from "lit";
2
+ import { SinglebaseElementBase } from "./base.js";
3
+ /**
4
+ * Displays one value from the signed-in profile, as text or as an avatar.
5
+ *
6
+ * Hello <singlebase-authui-display path="first_name" fallback="there"></singlebase-authui-display>
7
+ * <singlebase-authui-display avatar></singlebase-authui-display>
8
+ *
9
+ * Three rules shape the behaviour:
10
+ *
11
+ * - While auth state is still loading it renders nothing. Showing the
12
+ * fallback first would mean every signed-in visitor watches "Hello there"
13
+ * flip to "Hello Ada" on each page load.
14
+ * - Values resolve against `user_profile` and nothing else. The element
15
+ * deliberately cannot reach session or token data, which must never be
16
+ * written into the DOM.
17
+ * - Values are rendered as text, never as markup. Fields like first_name are
18
+ * supplied by the user at signup, so they are untrusted by definition.
19
+ */
20
+ export declare class SinglebaseAuthDisplay extends SinglebaseElementBase {
21
+ static styles: import("lit").CSSResult[];
22
+ /**
23
+ * Dot-notated path into the profile: "first_name", "metadata.tier". In
24
+ * avatar mode it names the image field and defaults to "profile_photo".
25
+ */
26
+ accessor path: string;
27
+ /** Shown when signed out, or when the value is empty. Text mode only. */
28
+ accessor fallback: string;
29
+ /** Render an image with an initials fallback instead of text. */
30
+ accessor avatar: boolean;
31
+ /**
32
+ * Alternative text for the avatar image. Left empty the image is treated as
33
+ * decorative, which is right when a name sits beside it — pass a value only
34
+ * when the avatar is the sole identifier.
35
+ */
36
+ accessor alt: string;
37
+ /** Set when the image 404s, so a dead URL falls back to initials. */
38
+ private accessor imageFailed;
39
+ willUpdate(changed: PropertyValues): void;
40
+ private get profile();
41
+ /** The resolved value as a string, or "" when there is nothing to show. */
42
+ private get textValue();
43
+ private get avatarSrc();
44
+ protected render(): import("lit").TemplateResult<1> | typeof nothing;
45
+ private renderAvatar;
46
+ }
47
+ declare global {
48
+ interface HTMLElementTagNameMap {
49
+ "singlebase-authui-display": SinglebaseAuthDisplay;
50
+ }
51
+ }
@@ -0,0 +1,23 @@
1
+ import { SinglebaseElementBase } from "./base.js";
2
+ export type FormPhase = "idle" | "submitting" | "success" | "error";
3
+ /**
4
+ * Adds the spec's required cross-cutting form behavior to SinglebaseElementBase:
5
+ * idle/submitting/success/error phase, a form-level error message, disabled
6
+ * controls while submitting, an aria-live status region, and cancellation of
7
+ * in-flight work on disconnect.
8
+ */
9
+ export declare abstract class SinglebaseFormBase extends SinglebaseElementBase {
10
+ protected accessor phase: FormPhase;
11
+ protected accessor formError: string;
12
+ protected accessor noticeMessage: string;
13
+ protected abortController: AbortController | null;
14
+ disconnectedCallback(): void;
15
+ protected get signal(): AbortSignal;
16
+ /** Runs task, tracking idle→submitting→success/error and mapping errors to a message. */
17
+ protected run(task: () => Promise<void>): Promise<void>;
18
+ private isAbortError;
19
+ protected describeError(error: unknown): string;
20
+ protected get submitting(): boolean;
21
+ /** aria-live region for async status — spec requires one per form. */
22
+ protected renderStatus(): import("lit").TemplateResult<1>;
23
+ }
@@ -0,0 +1,65 @@
1
+ import { type Predicate } from "@singlebase/core";
2
+ import { SinglebaseElementBase } from "./base.js";
3
+ export type GuardState = "loading" | "authenticated" | "unauthenticated";
4
+ /**
5
+ * Renders content conditionally on auth state, and optionally on a predicate
6
+ * over the signed-in profile.
7
+ *
8
+ * Two shapes, because wrapping a single line of markup in a slotted <div> is
9
+ * a lot of ceremony:
10
+ *
11
+ * <!-- long form: one guard, several states -->
12
+ * <singlebase-authui-guard>
13
+ * <div slot="loading">Checking…</div>
14
+ * <div slot="authenticated">Welcome back</div>
15
+ * <div slot="unauthenticated"><singlebase-authui></singlebase-authui></div>
16
+ * </singlebase-authui-guard>
17
+ *
18
+ * <!-- short form: the guard itself carries the state -->
19
+ * <singlebase-authui-guard slot="loading">Checking…</singlebase-authui-guard>
20
+ *
21
+ * With a predicate it also filters on the profile:
22
+ *
23
+ * <singlebase-authui-guard slot="authenticated" predicate='{"roles":{"$in":["admin"]}}'>
24
+ * <admin-panel></admin-panel>
25
+ * </singlebase-authui-guard>
26
+ *
27
+ * A predicate only ever narrows the *authenticated* state: a signed-out
28
+ * visitor is unauthenticated whether or not a filter is present, so a guard
29
+ * can never leak content by failing open.
30
+ */
31
+ export declare class SinglebaseAuthGuard extends SinglebaseElementBase {
32
+ /**
33
+ * Short form. When set, every child of this guard belongs to that state and
34
+ * no per-child `slot` attributes are needed.
35
+ *
36
+ * Note this reuses the standard `slot` attribute name, which is inert here:
37
+ * the guard is not itself inside another shadow root's slot, so the name is
38
+ * free to mean "the state these children belong to".
39
+ */
40
+ accessor slot: string;
41
+ /**
42
+ * One-level, Mongo-style filter over the signed-in `user_profile`.
43
+ * Dot notation reaches nested values; see core's predicate module for the
44
+ * supported operators.
45
+ */
46
+ accessor predicate: Predicate | null;
47
+ /**
48
+ * What to show when the user is signed in but the predicate does not match.
49
+ * Defaults to "unauthenticated" so a filtered guard degrades to the
50
+ * signed-out branch; set "hidden" to render nothing at all.
51
+ */
52
+ accessor onMismatch: "unauthenticated" | "hidden";
53
+ private warnedFor;
54
+ protected createRenderRoot(): this;
55
+ /** The state this guard currently resolves to, after applying the predicate. */
56
+ get activeState(): GuardState | "hidden";
57
+ /** Surfaces a malformed predicate once, rather than silently never matching. */
58
+ private warnOnce;
59
+ render(): import("lit").TemplateResult<1>;
60
+ }
61
+ declare global {
62
+ interface HTMLElementTagNameMap {
63
+ "singlebase-authui-guard": SinglebaseAuthGuard;
64
+ }
65
+ }