@glydi/passkey-svelte 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @glydi/passkey-svelte
2
+
3
+ Thin Svelte adapter for the Glide passkey Web Component.
4
+
5
+ ## Install
6
+
7
+ Glide is not yet published to npm. Install from a packed tarball or via `pnpm link`.
8
+
9
+ **Tarball (recommended):**
10
+
11
+ ```json
12
+ {
13
+ "dependencies": {
14
+ "@glydi/passkey-svelte": "file:../glide/dist-packs/glydi-passkey-svelte-0.1.0.tgz",
15
+ "@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
16
+ },
17
+ "pnpm": {
18
+ "overrides": {
19
+ "@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ The `pnpm.overrides` entry for `@glydi/passkey-core` prevents pnpm from trying to fetch
26
+ the transitive peer from the npm registry. See [docs/DISTRIBUTION.md](../../docs/DISTRIBUTION.md)
27
+ for full tarball and `pnpm link` instructions, including how to produce the tarballs.
28
+
29
+ > **Forthcoming:** `npm install @glydi/passkey-svelte` will be the public form once the
30
+ > package is published. It is not yet available on npm.
31
+
32
+ ## Minimal Usage
33
+
34
+ This package has **two entry points**:
35
+
36
+ | Entry point | Import path | What you get |
37
+ |---|---|---|
38
+ | `"."` (main) | `@glydi/passkey-svelte` | `createPasskeyAuth` headless action + types |
39
+ | `"./PasskeyButton.svelte"` | `@glydi/passkey-svelte/PasskeyButton.svelte` | Ready-made Svelte component |
40
+
41
+ > **Note:** `PasskeyButton.svelte` is **not** exported from the `"."` entry — it lives at the
42
+ > separate `./PasskeyButton.svelte` subpath and ships as source so your Svelte compiler handles it.
43
+
44
+ ### 1. Component: `PasskeyButton.svelte`
45
+
46
+ Import from the `./PasskeyButton.svelte` subpath:
47
+
48
+ ```svelte
49
+ <!-- Source: packages/svelte/src/PasskeyButton.svelte -->
50
+ <script>
51
+ import PasskeyButton from "@glydi/passkey-svelte/PasskeyButton.svelte";
52
+ </script>
53
+
54
+ <PasskeyButton
55
+ mode="signup"
56
+ label="Register passkey"
57
+ onSuccess={(r) => console.log(r.user.id)}
58
+ onError={(e) => console.warn(e.code)}
59
+ />
60
+ ```
61
+
62
+ ### 2. Headless: `createPasskeyAuth` action
63
+
64
+ For custom UI, use the headless `createPasskeyAuth` action from the main entry point.
65
+ It returns a `PasskeyAuth` store with a Svelte action (`use:auth.attach`) and reactive
66
+ state (`$auth`):
67
+
68
+ ```svelte
69
+ <!-- Source: packages/svelte/src/usePasskeyAuth.ts -->
70
+ <script>
71
+ import { createPasskeyAuth } from "@glydi/passkey-svelte";
72
+ const auth = createPasskeyAuth({ mode: "auto" });
73
+ $: ({ phase, isPending, error, user } = $auth);
74
+ </script>
75
+
76
+ <biometric-auth-button use:auth.attach mode="auto" />
77
+ ```
78
+
79
+ ## API
80
+
81
+ ### `PasskeyButton.svelte`
82
+
83
+ A ready-made Svelte component wrapping `<biometric-auth-button>`. Import from
84
+ `@glydi/passkey-svelte/PasskeyButton.svelte` (the `./PasskeyButton.svelte` subpath).
85
+ The component ships as **Svelte source** — your project's Svelte compiler handles it.
86
+ Any extra attributes are forwarded via `{...$$restProps}` to `<biometric-auth-button>`.
87
+
88
+ **Props:**
89
+
90
+ | Prop | Type | Default | Description |
91
+ |------|------|---------|-------------|
92
+ | `mode` | `"auto" \| "signin" \| "signup"` | `"auto"` | Auth mode. See AuthMode callout below. |
93
+ | `username` | `string \| undefined` | `undefined` | Username hint for registration/conditional UI. |
94
+ | `label` | `string \| undefined` | `undefined` | Button label text. |
95
+ | `endpoints` | `GlideEndpoints \| undefined` | `undefined` | Override API endpoint paths. |
96
+ | `fetchOptions` | `RequestInit \| undefined` | `undefined` | Merged into every `fetch` call. |
97
+ | `onSuccess` | `(result: AuthResult) => void \| undefined` | `undefined` | Called after successful authentication. |
98
+ | `onError` | `(error: GlideError) => void \| undefined` | `undefined` | Called when authentication fails. |
99
+ | `onPhaseChange` | `(detail: { phase: AuthPhase }) => void \| undefined` | `undefined` | Called on every phase transition. |
100
+
101
+ > **Valid `mode` values are `"auto"`, `"signin"`, and `"signup"` only.** Using `"register"`
102
+ > or `"authenticate"` is invalid and will silently no-op.
103
+
104
+ ### `createPasskeyAuth(options?)`
105
+
106
+ Headless action factory. Returns a `PasskeyAuth` object:
107
+
108
+ | Member | Type | Description |
109
+ |--------|------|-------------|
110
+ | `subscribe` | `Readable<PasskeyAuthState>["subscribe"]` | Svelte store protocol — use `$auth` in markup. |
111
+ | `attach(node)` | `(node: HTMLElement) => { destroy(): void }` | Svelte action — apply as `use:auth.attach`. |
112
+ | `trigger()` | `() => void` | Imperatively start auth (same as clicking the button). |
113
+ | `reset()` | `() => void` | Reset phase and error back to `"idle"`. |
114
+
115
+ **`PasskeyAuthState`** (readable via `$auth`):
116
+
117
+ | Field | Type | Description |
118
+ |-------|------|-------------|
119
+ | `phase` | `AuthPhase` | Current auth phase (`"idle"`, `"loading"`, `"success"`, etc.). |
120
+ | `isPending` | `boolean` | `true` during `"loading"` or `"authenticating"`. |
121
+ | `isUnsupported` | `boolean` | `true` when `phase === "unsupported"`. |
122
+ | `error` | `GlideError \| null` | Last error, or `null`. |
123
+ | `user` | `GlideUser \| null` | Authenticated user after success, or `null`. |
124
+
125
+ **`UsePasskeyAuthOptions`** (all optional):
126
+
127
+ | Option | Type | Description |
128
+ |--------|------|-------------|
129
+ | `mode` | `"auto" \| "signin" \| "signup"` | Auth mode. Default `"auto"`. |
130
+ | `username` | `string` | Username hint. |
131
+ | `endpoints` | `Partial<GlideEndpoints>` | Override API endpoint paths. |
132
+ | `fetchOptions` | `RequestInit` | Merged into every `fetch` call. |
133
+ | `onSuccess` | `(result: AuthResult) => void` | Success callback. |
134
+ | `onError` | `(error: GlideError) => void` | Error callback. |
135
+
136
+ ### Re-exported types
137
+
138
+ `AuthMode`, `AuthPhase`, `AuthResult`, `GlideEndpoints`, `GlideError`, `GlideUser`
139
+
140
+ ## Links
141
+
142
+ - [Root README](../../README.md) — architecture overview
143
+ - [Quickstart](../../docs/QUICKSTART.md) — full Next.js App Router integration walkthrough
144
+ - [Distribution guide](../../docs/DISTRIBUTION.md) — tarball and pnpm link install details
@@ -0,0 +1,56 @@
1
+ import { Readable } from 'svelte/store';
2
+ import { AuthPhase, GlideError, GlideUser, GlideEndpoints, AuthResult } from '@glydi/passkey-core';
3
+ export { AuthMode, AuthPhase, AuthResult, GlideEndpoints, GlideError, GlideUser } from '@glydi/passkey-core';
4
+
5
+ interface UsePasskeyAuthOptions {
6
+ mode?: "auto" | "signin" | "signup";
7
+ username?: string;
8
+ endpoints?: Partial<GlideEndpoints>;
9
+ /** Forwarded to fetch (e.g. custom headers). credentials:"include" is default. */
10
+ fetchOptions?: RequestInit;
11
+ onSuccess?: (result: AuthResult) => void;
12
+ onError?: (error: GlideError) => void;
13
+ }
14
+ /** Reactive snapshot of the auth flow, exposed as a Svelte readable store. */
15
+ interface PasskeyAuthState {
16
+ phase: AuthPhase;
17
+ /** True while the ceremony is in flight. */
18
+ isPending: boolean;
19
+ /** True once a feature-detect failure is reflected by the element. */
20
+ isUnsupported: boolean;
21
+ error: GlideError | null;
22
+ user: GlideUser | null;
23
+ }
24
+ /** Svelte action: cleanup contract returned from `attach`. */
25
+ interface ActionReturn {
26
+ destroy(): void;
27
+ }
28
+ interface PasskeyAuth {
29
+ /**
30
+ * Subscribe to the reactive auth state. Use directly in markup with `$`:
31
+ * const auth = createPasskeyAuth();
32
+ * $: ({ phase, isPending, error, user } = $auth);
33
+ */
34
+ subscribe: Readable<PasskeyAuthState>["subscribe"];
35
+ /**
36
+ * Svelte action — attach to the element:
37
+ * <biometric-auth-button use:auth.attach />
38
+ * Forwards object props and bridges the `glide:*` CustomEvents into the store.
39
+ */
40
+ attach(node: HTMLElement): ActionReturn;
41
+ /** Imperatively start auth (same as a user click). */
42
+ trigger(): void;
43
+ /** Clear the error/user without touching the element. */
44
+ reset(): void;
45
+ }
46
+ /**
47
+ * Headless Svelte helper wrapping the <biometric-auth-button> Web Component.
48
+ *
49
+ * It owns nothing about rendering — you bring the element (via <PasskeyButton>
50
+ * or a raw tag) and wire `use:auth.attach`. The helper subscribes to the
51
+ * element's `glide:*` CustomEvents and projects them into a Svelte store +
52
+ * callbacks. Mirrors the React `usePasskeyAuth` hook semantics.
53
+ */
54
+ declare function createPasskeyAuth(options?: UsePasskeyAuthOptions): PasskeyAuth;
55
+
56
+ export { type PasskeyAuth, type PasskeyAuthState, type UsePasskeyAuthOptions, createPasskeyAuth };
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ import { writable } from 'svelte/store';
2
+
3
+ // src/usePasskeyAuth.ts
4
+ var INITIAL = {
5
+ phase: "idle",
6
+ isPending: false,
7
+ isUnsupported: false,
8
+ error: null,
9
+ user: null
10
+ };
11
+ function derive(phase) {
12
+ return {
13
+ isPending: phase === "loading" || phase === "authenticating",
14
+ isUnsupported: phase === "unsupported"
15
+ };
16
+ }
17
+ function createPasskeyAuth(options = {}) {
18
+ const store = writable(INITIAL);
19
+ let current = null;
20
+ function attach(node) {
21
+ const el = node;
22
+ current = el;
23
+ if (options.endpoints)
24
+ el.endpoints = options.endpoints;
25
+ if (options.fetchOptions)
26
+ el.fetchOptions = options.fetchOptions;
27
+ const onSuccess = (e) => {
28
+ const detail = e.detail;
29
+ store.update((s) => ({ ...s, user: detail.user, error: null }));
30
+ options.onSuccess?.(detail);
31
+ };
32
+ const onError = (e) => {
33
+ const detail = e.detail;
34
+ store.update((s) => ({ ...s, error: detail }));
35
+ options.onError?.(detail);
36
+ };
37
+ const onPhase = (e) => {
38
+ const phase2 = e.detail.phase;
39
+ store.update((s) => ({ ...s, phase: phase2, ...derive(phase2) }));
40
+ };
41
+ el.addEventListener("glide:success", onSuccess);
42
+ el.addEventListener("glide:error", onError);
43
+ el.addEventListener("glide:phasechange", onPhase);
44
+ const phase = el.phase;
45
+ store.update((s) => ({ ...s, phase, ...derive(phase) }));
46
+ return {
47
+ destroy() {
48
+ el.removeEventListener("glide:success", onSuccess);
49
+ el.removeEventListener("glide:error", onError);
50
+ el.removeEventListener("glide:phasechange", onPhase);
51
+ if (current === el)
52
+ current = null;
53
+ }
54
+ };
55
+ }
56
+ function trigger() {
57
+ current?.click();
58
+ }
59
+ function reset() {
60
+ store.update((s) => ({ ...s, error: null, user: null }));
61
+ }
62
+ return { subscribe: store.subscribe, attach, trigger, reset };
63
+ }
64
+
65
+ export { createPasskeyAuth };
66
+ //# sourceMappingURL=out.js.map
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usePasskeyAuth.ts"],"names":["phase"],"mappings":";AAAA,SAAS,gBAA+B;AA6DxC,IAAM,UAA4B;AAAA,EAChC,OAAO;AAAA,EACP,WAAW;AAAA,EACX,eAAe;AAAA,EACf,OAAO;AAAA,EACP,MAAM;AACR;AAEA,SAAS,OAAO,OAGd;AACA,SAAO;AAAA,IACL,WAAW,UAAU,aAAa,UAAU;AAAA,IAC5C,eAAe,UAAU;AAAA,EAC3B;AACF;AAUO,SAAS,kBACd,UAAiC,CAAC,GACrB;AACb,QAAM,QAAQ,SAA2B,OAAO;AAChD,MAAI,UAA+B;AAEnC,WAAS,OAAO,MAAiC;AAC/C,UAAM,KAAK;AACX,cAAU;AAGV,QAAI,QAAQ;AAAW,SAAG,YAAY,QAAQ;AAC9C,QAAI,QAAQ;AAAc,SAAG,eAAe,QAAQ;AAEpD,UAAM,YAAY,CAAC,MAAa;AAC9B,YAAM,SAAU,EAA8B;AAC9C,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,OAAO,MAAM,OAAO,KAAK,EAAE;AAC9D,cAAQ,YAAY,MAAM;AAAA,IAC5B;AACA,UAAM,UAAU,CAAC,MAAa;AAC5B,YAAM,SAAU,EAA8B;AAC9C,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,OAAO,EAAE;AAC7C,cAAQ,UAAU,MAAM;AAAA,IAC1B;AACA,UAAM,UAAU,CAAC,MAAa;AAC5B,YAAMA,SAAS,EAAwC,OAAO;AAC9D,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAAA,QAAO,GAAG,OAAOA,MAAK,EAAE,EAAE;AAAA,IACzD;AAEA,OAAG,iBAAiB,iBAAiB,SAAS;AAC9C,OAAG,iBAAiB,eAAe,OAAO;AAC1C,OAAG,iBAAiB,qBAAqB,OAAO;AAEhD,UAAM,QAAQ,GAAG;AACjB,UAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,GAAG,OAAO,KAAK,EAAE,EAAE;AAEvD,WAAO;AAAA,MACL,UAAU;AACR,WAAG,oBAAoB,iBAAiB,SAAS;AACjD,WAAG,oBAAoB,eAAe,OAAO;AAC7C,WAAG,oBAAoB,qBAAqB,OAAO;AACnD,YAAI,YAAY;AAAI,oBAAU;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,aAAS,MAAM;AAAA,EACjB;AAEA,WAAS,QAAc;AACrB,UAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,MAAM,KAAK,EAAE;AAAA,EACzD;AAEA,SAAO,EAAE,WAAW,MAAM,WAAW,QAAQ,SAAS,MAAM;AAC9D","sourcesContent":["import { writable, type Readable } from \"svelte/store\";\nimport type {\n AuthPhase,\n AuthResult,\n GlideEndpoints,\n GlideError,\n GlideUser,\n} from \"@glydi/passkey-core\";\n\n/** Element instance shape we rely on (subset of BiometricAuthButton). */\ninterface GlideElement extends HTMLElement {\n phase: AuthPhase;\n endpoints: Partial<GlideEndpoints>;\n fetchOptions: RequestInit;\n}\n\nexport interface UsePasskeyAuthOptions {\n mode?: \"auto\" | \"signin\" | \"signup\";\n username?: string;\n endpoints?: Partial<GlideEndpoints>;\n /** Forwarded to fetch (e.g. custom headers). credentials:\"include\" is default. */\n fetchOptions?: RequestInit;\n onSuccess?: (result: AuthResult) => void;\n onError?: (error: GlideError) => void;\n}\n\n/** Reactive snapshot of the auth flow, exposed as a Svelte readable store. */\nexport interface PasskeyAuthState {\n phase: AuthPhase;\n /** True while the ceremony is in flight. */\n isPending: boolean;\n /** True once a feature-detect failure is reflected by the element. */\n isUnsupported: boolean;\n error: GlideError | null;\n user: GlideUser | null;\n}\n\n/** Svelte action: cleanup contract returned from `attach`. */\ninterface ActionReturn {\n destroy(): void;\n}\n\nexport interface PasskeyAuth {\n /**\n * Subscribe to the reactive auth state. Use directly in markup with `$`:\n * const auth = createPasskeyAuth();\n * $: ({ phase, isPending, error, user } = $auth);\n */\n subscribe: Readable<PasskeyAuthState>[\"subscribe\"];\n /**\n * Svelte action — attach to the element:\n * <biometric-auth-button use:auth.attach />\n * Forwards object props and bridges the `glide:*` CustomEvents into the store.\n */\n attach(node: HTMLElement): ActionReturn;\n /** Imperatively start auth (same as a user click). */\n trigger(): void;\n /** Clear the error/user without touching the element. */\n reset(): void;\n}\n\nconst INITIAL: PasskeyAuthState = {\n phase: \"idle\",\n isPending: false,\n isUnsupported: false,\n error: null,\n user: null,\n};\n\nfunction derive(phase: AuthPhase): Pick<\n PasskeyAuthState,\n \"isPending\" | \"isUnsupported\"\n> {\n return {\n isPending: phase === \"loading\" || phase === \"authenticating\",\n isUnsupported: phase === \"unsupported\",\n };\n}\n\n/**\n * Headless Svelte helper wrapping the <biometric-auth-button> Web Component.\n *\n * It owns nothing about rendering — you bring the element (via <PasskeyButton>\n * or a raw tag) and wire `use:auth.attach`. The helper subscribes to the\n * element's `glide:*` CustomEvents and projects them into a Svelte store +\n * callbacks. Mirrors the React `usePasskeyAuth` hook semantics.\n */\nexport function createPasskeyAuth(\n options: UsePasskeyAuthOptions = {},\n): PasskeyAuth {\n const store = writable<PasskeyAuthState>(INITIAL);\n let current: GlideElement | null = null;\n\n function attach(node: HTMLElement): ActionReturn {\n const el = node as GlideElement;\n current = el;\n\n // Push imperative config (objects can't be set as attributes).\n if (options.endpoints) el.endpoints = options.endpoints;\n if (options.fetchOptions) el.fetchOptions = options.fetchOptions;\n\n const onSuccess = (e: Event) => {\n const detail = (e as CustomEvent<AuthResult>).detail;\n store.update((s) => ({ ...s, user: detail.user, error: null }));\n options.onSuccess?.(detail);\n };\n const onError = (e: Event) => {\n const detail = (e as CustomEvent<GlideError>).detail;\n store.update((s) => ({ ...s, error: detail }));\n options.onError?.(detail);\n };\n const onPhase = (e: Event) => {\n const phase = (e as CustomEvent<{ phase: AuthPhase }>).detail.phase;\n store.update((s) => ({ ...s, phase, ...derive(phase) }));\n };\n\n el.addEventListener(\"glide:success\", onSuccess);\n el.addEventListener(\"glide:error\", onError);\n el.addEventListener(\"glide:phasechange\", onPhase);\n // Sync any phase already reflected before listeners attached.\n const phase = el.phase;\n store.update((s) => ({ ...s, phase, ...derive(phase) }));\n\n return {\n destroy() {\n el.removeEventListener(\"glide:success\", onSuccess);\n el.removeEventListener(\"glide:error\", onError);\n el.removeEventListener(\"glide:phasechange\", onPhase);\n if (current === el) current = null;\n },\n };\n }\n\n function trigger(): void {\n current?.click();\n }\n\n function reset(): void {\n store.update((s) => ({ ...s, error: null, user: null }));\n }\n\n return { subscribe: store.subscribe, attach, trigger, reset };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@glydi/passkey-svelte",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Thin Svelte adapter for the Glide passkey Web Component.",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./PasskeyButton.svelte": {
16
+ "svelte": "./src/PasskeyButton.svelte"
17
+ }
18
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "svelte": "./dist/index.js",
23
+ "files": [
24
+ "dist",
25
+ "src/PasskeyButton.svelte"
26
+ ],
27
+ "peerDependencies": {
28
+ "svelte": ">=4"
29
+ },
30
+ "dependencies": {
31
+ "@glydi/passkey-core": "0.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "svelte": "^4.2.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.4.0"
37
+ },
38
+ "keywords": [
39
+ "passkey",
40
+ "webauthn",
41
+ "svelte",
42
+ "authentication",
43
+ "biometric",
44
+ "fido2"
45
+ ],
46
+ "license": "MIT",
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "clean": "rm -rf dist .turbo"
52
+ }
53
+ }
@@ -0,0 +1,81 @@
1
+ <!--
2
+ PasskeyButton.svelte — thin Svelte wrapper around <biometric-auth-button>.
3
+
4
+ This is intentionally minimal: all real WebAuthn work lives in the core Web
5
+ Component. The wrapper only:
6
+ 1. ensures the element is registered (client-only import in onMount),
7
+ 2. forwards object props (endpoints, fetchOptions) imperatively, and
8
+ 3. bridges the element's CustomEvents → Svelte callback props.
9
+
10
+ Svelte natively renders custom elements in markup, so attribute props
11
+ (mode/username/label) pass straight through. For full reactive state
12
+ (phase/error/user) use `createPasskeyAuth` instead.
13
+ -->
14
+ <script>
15
+ import { onMount, tick } from "svelte";
16
+
17
+ /** @type {"auto" | "signin" | "signup"} */
18
+ export let mode = "auto";
19
+ /** @type {string | undefined} */
20
+ export let username = undefined;
21
+ /** @type {string | undefined} */
22
+ export let label = undefined;
23
+ /** @type {import("@glydi/passkey-core").GlideEndpoints | undefined} */
24
+ export let endpoints = undefined;
25
+ /** @type {RequestInit | undefined} */
26
+ export let fetchOptions = undefined;
27
+ /** @type {((result: import("@glydi/passkey-core").AuthResult) => void) | undefined} */
28
+ export let onSuccess = undefined;
29
+ /** @type {((error: import("@glydi/passkey-core").GlideError) => void) | undefined} */
30
+ export let onError = undefined;
31
+ /** @type {((detail: { phase: import("@glydi/passkey-core").AuthPhase }) => void) | undefined} */
32
+ export let onPhaseChange = undefined;
33
+
34
+ /** @type {HTMLElement | undefined} */
35
+ let el;
36
+
37
+ // Forward non-serializable object props imperatively whenever they change
38
+ // (and once the element is bound).
39
+ $: if (el && endpoints) /** @type {any} */ (el).endpoints = endpoints;
40
+ $: if (el && fetchOptions) /** @type {any} */ (el).fetchOptions = fetchOptions;
41
+
42
+ onMount(() => {
43
+ let disposed = false;
44
+
45
+ /** @param {Event} e */
46
+ const handleSuccess = (e) =>
47
+ onSuccess?.(/** @type {CustomEvent} */ (e).detail);
48
+ /** @param {Event} e */
49
+ const handleError = (e) => onError?.(/** @type {CustomEvent} */ (e).detail);
50
+ /** @param {Event} e */
51
+ const handlePhase = (e) =>
52
+ onPhaseChange?.(/** @type {CustomEvent} */ (e).detail);
53
+
54
+ // Register the element client-side, then bind listeners + object props.
55
+ import("@glydi/passkey-core/define").then(async () => {
56
+ await tick();
57
+ if (disposed || !el) return;
58
+ if (endpoints) /** @type {any} */ (el).endpoints = endpoints;
59
+ if (fetchOptions) /** @type {any} */ (el).fetchOptions = fetchOptions;
60
+ el.addEventListener("glide:success", handleSuccess);
61
+ el.addEventListener("glide:error", handleError);
62
+ el.addEventListener("glide:phasechange", handlePhase);
63
+ });
64
+
65
+ return () => {
66
+ disposed = true;
67
+ if (!el) return;
68
+ el.removeEventListener("glide:success", handleSuccess);
69
+ el.removeEventListener("glide:error", handleError);
70
+ el.removeEventListener("glide:phasechange", handlePhase);
71
+ };
72
+ });
73
+ </script>
74
+
75
+ <biometric-auth-button
76
+ bind:this={el}
77
+ {mode}
78
+ {username}
79
+ {label}
80
+ {...$$restProps}
81
+ />