@h-ai/ui 0.1.0-alpha.60 → 0.1.0-alpha.61
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.en.md +2 -0
- package/README.ja.md +2 -0
- package/README.md +2 -0
- package/dist/components/scenes/iam/AuthPanel.svelte +45 -38
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -752,6 +752,8 @@ agreements:
|
|
|
752
752
|
|
|
753
753
|
AuthPanel accepts public IAM config and actions for password/LDAP/email/SMS login, registration and recovery. config.ui supplies layout, branding and images. LoginForm supports methods, onsendotp, otpLength and resendInterval; password forms accept passwordPolicy. Network and token storage remain in actions. Known IAM authentication errors use the current UI language; custom application messages are preserved.
|
|
754
754
|
|
|
755
|
+
AuthPanel disables native form controls during SSR and enables input after client mount binds events, preventing early input from being lost during hydration. Applications need no extra readiness code. E2E tests wait for editable controls and cover delayed client scripts.
|
|
756
|
+
|
|
755
757
|
[完整 IAM 配置 / IAM configuration](../iam/README.en.md)
|
|
756
758
|
|
|
757
759
|
## Other exports
|
package/README.ja.md
CHANGED
|
@@ -752,6 +752,8 @@ agreements:
|
|
|
752
752
|
|
|
753
753
|
AuthPanel に IAM 公開設定と actions を渡すと、複数方式のログイン、登録、復旧、規約を利用できます。config.ui でレイアウトとブランド画像を指定し、パスワードフォームは passwordPolicy を受け取ります。通信と Token 保存は actions の責務です。既知の IAM 認証エラーは現在の UI 言語で表示し、アプリ独自のメッセージはそのまま保持します。
|
|
754
754
|
|
|
755
|
+
AuthPanel は SSR 中のフォーム操作を無効にし、クライアントのマウントとイベント登録が完了してから入力を有効にします。ハイドレーションによる入力の消失を防ぎ、アプリ側の追加の準備処理は不要です。E2E では入力可能な状態を待ち、クライアントスクリプトの読み込みが遅れる場合も検証します。
|
|
756
|
+
|
|
755
757
|
[完整 IAM 配置 / IAM configuration](../iam/README.ja.md)
|
|
756
758
|
|
|
757
759
|
## その他のエクスポート
|
package/README.md
CHANGED
|
@@ -711,6 +711,8 @@ agreements:
|
|
|
711
711
|
|
|
712
712
|
AuthPanel 直接接收 IAM 白名单配置和 actions,内置登录(密码/LDAP/邮件/短信)、注册、找回、重置及协议展示。使用 `config.ui` 配置卡片/分栏、标题、品牌、Logo 和配图。`LoginForm` 提供 methods、onsendotp、otpLength、resendInterval;密码表单接受 passwordPolicy。组件只渲染与交互,网络和 Token 存储通过 actions 注入。认证场景按公开 IAM 错误码显示当前 UI 语言的提示,应用自定义错误保留原文。
|
|
713
713
|
|
|
714
|
+
AuthPanel 在 SSR 阶段禁用原生表单控件,客户端挂载并绑定事件后自动开放输入,避免接管时丢失早到的输入。应用无需额外的就绪适配;E2E 等待控件可编辑,并覆盖客户端脚本延迟加载的场景。
|
|
715
|
+
|
|
714
716
|
[完整 IAM 配置 / IAM configuration](../iam/README.md)
|
|
715
717
|
|
|
716
718
|
### createLocaleStore
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<script lang='ts'>
|
|
3
3
|
import type { HaiResult } from '@h-ai/core'
|
|
4
4
|
import type { AuthLoginMethod, AuthPanelMode, AuthPanelProps } from '../types.js'
|
|
5
|
-
import { untrack } from 'svelte'
|
|
5
|
+
import { onMount, untrack } from 'svelte'
|
|
6
6
|
import { uiM } from '../../../messages.js'
|
|
7
7
|
import { getDataAttributes } from '../../../utils.js'
|
|
8
8
|
import Alert from '../../compounds/Alert.svelte'
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
let loading = $state(false)
|
|
23
23
|
let errors = $state<Record<string, string>>({})
|
|
24
24
|
let notice = $state('')
|
|
25
|
+
// SSR 表单先显示但不可编辑;客户端绑定事件后开放输入,避免接管时丢失早到的输入。
|
|
26
|
+
let interactive = $state(false)
|
|
27
|
+
onMount(() => {
|
|
28
|
+
interactive = true
|
|
29
|
+
})
|
|
25
30
|
const methods = $derived(config ? (['password', 'ldap', 'email', 'sms'] as AuthLoginMethod[]).filter(method => config.login[method]) : [])
|
|
26
31
|
const title = $derived(mode === 'register' ? uiM('register_title') : mode === 'forgot' ? uiM('forgot_password_title') : mode === 'reset' ? uiM('reset_password_title') : uiM('login_title'))
|
|
27
32
|
const available = $derived(config && (mode === 'login' || (mode === 'register' ? config.register.enabled : config.passwordReset.enabled)))
|
|
@@ -74,43 +79,45 @@
|
|
|
74
79
|
{/snippet}
|
|
75
80
|
|
|
76
81
|
<AuthShell {...dataAttributes} {...config?.ui} title={config?.ui.title || title} brandTitle={config?.ui.brandTitle || uiM('auth_brand')}>
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{:else}
|
|
86
|
-
{#if notice}<Alert variant='success' class='mb-4'>{notice}</Alert>{/if}
|
|
87
|
-
{#if mode === 'login'}
|
|
88
|
-
<LoginForm {methods} {loading} {errors} header={loginHeader} extraFields={loginFields} showRememberMe={false} showForgotPassword={config.passwordReset.enabled} {forgotPasswordUrl}
|
|
89
|
-
onforgotpassword={forgotPasswordUrl ? undefined : () => switchMode('forgot')}
|
|
90
|
-
otpLength={config.otp.length} resendInterval={config.otp.resendInterval} onsendotp={actions.sendOtp}
|
|
91
|
-
agreements={config.agreements.showOnLogin ? config.agreements : undefined}
|
|
92
|
-
onsubmit={data => run(() => actions.login({ identifier: data.username, password: data.password, method: data.method, code: data.code }), true)}>
|
|
93
|
-
{#snippet footer()}
|
|
94
|
-
{#if loginFooter}{@render loginFooter()}{/if}
|
|
95
|
-
{#if config?.register.enabled}<div class='text-center text-sm'>{uiM('login_no_account')} {@render link('register', uiM('login_register_now'), registerUrl)}</div>{/if}
|
|
96
|
-
{/snippet}
|
|
97
|
-
</LoginForm>
|
|
98
|
-
{:else if mode === 'register'}
|
|
99
|
-
<RegisterForm {loading} {errors} header={registerHeader} extraFields={registerFields} fields={config.passwordReset.mode === 'phone' ? ['username', 'phone', 'password'] : ['username', 'email', 'password']} passwordPolicy={config.password} minPasswordLength={config.password.minLength}
|
|
100
|
-
agreements={config.agreements.showOnRegister ? config.agreements : undefined} onsubmit={data => run(() => actions.register({ username: data.username || '', password: data.password, email: data.email, phone: data.phone, displayName: data.nickname }), true)}>
|
|
101
|
-
{#snippet footer()}
|
|
102
|
-
{#if registerFooter}{@render registerFooter()}{/if}
|
|
103
|
-
<div class='text-center'>{@render link('login', uiM('register_login_now'), loginUrl)}</div>
|
|
104
|
-
{/snippet}
|
|
105
|
-
</RegisterForm>
|
|
106
|
-
{:else if mode === 'forgot'}
|
|
107
|
-
<ForgotPasswordForm {loading} {errors} mode={config.passwordReset.mode ?? 'email'} onsubmit={data => run(() => actions.requestPasswordReset(data.email || data.phone || ''), false, uiM('auth_reset_sent'))}>
|
|
108
|
-
{#snippet footer()}<div class='flex justify-between gap-3'>{@render link('login', uiM('login_submit'), loginUrl)}{@render link('reset', uiM('auth_have_token'))}</div>{/snippet}
|
|
109
|
-
</ForgotPasswordForm>
|
|
110
|
-
{:else}
|
|
111
|
-
<ResetPasswordForm {loading} {errors} showCode={!resetToken} passwordPolicy={config.password} minPasswordLength={config.password.minLength}
|
|
112
|
-
onsubmit={data => run(() => actions.confirmPasswordReset(resetToken || data.code || '', data.newPassword), false, uiM('auth_reset_done'))} />
|
|
82
|
+
<fieldset class='m-0 min-w-0 border-0 p-0' disabled={!interactive}>
|
|
83
|
+
{#if error}
|
|
84
|
+
<Alert variant='error'>{error}</Alert>
|
|
85
|
+
{#if onretry}<Button class='mt-4 w-full' onclick={onretry}>{uiM('crud_retry')}</Button>{/if}
|
|
86
|
+
{:else if !config}
|
|
87
|
+
<div class='flex justify-center' role='status'><Spinner /></div>
|
|
88
|
+
{:else if !available}
|
|
89
|
+
<Alert variant='warning'>{uiM('auth_unavailable')}</Alert>
|
|
113
90
|
<div class='mt-4 text-center'>{@render link('login', uiM('login_submit'), loginUrl)}</div>
|
|
91
|
+
{:else}
|
|
92
|
+
{#if notice}<Alert variant='success' class='mb-4'>{notice}</Alert>{/if}
|
|
93
|
+
{#if mode === 'login'}
|
|
94
|
+
<LoginForm {methods} {loading} {errors} header={loginHeader} extraFields={loginFields} showRememberMe={false} showForgotPassword={config.passwordReset.enabled} {forgotPasswordUrl}
|
|
95
|
+
onforgotpassword={forgotPasswordUrl ? undefined : () => switchMode('forgot')}
|
|
96
|
+
otpLength={config.otp.length} resendInterval={config.otp.resendInterval} onsendotp={actions.sendOtp}
|
|
97
|
+
agreements={config.agreements.showOnLogin ? config.agreements : undefined}
|
|
98
|
+
onsubmit={data => run(() => actions.login({ identifier: data.username, password: data.password, method: data.method, code: data.code }), true)}>
|
|
99
|
+
{#snippet footer()}
|
|
100
|
+
{#if loginFooter}{@render loginFooter()}{/if}
|
|
101
|
+
{#if config?.register.enabled}<div class='text-center text-sm'>{uiM('login_no_account')} {@render link('register', uiM('login_register_now'), registerUrl)}</div>{/if}
|
|
102
|
+
{/snippet}
|
|
103
|
+
</LoginForm>
|
|
104
|
+
{:else if mode === 'register'}
|
|
105
|
+
<RegisterForm {loading} {errors} header={registerHeader} extraFields={registerFields} fields={config.passwordReset.mode === 'phone' ? ['username', 'phone', 'password'] : ['username', 'email', 'password']} passwordPolicy={config.password} minPasswordLength={config.password.minLength}
|
|
106
|
+
agreements={config.agreements.showOnRegister ? config.agreements : undefined} onsubmit={data => run(() => actions.register({ username: data.username || '', password: data.password, email: data.email, phone: data.phone, displayName: data.nickname }), true)}>
|
|
107
|
+
{#snippet footer()}
|
|
108
|
+
{#if registerFooter}{@render registerFooter()}{/if}
|
|
109
|
+
<div class='text-center'>{@render link('login', uiM('register_login_now'), loginUrl)}</div>
|
|
110
|
+
{/snippet}
|
|
111
|
+
</RegisterForm>
|
|
112
|
+
{:else if mode === 'forgot'}
|
|
113
|
+
<ForgotPasswordForm {loading} {errors} mode={config.passwordReset.mode ?? 'email'} onsubmit={data => run(() => actions.requestPasswordReset(data.email || data.phone || ''), false, uiM('auth_reset_sent'))}>
|
|
114
|
+
{#snippet footer()}<div class='flex justify-between gap-3'>{@render link('login', uiM('login_submit'), loginUrl)}{@render link('reset', uiM('auth_have_token'))}</div>{/snippet}
|
|
115
|
+
</ForgotPasswordForm>
|
|
116
|
+
{:else}
|
|
117
|
+
<ResetPasswordForm {loading} {errors} showCode={!resetToken} passwordPolicy={config.password} minPasswordLength={config.password.minLength}
|
|
118
|
+
onsubmit={data => run(() => actions.confirmPasswordReset(resetToken || data.code || '', data.newPassword), false, uiM('auth_reset_done'))} />
|
|
119
|
+
<div class='mt-4 text-center'>{@render link('login', uiM('login_submit'), loginUrl)}</div>
|
|
120
|
+
{/if}
|
|
114
121
|
{/if}
|
|
115
|
-
|
|
122
|
+
</fieldset>
|
|
116
123
|
</AuthShell>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.61",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"svelte": "^5.57.2"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
52
|
+
"@h-ai/core": "0.1.0-alpha.61",
|
|
53
53
|
"@internationalized/date": "^3.12.4",
|
|
54
54
|
"@shikijs/langs": "^4.5.0",
|
|
55
55
|
"bits-ui": "^2.19.5",
|