@ternkit/tern 1.5.0 → 1.7.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/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/runtime/builtin.d.ts +1 -0
- package/dist/runtime/builtin.js +182 -0
- package/dist/runtime/deferred.d.ts +1 -0
- package/dist/runtime/deferred.js +4 -2
- package/dist/runtime/i18n.svelte.d.ts +6 -0
- package/dist/runtime/i18n.svelte.js +10 -0
- package/dist/runtime/passkeys.d.ts +21 -0
- package/dist/runtime/passkeys.js +107 -0
- package/dist/runtime/router.svelte.js +2 -1
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate.d.ts +2 -1
- package/dist/runtime/validate.js +20 -18
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +8 -4
- package/dist/ui/AccountSection.svelte +75 -0
- package/dist/ui/AccountSection.svelte.d.ts +12 -0
- package/dist/ui/AlertDialog.svelte +5 -2
- package/dist/ui/AuthCard.svelte +64 -0
- package/dist/ui/AuthCard.svelte.d.ts +12 -0
- package/dist/ui/BarChart.svelte +2 -1
- package/dist/ui/Breadcrumb.svelte +2 -1
- package/dist/ui/Calendar.svelte +3 -2
- package/dist/ui/Carousel.svelte +4 -3
- package/dist/ui/ChartFrame.svelte +2 -1
- package/dist/ui/Combobox.svelte +6 -3
- package/dist/ui/Command.svelte +5 -1
- package/dist/ui/ConsentScreen.svelte +7 -6
- package/dist/ui/DataTable.svelte +7 -5
- package/dist/ui/DatePicker.svelte +8 -5
- package/dist/ui/Devices.svelte +50 -0
- package/dist/ui/Devices.svelte.d.ts +23 -0
- package/dist/ui/Dialog.svelte +2 -1
- package/dist/ui/DonutChart.svelte +3 -1
- package/dist/ui/Field.svelte +1 -1
- package/dist/ui/ForgotPassword.svelte +43 -0
- package/dist/ui/ForgotPassword.svelte.d.ts +16 -0
- package/dist/ui/Form.svelte +27 -4
- package/dist/ui/LinkedAccounts.svelte +54 -0
- package/dist/ui/LinkedAccounts.svelte.d.ts +27 -0
- package/dist/ui/NavigationMenu.svelte +3 -1
- package/dist/ui/Pagination.svelte +5 -3
- package/dist/ui/Passkeys.svelte +73 -0
- package/dist/ui/Passkeys.svelte.d.ts +21 -0
- package/dist/ui/PasswordInput.svelte +2 -1
- package/dist/ui/ResetPassword.svelte +54 -0
- package/dist/ui/ResetPassword.svelte.d.ts +23 -0
- package/dist/ui/Resizable.svelte +3 -1
- package/dist/ui/SelectMenu.svelte +5 -2
- package/dist/ui/Sidebar.svelte +5 -3
- package/dist/ui/SignIn.svelte +61 -13
- package/dist/ui/SignIn.svelte.d.ts +5 -1
- package/dist/ui/SignUp.svelte +10 -8
- package/dist/ui/Slider.svelte +2 -1
- package/dist/ui/Spinner.svelte +3 -1
- package/dist/ui/Switch.svelte +2 -1
- package/dist/ui/Table.svelte +3 -1
- package/dist/ui/Toaster.svelte +3 -2
- package/dist/ui/Toggle.svelte +2 -0
- package/dist/ui/ToggleGroup.svelte +2 -0
- package/dist/ui/TwoStep.svelte +155 -0
- package/dist/ui/TwoStep.svelte.d.ts +31 -0
- package/dist/ui/TwoStepCode.svelte +70 -0
- package/dist/ui/TwoStepCode.svelte.d.ts +16 -0
- package/dist/ui/accountDates.d.ts +1 -0
- package/dist/ui/accountDates.js +15 -0
- package/dist/ui/announce.d.ts +4 -0
- package/dist/ui/announce.js +14 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +7 -0
- package/dist/vite/native.js +21 -4
- package/package.json +1 -1
package/dist/ui/SignUp.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { FormField } from '../runtime/validate.js';
|
|
4
5
|
import Button from './Button.svelte';
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
footer?: Snippet;
|
|
27
28
|
class?: string;
|
|
28
29
|
};
|
|
29
|
-
let { title
|
|
30
|
+
let { title: titleProp, description, level = 1, action, rules, form, name = true, confirm = false, termsHref, signInHref, footer, class: klass = '' }: Props = $props();
|
|
31
|
+
const title = $derived(titleProp ?? tb('tern.ui.signUp.title'));
|
|
30
32
|
|
|
31
33
|
let submitting = $state(false);
|
|
32
34
|
const minPassword = $derived(rules?.find((f) => f.name === 'password')?.rules?.find((r) => r.name === 'min')?.arg);
|
|
@@ -39,21 +41,21 @@
|
|
|
39
41
|
</header>
|
|
40
42
|
<Form {rules} {form} {action} bind:submitting reset={false}>
|
|
41
43
|
{#if name}
|
|
42
|
-
<Field label=
|
|
44
|
+
<Field label={tb('tern.ui.signUp.name')} name="name"><Input autocomplete="name" /></Field>
|
|
43
45
|
{/if}
|
|
44
|
-
<Field label=
|
|
45
|
-
<Field label=
|
|
46
|
+
<Field label={tb('tern.ui.signUp.email')} name="email"><Input type="email" autocomplete="email" spellcheck="false" /></Field>
|
|
47
|
+
<Field label={tb('tern.ui.signUp.password')} name="password" description={minPassword ? tb('tern.ui.signUp.minPassword', { n: minPassword }) : undefined}>
|
|
46
48
|
<PasswordInput autocomplete="new-password" />
|
|
47
49
|
</Field>
|
|
48
50
|
{#if confirm}
|
|
49
|
-
<Field label=
|
|
51
|
+
<Field label={tb('tern.ui.signUp.confirm')} name="confirm"><PasswordInput autocomplete="new-password" /></Field>
|
|
50
52
|
{/if}
|
|
51
53
|
{#if termsHref}
|
|
52
|
-
<Checkbox name="terms">
|
|
54
|
+
<Checkbox name="terms">{tb('tern.ui.signUp.agree')} <a href={termsHref}>{tb('tern.ui.signUp.terms')}</a></Checkbox>
|
|
53
55
|
{/if}
|
|
54
|
-
<Button type="submit" loading={submitting}>
|
|
56
|
+
<Button type="submit" loading={submitting}>{tb('tern.ui.signUp.submit')}</Button>
|
|
55
57
|
</Form>
|
|
56
|
-
{#if signInHref}<p class="switch">
|
|
58
|
+
{#if signInHref}<p class="switch">{tb('tern.ui.signUp.haveAccount')} <a href={signInHref}>{tb('tern.ui.signUp.signIn')}</a></p>{/if}
|
|
57
59
|
{#if footer}<div class="footer">{@render footer()}</div>{/if}
|
|
58
60
|
</section>
|
|
59
61
|
|
package/dist/ui/Slider.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { announce } from './announce.js';
|
|
2
3
|
import { getField } from './field.js';
|
|
3
4
|
|
|
4
5
|
type Props = {
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
}
|
|
89
90
|
</script>
|
|
90
91
|
|
|
91
|
-
<div class="tern-slider {klass}" class:disabled={off}>
|
|
92
|
+
<div class="tern-slider {klass}" class:disabled={off} use:announce={value}>
|
|
92
93
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
93
94
|
<div
|
|
94
95
|
bind:this={track}
|
package/dist/ui/Spinner.svelte
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
2
3
|
type Props = { label?: string; size?: string; class?: string };
|
|
3
|
-
let { label
|
|
4
|
+
let { label: labelProp, size = '1.25rem', class: klass = '' }: Props = $props();
|
|
5
|
+
const label = $derived(labelProp ?? tb('tern.ui.spinner.label'));
|
|
4
6
|
</script>
|
|
5
7
|
|
|
6
8
|
<span class="tern-spinner {klass}" role="status" style:--size={size}>
|
package/dist/ui/Switch.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { announce } from './announce.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
5
|
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
33
34
|
<span class="label" id="tern-switch-{uid}" onclick={() => !disabled && (checked = !checked)}>{@render children()}</span>
|
|
34
35
|
{/if}
|
|
35
|
-
{#if name}<input type="hidden" {name} value={checked ? 'on' : 'off'} />{/if}
|
|
36
|
+
{#if name}<input type="hidden" {name} value={checked ? 'on' : 'off'} use:announce={checked} />{/if}
|
|
36
37
|
</span>
|
|
37
38
|
|
|
38
39
|
<style>
|
package/dist/ui/Table.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts" generics="Row">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { Column } from './table.js';
|
|
4
5
|
|
|
@@ -15,7 +16,8 @@
|
|
|
15
16
|
empty?: string;
|
|
16
17
|
class?: string;
|
|
17
18
|
};
|
|
18
|
-
let { columns, rows, caption, hideCaption = false, key = (r) => JSON.stringify(r), empty
|
|
19
|
+
let { columns, rows, caption, hideCaption = false, key = (r) => JSON.stringify(r), empty: emptyProp, class: klass = '' }: Props = $props();
|
|
20
|
+
const empty = $derived(emptyProp ?? tb('tern.ui.table.empty'));
|
|
19
21
|
</script>
|
|
20
22
|
|
|
21
23
|
<!-- A semantic table, with a caption, column headers and numeric alignment. -->
|
package/dist/ui/Toaster.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
2
3
|
import { dismiss, toasts, type Toast } from './toast.svelte.js';
|
|
3
4
|
|
|
4
5
|
type Props = { position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' };
|
|
@@ -36,14 +37,14 @@
|
|
|
36
37
|
<p class="message">{t.message}</p>
|
|
37
38
|
{#if t.description}<p class="description">{t.description}</p>{/if}
|
|
38
39
|
</div>
|
|
39
|
-
<button type="button" aria-label=
|
|
40
|
+
<button type="button" aria-label={tb('tern.ui.toaster.dismiss')} onclick={() => dismiss(t.id)}>
|
|
40
41
|
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" /></svg>
|
|
41
42
|
</button>
|
|
42
43
|
</li>
|
|
43
44
|
{/each}
|
|
44
45
|
{/snippet}
|
|
45
46
|
|
|
46
|
-
<section class="tern-toaster {position}" aria-label=
|
|
47
|
+
<section class="tern-toaster {position}" aria-label={tb('tern.ui.toaster.label')}>
|
|
47
48
|
<!-- Errors are announced at once; the rest when the reader is idle. -->
|
|
48
49
|
<ol aria-live="assertive" aria-relevant="additions">{@render list(urgent)}</ol>
|
|
49
50
|
<ol aria-live="polite" aria-relevant="additions">{@render list(polite)}</ol>
|
package/dist/ui/Toggle.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { announce } from './announce.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
5
|
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
</script>
|
|
19
20
|
|
|
20
21
|
<button
|
|
22
|
+
use:announce={pressed}
|
|
21
23
|
type="button"
|
|
22
24
|
class="tern-toggle {variant} {size} {klass}"
|
|
23
25
|
aria-pressed={pressed}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { announce } from './announce.js';
|
|
2
3
|
import type { Snippet } from 'svelte';
|
|
3
4
|
import { getField } from './field.js';
|
|
4
5
|
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
</script>
|
|
59
60
|
|
|
60
61
|
<div
|
|
62
|
+
use:announce={value}
|
|
61
63
|
class="tern-toggle-group {size} {klass}"
|
|
62
64
|
role={type === 'single' ? 'radiogroup' : 'group'}
|
|
63
65
|
aria-label={field ? undefined : label}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
3
|
+
import AccountSection from './AccountSection.svelte';
|
|
4
|
+
import Alert from './Alert.svelte';
|
|
5
|
+
import Badge from './Badge.svelte';
|
|
6
|
+
import Button from './Button.svelte';
|
|
7
|
+
import Field from './Field.svelte';
|
|
8
|
+
import Form from './Form.svelte';
|
|
9
|
+
import Input from './Input.svelte';
|
|
10
|
+
import InputOTP from './InputOTP.svelte';
|
|
11
|
+
|
|
12
|
+
type Setup = { secret: string; uri: string; qr: string };
|
|
13
|
+
type Props = {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
level?: 2 | 3 | 4;
|
|
17
|
+
/** User.twoStep: whether it's on. */
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
/** From acc.SetUpTwoStep or acc.PendingTwoStepSetup, while turning it on. */
|
|
20
|
+
setup?: Setup | null;
|
|
21
|
+
/** Recovery codes to show once: from acc.ConfirmTwoStep or acc.RecoveryCodes. */
|
|
22
|
+
recoveryCodes?: string[] | null;
|
|
23
|
+
/** How many recovery codes are left, from acc.RecoveryCodesLeft. */
|
|
24
|
+
codesLeft?: number;
|
|
25
|
+
/** The action that calls acc.SetUpTwoStep. */
|
|
26
|
+
setupAction?: string;
|
|
27
|
+
/** The action that calls acc.ConfirmTwoStep with the posted code. */
|
|
28
|
+
confirmAction?: string;
|
|
29
|
+
/** The action that calls acc.TurnOffTwoStep with the posted code. */
|
|
30
|
+
offAction?: string;
|
|
31
|
+
/** The action that calls acc.RecoveryCodes. */
|
|
32
|
+
codesAction?: string;
|
|
33
|
+
form?: unknown;
|
|
34
|
+
class?: string;
|
|
35
|
+
};
|
|
36
|
+
let { title, description, level = 2, enabled, setup, recoveryCodes, codesLeft, setupAction, confirmAction, offAction, codesAction, form, class: klass = '' }: Props = $props();
|
|
37
|
+
let turningOff = $state(false);
|
|
38
|
+
let copied = $state(false);
|
|
39
|
+
async function copy() {
|
|
40
|
+
try {
|
|
41
|
+
await navigator.clipboard.writeText((recoveryCodes ?? []).join('\n'));
|
|
42
|
+
copied = true;
|
|
43
|
+
} catch {
|
|
44
|
+
copied = false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<AccountSection id="tern-two-step-settings" title={title ?? tb('tern.ui.twoStep.title')} description={description ?? tb('tern.ui.twoStep.description')} {level} class={klass}>
|
|
50
|
+
{#if recoveryCodes?.length}
|
|
51
|
+
<Alert tone="warning" title={tb('tern.ui.twoStep.codesTitle')}>{tb('tern.ui.twoStep.codesHelp')}</Alert>
|
|
52
|
+
<ol class="codes" data-testid="recovery-codes">
|
|
53
|
+
{#each recoveryCodes as c (c)}<li><code>{c}</code></li>{/each}
|
|
54
|
+
</ol>
|
|
55
|
+
<div><Button type="button" variant="secondary" size="sm" onclick={copy}>{copied ? tb('tern.ui.twoStep.copied') : tb('tern.ui.twoStep.copy')}</Button></div>
|
|
56
|
+
{/if}
|
|
57
|
+
{#if enabled}
|
|
58
|
+
<p class="status">
|
|
59
|
+
<Badge tone="success">{tb('tern.ui.twoStep.on')}</Badge>
|
|
60
|
+
{#if codesLeft !== undefined}<span class="tern-account-meta">{tb('tern.ui.twoStep.codesLeft', { n: codesLeft })}</span>{/if}
|
|
61
|
+
</p>
|
|
62
|
+
<div class="row">
|
|
63
|
+
{#if codesAction}
|
|
64
|
+
<Form action={codesAction} reset={false}><Button type="submit" variant="secondary" size="sm">{tb('tern.ui.twoStep.newCodes')}</Button></Form>
|
|
65
|
+
{/if}
|
|
66
|
+
{#if offAction && !turningOff}
|
|
67
|
+
<Button type="button" variant="ghost" size="sm" onclick={() => (turningOff = true)}>{tb('tern.ui.twoStep.turnOff')}</Button>
|
|
68
|
+
{/if}
|
|
69
|
+
</div>
|
|
70
|
+
{#if offAction && turningOff}
|
|
71
|
+
<Form action={offAction} {form} reset={false}>
|
|
72
|
+
<Field label={tb('tern.ui.twoStep.offLabel')} name="code"><Input autocomplete="one-time-code" spellcheck="false" /></Field>
|
|
73
|
+
<div class="row">
|
|
74
|
+
<Button type="submit" variant="danger" size="sm">{tb('tern.ui.twoStep.turnOff')}</Button>
|
|
75
|
+
<Button type="button" variant="ghost" size="sm" onclick={() => (turningOff = false)}>{tb('tern.ui.alertDialog.cancel')}</Button>
|
|
76
|
+
</div>
|
|
77
|
+
</Form>
|
|
78
|
+
{/if}
|
|
79
|
+
{:else if setup}
|
|
80
|
+
<ol class="steps">
|
|
81
|
+
<li>
|
|
82
|
+
{tb('tern.ui.twoStep.scan')}
|
|
83
|
+
<img src={setup.qr} alt={tb('tern.ui.twoStep.qrAlt')} width="200" height="200" />
|
|
84
|
+
<details>
|
|
85
|
+
<summary>{tb('tern.ui.twoStep.cantScan')}</summary>
|
|
86
|
+
<p><code data-testid="two-step-secret">{setup.secret}</code></p>
|
|
87
|
+
<p><a href={setup.uri}>{tb('tern.ui.twoStep.openApp')}</a></p>
|
|
88
|
+
</details>
|
|
89
|
+
</li>
|
|
90
|
+
<li>
|
|
91
|
+
<Form action={confirmAction} {form} reset={false}>
|
|
92
|
+
<Field label={tb('tern.ui.twoStep.codeLabel')} name="code"><InputOTP length={6} /></Field>
|
|
93
|
+
<div><Button type="submit" size="sm">{tb('tern.ui.twoStep.turnOn')}</Button></div>
|
|
94
|
+
</Form>
|
|
95
|
+
</li>
|
|
96
|
+
</ol>
|
|
97
|
+
{:else if setupAction}
|
|
98
|
+
<Form action={setupAction} reset={false}>
|
|
99
|
+
<div><Button type="submit" variant="secondary" size="sm">{tb('tern.ui.twoStep.setUp')}</Button></div>
|
|
100
|
+
</Form>
|
|
101
|
+
{/if}
|
|
102
|
+
</AccountSection>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
.codes {
|
|
106
|
+
display: grid;
|
|
107
|
+
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
|
108
|
+
gap: 0.5rem;
|
|
109
|
+
margin: 0;
|
|
110
|
+
padding: 1rem 1rem 1rem 2.25rem;
|
|
111
|
+
border: 1px solid var(--tern-border);
|
|
112
|
+
border-radius: var(--tern-radius);
|
|
113
|
+
background: var(--tern-surface);
|
|
114
|
+
}
|
|
115
|
+
.codes code,
|
|
116
|
+
.steps code {
|
|
117
|
+
font-family: var(--tern-mono, ui-monospace, monospace);
|
|
118
|
+
font-size: 0.9375rem;
|
|
119
|
+
}
|
|
120
|
+
.status,
|
|
121
|
+
.row {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-wrap: wrap;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 0.75rem;
|
|
126
|
+
margin: 0;
|
|
127
|
+
}
|
|
128
|
+
.steps {
|
|
129
|
+
display: grid;
|
|
130
|
+
gap: 1.25rem;
|
|
131
|
+
margin: 0;
|
|
132
|
+
padding-left: 1.25rem;
|
|
133
|
+
}
|
|
134
|
+
.steps li {
|
|
135
|
+
display: grid;
|
|
136
|
+
gap: 0.75rem;
|
|
137
|
+
justify-items: start;
|
|
138
|
+
}
|
|
139
|
+
.steps img {
|
|
140
|
+
border-radius: var(--tern-radius);
|
|
141
|
+
background: #fff;
|
|
142
|
+
padding: 0.5rem;
|
|
143
|
+
image-rendering: pixelated;
|
|
144
|
+
}
|
|
145
|
+
details {
|
|
146
|
+
font-size: 0.875rem;
|
|
147
|
+
color: var(--tern-muted);
|
|
148
|
+
}
|
|
149
|
+
summary {
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
}
|
|
152
|
+
.steps a {
|
|
153
|
+
color: var(--tern-accent);
|
|
154
|
+
}
|
|
155
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type Setup = {
|
|
2
|
+
secret: string;
|
|
3
|
+
uri: string;
|
|
4
|
+
qr: string;
|
|
5
|
+
};
|
|
6
|
+
type Props = {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
level?: 2 | 3 | 4;
|
|
10
|
+
/** User.twoStep: whether it's on. */
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
/** From acc.SetUpTwoStep or acc.PendingTwoStepSetup, while turning it on. */
|
|
13
|
+
setup?: Setup | null;
|
|
14
|
+
/** Recovery codes to show once: from acc.ConfirmTwoStep or acc.RecoveryCodes. */
|
|
15
|
+
recoveryCodes?: string[] | null;
|
|
16
|
+
/** How many recovery codes are left, from acc.RecoveryCodesLeft. */
|
|
17
|
+
codesLeft?: number;
|
|
18
|
+
/** The action that calls acc.SetUpTwoStep. */
|
|
19
|
+
setupAction?: string;
|
|
20
|
+
/** The action that calls acc.ConfirmTwoStep with the posted code. */
|
|
21
|
+
confirmAction?: string;
|
|
22
|
+
/** The action that calls acc.TurnOffTwoStep with the posted code. */
|
|
23
|
+
offAction?: string;
|
|
24
|
+
/** The action that calls acc.RecoveryCodes. */
|
|
25
|
+
codesAction?: string;
|
|
26
|
+
form?: unknown;
|
|
27
|
+
class?: string;
|
|
28
|
+
};
|
|
29
|
+
declare const TwoStep: import("svelte").Component<Props, {}, "">;
|
|
30
|
+
type TwoStep = ReturnType<typeof TwoStep>;
|
|
31
|
+
export default TwoStep;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tb } from '../runtime/i18n.svelte.js';
|
|
3
|
+
import type { FormField } from '../runtime/validate.js';
|
|
4
|
+
import AuthCard from './AuthCard.svelte';
|
|
5
|
+
import Button from './Button.svelte';
|
|
6
|
+
import Field from './Field.svelte';
|
|
7
|
+
import Form from './Form.svelte';
|
|
8
|
+
import Input from './Input.svelte';
|
|
9
|
+
import InputOTP from './InputOTP.svelte';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
level?: 1 | 2 | 3;
|
|
15
|
+
/** The action that calls acc.CompleteSignIn with the posted code. */
|
|
16
|
+
action?: string;
|
|
17
|
+
rules?: FormField[];
|
|
18
|
+
form?: unknown;
|
|
19
|
+
/** Where to go back to sign in. */
|
|
20
|
+
signInHref?: string;
|
|
21
|
+
class?: string;
|
|
22
|
+
};
|
|
23
|
+
let { title, description, level = 1, action, rules, form, signInHref, class: klass = '' }: Props = $props();
|
|
24
|
+
|
|
25
|
+
let submitting = $state(false);
|
|
26
|
+
let recovery = $state(false);
|
|
27
|
+
let el = $state<HTMLElement>();
|
|
28
|
+
// A full code posts the form, as typing the last digit should.
|
|
29
|
+
const submit = () => el?.querySelector('form')?.requestSubmit();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div bind:this={el} style="display: contents">
|
|
33
|
+
<AuthCard id="tern-two-step-title" title={title ?? tb('tern.ui.twoStep.codeTitle')} description={description ?? (recovery ? tb('tern.ui.twoStep.recoveryDescription') : tb('tern.ui.twoStep.codeDescription'))} {level} class={klass}>
|
|
34
|
+
<Form {rules} {form} {action} bind:submitting reset={false}>
|
|
35
|
+
{#if recovery}
|
|
36
|
+
<Field label={tb('tern.ui.twoStep.recoveryLabel')} name="code">
|
|
37
|
+
<Input autocomplete="off" spellcheck="false" autocapitalize="off" />
|
|
38
|
+
</Field>
|
|
39
|
+
{:else}
|
|
40
|
+
<Field label={tb('tern.ui.twoStep.codeLabel')} name="code">
|
|
41
|
+
<InputOTP length={6} oncomplete={submit} />
|
|
42
|
+
</Field>
|
|
43
|
+
{/if}
|
|
44
|
+
<Button type="submit" loading={submitting}>{tb('tern.ui.twoStep.verify')}</Button>
|
|
45
|
+
</Form>
|
|
46
|
+
<p class="tern-auth-note">
|
|
47
|
+
<button type="button" class="link" onclick={() => (recovery = !recovery)}>
|
|
48
|
+
{recovery ? tb('tern.ui.twoStep.useApp') : tb('tern.ui.twoStep.useRecovery')}
|
|
49
|
+
</button>
|
|
50
|
+
</p>
|
|
51
|
+
{#if signInHref}<p class="tern-auth-note"><a href={signInHref}>{tb('tern.ui.forgotPassword.back')}</a></p>{/if}
|
|
52
|
+
</AuthCard>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<style>
|
|
56
|
+
.link {
|
|
57
|
+
padding: 0;
|
|
58
|
+
border: 0;
|
|
59
|
+
background: none;
|
|
60
|
+
color: var(--tern-accent);
|
|
61
|
+
font: inherit;
|
|
62
|
+
font-weight: 550;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
}
|
|
65
|
+
.link:focus-visible {
|
|
66
|
+
outline: 2px solid var(--tern-ring);
|
|
67
|
+
outline-offset: 2px;
|
|
68
|
+
border-radius: 2px;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FormField } from '../runtime/validate.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
level?: 1 | 2 | 3;
|
|
6
|
+
/** The action that calls acc.CompleteSignIn with the posted code. */
|
|
7
|
+
action?: string;
|
|
8
|
+
rules?: FormField[];
|
|
9
|
+
form?: unknown;
|
|
10
|
+
/** Where to go back to sign in. */
|
|
11
|
+
signInHref?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
};
|
|
14
|
+
declare const TwoStepCode: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type TwoStepCode = ReturnType<typeof TwoStepCode>;
|
|
16
|
+
export default TwoStepCode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function when(iso: string | null | undefined): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Dates on the account screens, in the user's locale.
|
|
2
|
+
import { i18n } from '../runtime/i18n.svelte.js';
|
|
3
|
+
export function when(iso) {
|
|
4
|
+
if (!iso)
|
|
5
|
+
return '';
|
|
6
|
+
const d = new Date(iso);
|
|
7
|
+
if (Number.isNaN(d.getTime()))
|
|
8
|
+
return '';
|
|
9
|
+
try {
|
|
10
|
+
return new Intl.DateTimeFormat(i18n.locale || undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(d);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return d.toISOString().slice(0, 16).replace('T', ' ');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Controls that post through hidden inputs (Combobox, SelectMenu,
|
|
2
|
+
// DatePicker, Slider, …) don't fire input events as the user changes
|
|
3
|
+
// them. announce does: on the element it's on, after each change of its
|
|
4
|
+
// parameter, so a Form checks the field as the user changes it.
|
|
5
|
+
/** A Svelte action: use:announce={value} fires input when value changes. */
|
|
6
|
+
export function announce(node, _value) {
|
|
7
|
+
return {
|
|
8
|
+
update() {
|
|
9
|
+
// After Svelte has written the new value to the DOM, which it does
|
|
10
|
+
// after calling update, so the form reads what the user chose.
|
|
11
|
+
queueMicrotask(() => node.dispatchEvent(new Event('input', { bubbles: true })));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -51,6 +51,13 @@ export { default as Sheet } from './Sheet.svelte';
|
|
|
51
51
|
export { default as Sidebar } from './Sidebar.svelte';
|
|
52
52
|
export { default as SignIn } from './SignIn.svelte';
|
|
53
53
|
export { default as SignUp } from './SignUp.svelte';
|
|
54
|
+
export { default as ForgotPassword } from './ForgotPassword.svelte';
|
|
55
|
+
export { default as ResetPassword } from './ResetPassword.svelte';
|
|
56
|
+
export { default as Devices } from './Devices.svelte';
|
|
57
|
+
export { default as Passkeys } from './Passkeys.svelte';
|
|
58
|
+
export { default as LinkedAccounts } from './LinkedAccounts.svelte';
|
|
59
|
+
export { default as TwoStep } from './TwoStep.svelte';
|
|
60
|
+
export { default as TwoStepCode } from './TwoStepCode.svelte';
|
|
54
61
|
export { default as Skeleton } from './Skeleton.svelte';
|
|
55
62
|
export { default as Slider } from './Slider.svelte';
|
|
56
63
|
export { default as Sparkline } from './Sparkline.svelte';
|
package/dist/ui/index.js
CHANGED
|
@@ -54,6 +54,13 @@ export { default as Sheet } from './Sheet.svelte';
|
|
|
54
54
|
export { default as Sidebar } from './Sidebar.svelte';
|
|
55
55
|
export { default as SignIn } from './SignIn.svelte';
|
|
56
56
|
export { default as SignUp } from './SignUp.svelte';
|
|
57
|
+
export { default as ForgotPassword } from './ForgotPassword.svelte';
|
|
58
|
+
export { default as ResetPassword } from './ResetPassword.svelte';
|
|
59
|
+
export { default as Devices } from './Devices.svelte';
|
|
60
|
+
export { default as Passkeys } from './Passkeys.svelte';
|
|
61
|
+
export { default as LinkedAccounts } from './LinkedAccounts.svelte';
|
|
62
|
+
export { default as TwoStep } from './TwoStep.svelte';
|
|
63
|
+
export { default as TwoStepCode } from './TwoStepCode.svelte';
|
|
57
64
|
export { default as Skeleton } from './Skeleton.svelte';
|
|
58
65
|
export { default as Slider } from './Slider.svelte';
|
|
59
66
|
export { default as Sparkline } from './Sparkline.svelte';
|
package/dist/vite/native.js
CHANGED
|
@@ -58,11 +58,28 @@ export function nativePlugin(getRoot, entry) {
|
|
|
58
58
|
try {
|
|
59
59
|
const ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
60
60
|
const imports = {};
|
|
61
|
+
const sources = [];
|
|
61
62
|
for (const node of ast.body) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if (node.type === 'ImportDeclaration' || node.type === 'ExportAllDeclaration' || (node.type === 'ExportNamedDeclaration' && node.source)) {
|
|
64
|
+
sources.push(node.source.value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// import('./x') with a literal resolves like a static import.
|
|
68
|
+
const walk = (n) => {
|
|
69
|
+
if (!n || typeof n !== 'object')
|
|
70
|
+
return;
|
|
71
|
+
if (Array.isArray(n))
|
|
72
|
+
return n.forEach(walk);
|
|
73
|
+
const node = n;
|
|
74
|
+
if (node.type === 'ImportExpression' && node.source?.type === 'Literal' && typeof node.source.value === 'string')
|
|
75
|
+
sources.push(node.source.value);
|
|
76
|
+
for (const v of Object.values(node))
|
|
77
|
+
if (v && typeof v === 'object')
|
|
78
|
+
walk(v);
|
|
79
|
+
};
|
|
80
|
+
walk(ast.body);
|
|
81
|
+
for (const source of sources) {
|
|
82
|
+
if (source in imports)
|
|
66
83
|
continue;
|
|
67
84
|
const resolved = await this.resolve(source, id, { ssr: true });
|
|
68
85
|
const target = resolved && !resolved.external && !resolved.id.startsWith('\0') ? resolved.id : null;
|