@h-ai/ui 0.1.0-alpha.62 → 0.1.0-alpha.63

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 (57) hide show
  1. package/README.en.md +33 -7
  2. package/README.ja.md +33 -7
  3. package/README.md +33 -7
  4. package/auto-import.d.ts +1 -0
  5. package/auto-import.js +1 -0
  6. package/dist/components/compounds/Combobox.svelte +19 -6
  7. package/dist/components/compounds/Combobox.svelte.d.ts +2 -2
  8. package/dist/components/compounds/DatePicker.svelte +14 -5
  9. package/dist/components/compounds/DatePicker.svelte.d.ts +2 -2
  10. package/dist/components/compounds/Form.svelte +10 -6
  11. package/dist/components/compounds/FormField.svelte +78 -13
  12. package/dist/components/compounds/FormLayout.svelte +59 -0
  13. package/dist/components/compounds/FormLayout.svelte.d.ts +4 -0
  14. package/dist/components/compounds/TagInput.svelte +1 -1
  15. package/dist/components/compounds/index.d.ts +1 -0
  16. package/dist/components/compounds/index.js +1 -0
  17. package/dist/components/primitives/BareInput.svelte +7 -1
  18. package/dist/components/primitives/Checkbox.svelte +8 -2
  19. package/dist/components/primitives/Input.svelte +22 -11
  20. package/dist/components/primitives/Select.svelte +77 -18
  21. package/dist/components/primitives/Textarea.svelte +27 -13
  22. package/dist/components/primitives/Textarea.svelte.d.ts +1 -1
  23. package/dist/components/scenes/app/FeedbackModal.svelte +29 -19
  24. package/dist/components/scenes/app/FeedbackModal.svelte.d.ts +2 -2
  25. package/dist/components/scenes/crud/CrudEditPanel.svelte +148 -118
  26. package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +4 -25
  27. package/dist/components/scenes/crud/CrudPage.svelte +5 -41
  28. package/dist/components/scenes/crud/CrudPage.svelte.d.ts +2 -53
  29. package/dist/components/scenes/crud/crud-controller.svelte.d.ts +4 -1
  30. package/dist/components/scenes/crud/crud-form-validation.d.ts +4 -0
  31. package/dist/components/scenes/crud/crud-form-validation.js +39 -0
  32. package/dist/components/scenes/crud/crud-types.d.ts +9 -42
  33. package/dist/components/scenes/iam/AuthPanel.svelte +55 -47
  34. package/dist/components/scenes/iam/ChangePasswordForm.svelte +57 -59
  35. package/dist/components/scenes/iam/ForgotPasswordForm.svelte +89 -84
  36. package/dist/components/scenes/iam/LoginForm.svelte +134 -132
  37. package/dist/components/scenes/iam/PasswordInput.svelte +25 -7
  38. package/dist/components/scenes/iam/PasswordInput.svelte.d.ts +2 -2
  39. package/dist/components/scenes/iam/RegisterForm.svelte +95 -97
  40. package/dist/components/scenes/iam/ResetPasswordForm.svelte +83 -85
  41. package/dist/components/scenes/iam/UserProfile.svelte +9 -5
  42. package/dist/components/scenes/storage/FileUpload.svelte +1 -1
  43. package/dist/components/scenes/storage/ImageUpload.svelte +1 -0
  44. package/dist/components/scenes/types.d.ts +9 -9
  45. package/dist/i18n.svelte.d.ts +2 -1
  46. package/dist/i18n.svelte.js +2 -1
  47. package/dist/internal/form-context.d.ts +12 -0
  48. package/dist/internal/form-context.js +9 -0
  49. package/dist/internal/form-validation.svelte.d.ts +16 -0
  50. package/dist/internal/form-validation.svelte.js +76 -0
  51. package/dist/internal/locale.d.ts +2 -0
  52. package/dist/internal/locale.js +22 -0
  53. package/dist/messages/en-US.json +13 -1
  54. package/dist/messages/zh-CN.json +13 -1
  55. package/dist/messages.js +4 -3
  56. package/dist/types.d.ts +57 -10
  57. 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
- {#if showTitle}
170
- <h2 class='text-xl font-semibold text-center mb-5'>{uiM('register_title')}</h2>
171
- {/if}
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
- {#each fields as field (field)}
185
- {#if field === 'password'}
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
- {#if requireConfirmPassword}
206
- <div class='space-y-1.5'>
207
- <label class='text-sm font-medium text-base-content/70' for='register-confirm-password'>
208
- {getFieldLabel('confirmPassword')}
209
- </label>
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-confirm-password'
212
- bind:value={confirmPassword}
213
- placeholder={getFieldPlaceholder('confirmPassword')}
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={false}
218
- error={confirmPasswordError}
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
- </div>
236
+ </FormField>
221
237
  {/if}
222
- {:else if field !== 'confirmPassword'}
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
- {#if extraFields}
245
- <div class='register-form-extra-fields'>{@render extraFields()}</div>
246
- {/if}
247
- {#if errors.general}
248
- <Alert variant='error'>
249
- {errors.general}
250
- </Alert>
251
- {/if}
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
- <Button
255
- type='submit'
256
- variant='primary'
257
- class='w-full'
258
- disabled={loading || disabled || !passwordsMatch || Boolean(policyError)}
259
- loading={loading}
260
- >
261
- {submitText || uiM('register_submit')}
262
- </Button>
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
- <AgreementNotice {agreements} />
261
+ <!-- 协议提示 -->
262
+ <AgreementNotice {agreements} />
266
263
 
267
- <!-- 自定义底部 -->
268
- {#if footer}
269
- <div class='register-form-footer'>
270
- {@render footer()}
271
- </div>
272
- {:else if showLoginLink}
273
- <div class='text-center mt-4 text-sm text-base-content/60'>
274
- <span>{uiM('register_has_account')}</span>
275
- <a href={loginUrl} class='link link-primary ml-1'>{uiM('register_login_now')}</a>
276
- </div>
277
- {/if}
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
- {#if showTitle}
77
- <h2 class='text-xl font-semibold text-center mb-4'>{uiM('reset_password_title')}</h2>
78
- {/if}
79
-
80
- <!-- 描述 -->
81
- {#if showDescription}
82
- <p class='text-center text-base-content/45 text-sm mb-5'>
83
- {uiM('reset_password_desc')}
84
- </p>
85
- {/if}
86
-
87
- <!-- 验证码 -->
88
- {#if showCode}
89
- <div class='space-y-1.5'>
90
- <label class='text-sm font-medium text-base-content/70' for='reset-code'>
91
- {uiM('reset_password_code_label')}
92
- </label>
93
- <Input
94
- id='reset-code'
95
- name='code'
96
- type='text'
97
- placeholder={uiM('reset_password_code_placeholder')}
98
- bind:value={code}
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
- required
101
- error={errors.code}
115
+ error={errors.newPassword}
116
+ showStrength={showPasswordStrength}
117
+ minLength={minPasswordLength}
102
118
  />
103
- </div>
104
- {/if}
105
-
106
- <!-- 新密码 -->
107
- <div class='space-y-1.5'>
108
- <label class='text-sm font-medium text-base-content/70' for='reset-new-password'>
109
- {uiM('reset_password_new_label')}
110
- </label>
111
- <PasswordInput
112
- bind:value={newPassword}
113
- placeholder={uiM('reset_password_new_placeholder')}
114
- {disabled}
115
- error={errors.newPassword}
116
- showStrength={showPasswordStrength}
117
- minLength={minPasswordLength}
118
- />
119
- </div>
120
-
121
- <!-- 确认新密码 -->
122
- <div class='space-y-1.5'>
123
- <label class='text-sm font-medium text-base-content/70' for='reset-confirm-password'>
124
- {uiM('reset_password_confirm_label')}
125
- </label>
126
- <PasswordInput
127
- bind:value={confirmPassword}
128
- placeholder={uiM('reset_password_confirm_placeholder')}
129
- {disabled}
130
- error={errors.confirmPassword || (!passwordsMatch && confirmPassword ? uiM('reset_password_mismatch') : '')}
131
- showStrength={false}
132
- />
133
- </div>
134
-
135
- <!-- 通用错误 -->
136
- {#if errors.general}
137
- <Alert variant='error'>
138
- {errors.general}
139
- </Alert>
140
- {/if}
141
-
142
- {#if passwordPolicy}<p class='text-xs text-base-content/60'>{passwordPolicyHint(passwordPolicy)}</p>{/if}
143
- {#if policyError}<p role='alert' class='text-sm text-error'>{policyError}</p>{/if}
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,6 +27,9 @@
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'],
29
34
  avatarAccept = 'image/*',
30
35
  avatarHint,
@@ -157,7 +162,7 @@
157
162
 
158
163
  <div {...dataAttributes} class={containerClass}>
159
164
  <form onsubmit={handleSubmit}>
160
- <div class='space-y-6'>
165
+ <FormLayout {columns} {labelPosition} {labelWidth}>
161
166
  {#each fields as field (field)}
162
167
  {@const modelKey = getModelKey(field)}
163
168
  {#if field === 'avatar'}
@@ -191,8 +196,7 @@
191
196
  </div>
192
197
  {:else}
193
198
  <!-- 其他字段 -->
194
- <div class='fieldset'>
195
- <legend class='fieldset-legend font-medium'>{getFieldLabel(field)}</legend>
199
+ <FormField label={getFieldLabel(field)} colSpan={field === 'bio' ? 2 : 'auto'}>
196
200
  {#if editable && (editMode || alwaysEditable)}
197
201
  {#if field === 'bio'}
198
202
  <Textarea
@@ -216,7 +220,7 @@
216
220
  {formData[modelKey] || '-'}
217
221
  </p>
218
222
  {/if}
219
- </div>
223
+ </FormField>
220
224
  {/if}
221
225
  {/each}
222
226
 
@@ -259,6 +263,6 @@
259
263
  {/if}
260
264
  </div>
261
265
  {/if}
262
- </div>
266
+ </FormLayout>
263
267
  </form>
264
268
  </div>
@@ -300,7 +300,7 @@
300
300
  }
301
301
  </script>
302
302
 
303
- <div {...dataAttributes} class={containerClass}>
303
+ <div {...dataAttributes} class={containerClass} data-form-wide>
304
304
  <!-- 上传区域 -->
305
305
  <div
306
306
  class={dropzoneClass}
@@ -195,6 +195,7 @@
195
195
  </script>
196
196
 
197
197
  <div {...dataAttributes}
198
+ data-form-wide
198
199
  class={containerClass}
199
200
  style={containerStyle}
200
201
  role='button'
@@ -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, Size } from '../../types.js';
10
10
  /**
11
11
  * 密码输入框属性
12
12
  */
@@ -28,7 +28,7 @@ export interface PasswordInputProps extends DataAttributes {
28
28
  /** 是否必填 */
29
29
  required?: boolean;
30
30
  /** 错误消息 */
31
- error?: string;
31
+ error?: FormText;
32
32
  /** 是否显示切换按钮 */
33
33
  showToggle?: boolean;
34
34
  /** 是否显示密码强度 */
@@ -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
  /** 是否可编辑 */
@@ -5,7 +5,8 @@
5
5
  * @module i18n.svelte
6
6
  */
7
7
  import type { Locale, LocaleInfo } from '@h-ai/core';
8
- declare const DEFAULT_LOCALE: string, DEFAULT_LOCALES: LocaleInfo[], setGlobalLocale: (locale: Locale) => void;
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;
@@ -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, setGlobalLocale, } = core.i18n;
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,12 @@
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 interface FormFieldContext {
6
+ readonly controlId: string;
7
+ readonly labelId: string;
8
+ readonly messageId: string | undefined;
9
+ readonly error: string;
10
+ }
11
+ export declare function getFormLayoutContext(): FormLayoutOptions | undefined;
12
+ export declare function getFormFieldContext(): FormFieldContext | undefined;
@@ -0,0 +1,9 @@
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 function getFormLayoutContext() {
5
+ return getContext(FORM_LAYOUT_CONTEXT);
6
+ }
7
+ export function getFormFieldContext() {
8
+ return getContext(FORM_FIELD_CONTEXT);
9
+ }
@@ -0,0 +1,16 @@
1
+ /** 将原生校验状态保留为消息键,显示时按当前 UI 语言解析。 */
2
+ import type { UIMessageKey } from '../messages.js';
3
+ import type { FormText } from '../types.js';
4
+ export interface FormValidationIssue {
5
+ key: UIMessageKey;
6
+ params?: Record<string, string | number>;
7
+ }
8
+ type FormControl = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
9
+ export declare function resolveFormText(text?: FormText): string;
10
+ export declare function createControlValidation(getOverride?: () => FormText | undefined, getValue?: () => unknown): {
11
+ readonly message: string;
12
+ invalid(control: FormControl): void;
13
+ input(control: FormControl): void;
14
+ reset: () => void;
15
+ };
16
+ export {};