@mrgnw/anahtar 0.0.19 → 0.0.21

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.
@@ -22,6 +22,7 @@ interface Props {
22
22
  onSignOut?: () => void | Promise<void>;
23
23
  onPasskeysChange?: () => void | Promise<void>;
24
24
  getPasskeys?: () => Promise<PasskeyInfo[]>;
25
+ onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
25
26
  }
26
27
 
27
28
  let {
@@ -34,6 +35,7 @@ let {
34
35
  onSignOut,
35
36
  onPasskeysChange,
36
37
  getPasskeys,
38
+ onStepChange,
37
39
  }: Props = $props();
38
40
 
39
41
  let expanded = $state(false);
@@ -47,6 +49,7 @@ let otpStep = $state(false);
47
49
  let otpDigits = $state<string[]>(['', '', '', '', '']);
48
50
  let otpInputs = $state<HTMLInputElement[]>([]);
49
51
  let showPasskeys = $state(false);
52
+ let pendingSuccess = $state(false);
50
53
  let isTouch = $state(false);
51
54
  let hoveredKey = $state<string | null>(null);
52
55
  let passkeyRefresh = $state(0);
@@ -75,9 +78,11 @@ async function handleSignOut() {
75
78
  email = '';
76
79
  otpStep = false;
77
80
  passkeyOnboarding = false;
81
+ pendingSuccess = false;
78
82
  showPasskeys = false;
79
83
  expanded = false;
80
84
  error = '';
85
+ onStepChange?.('email');
81
86
  await onSignOut?.();
82
87
  }
83
88
 
@@ -158,6 +163,7 @@ async function handleEmailSubmit(e: SubmitEvent) {
158
163
  const data = ((await res.json().catch(() => ({}))) as any);
159
164
  otpDigits = ['', '', '', '', ''];
160
165
  otpStep = true;
166
+ onStepChange?.('otp');
161
167
  if (data?.devCode) {
162
168
  const code = String(data.devCode);
163
169
  for (let i = 0; i < 5; i++) otpDigits[i] = code[i] ?? '';
@@ -217,10 +223,13 @@ async function verifyOtp(code: string) {
217
223
  return;
218
224
  }
219
225
  const data = ((await res.json().catch(() => ({}))) as any);
220
- await onSuccess?.();
221
226
  otpStep = false;
222
227
  if (!data.hasPasskey && !data.skipPasskeyPrompt) {
228
+ pendingSuccess = true;
223
229
  passkeyOnboarding = true;
230
+ } else {
231
+ await onSuccess?.();
232
+ onStepChange?.('authenticated');
224
233
  }
225
234
  } catch {
226
235
  error = m.errorGeneric;
@@ -261,11 +270,21 @@ async function handlePasskeyRegister() {
261
270
  passkeyOnboarding = false;
262
271
  passkeyRefresh++;
263
272
  await onPasskeysChange?.();
273
+ if (pendingSuccess) {
274
+ pendingSuccess = false;
275
+ await onSuccess?.();
276
+ onStepChange?.('authenticated');
277
+ }
264
278
  }
265
279
 
266
- function handlePasskeySkip() {
280
+ async function handlePasskeySkip() {
267
281
  fetch(`${apiBase}/skip-passkey`, { method: 'POST' });
268
282
  passkeyOnboarding = false;
283
+ if (pendingSuccess) {
284
+ pendingSuccess = false;
285
+ await onSuccess?.();
286
+ onStepChange?.('authenticated');
287
+ }
269
288
  }
270
289
 
271
290
  async function addPasskey() {
@@ -385,9 +404,15 @@ async function removePasskey(id: string) {
385
404
  autocomplete="username webauthn"
386
405
  disabled={loading}
387
406
  />
388
- <button type="submit" class="anahtar-pill-go" disabled={loading || !email.includes('@')}>
389
- {loading ? '...' : m.continue}
390
- </button>
407
+ <button type="submit" class="anahtar-pill-go" disabled={loading || !email.includes('@')}>
408
+ {#if loading}
409
+ ...
410
+ {:else}
411
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
412
+ <path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
413
+ </svg>
414
+ {/if}
415
+ </button>
391
416
  </form>
392
417
  {/if}
393
418
  </div>
@@ -396,7 +421,7 @@ async function removePasskey(id: string) {
396
421
  <div class="anahtar-pill-otp-help" transition:slide={{ duration: 150 }}>
397
422
  <span class="anahtar-pill-otp-help-text">{m.codeSentTo}</span>
398
423
  <span class="anahtar-pill-otp-help-sep">&middot;</span>
399
- <button class="anahtar-pill-otp-help-link" onclick={() => { otpStep = false; error = ''; }}>{m.differentEmail}</button>
424
+ <button class="anahtar-pill-otp-help-link" onclick={() => { otpStep = false; error = ''; onStepChange?.('email'); }}>{m.differentEmail}</button>
400
425
  <span class="anahtar-pill-otp-help-sep">&middot;</span>
401
426
  <button class="anahtar-pill-otp-help-link" onclick={resend} disabled={loading}>{m.resend}</button>
402
427
  </div>
@@ -406,7 +431,7 @@ async function removePasskey(id: string) {
406
431
  <p class="anahtar-pill-error" transition:slide={{ duration: 150 }}>{error}</p>
407
432
  {/if}
408
433
 
409
- {#if passkeyOnboarding && isAuthenticated}
434
+ {#if passkeyOnboarding}
410
435
  <div class="anahtar-pill-onboarding" transition:slide={{ duration: 200 }}>
411
436
  <PasskeyPrompt {m} onRegister={handlePasskeyRegister} onSkip={handlePasskeySkip} />
412
437
  </div>
@@ -475,6 +500,7 @@ async function removePasskey(id: string) {
475
500
  white-space: nowrap;
476
501
  height: 2.25rem;
477
502
  box-sizing: border-box;
503
+ overflow: hidden;
478
504
  }
479
505
 
480
506
  .anahtar-pill-sep {
@@ -515,6 +541,9 @@ async function removePasskey(id: string) {
515
541
  display: flex;
516
542
  align-items: center;
517
543
  gap: 0.375rem;
544
+ flex: 1;
545
+ min-width: 0;
546
+ overflow: hidden;
518
547
  }
519
548
 
520
549
  .anahtar-pill-email-input {
@@ -523,7 +552,8 @@ async function removePasskey(id: string) {
523
552
  outline: none;
524
553
  font-size: 0.875rem;
525
554
  color: var(--anahtar-pill-fg, #111827);
526
- width: 190px;
555
+ flex: 1;
556
+ min-width: 0;
527
557
  padding: 0.125rem 0;
528
558
  }
529
559
  .anahtar-pill-email-input::placeholder { color: var(--anahtar-pill-placeholder, #9ca3af); }
@@ -533,12 +563,14 @@ async function removePasskey(id: string) {
533
563
  color: var(--anahtar-primary-fg, #fff);
534
564
  border: none;
535
565
  border-radius: 9999px;
536
- padding: 0.25rem 0.75rem;
537
- font-size: 0.8125rem;
538
- font-weight: 500;
566
+ padding: 0.3rem;
567
+ display: flex;
568
+ align-items: center;
569
+ justify-content: center;
570
+ flex-shrink: 0;
539
571
  cursor: pointer;
540
572
  transition: opacity 0.15s;
541
- white-space: nowrap;
573
+ line-height: 1;
542
574
  }
543
575
  .anahtar-pill-go:disabled { opacity: 0.4; cursor: not-allowed; }
544
576
  .anahtar-pill-go:hover:not(:disabled) { opacity: 0.85; }
@@ -17,6 +17,7 @@ interface Props {
17
17
  onSignOut?: () => void | Promise<void>;
18
18
  onPasskeysChange?: () => void | Promise<void>;
19
19
  getPasskeys?: () => Promise<PasskeyInfo[]>;
20
+ onStepChange?: (step: 'email' | 'otp' | 'authenticated') => void;
20
21
  }
21
22
  declare const AuthPill: import("svelte").Component<Props, {}, "">;
22
23
  type AuthPill = ReturnType<typeof AuthPill>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrgnw/anahtar",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Opinionated, reusable auth for SvelteKit. Email+OTP + passkeys.",
5
5
  "license": "MIT",
6
6
  "type": "module",