@h-ai/ui 0.1.0-alpha.62 → 0.1.0-alpha.64
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 +37 -7
- package/README.ja.md +37 -7
- package/README.md +37 -7
- package/auto-import.d.ts +1 -0
- package/auto-import.js +1 -0
- package/dist/components/compounds/Combobox.svelte +19 -6
- package/dist/components/compounds/Combobox.svelte.d.ts +2 -2
- package/dist/components/compounds/DatePicker.svelte +14 -5
- package/dist/components/compounds/DatePicker.svelte.d.ts +2 -2
- package/dist/components/compounds/Drawer.svelte +5 -1
- package/dist/components/compounds/Form.svelte +10 -6
- package/dist/components/compounds/FormField.svelte +78 -13
- package/dist/components/compounds/FormLayout.svelte +59 -0
- package/dist/components/compounds/FormLayout.svelte.d.ts +4 -0
- package/dist/components/compounds/Modal.svelte +5 -0
- package/dist/components/compounds/TagInput.svelte +1 -1
- package/dist/components/compounds/index.d.ts +1 -0
- package/dist/components/compounds/index.js +1 -0
- package/dist/components/primitives/BareInput.svelte +7 -1
- package/dist/components/primitives/Checkbox.svelte +8 -2
- package/dist/components/primitives/Input.svelte +23 -11
- package/dist/components/primitives/Select.svelte +90 -18
- package/dist/components/primitives/Textarea.svelte +28 -13
- package/dist/components/primitives/Textarea.svelte.d.ts +1 -1
- package/dist/components/scenes/app/FeedbackModal.svelte +29 -19
- package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +2 -2
- package/dist/components/scenes/crud/CrudEditPanel.svelte +158 -118
- package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +4 -25
- package/dist/components/scenes/crud/CrudPage.svelte +5 -41
- package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -53
- package/dist/components/scenes/crud/crud-controller.svelte.d.ts +4 -1
- package/dist/components/scenes/crud/crud-form-validation.d.ts +4 -0
- package/dist/components/scenes/crud/crud-form-validation.js +39 -0
- package/dist/components/scenes/crud/crud-types.d.ts +9 -42
- package/dist/components/scenes/iam/AuthPanel.svelte +55 -47
- package/dist/components/scenes/iam/ChangePasswordForm.svelte +66 -59
- package/dist/components/scenes/iam/ForgotPasswordForm.svelte +89 -84
- package/dist/components/scenes/iam/LoginForm.svelte +134 -132
- package/dist/components/scenes/iam/PasswordInput.svelte +30 -10
- package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +3 -3
- package/dist/components/scenes/iam/RegisterForm.svelte +95 -97
- package/dist/components/scenes/iam/ResetPasswordForm.svelte +83 -85
- package/dist/components/scenes/iam/UserProfile.svelte +14 -7
- package/dist/components/scenes/storage/FileUpload.svelte +1 -1
- package/dist/components/scenes/storage/ImageUpload.svelte +1 -0
- package/dist/components/scenes/types.d.ts +12 -10
- package/dist/i18n.svelte.d.ts +2 -1
- package/dist/i18n.svelte.js +2 -1
- package/dist/internal/form-context.d.ts +18 -0
- package/dist/internal/form-context.js +13 -0
- package/dist/internal/form-validation.svelte.d.ts +16 -0
- package/dist/internal/form-validation.svelte.js +86 -0
- package/dist/internal/locale.d.ts +2 -0
- package/dist/internal/locale.js +22 -0
- package/dist/messages/en-US.json +13 -1
- package/dist/messages/zh-CN.json +13 -1
- package/dist/messages.js +4 -3
- package/dist/types.d.ts +57 -10
- package/package.json +7 -2
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { uiM } from '../../../messages.js'
|
|
9
9
|
import { cn, getDataAttributes } from '../../../utils.js'
|
|
10
10
|
import Alert from '../../compounds/Alert.svelte'
|
|
11
|
+
import FormField from '../../compounds/FormField.svelte'
|
|
12
|
+
import FormLayout from '../../compounds/FormLayout.svelte'
|
|
11
13
|
import Button from '../../primitives/Button.svelte'
|
|
12
14
|
import Input from '../../primitives/Input.svelte'
|
|
13
15
|
import AgreementNotice from './AgreementNotice.svelte'
|
|
@@ -16,6 +18,9 @@
|
|
|
16
18
|
|
|
17
19
|
const {
|
|
18
20
|
loading = false,
|
|
21
|
+
columns = 1,
|
|
22
|
+
labelPosition = 'top',
|
|
23
|
+
labelWidth = '8rem',
|
|
19
24
|
disabled = false,
|
|
20
25
|
showTitle = false,
|
|
21
26
|
showPasswordStrength = true,
|
|
@@ -165,114 +170,107 @@
|
|
|
165
170
|
</script>
|
|
166
171
|
|
|
167
172
|
<form {...dataAttributes} class={formClass} onsubmit={handleSubmit}>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<!-- 自定义头部 -->
|
|
174
|
-
{#if header}
|
|
175
|
-
<div class='register-form-header'>
|
|
176
|
-
{@render header()}
|
|
177
|
-
</div>
|
|
178
|
-
{/if}
|
|
179
|
-
|
|
180
|
-
{#if passwordPolicy}<p class='text-xs text-base-content/60'>{passwordPolicyHint(passwordPolicy)}</p>{/if}
|
|
181
|
-
{#if policyError}<p role='alert' class='text-sm text-error'>{policyError}</p>{/if}
|
|
173
|
+
<FormLayout {columns} {labelPosition} {labelWidth}>
|
|
174
|
+
<!-- 标题 -->
|
|
175
|
+
{#if showTitle}
|
|
176
|
+
<h2 class='text-xl font-semibold text-center mb-5'>{uiM('register_title')}</h2>
|
|
177
|
+
{/if}
|
|
182
178
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<div class='space-y-1.5'>
|
|
188
|
-
<label class='text-sm font-medium text-base-content/70' for='register-password'>
|
|
189
|
-
{getFieldLabel('password')}
|
|
190
|
-
</label>
|
|
191
|
-
<PasswordInput
|
|
192
|
-
id='register-password'
|
|
193
|
-
bind:value={password}
|
|
194
|
-
placeholder={getFieldPlaceholder('password')}
|
|
195
|
-
autocomplete='new-password'
|
|
196
|
-
{disabled}
|
|
197
|
-
required
|
|
198
|
-
showStrength={showPasswordStrength}
|
|
199
|
-
minLength={minPasswordLength}
|
|
200
|
-
error={errors.password}
|
|
201
|
-
/>
|
|
179
|
+
<!-- 自定义头部 -->
|
|
180
|
+
{#if header}
|
|
181
|
+
<div class='register-form-header'>
|
|
182
|
+
{@render header()}
|
|
202
183
|
</div>
|
|
184
|
+
{/if}
|
|
203
185
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
186
|
+
{#if passwordPolicy}<p class='text-xs text-base-content/60'>{passwordPolicyHint(passwordPolicy)}</p>{/if}
|
|
187
|
+
{#if policyError}<p role='alert' class='text-sm text-error'>{policyError}</p>{/if}
|
|
188
|
+
|
|
189
|
+
<!-- 动态字段 -->
|
|
190
|
+
{#each fields as field (field)}
|
|
191
|
+
{#if field === 'password'}
|
|
192
|
+
<!-- 密码字段 -->
|
|
193
|
+
<FormField for='register-password' label={getFieldLabel('password')}>
|
|
210
194
|
<PasswordInput
|
|
211
|
-
id='register-
|
|
212
|
-
bind:value={
|
|
213
|
-
placeholder={getFieldPlaceholder('
|
|
195
|
+
id='register-password'
|
|
196
|
+
bind:value={password}
|
|
197
|
+
placeholder={getFieldPlaceholder('password')}
|
|
214
198
|
autocomplete='new-password'
|
|
215
199
|
{disabled}
|
|
216
200
|
required
|
|
217
|
-
showStrength={
|
|
218
|
-
|
|
201
|
+
showStrength={showPasswordStrength}
|
|
202
|
+
minLength={minPasswordLength}
|
|
203
|
+
error={errors.password}
|
|
204
|
+
/>
|
|
205
|
+
</FormField>
|
|
206
|
+
|
|
207
|
+
<!-- 确认密码字段 -->
|
|
208
|
+
{#if requireConfirmPassword}
|
|
209
|
+
<FormField for='register-confirm-password' label={getFieldLabel('confirmPassword')}>
|
|
210
|
+
<PasswordInput
|
|
211
|
+
id='register-confirm-password'
|
|
212
|
+
bind:value={confirmPassword}
|
|
213
|
+
placeholder={getFieldPlaceholder('confirmPassword')}
|
|
214
|
+
autocomplete='new-password'
|
|
215
|
+
{disabled}
|
|
216
|
+
required
|
|
217
|
+
showStrength={false}
|
|
218
|
+
error={confirmPasswordError}
|
|
219
|
+
/>
|
|
220
|
+
</FormField>
|
|
221
|
+
{/if}
|
|
222
|
+
{:else if field !== 'confirmPassword'}
|
|
223
|
+
<FormField for='register-{field}' label={getFieldLabel(field)}>
|
|
224
|
+
<Input
|
|
225
|
+
id={`register-${field}`}
|
|
226
|
+
type={getFieldType(field)}
|
|
227
|
+
name={field}
|
|
228
|
+
placeholder={getFieldPlaceholder(field)}
|
|
229
|
+
value={getFieldValue(field)}
|
|
230
|
+
oninput={(e: Event & { currentTarget: HTMLInputElement }) => setFieldValue(field, e.currentTarget.value)}
|
|
231
|
+
{disabled}
|
|
232
|
+
required={field === 'username' || field === 'email'}
|
|
233
|
+
autocomplete={field === 'username' ? 'username' : field === 'email' ? 'email' : undefined}
|
|
234
|
+
error={errors[field]}
|
|
219
235
|
/>
|
|
220
|
-
</
|
|
236
|
+
</FormField>
|
|
221
237
|
{/if}
|
|
222
|
-
{
|
|
223
|
-
<div class='space-y-1.5'>
|
|
224
|
-
<label class='text-sm font-medium text-base-content/70' for='register-{field}'>
|
|
225
|
-
{getFieldLabel(field)}
|
|
226
|
-
</label>
|
|
227
|
-
<Input
|
|
228
|
-
id={`register-${field}`}
|
|
229
|
-
type={getFieldType(field)}
|
|
230
|
-
name={field}
|
|
231
|
-
placeholder={getFieldPlaceholder(field)}
|
|
232
|
-
value={getFieldValue(field)}
|
|
233
|
-
oninput={(e: Event & { currentTarget: HTMLInputElement }) => setFieldValue(field, e.currentTarget.value)}
|
|
234
|
-
{disabled}
|
|
235
|
-
required={field === 'username' || field === 'email'}
|
|
236
|
-
autocomplete={field === 'username' ? 'username' : field === 'email' ? 'email' : undefined}
|
|
237
|
-
error={errors[field]}
|
|
238
|
-
/>
|
|
239
|
-
</div>
|
|
240
|
-
{/if}
|
|
241
|
-
{/each}
|
|
238
|
+
{/each}
|
|
242
239
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
<!-- 通用错误 -->
|
|
241
|
+
{#if extraFields}
|
|
242
|
+
<div class='register-form-extra-fields'>{@render extraFields()}</div>
|
|
243
|
+
{/if}
|
|
244
|
+
{#if errors.general}
|
|
245
|
+
<Alert variant='error'>
|
|
246
|
+
{errors.general}
|
|
247
|
+
</Alert>
|
|
248
|
+
{/if}
|
|
252
249
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
250
|
+
<!-- 提交按钮 -->
|
|
251
|
+
<Button
|
|
252
|
+
type='submit'
|
|
253
|
+
variant='primary'
|
|
254
|
+
class='w-full'
|
|
255
|
+
disabled={loading || disabled || !passwordsMatch || Boolean(policyError)}
|
|
256
|
+
loading={loading}
|
|
257
|
+
>
|
|
258
|
+
{submitText || uiM('register_submit')}
|
|
259
|
+
</Button>
|
|
263
260
|
|
|
264
|
-
|
|
265
|
-
|
|
261
|
+
<!-- 协议提示 -->
|
|
262
|
+
<AgreementNotice {agreements} />
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
264
|
+
<!-- 自定义底部 -->
|
|
265
|
+
{#if footer}
|
|
266
|
+
<div class='register-form-footer'>
|
|
267
|
+
{@render footer()}
|
|
268
|
+
</div>
|
|
269
|
+
{:else if showLoginLink}
|
|
270
|
+
<div class='text-center mt-4 text-sm text-base-content/60'>
|
|
271
|
+
<span>{uiM('register_has_account')}</span>
|
|
272
|
+
<a href={loginUrl} class='link link-primary ml-1'>{uiM('register_login_now')}</a>
|
|
273
|
+
</div>
|
|
274
|
+
{/if}
|
|
275
|
+
</FormLayout>
|
|
278
276
|
</form>
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { uiM } from '../../../messages.js'
|
|
9
9
|
import { cn, getDataAttributes } from '../../../utils.js'
|
|
10
10
|
import Alert from '../../compounds/Alert.svelte'
|
|
11
|
+
import FormField from '../../compounds/FormField.svelte'
|
|
12
|
+
import FormLayout from '../../compounds/FormLayout.svelte'
|
|
11
13
|
import Button from '../../primitives/Button.svelte'
|
|
12
14
|
import Input from '../../primitives/Input.svelte'
|
|
13
15
|
import { arePasswordsEqual, passwordPolicyError, passwordPolicyHint } from './password-utils.js'
|
|
@@ -15,6 +17,9 @@
|
|
|
15
17
|
|
|
16
18
|
const {
|
|
17
19
|
loading = false,
|
|
20
|
+
columns = 1,
|
|
21
|
+
labelPosition = 'top',
|
|
22
|
+
labelWidth = '8rem',
|
|
18
23
|
disabled = false,
|
|
19
24
|
showTitle = false,
|
|
20
25
|
showDescription = false,
|
|
@@ -72,91 +77,84 @@
|
|
|
72
77
|
</script>
|
|
73
78
|
|
|
74
79
|
<form {...dataAttributes} class={formClass} onsubmit={handleSubmit}>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
<FormLayout {columns} {labelPosition} {labelWidth}>
|
|
81
|
+
<!-- 标题 -->
|
|
82
|
+
{#if showTitle}
|
|
83
|
+
<h2 class='text-xl font-semibold text-center mb-4'>{uiM('reset_password_title')}</h2>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
<!-- 描述 -->
|
|
87
|
+
{#if showDescription}
|
|
88
|
+
<p class='text-center text-base-content/45 text-sm mb-5'>
|
|
89
|
+
{uiM('reset_password_desc')}
|
|
90
|
+
</p>
|
|
91
|
+
{/if}
|
|
92
|
+
|
|
93
|
+
<!-- 验证码 -->
|
|
94
|
+
{#if showCode}
|
|
95
|
+
<FormField for='reset-code' label={uiM('reset_password_code_label')}>
|
|
96
|
+
<Input
|
|
97
|
+
id='reset-code'
|
|
98
|
+
name='code'
|
|
99
|
+
type='text'
|
|
100
|
+
placeholder={uiM('reset_password_code_placeholder')}
|
|
101
|
+
bind:value={code}
|
|
102
|
+
{disabled}
|
|
103
|
+
required
|
|
104
|
+
error={errors.code}
|
|
105
|
+
/>
|
|
106
|
+
</FormField>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
<!-- 新密码 -->
|
|
110
|
+
<FormField for='reset-new-password' label={uiM('reset_password_new_label')}>
|
|
111
|
+
<PasswordInput
|
|
112
|
+
bind:value={newPassword}
|
|
113
|
+
placeholder={uiM('reset_password_new_placeholder')}
|
|
99
114
|
{disabled}
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
error={errors.newPassword}
|
|
116
|
+
showStrength={showPasswordStrength}
|
|
117
|
+
minLength={minPasswordLength}
|
|
102
118
|
/>
|
|
103
|
-
</
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<!-- 提交按钮 -->
|
|
146
|
-
<Button
|
|
147
|
-
type='submit'
|
|
148
|
-
variant='primary'
|
|
149
|
-
{loading}
|
|
150
|
-
disabled={loading || disabled || !canSubmit || Boolean(policyError)}
|
|
151
|
-
class='w-full'
|
|
152
|
-
>
|
|
153
|
-
{submitText || uiM('reset_password_submit')}
|
|
154
|
-
</Button>
|
|
155
|
-
|
|
156
|
-
<!-- 返回链接 -->
|
|
157
|
-
{#if showBackLink}
|
|
158
|
-
<div class='text-center mt-4 text-sm'>
|
|
159
|
-
<a href={loginUrl} class='link link-primary'>← {uiM('reset_password_back')}</a>
|
|
160
|
-
</div>
|
|
161
|
-
{/if}
|
|
119
|
+
</FormField>
|
|
120
|
+
|
|
121
|
+
<!-- 确认新密码 -->
|
|
122
|
+
<FormField for='reset-confirm-password' label={uiM('reset_password_confirm_label')}>
|
|
123
|
+
<PasswordInput
|
|
124
|
+
bind:value={confirmPassword}
|
|
125
|
+
placeholder={uiM('reset_password_confirm_placeholder')}
|
|
126
|
+
{disabled}
|
|
127
|
+
error={errors.confirmPassword || (!passwordsMatch && confirmPassword ? uiM('reset_password_mismatch') : '')}
|
|
128
|
+
showStrength={false}
|
|
129
|
+
/>
|
|
130
|
+
</FormField>
|
|
131
|
+
|
|
132
|
+
<!-- 通用错误 -->
|
|
133
|
+
{#if errors.general}
|
|
134
|
+
<Alert variant='error'>
|
|
135
|
+
{errors.general}
|
|
136
|
+
</Alert>
|
|
137
|
+
{/if}
|
|
138
|
+
|
|
139
|
+
{#if passwordPolicy}<p class='text-xs text-base-content/60'>{passwordPolicyHint(passwordPolicy)}</p>{/if}
|
|
140
|
+
{#if policyError}<p role='alert' class='text-sm text-error'>{policyError}</p>{/if}
|
|
141
|
+
|
|
142
|
+
<!-- 提交按钮 -->
|
|
143
|
+
<Button
|
|
144
|
+
type='submit'
|
|
145
|
+
variant='primary'
|
|
146
|
+
{loading}
|
|
147
|
+
disabled={loading || disabled || !canSubmit || Boolean(policyError)}
|
|
148
|
+
class='w-full'
|
|
149
|
+
>
|
|
150
|
+
{submitText || uiM('reset_password_submit')}
|
|
151
|
+
</Button>
|
|
152
|
+
|
|
153
|
+
<!-- 返回链接 -->
|
|
154
|
+
{#if showBackLink}
|
|
155
|
+
<div class='text-center mt-4 text-sm'>
|
|
156
|
+
<a href={loginUrl} class='link link-primary'>← {uiM('reset_password_back')}</a>
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
159
|
+
</FormLayout>
|
|
162
160
|
</form>
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import { uiM } from '../../../messages.js'
|
|
15
15
|
import { cn, getDataAttributes } from '../../../utils.js'
|
|
16
16
|
import Alert from '../../compounds/Alert.svelte'
|
|
17
|
+
import FormField from '../../compounds/FormField.svelte'
|
|
18
|
+
import FormLayout from '../../compounds/FormLayout.svelte'
|
|
17
19
|
import Avatar from '../../primitives/Avatar.svelte'
|
|
18
20
|
import BareInput from '../../primitives/BareInput.svelte'
|
|
19
21
|
import Button from '../../primitives/Button.svelte'
|
|
@@ -25,7 +27,11 @@
|
|
|
25
27
|
editable = false,
|
|
26
28
|
alwaysEditable = false,
|
|
27
29
|
loading = false,
|
|
30
|
+
columns = 1,
|
|
31
|
+
labelPosition = 'top',
|
|
32
|
+
labelWidth = '8rem',
|
|
28
33
|
fields = ['avatar', 'username', 'email', 'displayName', 'phone'],
|
|
34
|
+
fieldConstraints = {},
|
|
29
35
|
avatarAccept = 'image/*',
|
|
30
36
|
avatarHint,
|
|
31
37
|
class: className = '',
|
|
@@ -157,12 +163,12 @@
|
|
|
157
163
|
|
|
158
164
|
<div {...dataAttributes} class={containerClass}>
|
|
159
165
|
<form onsubmit={handleSubmit}>
|
|
160
|
-
<
|
|
166
|
+
<FormLayout {columns} {labelPosition} {labelWidth}>
|
|
161
167
|
{#each fields as field (field)}
|
|
162
168
|
{@const modelKey = getModelKey(field)}
|
|
163
169
|
{#if field === 'avatar'}
|
|
164
170
|
<!-- 头像 -->
|
|
165
|
-
<div class='flex items-center gap-4'>
|
|
171
|
+
<div class='flex flex-wrap items-center gap-4 border-b border-base-content/8 pb-5'>
|
|
166
172
|
<Avatar
|
|
167
173
|
src={user?.avatarUrl ?? user?.avatar}
|
|
168
174
|
alt={uiM('user_profile_avatar')}
|
|
@@ -171,7 +177,7 @@
|
|
|
171
177
|
class='shadow-sm'
|
|
172
178
|
/>
|
|
173
179
|
{#if editable && (editMode || alwaysEditable)}
|
|
174
|
-
<div>
|
|
180
|
+
<div class='min-w-0 flex-1 basis-48'>
|
|
175
181
|
<BareInput
|
|
176
182
|
id={avatarInputId}
|
|
177
183
|
type='file'
|
|
@@ -191,11 +197,11 @@
|
|
|
191
197
|
</div>
|
|
192
198
|
{:else}
|
|
193
199
|
<!-- 其他字段 -->
|
|
194
|
-
<
|
|
195
|
-
<legend class='fieldset-legend font-medium'>{getFieldLabel(field)}</legend>
|
|
200
|
+
<FormField label={getFieldLabel(field)} required={fieldConstraints[field]?.required} colSpan={field === 'bio' ? 2 : 'auto'}>
|
|
196
201
|
{#if editable && (editMode || alwaysEditable)}
|
|
197
202
|
{#if field === 'bio'}
|
|
198
203
|
<Textarea
|
|
204
|
+
{...fieldConstraints[field]}
|
|
199
205
|
name={modelKey}
|
|
200
206
|
rows={3}
|
|
201
207
|
bind:value={formData[modelKey]}
|
|
@@ -204,6 +210,7 @@
|
|
|
204
210
|
/>
|
|
205
211
|
{:else}
|
|
206
212
|
<Input
|
|
213
|
+
{...fieldConstraints[field]}
|
|
207
214
|
type={field === 'email' ? 'email' : field === 'phone' ? 'tel' : 'text'}
|
|
208
215
|
name={modelKey}
|
|
209
216
|
bind:value={formData[modelKey]}
|
|
@@ -216,7 +223,7 @@
|
|
|
216
223
|
{formData[modelKey] || '-'}
|
|
217
224
|
</p>
|
|
218
225
|
{/if}
|
|
219
|
-
</
|
|
226
|
+
</FormField>
|
|
220
227
|
{/if}
|
|
221
228
|
{/each}
|
|
222
229
|
|
|
@@ -259,6 +266,6 @@
|
|
|
259
266
|
{/if}
|
|
260
267
|
</div>
|
|
261
268
|
{/if}
|
|
262
|
-
</
|
|
269
|
+
</FormLayout>
|
|
263
270
|
</form>
|
|
264
271
|
</div>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { HaiResult } from '@h-ai/core';
|
|
8
8
|
import type { Snippet } from 'svelte';
|
|
9
|
-
import type { DataAttributes, Size } from '../../types.js';
|
|
9
|
+
import type { DataAttributes, FormLayoutOptions, FormText, InputProps, Size } from '../../types.js';
|
|
10
10
|
/**
|
|
11
11
|
* 密码输入框属性
|
|
12
12
|
*/
|
|
@@ -28,12 +28,12 @@ export interface PasswordInputProps extends DataAttributes {
|
|
|
28
28
|
/** 是否必填 */
|
|
29
29
|
required?: boolean;
|
|
30
30
|
/** 错误消息 */
|
|
31
|
-
error?:
|
|
31
|
+
error?: FormText;
|
|
32
32
|
/** 是否显示切换按钮 */
|
|
33
33
|
showToggle?: boolean;
|
|
34
34
|
/** 是否显示密码强度 */
|
|
35
35
|
showStrength?: boolean;
|
|
36
|
-
/**
|
|
36
|
+
/** 最小长度;提供时校验,省略时仅以 8 位为基准计算强度。 */
|
|
37
37
|
minLength?: number;
|
|
38
38
|
/** 自定义类名 */
|
|
39
39
|
class?: string;
|
|
@@ -74,7 +74,7 @@ export interface LoginFormData {
|
|
|
74
74
|
/**
|
|
75
75
|
* 登录表单属性
|
|
76
76
|
*/
|
|
77
|
-
export interface LoginFormProps extends DataAttributes {
|
|
77
|
+
export interface LoginFormProps extends FormLayoutOptions, DataAttributes {
|
|
78
78
|
/** 实际可用的方式;空数组表示无可用登录方式。 */
|
|
79
79
|
methods?: AuthLoginMethod[];
|
|
80
80
|
/** 验证码长度 */
|
|
@@ -136,7 +136,7 @@ export interface RegisterFormData {
|
|
|
136
136
|
/**
|
|
137
137
|
* 注册表单属性
|
|
138
138
|
*/
|
|
139
|
-
export interface RegisterFormProps extends DataAttributes {
|
|
139
|
+
export interface RegisterFormProps extends FormLayoutOptions, DataAttributes {
|
|
140
140
|
passwordPolicy?: PasswordPolicy;
|
|
141
141
|
/** 加载状态 */
|
|
142
142
|
loading?: boolean;
|
|
@@ -184,7 +184,7 @@ export interface ChangePasswordFormData {
|
|
|
184
184
|
/**
|
|
185
185
|
* 修改密码表单属性
|
|
186
186
|
*/
|
|
187
|
-
export interface ChangePasswordFormProps extends DataAttributes {
|
|
187
|
+
export interface ChangePasswordFormProps extends FormLayoutOptions, DataAttributes {
|
|
188
188
|
passwordPolicy?: PasswordPolicy;
|
|
189
189
|
/** 加载状态 */
|
|
190
190
|
loading?: boolean;
|
|
@@ -215,7 +215,7 @@ export interface ForgotPasswordFormData {
|
|
|
215
215
|
/**
|
|
216
216
|
* 找回密码表单属性
|
|
217
217
|
*/
|
|
218
|
-
export interface ForgotPasswordFormProps extends DataAttributes {
|
|
218
|
+
export interface ForgotPasswordFormProps extends FormLayoutOptions, DataAttributes {
|
|
219
219
|
/** 加载状态 */
|
|
220
220
|
loading?: boolean;
|
|
221
221
|
/** 禁用状态 */
|
|
@@ -254,7 +254,7 @@ export interface ResetPasswordFormData {
|
|
|
254
254
|
/**
|
|
255
255
|
* 重置密码表单属性
|
|
256
256
|
*/
|
|
257
|
-
export interface ResetPasswordFormProps extends DataAttributes {
|
|
257
|
+
export interface ResetPasswordFormProps extends FormLayoutOptions, DataAttributes {
|
|
258
258
|
passwordPolicy?: PasswordPolicy;
|
|
259
259
|
/** 加载状态 */
|
|
260
260
|
loading?: boolean;
|
|
@@ -355,7 +355,7 @@ export interface AuthPanelActions {
|
|
|
355
355
|
/** AuthPanel 的初始页面;未传独立路由时由组件内部导航。 */
|
|
356
356
|
export type AuthPanelMode = 'login' | 'register' | 'forgot' | 'reset';
|
|
357
357
|
/** 配置驱动认证场景;网络、通知和令牌存储由应用注入 actions。 */
|
|
358
|
-
export interface AuthPanelProps extends DataAttributes {
|
|
358
|
+
export interface AuthPanelProps extends FormLayoutOptions, DataAttributes {
|
|
359
359
|
/** 未取得配置时显示加载状态,只消费 IAM 的公开配置。 */
|
|
360
360
|
config: AuthPanelConfig | null;
|
|
361
361
|
/** 由 Kit 或 API client 提供标准动作,组件不处理底层传输。 */
|
|
@@ -408,7 +408,7 @@ export interface UserProfileSubmitData {
|
|
|
408
408
|
/**
|
|
409
409
|
* 用户个人信息属性
|
|
410
410
|
*/
|
|
411
|
-
export interface UserProfileProps extends DataAttributes {
|
|
411
|
+
export interface UserProfileProps extends FormLayoutOptions, DataAttributes {
|
|
412
412
|
/** 用户信息 */
|
|
413
413
|
user?: UserProfileData;
|
|
414
414
|
/** 是否可编辑 */
|
|
@@ -418,6 +418,8 @@ export interface UserProfileProps extends DataAttributes {
|
|
|
418
418
|
loading?: boolean;
|
|
419
419
|
/** 显示的字段 */
|
|
420
420
|
fields?: UserProfileField[];
|
|
421
|
+
/** 按业务规则声明字段的原生约束,复用控件失焦校验。 */
|
|
422
|
+
fieldConstraints?: Partial<Record<Exclude<UserProfileField, 'avatar'>, Pick<InputProps, 'required' | 'minlength' | 'maxlength' | 'pattern' | 'validationMessage'>>>;
|
|
421
423
|
/** 头像上传地址 */
|
|
422
424
|
avatarUploadUrl?: string;
|
|
423
425
|
/** 文件选择器允许的头像类型;最终校验由业务上传服务负责。 */
|
package/dist/i18n.svelte.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* @module i18n.svelte
|
|
6
6
|
*/
|
|
7
7
|
import type { Locale, LocaleInfo } from '@h-ai/core';
|
|
8
|
-
|
|
8
|
+
import { changeUILocale as setGlobalLocale } from './internal/locale.js';
|
|
9
|
+
declare const DEFAULT_LOCALE: string, DEFAULT_LOCALES: LocaleInfo[];
|
|
9
10
|
declare function isLocaleSupported(locale: Locale, supportedLocales?: LocaleInfo[]): boolean;
|
|
10
11
|
declare function resolveLocale(locale: Locale, supportedLocales?: LocaleInfo[], defaultLocale?: Locale): Locale;
|
|
11
12
|
declare function detectBrowserLocale(supportedLocales?: LocaleInfo[]): Locale | null;
|
package/dist/i18n.svelte.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { core } from '@h-ai/core';
|
|
8
8
|
import { readStoredValue, writeStoredValue } from './internal/browser-safety.js';
|
|
9
|
+
import { changeUILocale as setGlobalLocale } from './internal/locale.js';
|
|
9
10
|
// 从 core.i18n 解构常用函数
|
|
10
|
-
const { DEFAULT_LOCALE, DEFAULT_LOCALES,
|
|
11
|
+
const { DEFAULT_LOCALE, DEFAULT_LOCALES, } = core.i18n;
|
|
11
12
|
function isLocaleSupported(locale, supportedLocales = DEFAULT_LOCALES) {
|
|
12
13
|
const normalized = locale === 'en' ? 'en-US' : locale === 'zh' ? 'zh-CN' : locale;
|
|
13
14
|
return supportedLocales.some(item => item.code === normalized);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** 表单布局和字段提示的内部上下文,不要求应用转发控件属性。 */
|
|
2
|
+
import type { FormLayoutOptions } from '../types.js';
|
|
3
|
+
export declare const FORM_LAYOUT_CONTEXT: unique symbol;
|
|
4
|
+
export declare const FORM_FIELD_CONTEXT: unique symbol;
|
|
5
|
+
export declare const FORM_VISIBILITY_CONTEXT: unique symbol;
|
|
6
|
+
interface FormVisibilityContext {
|
|
7
|
+
readonly open: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function getFormVisibilityContext(): FormVisibilityContext | undefined;
|
|
10
|
+
export interface FormFieldContext {
|
|
11
|
+
readonly controlId: string;
|
|
12
|
+
readonly labelId: string;
|
|
13
|
+
readonly messageId: string | undefined;
|
|
14
|
+
readonly error: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getFormLayoutContext(): FormLayoutOptions | undefined;
|
|
17
|
+
export declare function getFormFieldContext(): FormFieldContext | undefined;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getContext } from 'svelte';
|
|
2
|
+
export const FORM_LAYOUT_CONTEXT = Symbol('hai-form-layout');
|
|
3
|
+
export const FORM_FIELD_CONTEXT = Symbol('hai-form-field');
|
|
4
|
+
export const FORM_VISIBILITY_CONTEXT = Symbol('hai-form-visibility');
|
|
5
|
+
export function getFormVisibilityContext() {
|
|
6
|
+
return getContext(FORM_VISIBILITY_CONTEXT);
|
|
7
|
+
}
|
|
8
|
+
export function getFormLayoutContext() {
|
|
9
|
+
return getContext(FORM_LAYOUT_CONTEXT);
|
|
10
|
+
}
|
|
11
|
+
export function getFormFieldContext() {
|
|
12
|
+
return getContext(FORM_FIELD_CONTEXT);
|
|
13
|
+
}
|