@authrim/sveltekit 0.1.0 → 0.1.2

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 (34) hide show
  1. package/LICENSE +191 -191
  2. package/README.md +527 -531
  3. package/dist/components/AuthProvider.svelte +56 -56
  4. package/dist/components/ProtectedRoute.svelte +71 -71
  5. package/dist/components/SignInButton.svelte +93 -93
  6. package/dist/components/SignOutButton.svelte +72 -72
  7. package/dist/components/UserProfile.svelte +71 -71
  8. package/dist/ui/account/LinkAccountButton.svelte +133 -133
  9. package/dist/ui/account/LinkedAccountsList.svelte +233 -233
  10. package/dist/ui/account/UnlinkAccountButton.svelte +179 -179
  11. package/dist/ui/forms/EmailCodeForm.svelte +224 -224
  12. package/dist/ui/forms/PasskeyConditionalInput.svelte +173 -173
  13. package/dist/ui/forms/SocialLoginButtons.svelte +209 -209
  14. package/dist/ui/helpers/AuthError.svelte +124 -124
  15. package/dist/ui/helpers/AuthLoading.svelte +83 -83
  16. package/dist/ui/helpers/OTPInput.svelte +214 -214
  17. package/dist/ui/helpers/ResendCodeButton.svelte +140 -140
  18. package/dist/ui/passkey/PasskeyDeleteButton.svelte +177 -177
  19. package/dist/ui/passkey/PasskeyList.svelte +225 -225
  20. package/dist/ui/passkey/PasskeyRegisterButton.svelte +52 -52
  21. package/dist/ui/session/SessionExpiryIndicator.svelte +109 -109
  22. package/dist/ui/session/SessionList.svelte +231 -231
  23. package/dist/ui/session/SessionRevokeButton.svelte +72 -72
  24. package/dist/ui/shared/Badge.svelte +100 -100
  25. package/dist/ui/shared/Button.svelte +213 -213
  26. package/dist/ui/shared/Card.svelte +85 -85
  27. package/dist/ui/shared/Input.svelte +192 -192
  28. package/dist/ui/shared/Spinner.svelte +75 -75
  29. package/dist/ui/styles/base.css +168 -168
  30. package/dist/ui/styles/theme.css +279 -279
  31. package/dist/ui/templates/AccountSettingsTemplate.svelte +205 -205
  32. package/dist/ui/templates/LoginTemplate.svelte +234 -234
  33. package/dist/ui/templates/SignUpTemplate.svelte +345 -345
  34. package/package.json +112 -111
@@ -1,83 +1,83 @@
1
- <!--
2
- AuthLoading Component
3
- Loading indicator with optional message and refined animation
4
- -->
5
- <script lang="ts">
6
- import type { Size } from '../types.js';
7
- import Spinner from '../shared/Spinner.svelte';
8
-
9
- export let message = '';
10
- export let size: Size = 'md';
11
- let className = '';
12
- export { className as class };
13
- </script>
14
-
15
- <div
16
- class="authrim-loading authrim-loading--{size} {className}"
17
- role="status"
18
- aria-live="polite"
19
- {...$$restProps}
20
- >
21
- <div class="authrim-loading__spinner">
22
- <Spinner {size} />
23
- </div>
24
- {#if message}
25
- <p class="authrim-loading__message">{message}</p>
26
- {/if}
27
- <span class="authrim-sr-only">Loading</span>
28
- </div>
29
-
30
- <style>
31
- .authrim-loading {
32
- display: flex;
33
- flex-direction: column;
34
- align-items: center;
35
- justify-content: center;
36
- gap: var(--authrim-space-4);
37
- padding: var(--authrim-space-6);
38
- animation: fade-in var(--authrim-duration-normal) var(--authrim-ease-out);
39
- }
40
-
41
- @keyframes fade-in {
42
- from { opacity: 0; }
43
- to { opacity: 1; }
44
- }
45
-
46
- .authrim-loading--sm {
47
- gap: var(--authrim-space-3);
48
- padding: var(--authrim-space-4);
49
- }
50
-
51
- .authrim-loading--lg {
52
- gap: var(--authrim-space-5);
53
- padding: var(--authrim-space-8);
54
- }
55
-
56
- .authrim-loading__spinner {
57
- color: var(--authrim-color-primary);
58
- }
59
-
60
- .authrim-loading__message {
61
- margin: 0;
62
- font-size: var(--authrim-text-sm);
63
- color: var(--authrim-color-text-secondary);
64
- text-align: center;
65
- letter-spacing: var(--authrim-tracking-tight);
66
- }
67
-
68
- .authrim-loading--lg .authrim-loading__message {
69
- font-size: var(--authrim-text-base);
70
- }
71
-
72
- .authrim-sr-only {
73
- position: absolute;
74
- width: 1px;
75
- height: 1px;
76
- padding: 0;
77
- margin: -1px;
78
- overflow: hidden;
79
- clip: rect(0, 0, 0, 0);
80
- white-space: nowrap;
81
- border: 0;
82
- }
83
- </style>
1
+ <!--
2
+ AuthLoading Component
3
+ Loading indicator with optional message and refined animation
4
+ -->
5
+ <script lang="ts">
6
+ import type { Size } from '../types.js';
7
+ import Spinner from '../shared/Spinner.svelte';
8
+
9
+ export let message = '';
10
+ export let size: Size = 'md';
11
+ let className = '';
12
+ export { className as class };
13
+ </script>
14
+
15
+ <div
16
+ class="authrim-loading authrim-loading--{size} {className}"
17
+ role="status"
18
+ aria-live="polite"
19
+ {...$$restProps}
20
+ >
21
+ <div class="authrim-loading__spinner">
22
+ <Spinner {size} />
23
+ </div>
24
+ {#if message}
25
+ <p class="authrim-loading__message">{message}</p>
26
+ {/if}
27
+ <span class="authrim-sr-only">Loading</span>
28
+ </div>
29
+
30
+ <style>
31
+ .authrim-loading {
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ justify-content: center;
36
+ gap: var(--authrim-space-4);
37
+ padding: var(--authrim-space-6);
38
+ animation: fade-in var(--authrim-duration-normal) var(--authrim-ease-out);
39
+ }
40
+
41
+ @keyframes fade-in {
42
+ from { opacity: 0; }
43
+ to { opacity: 1; }
44
+ }
45
+
46
+ .authrim-loading--sm {
47
+ gap: var(--authrim-space-3);
48
+ padding: var(--authrim-space-4);
49
+ }
50
+
51
+ .authrim-loading--lg {
52
+ gap: var(--authrim-space-5);
53
+ padding: var(--authrim-space-8);
54
+ }
55
+
56
+ .authrim-loading__spinner {
57
+ color: var(--authrim-color-primary);
58
+ }
59
+
60
+ .authrim-loading__message {
61
+ margin: 0;
62
+ font-size: var(--authrim-text-sm);
63
+ color: var(--authrim-color-text-secondary);
64
+ text-align: center;
65
+ letter-spacing: var(--authrim-tracking-tight);
66
+ }
67
+
68
+ .authrim-loading--lg .authrim-loading__message {
69
+ font-size: var(--authrim-text-base);
70
+ }
71
+
72
+ .authrim-sr-only {
73
+ position: absolute;
74
+ width: 1px;
75
+ height: 1px;
76
+ padding: 0;
77
+ margin: -1px;
78
+ overflow: hidden;
79
+ clip: rect(0, 0, 0, 0);
80
+ white-space: nowrap;
81
+ border: 0;
82
+ }
83
+ </style>
@@ -1,214 +1,214 @@
1
- <!--
2
- OTPInput Component
3
- Segmented one-time password input with satisfying interactions
4
- -->
5
- <script lang="ts">
6
- import { createEventDispatcher, onMount } from 'svelte';
7
-
8
- export let length = 6;
9
- export let value = '';
10
- export let disabled = false;
11
- export let autoFocus = true;
12
- export let error = false;
13
- let className = '';
14
- export { className as class };
15
-
16
- const dispatch = createEventDispatcher<{
17
- input: { value: string };
18
- complete: { value: string };
19
- }>();
20
-
21
- let inputs: HTMLInputElement[] = [];
22
- let digits: string[] = [];
23
- let activeIndex = -1;
24
-
25
- $: {
26
- digits = value.split('').slice(0, length);
27
- while (digits.length < length) digits.push('');
28
- }
29
-
30
- onMount(() => {
31
- if (autoFocus && inputs[0] && !disabled) {
32
- inputs[0].focus();
33
- }
34
- });
35
-
36
- function handleInput(index: number, event: Event) {
37
- const input = event.target as HTMLInputElement;
38
- const inputValue = input.value.replace(/\D/g, '');
39
-
40
- if (inputValue.length === 1) {
41
- digits[index] = inputValue;
42
- updateValue();
43
- if (index < length - 1) inputs[index + 1]?.focus();
44
- } else if (inputValue.length > 1) {
45
- const chars = inputValue.split('').slice(0, length - index);
46
- chars.forEach((char, i) => {
47
- if (index + i < length) digits[index + i] = char;
48
- });
49
- updateValue();
50
- const next = Math.min(index + chars.length, length - 1);
51
- inputs[next]?.focus();
52
- }
53
- }
54
-
55
- function handleKeyDown(index: number, event: KeyboardEvent) {
56
- if (event.key === 'Backspace') {
57
- event.preventDefault();
58
- if (digits[index]) {
59
- digits[index] = '';
60
- updateValue();
61
- } else if (index > 0) {
62
- inputs[index - 1]?.focus();
63
- digits[index - 1] = '';
64
- updateValue();
65
- }
66
- } else if (event.key === 'ArrowLeft' && index > 0) {
67
- event.preventDefault();
68
- inputs[index - 1]?.focus();
69
- } else if (event.key === 'ArrowRight' && index < length - 1) {
70
- event.preventDefault();
71
- inputs[index + 1]?.focus();
72
- }
73
- }
74
-
75
- function handlePaste(event: ClipboardEvent) {
76
- event.preventDefault();
77
- const pasted = event.clipboardData?.getData('text').replace(/\D/g, '');
78
- if (pasted) {
79
- const chars = pasted.split('').slice(0, length);
80
- chars.forEach((char, i) => (digits[i] = char));
81
- for (let i = chars.length; i < length; i++) digits[i] = '';
82
- updateValue();
83
- inputs[Math.min(chars.length, length - 1)]?.focus();
84
- }
85
- }
86
-
87
- function handleFocus(index: number) {
88
- activeIndex = index;
89
- inputs[index]?.select();
90
- }
91
-
92
- function handleBlur() {
93
- activeIndex = -1;
94
- }
95
-
96
- function updateValue() {
97
- value = digits.join('');
98
- dispatch('input', { value });
99
- if (value.length === length && !digits.includes('')) {
100
- dispatch('complete', { value });
101
- }
102
- }
103
- </script>
104
-
105
- <div
106
- class="authrim-otp {className}"
107
- class:authrim-otp--error={error}
108
- role="group"
109
- aria-label="Verification code"
110
- {...$$restProps}
111
- >
112
- {#each Array(length) as _, index}
113
- <input
114
- bind:this={inputs[index]}
115
- type="text"
116
- inputmode="numeric"
117
- autocomplete="one-time-code"
118
- maxlength="1"
119
- value={digits[index]}
120
- {disabled}
121
- class="authrim-otp__digit"
122
- class:authrim-otp__digit--filled={digits[index]}
123
- class:authrim-otp__digit--active={activeIndex === index}
124
- aria-label="Digit {index + 1} of {length}"
125
- on:input={(e) => handleInput(index, e)}
126
- on:keydown={(e) => handleKeyDown(index, e)}
127
- on:paste={handlePaste}
128
- on:focus={() => handleFocus(index)}
129
- on:blur={handleBlur}
130
- />
131
- {/each}
132
- </div>
133
-
134
- <style>
135
- .authrim-otp {
136
- display: flex;
137
- gap: var(--authrim-space-2);
138
- justify-content: center;
139
- }
140
-
141
- .authrim-otp__digit {
142
- width: 52px;
143
- height: 64px;
144
- padding: 0;
145
- font-family: var(--authrim-font-mono);
146
- font-size: var(--authrim-text-2xl);
147
- font-weight: 600;
148
- text-align: center;
149
- background: var(--authrim-color-bg);
150
- border: 2px solid var(--authrim-color-border);
151
- border-radius: var(--authrim-radius-md);
152
- color: var(--authrim-color-text);
153
- caret-color: var(--authrim-color-primary);
154
- transition:
155
- border-color var(--authrim-duration-fast) var(--authrim-ease-default),
156
- box-shadow var(--authrim-duration-fast) var(--authrim-ease-default),
157
- transform var(--authrim-duration-fast) var(--authrim-ease-bounce);
158
- }
159
-
160
- .authrim-otp__digit:hover:not(:disabled):not(:focus) {
161
- border-color: var(--authrim-color-border-strong);
162
- }
163
-
164
- .authrim-otp__digit:focus {
165
- outline: none;
166
- border-color: var(--authrim-color-primary);
167
- box-shadow: var(--authrim-shadow-focus);
168
- }
169
-
170
- .authrim-otp__digit--filled {
171
- border-color: var(--authrim-color-border-strong);
172
- animation: digit-pop var(--authrim-duration-fast) var(--authrim-ease-bounce);
173
- }
174
-
175
- @keyframes digit-pop {
176
- 0% { transform: scale(1); }
177
- 50% { transform: scale(1.05); }
178
- 100% { transform: scale(1); }
179
- }
180
-
181
- .authrim-otp__digit--active {
182
- border-color: var(--authrim-color-primary);
183
- }
184
-
185
- .authrim-otp__digit:disabled {
186
- background: var(--authrim-color-bg-subtle);
187
- color: var(--authrim-color-text-muted);
188
- cursor: not-allowed;
189
- }
190
-
191
- .authrim-otp--error .authrim-otp__digit {
192
- border-color: var(--authrim-color-error);
193
- animation: shake 0.4s ease-in-out;
194
- }
195
-
196
- .authrim-otp--error .authrim-otp__digit:focus {
197
- box-shadow: var(--authrim-shadow-focus-error);
198
- }
199
-
200
- @keyframes shake {
201
- 0%, 100% { transform: translateX(0); }
202
- 20%, 60% { transform: translateX(-3px); }
203
- 40%, 80% { transform: translateX(3px); }
204
- }
205
-
206
- /* Responsive */
207
- @media (max-width: 420px) {
208
- .authrim-otp__digit {
209
- width: 44px;
210
- height: 56px;
211
- font-size: var(--authrim-text-xl);
212
- }
213
- }
214
- </style>
1
+ <!--
2
+ OTPInput Component
3
+ Segmented one-time password input with satisfying interactions
4
+ -->
5
+ <script lang="ts">
6
+ import { createEventDispatcher, onMount } from 'svelte';
7
+
8
+ export let length = 6;
9
+ export let value = '';
10
+ export let disabled = false;
11
+ export let autoFocus = true;
12
+ export let error = false;
13
+ let className = '';
14
+ export { className as class };
15
+
16
+ const dispatch = createEventDispatcher<{
17
+ input: { value: string };
18
+ complete: { value: string };
19
+ }>();
20
+
21
+ let inputs: HTMLInputElement[] = [];
22
+ let digits: string[] = [];
23
+ let activeIndex = -1;
24
+
25
+ $: {
26
+ digits = value.split('').slice(0, length);
27
+ while (digits.length < length) digits.push('');
28
+ }
29
+
30
+ onMount(() => {
31
+ if (autoFocus && inputs[0] && !disabled) {
32
+ inputs[0].focus();
33
+ }
34
+ });
35
+
36
+ function handleInput(index: number, event: Event) {
37
+ const input = event.target as HTMLInputElement;
38
+ const inputValue = input.value.replace(/\D/g, '');
39
+
40
+ if (inputValue.length === 1) {
41
+ digits[index] = inputValue;
42
+ updateValue();
43
+ if (index < length - 1) inputs[index + 1]?.focus();
44
+ } else if (inputValue.length > 1) {
45
+ const chars = inputValue.split('').slice(0, length - index);
46
+ chars.forEach((char, i) => {
47
+ if (index + i < length) digits[index + i] = char;
48
+ });
49
+ updateValue();
50
+ const next = Math.min(index + chars.length, length - 1);
51
+ inputs[next]?.focus();
52
+ }
53
+ }
54
+
55
+ function handleKeyDown(index: number, event: KeyboardEvent) {
56
+ if (event.key === 'Backspace') {
57
+ event.preventDefault();
58
+ if (digits[index]) {
59
+ digits[index] = '';
60
+ updateValue();
61
+ } else if (index > 0) {
62
+ inputs[index - 1]?.focus();
63
+ digits[index - 1] = '';
64
+ updateValue();
65
+ }
66
+ } else if (event.key === 'ArrowLeft' && index > 0) {
67
+ event.preventDefault();
68
+ inputs[index - 1]?.focus();
69
+ } else if (event.key === 'ArrowRight' && index < length - 1) {
70
+ event.preventDefault();
71
+ inputs[index + 1]?.focus();
72
+ }
73
+ }
74
+
75
+ function handlePaste(event: ClipboardEvent) {
76
+ event.preventDefault();
77
+ const pasted = event.clipboardData?.getData('text').replace(/\D/g, '');
78
+ if (pasted) {
79
+ const chars = pasted.split('').slice(0, length);
80
+ chars.forEach((char, i) => (digits[i] = char));
81
+ for (let i = chars.length; i < length; i++) digits[i] = '';
82
+ updateValue();
83
+ inputs[Math.min(chars.length, length - 1)]?.focus();
84
+ }
85
+ }
86
+
87
+ function handleFocus(index: number) {
88
+ activeIndex = index;
89
+ inputs[index]?.select();
90
+ }
91
+
92
+ function handleBlur() {
93
+ activeIndex = -1;
94
+ }
95
+
96
+ function updateValue() {
97
+ value = digits.join('');
98
+ dispatch('input', { value });
99
+ if (value.length === length && !digits.includes('')) {
100
+ dispatch('complete', { value });
101
+ }
102
+ }
103
+ </script>
104
+
105
+ <div
106
+ class="authrim-otp {className}"
107
+ class:authrim-otp--error={error}
108
+ role="group"
109
+ aria-label="Verification code"
110
+ {...$$restProps}
111
+ >
112
+ {#each Array(length) as _, index}
113
+ <input
114
+ bind:this={inputs[index]}
115
+ type="text"
116
+ inputmode="numeric"
117
+ autocomplete="one-time-code"
118
+ maxlength="1"
119
+ value={digits[index]}
120
+ {disabled}
121
+ class="authrim-otp__digit"
122
+ class:authrim-otp__digit--filled={digits[index]}
123
+ class:authrim-otp__digit--active={activeIndex === index}
124
+ aria-label="Digit {index + 1} of {length}"
125
+ on:input={(e) => handleInput(index, e)}
126
+ on:keydown={(e) => handleKeyDown(index, e)}
127
+ on:paste={handlePaste}
128
+ on:focus={() => handleFocus(index)}
129
+ on:blur={handleBlur}
130
+ />
131
+ {/each}
132
+ </div>
133
+
134
+ <style>
135
+ .authrim-otp {
136
+ display: flex;
137
+ gap: var(--authrim-space-2);
138
+ justify-content: center;
139
+ }
140
+
141
+ .authrim-otp__digit {
142
+ width: 52px;
143
+ height: 64px;
144
+ padding: 0;
145
+ font-family: var(--authrim-font-mono);
146
+ font-size: var(--authrim-text-2xl);
147
+ font-weight: 600;
148
+ text-align: center;
149
+ background: var(--authrim-color-bg);
150
+ border: 2px solid var(--authrim-color-border);
151
+ border-radius: var(--authrim-radius-md);
152
+ color: var(--authrim-color-text);
153
+ caret-color: var(--authrim-color-primary);
154
+ transition:
155
+ border-color var(--authrim-duration-fast) var(--authrim-ease-default),
156
+ box-shadow var(--authrim-duration-fast) var(--authrim-ease-default),
157
+ transform var(--authrim-duration-fast) var(--authrim-ease-bounce);
158
+ }
159
+
160
+ .authrim-otp__digit:hover:not(:disabled):not(:focus) {
161
+ border-color: var(--authrim-color-border-strong);
162
+ }
163
+
164
+ .authrim-otp__digit:focus {
165
+ outline: none;
166
+ border-color: var(--authrim-color-primary);
167
+ box-shadow: var(--authrim-shadow-focus);
168
+ }
169
+
170
+ .authrim-otp__digit--filled {
171
+ border-color: var(--authrim-color-border-strong);
172
+ animation: digit-pop var(--authrim-duration-fast) var(--authrim-ease-bounce);
173
+ }
174
+
175
+ @keyframes digit-pop {
176
+ 0% { transform: scale(1); }
177
+ 50% { transform: scale(1.05); }
178
+ 100% { transform: scale(1); }
179
+ }
180
+
181
+ .authrim-otp__digit--active {
182
+ border-color: var(--authrim-color-primary);
183
+ }
184
+
185
+ .authrim-otp__digit:disabled {
186
+ background: var(--authrim-color-bg-subtle);
187
+ color: var(--authrim-color-text-muted);
188
+ cursor: not-allowed;
189
+ }
190
+
191
+ .authrim-otp--error .authrim-otp__digit {
192
+ border-color: var(--authrim-color-error);
193
+ animation: shake 0.4s ease-in-out;
194
+ }
195
+
196
+ .authrim-otp--error .authrim-otp__digit:focus {
197
+ box-shadow: var(--authrim-shadow-focus-error);
198
+ }
199
+
200
+ @keyframes shake {
201
+ 0%, 100% { transform: translateX(0); }
202
+ 20%, 60% { transform: translateX(-3px); }
203
+ 40%, 80% { transform: translateX(3px); }
204
+ }
205
+
206
+ /* Responsive */
207
+ @media (max-width: 420px) {
208
+ .authrim-otp__digit {
209
+ width: 44px;
210
+ height: 56px;
211
+ font-size: var(--authrim-text-xl);
212
+ }
213
+ }
214
+ </style>