@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
|
@@ -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
|
+
validate(control: FormControl): void;
|
|
13
|
+
input(control: FormControl): void;
|
|
14
|
+
reset: () => void;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
2
|
+
import { getUILocale, uiM } from '../messages.js';
|
|
3
|
+
import { getFormVisibilityContext } from './form-context.js';
|
|
4
|
+
export function resolveFormText(text) {
|
|
5
|
+
if (typeof text !== 'function')
|
|
6
|
+
return text ?? '';
|
|
7
|
+
// 应用翻译函数也在 UI 语言同步后重新求值。
|
|
8
|
+
void getUILocale();
|
|
9
|
+
return text();
|
|
10
|
+
}
|
|
11
|
+
function readIssue(control) {
|
|
12
|
+
if (!control.willValidate)
|
|
13
|
+
return undefined;
|
|
14
|
+
const validity = control.validity;
|
|
15
|
+
if (validity.valueMissing)
|
|
16
|
+
return { key: 'form_required' };
|
|
17
|
+
if (validity.typeMismatch)
|
|
18
|
+
return { key: control.getAttribute('type') === 'email' ? 'form_email' : 'form_url' };
|
|
19
|
+
// 程序回填的值也需校验长度,原生 tooShort/tooLong 只覆盖用户输入。
|
|
20
|
+
const minLength = control.getAttribute('minlength');
|
|
21
|
+
const maxLength = control.getAttribute('maxlength');
|
|
22
|
+
if (validity.tooShort || (control.value && minLength !== null && control.value.length < Number(minLength)))
|
|
23
|
+
return { key: 'form_min_length', params: { min: control.getAttribute('minlength') ?? '' } };
|
|
24
|
+
if (validity.tooLong || (control.value && maxLength !== null && control.value.length > Number(maxLength)))
|
|
25
|
+
return { key: 'form_max_length', params: { max: control.getAttribute('maxlength') ?? '' } };
|
|
26
|
+
if (validity.rangeUnderflow)
|
|
27
|
+
return { key: 'form_min', params: { min: control.getAttribute('min') ?? '' } };
|
|
28
|
+
if (validity.rangeOverflow)
|
|
29
|
+
return { key: 'form_max', params: { max: control.getAttribute('max') ?? '' } };
|
|
30
|
+
if (validity.stepMismatch)
|
|
31
|
+
return { key: 'form_step', params: { step: control.getAttribute('step') ?? '1' } };
|
|
32
|
+
if (validity.badInput)
|
|
33
|
+
return { key: 'form_number' };
|
|
34
|
+
if (validity.patternMismatch)
|
|
35
|
+
return { key: 'form_pattern' };
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
export function createControlValidation(getOverride = () => undefined, getValue) {
|
|
39
|
+
const visibility = getFormVisibilityContext();
|
|
40
|
+
let issue = $state();
|
|
41
|
+
let currentControl = $state();
|
|
42
|
+
function reset() {
|
|
43
|
+
issue = undefined;
|
|
44
|
+
currentControl?.setCustomValidity('');
|
|
45
|
+
}
|
|
46
|
+
$effect(() => {
|
|
47
|
+
// 弹窗与抽屉保留子树;关闭时清理原生错误,重开不能沿用上次的触碰状态。
|
|
48
|
+
if (visibility && !visibility.open)
|
|
49
|
+
reset();
|
|
50
|
+
});
|
|
51
|
+
$effect(() => {
|
|
52
|
+
const form = currentControl?.form;
|
|
53
|
+
form?.addEventListener('reset', reset);
|
|
54
|
+
return () => form?.removeEventListener('reset', reset);
|
|
55
|
+
});
|
|
56
|
+
$effect(() => {
|
|
57
|
+
getValue?.();
|
|
58
|
+
const control = currentControl;
|
|
59
|
+
if (control) {
|
|
60
|
+
// 回填、重置及约束变更不一定触发 input;同步清除过期的原生错误。
|
|
61
|
+
untrack(() => {
|
|
62
|
+
control.setCustomValidity('');
|
|
63
|
+
if (issue)
|
|
64
|
+
issue = readIssue(control);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
get message() {
|
|
70
|
+
return issue ? resolveFormText(getOverride()) || uiM(issue.key, issue.params) : '';
|
|
71
|
+
},
|
|
72
|
+
validate(control) {
|
|
73
|
+
currentControl = control;
|
|
74
|
+
control.setCustomValidity('');
|
|
75
|
+
issue = readIssue(control);
|
|
76
|
+
control.setCustomValidity(issue ? resolveFormText(getOverride()) || uiM(issue.key, issue.params) : '');
|
|
77
|
+
},
|
|
78
|
+
input(control) {
|
|
79
|
+
currentControl = control;
|
|
80
|
+
control.setCustomValidity('');
|
|
81
|
+
if (issue)
|
|
82
|
+
issue = readIssue(control);
|
|
83
|
+
},
|
|
84
|
+
reset,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** UI 语言变更触发组件重新求值;实际语言仍由 core 统一管理。 */
|
|
2
|
+
import { core } from '@h-ai/core';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
5
|
+
let notifyLocaleChange;
|
|
6
|
+
const subscribe = createSubscriber((update) => {
|
|
7
|
+
notifyLocaleChange = update;
|
|
8
|
+
return () => {
|
|
9
|
+
notifyLocaleChange = undefined;
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
export function readUILocale() {
|
|
13
|
+
subscribe();
|
|
14
|
+
return core.i18n.getGlobalLocale();
|
|
15
|
+
}
|
|
16
|
+
export function changeUILocale(locale) {
|
|
17
|
+
core.i18n.setGlobalLocale(locale);
|
|
18
|
+
// 宿主可能在 $effect 中同步语言,setter 不能反向订阅自身状态。
|
|
19
|
+
untrack(() => {
|
|
20
|
+
notifyLocaleChange?.();
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/messages/en-US.json
CHANGED
|
@@ -368,5 +368,17 @@
|
|
|
368
368
|
"guide_tours": "Product tours",
|
|
369
369
|
"guide_unavailable": "This tour is unavailable. Choose another topic in the help center.",
|
|
370
370
|
"guide_targetMissing": "This action is not available on the current page. Try again later or read the help content.",
|
|
371
|
-
"guide_startFailed": "Unable to start the tour. Please try again later."
|
|
371
|
+
"guide_startFailed": "Unable to start the tour. Please try again later.",
|
|
372
|
+
"form_required": "This field is required.",
|
|
373
|
+
"form_email": "Enter a valid email address.",
|
|
374
|
+
"form_url": "Enter a valid URL.",
|
|
375
|
+
"form_number": "Enter a valid number.",
|
|
376
|
+
"form_min_length": "Enter at least {min} characters.",
|
|
377
|
+
"form_max_length": "Enter no more than {max} characters.",
|
|
378
|
+
"form_min": "Enter a value of at least {min}.",
|
|
379
|
+
"form_max": "Enter a value no greater than {max}.",
|
|
380
|
+
"form_step": "Enter a value in increments of {step}.",
|
|
381
|
+
"form_pattern": "Enter a value in the required format.",
|
|
382
|
+
"select_clear": "Clear selection",
|
|
383
|
+
"select_no_match": "No matching options"
|
|
372
384
|
}
|
package/dist/messages/zh-CN.json
CHANGED
|
@@ -368,5 +368,17 @@
|
|
|
368
368
|
"guide_tours": "操作导览",
|
|
369
369
|
"guide_unavailable": "此引导暂不可用,请从帮助中心选择其他内容。",
|
|
370
370
|
"guide_targetMissing": "当前页面暂未提供此操作。请稍后重试或查看帮助内容。",
|
|
371
|
-
"guide_startFailed": "无法开始引导,请稍后重试。"
|
|
371
|
+
"guide_startFailed": "无法开始引导,请稍后重试。",
|
|
372
|
+
"form_required": "请填写此项。",
|
|
373
|
+
"form_email": "请输入有效的邮箱地址。",
|
|
374
|
+
"form_url": "请输入有效的网址。",
|
|
375
|
+
"form_number": "请输入有效的数字。",
|
|
376
|
+
"form_min_length": "请至少输入 {min} 个字符。",
|
|
377
|
+
"form_max_length": "请最多输入 {max} 个字符。",
|
|
378
|
+
"form_min": "请输入不小于 {min} 的值。",
|
|
379
|
+
"form_max": "请输入不大于 {max} 的值。",
|
|
380
|
+
"form_step": "请按 {step} 的步长输入。",
|
|
381
|
+
"form_pattern": "请按要求的格式填写。",
|
|
382
|
+
"select_clear": "清空选择",
|
|
383
|
+
"select_no_match": "无匹配选项"
|
|
372
384
|
}
|
package/dist/messages.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module messages
|
|
6
6
|
*/
|
|
7
7
|
import { core } from '@h-ai/core';
|
|
8
|
+
import { changeUILocale, readUILocale } from './internal/locale.js';
|
|
8
9
|
// 导入消息文件
|
|
9
10
|
import messagesEnUS from './messages/en-US.json';
|
|
10
11
|
import messagesZhCN from './messages/zh-CN.json';
|
|
@@ -27,17 +28,17 @@ const getMessage = core.i18n.createMessageGetter({
|
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
30
|
export function uiM(key, params, locale) {
|
|
30
|
-
return getMessage(key, { params, locale });
|
|
31
|
+
return getMessage(key, { params, locale: locale ?? readUILocale() });
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* 获取当前 UI 模块使用的 locale
|
|
34
35
|
*/
|
|
35
36
|
export function getUILocale() {
|
|
36
|
-
return
|
|
37
|
+
return readUILocale();
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
40
|
* 手动设置 UI 模块的 locale(通常不需要,会自动同步)
|
|
40
41
|
*/
|
|
41
42
|
export function setUILocale(locale) {
|
|
42
|
-
|
|
43
|
+
changeUILocale(locale);
|
|
43
44
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -102,9 +102,9 @@ export interface InputProps extends DataAttributes {
|
|
|
102
102
|
/** 是否必填 */
|
|
103
103
|
required?: boolean;
|
|
104
104
|
/** 错误消息 */
|
|
105
|
-
error?:
|
|
105
|
+
error?: FormText;
|
|
106
106
|
/** 自定义验证消息(用于覆盖浏览器原生验证提示) */
|
|
107
|
-
validationMessage?:
|
|
107
|
+
validationMessage?: FormText;
|
|
108
108
|
/** 自定义类名 */
|
|
109
109
|
class?: string;
|
|
110
110
|
/** 元素 ID */
|
|
@@ -182,6 +182,10 @@ export interface BareInputProps extends DataAttributes {
|
|
|
182
182
|
ariaControls?: string;
|
|
183
183
|
/** aria-autocomplete */
|
|
184
184
|
ariaAutocomplete?: 'none' | 'list' | 'inline' | 'both';
|
|
185
|
+
/** 字段校验状态 */
|
|
186
|
+
ariaInvalid?: boolean;
|
|
187
|
+
/** 字段提示/错误元素 ID */
|
|
188
|
+
ariaDescribedby?: string;
|
|
185
189
|
/** 元素 ID */
|
|
186
190
|
id?: string;
|
|
187
191
|
/** input 引用 */
|
|
@@ -255,10 +259,19 @@ export interface TextareaProps extends DataAttributes {
|
|
|
255
259
|
required?: boolean;
|
|
256
260
|
/** 是否自动调整高度 */
|
|
257
261
|
autoResize?: boolean;
|
|
258
|
-
inputmode?:
|
|
259
|
-
error?:
|
|
262
|
+
inputmode?: InputProps['inputmode'];
|
|
263
|
+
error?: FormText;
|
|
260
264
|
/** 自定义验证消息(用于覆盖浏览器原生验证提示) */
|
|
261
|
-
validationMessage?:
|
|
265
|
+
validationMessage?: FormText;
|
|
266
|
+
/** 最小长度 */
|
|
267
|
+
minlength?: number;
|
|
268
|
+
/** 最大长度 */
|
|
269
|
+
maxlength?: number;
|
|
270
|
+
/** 原生控件引用 */
|
|
271
|
+
textareaRef?: HTMLTextAreaElement;
|
|
272
|
+
oninvalid?: (e: Event & {
|
|
273
|
+
currentTarget: HTMLTextAreaElement;
|
|
274
|
+
}) => void;
|
|
262
275
|
/** 自定义类名 */
|
|
263
276
|
class?: string;
|
|
264
277
|
/** 元素 ID */
|
|
@@ -302,7 +315,11 @@ export interface SelectProps<T = string> extends DataAttributes {
|
|
|
302
315
|
/** 是否可筛选(启用后可在输入框内输入过滤选项) */
|
|
303
316
|
filterable?: boolean;
|
|
304
317
|
/** 错误消息 */
|
|
305
|
-
error?:
|
|
318
|
+
error?: FormText;
|
|
319
|
+
/** 自定义验证提示,仅在校验失败时显示。 */
|
|
320
|
+
validationMessage?: FormText;
|
|
321
|
+
/** 表单字段名 */
|
|
322
|
+
name?: string;
|
|
306
323
|
/** 自定义类名 */
|
|
307
324
|
class?: string;
|
|
308
325
|
/** 元素 ID */
|
|
@@ -314,6 +331,8 @@ export interface SelectProps<T = string> extends DataAttributes {
|
|
|
314
331
|
* 复选框属性
|
|
315
332
|
*/
|
|
316
333
|
export interface CheckboxProps extends DataAttributes {
|
|
334
|
+
/** 元素 ID;用于 FormField 标签关联。 */
|
|
335
|
+
id?: string;
|
|
317
336
|
/** 是否选中 */
|
|
318
337
|
checked?: boolean;
|
|
319
338
|
/** 标签 */
|
|
@@ -766,7 +785,27 @@ export interface DropdownProps extends DataAttributes {
|
|
|
766
785
|
/**
|
|
767
786
|
* 表单属性
|
|
768
787
|
*/
|
|
769
|
-
|
|
788
|
+
/** 表单标签位置:独立成行或与控件同行。 */
|
|
789
|
+
export type FormLabelPosition = 'top' | 'left';
|
|
790
|
+
/** 双列表单字段占用列数;auto 根据控件类型判断。 */
|
|
791
|
+
export type FormColSpan = 'auto' | 1 | 2;
|
|
792
|
+
/** 支持延迟求值的表单文案,切换语言时重新解析。 */
|
|
793
|
+
export type FormText = string | (() => string);
|
|
794
|
+
/** 表单布局配置;窄容器自动收为单列。 */
|
|
795
|
+
export interface FormLayoutOptions {
|
|
796
|
+
/** 字段列数,默认 1。 */
|
|
797
|
+
columns?: 1 | 2;
|
|
798
|
+
/** 标签位置,默认 top。 */
|
|
799
|
+
labelPosition?: FormLabelPosition;
|
|
800
|
+
/** 同行标签宽度(CSS 值),默认 8rem。 */
|
|
801
|
+
labelWidth?: string;
|
|
802
|
+
}
|
|
803
|
+
/** 可嵌入原生 form 的布局容器。 */
|
|
804
|
+
export interface FormLayoutProps extends FormLayoutOptions, DataAttributes {
|
|
805
|
+
class?: string;
|
|
806
|
+
children?: Snippet;
|
|
807
|
+
}
|
|
808
|
+
export interface FormProps extends FormLayoutOptions, DataAttributes {
|
|
770
809
|
/** 表单 ID */
|
|
771
810
|
id?: string;
|
|
772
811
|
/** 自定义类名 */
|
|
@@ -789,13 +828,21 @@ export interface FormProps extends DataAttributes {
|
|
|
789
828
|
*/
|
|
790
829
|
export interface FormFieldProps extends DataAttributes {
|
|
791
830
|
/** 标签 */
|
|
792
|
-
label?:
|
|
831
|
+
label?: FormText;
|
|
832
|
+
/** 关联控件 ID;省略时为内部控件自动提供唯一 ID。 */
|
|
833
|
+
for?: string;
|
|
834
|
+
/** 覆盖所在表单的标签位置。 */
|
|
835
|
+
labelPosition?: FormLabelPosition;
|
|
836
|
+
/** 覆盖所在表单的同行标签宽度。 */
|
|
837
|
+
labelWidth?: string;
|
|
838
|
+
/** 双列时的占用列数,默认 auto;多行文本、上传和多选自动占两列。 */
|
|
839
|
+
colSpan?: FormColSpan;
|
|
793
840
|
/** 字段名 */
|
|
794
841
|
name?: string;
|
|
795
842
|
/** 错误消息 */
|
|
796
|
-
error?:
|
|
843
|
+
error?: FormText;
|
|
797
844
|
/** 提示信息 */
|
|
798
|
-
hint?:
|
|
845
|
+
hint?: FormText;
|
|
799
846
|
/** 是否必填 */
|
|
800
847
|
required?: boolean;
|
|
801
848
|
/** 自定义类名 */
|
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.64",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"types": "./vite.d.ts",
|
|
20
20
|
"import": "./vite.js"
|
|
21
21
|
},
|
|
22
|
+
"./i18n": {
|
|
23
|
+
"types": "./dist/i18n.svelte.d.ts",
|
|
24
|
+
"svelte": "./dist/i18n.svelte.js",
|
|
25
|
+
"import": "./dist/i18n.svelte.js"
|
|
26
|
+
},
|
|
22
27
|
"./theme": {
|
|
23
28
|
"types": "./dist/theme-config.d.ts",
|
|
24
29
|
"import": "./dist/theme-config.js"
|
|
@@ -54,7 +59,7 @@
|
|
|
54
59
|
"svelte": "^5.57.2"
|
|
55
60
|
},
|
|
56
61
|
"dependencies": {
|
|
57
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
62
|
+
"@h-ai/core": "0.1.0-alpha.64",
|
|
58
63
|
"@internationalized/date": "^3.12.4",
|
|
59
64
|
"@shikijs/langs": "^4.5.0",
|
|
60
65
|
"bits-ui": "^2.19.5",
|