@h-ai/ui 0.1.0-alpha.24 → 0.1.0-alpha.26

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 (36) hide show
  1. package/README.md +41 -11
  2. package/auto-import.d.ts +5 -0
  3. package/auto-import.js +4 -0
  4. package/dist/components/compounds/DataTable.svelte +81 -4
  5. package/dist/components/compounds/DataTable.svelte.d.ts +7 -0
  6. package/dist/components/compounds/Modal.svelte +123 -63
  7. package/dist/components/compounds/Pagination.svelte +72 -119
  8. package/dist/components/scenes/ai/markdown-parse.js +12 -4
  9. package/dist/components/scenes/ai/table/AiTableEditor.svelte +4 -2
  10. package/dist/components/scenes/app/SettingsLayout.svelte +111 -0
  11. package/dist/components/scenes/app/SettingsLayout.svelte.d.ts +41 -0
  12. package/dist/components/scenes/app/ThemeColorPicker.svelte +41 -0
  13. package/dist/components/scenes/app/index.d.ts +1 -0
  14. package/dist/components/scenes/app/index.js +1 -0
  15. package/dist/components/scenes/crud/CrudFilterBar.svelte +173 -35
  16. package/dist/components/scenes/crud/CrudFilterBar.svelte.d.ts +7 -1
  17. package/dist/components/scenes/crud/CrudPage.svelte +19 -15
  18. package/dist/components/scenes/error/ErrorPage.svelte +125 -0
  19. package/dist/components/scenes/error/ErrorPage.svelte.d.ts +42 -0
  20. package/dist/components/scenes/error/error-presets.d.ts +24 -0
  21. package/dist/components/scenes/error/error-presets.js +31 -0
  22. package/dist/components/scenes/error/index.d.ts +8 -0
  23. package/dist/components/scenes/error/index.js +8 -0
  24. package/dist/components/scenes/iam/AuthShell.svelte +138 -0
  25. package/dist/components/scenes/iam/AuthShell.svelte.d.ts +45 -0
  26. package/dist/components/scenes/iam/index.d.ts +1 -0
  27. package/dist/components/scenes/iam/index.js +2 -1
  28. package/dist/components/scenes/index.d.ts +1 -0
  29. package/dist/components/scenes/index.js +2 -0
  30. package/dist/messages/en-US.json +20 -1
  31. package/dist/messages/zh-CN.json +20 -1
  32. package/dist/styles/global.css +4 -1
  33. package/dist/theme-config.d.ts +60 -4
  34. package/dist/theme-config.js +77 -6
  35. package/dist/types.d.ts +6 -0
  36. package/package.json +2 -2
@@ -0,0 +1,138 @@
1
+ <!--
2
+ @component AuthShell
3
+ 认证页布局场景组件
4
+
5
+ 借鉴 shadcn-admin sign-in / sign-in-2:支持居中卡片(card)与左右分栏(split)两种布局。
6
+ 品牌区、标题、副标题、表单内容由使用方提供。
7
+
8
+ 使用 Svelte 5 Runes ($props, $derived)
9
+
10
+ @example
11
+ <AuthShell variant="card" title="登录" subtitle="欢迎回来">
12
+ <LoginForm onsubmit={handleLogin} />
13
+ </AuthShell>
14
+ -->
15
+ <script lang='ts'>
16
+ import type { Snippet } from 'svelte'
17
+ import { cn } from '../../../utils.js'
18
+
19
+ const {
20
+ variant = 'card',
21
+ title = '',
22
+ subtitle = '',
23
+ brandTitle = '',
24
+ brandText = '',
25
+ logo,
26
+ illustration,
27
+ description,
28
+ highlights,
29
+ brand,
30
+ footer,
31
+ children,
32
+ class: className = '',
33
+ }: {
34
+ /** 布局形态:'card'(居中卡片,默认)或 'split'(左右分栏) */
35
+ variant?: 'card' | 'split'
36
+ /** 表单区标题 */
37
+ title?: string
38
+ /** 表单区副标题 */
39
+ subtitle?: string
40
+ /** split 模式品牌区主标题 */
41
+ brandTitle?: string
42
+ /** split 模式品牌区描述文案 */
43
+ brandText?: string
44
+ /** 顶部 Logo 插槽 */
45
+ logo?: Snippet
46
+ /** split 模式品牌区插图(如大图/海报) */
47
+ illustration?: Snippet
48
+ /** split 模式品牌区描述区(覆盖 brandText) */
49
+ description?: Snippet
50
+ /** split 模式品牌区重点信息(如卖点列表) */
51
+ highlights?: Snippet
52
+ /** split 模式品牌区自定义插槽(覆盖 brandTitle/brandText) */
53
+ brand?: Snippet
54
+ /** 底部插槽(如注册/登录切换链接) */
55
+ footer?: Snippet
56
+ /** 表单内容 */
57
+ children?: Snippet
58
+ class?: string
59
+ } = $props()
60
+
61
+ const isSplit = $derived(variant === 'split')
62
+ // split 模式下品牌区已展示 Logo,表单区仅在小屏展示,避免桌面端重复
63
+ const logoWrapClass = $derived(isSplit ? 'mb-6 flex justify-center lg:hidden' : 'mb-6 flex justify-center')
64
+ </script>
65
+
66
+ {#snippet formPanel()}
67
+ <div class='flex w-full max-w-sm flex-col'>
68
+ {#if logo}
69
+ <div class={logoWrapClass}>{@render logo()}</div>
70
+ {/if}
71
+ {#if title}
72
+ <h1 class='text-center text-2xl font-semibold tracking-tight text-base-content'>{title}</h1>
73
+ {/if}
74
+ {#if subtitle}
75
+ <p class='mt-2 text-center text-sm text-base-content/55'>{subtitle}</p>
76
+ {/if}
77
+ <div class='mt-7'>
78
+ {#if children}{@render children()}{/if}
79
+ </div>
80
+ {#if footer}
81
+ <div class='mt-6 text-center text-sm text-base-content/60'>{@render footer()}</div>
82
+ {/if}
83
+ </div>
84
+ {/snippet}
85
+
86
+ {#if isSplit}
87
+ <div class={cn('grid min-h-screen lg:grid-cols-2', className)}>
88
+ <!-- 品牌区 -->
89
+ <div class='relative hidden overflow-hidden bg-primary p-10 text-primary-content lg:flex'>
90
+ <div class='pointer-events-none absolute inset-0 opacity-20' style='background-image: radial-gradient(circle at 20% 20%, currentColor 1px, transparent 1px); background-size: 28px 28px;'></div>
91
+ {#if brand}
92
+ {@render brand()}
93
+ {:else}
94
+ <div class='relative z-10 flex w-full flex-1 flex-col justify-between gap-10'>
95
+ <div class='space-y-6'>
96
+ {#if logo}
97
+ <div>{@render logo()}</div>
98
+ {/if}
99
+ {#if illustration}
100
+ <div class='overflow-hidden rounded-2xl border border-primary-content/15 bg-primary-content/5 p-2'>
101
+ {@render illustration()}
102
+ </div>
103
+ {/if}
104
+ </div>
105
+
106
+ <div class='space-y-4'>
107
+ {#if brandTitle}
108
+ <h2 class='text-3xl font-semibold leading-snug'>{brandTitle}</h2>
109
+ {/if}
110
+ {#if description}
111
+ <div class='max-w-xl text-sm text-primary-content/85'>
112
+ {@render description()}
113
+ </div>
114
+ {:else if brandText}
115
+ <p class='max-w-xl text-sm text-primary-content/80'>{brandText}</p>
116
+ {/if}
117
+ </div>
118
+
119
+ {#if highlights}
120
+ <div class='max-w-xl text-sm text-primary-content/90'>
121
+ {@render highlights()}
122
+ </div>
123
+ {/if}
124
+ </div>
125
+ {/if}
126
+ </div>
127
+ <!-- 表单区 -->
128
+ <div class='flex items-center justify-center p-6 sm:p-10'>
129
+ {@render formPanel()}
130
+ </div>
131
+ </div>
132
+ {:else}
133
+ <div class={cn('flex min-h-screen items-center justify-center bg-base-200/40 p-6', className)}>
134
+ <div class='w-full max-w-md rounded-2xl border border-base-content/8 bg-base-100 p-8 shadow-sm sm:p-10'>
135
+ {@render formPanel()}
136
+ </div>
137
+ </div>
138
+ {/if}
@@ -0,0 +1,45 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** 布局形态:'card'(居中卡片,默认)或 'split'(左右分栏) */
4
+ variant?: 'card' | 'split';
5
+ /** 表单区标题 */
6
+ title?: string;
7
+ /** 表单区副标题 */
8
+ subtitle?: string;
9
+ /** split 模式品牌区主标题 */
10
+ brandTitle?: string;
11
+ /** split 模式品牌区描述文案 */
12
+ brandText?: string;
13
+ /** 顶部 Logo 插槽 */
14
+ logo?: Snippet;
15
+ /** split 模式品牌区插图(如大图/海报) */
16
+ illustration?: Snippet;
17
+ /** split 模式品牌区描述区(覆盖 brandText) */
18
+ description?: Snippet;
19
+ /** split 模式品牌区重点信息(如卖点列表) */
20
+ highlights?: Snippet;
21
+ /** split 模式品牌区自定义插槽(覆盖 brandTitle/brandText) */
22
+ brand?: Snippet;
23
+ /** 底部插槽(如注册/登录切换链接) */
24
+ footer?: Snippet;
25
+ /** 表单内容 */
26
+ children?: Snippet;
27
+ class?: string;
28
+ };
29
+ /**
30
+ * AuthShell
31
+ * 认证页布局场景组件
32
+ *
33
+ * 借鉴 shadcn-admin sign-in / sign-in-2:支持居中卡片(card)与左右分栏(split)两种布局。
34
+ * 品牌区、标题、副标题、表单内容由使用方提供。
35
+ *
36
+ * 使用 Svelte 5 Runes ($props, $derived)
37
+ *
38
+ * @example
39
+ * <AuthShell variant="card" title="登录" subtitle="欢迎回来">
40
+ * <LoginForm onsubmit={handleLogin} />
41
+ * </AuthShell>
42
+ */
43
+ declare const AuthShell: import("svelte").Component<$$ComponentProps, {}, "">;
44
+ type AuthShell = ReturnType<typeof AuthShell>;
45
+ export default AuthShell;
@@ -4,6 +4,7 @@
4
4
  * 身份认证相关的场景化组件
5
5
  * @module index
6
6
  */
7
+ export { default as AuthShell } from './AuthShell.svelte';
7
8
  export { default as ChangePasswordForm } from './ChangePasswordForm.svelte';
8
9
  export { default as ForgotPasswordForm } from './ForgotPasswordForm.svelte';
9
10
  export { default as LoginForm } from './LoginForm.svelte';
@@ -4,9 +4,10 @@
4
4
  * 身份认证相关的场景化组件
5
5
  * @module index
6
6
  */
7
+ // 认证表单
8
+ export { default as AuthShell } from './AuthShell.svelte';
7
9
  export { default as ChangePasswordForm } from './ChangePasswordForm.svelte';
8
10
  export { default as ForgotPasswordForm } from './ForgotPasswordForm.svelte';
9
- // 认证表单
10
11
  export { default as LoginForm } from './LoginForm.svelte';
11
12
  export { default as PasswordInput } from './PasswordInput.svelte';
12
13
  // 权限控制
@@ -8,6 +8,7 @@ export * from './ai/index.js';
8
8
  export * from './app/index.js';
9
9
  export * from './crud/index.js';
10
10
  export * from './crypto/index.js';
11
+ export * from './error/index.js';
11
12
  export * from './iam/index.js';
12
13
  export * from './storage/index.js';
13
14
  export * from './types.js';
@@ -12,6 +12,8 @@ export * from './app/index.js';
12
12
  export * from './crud/index.js';
13
13
  // Crypto 场景组件
14
14
  export * from './crypto/index.js';
15
+ // 错误页场景组件
16
+ export * from './error/index.js';
15
17
  // IAM 场景组件
16
18
  export * from './iam/index.js';
17
19
  // Storage 场景组件
@@ -160,6 +160,8 @@
160
160
  "pagination_jump_to": "Go to",
161
161
  "pagination_page": "page",
162
162
  "pagination_page_size": "{size} / page",
163
+ "pagination_rows_per_page": "Rows per page",
164
+ "pagination_page_info": "Page {page} of {total}",
163
165
  "severity_critical": "Critical",
164
166
  "severity_high": "High",
165
167
  "severity_medium": "Medium",
@@ -275,7 +277,24 @@
275
277
  "crud_delete_failed": "Delete failed",
276
278
  "crud_network_error": "Network error, please try again later",
277
279
  "crud_filter_all": "All",
280
+ "crud_filter_yes": "Yes",
281
+ "crud_filter_no": "No",
282
+ "crud_filter_from": "From",
283
+ "crud_filter_to": "To",
284
+ "crud_reset": "Reset",
278
285
  "crud_total": "{count} items total",
279
286
  "crud_detail_empty": "-",
280
- "crud_field_required": "This field is required"
287
+ "crud_field_required": "This field is required",
288
+ "error_go_back": "Go Back",
289
+ "error_back_home": "Back to Home",
290
+ "error_401_title": "Unauthorized",
291
+ "error_401_desc": "You need to sign in to access this page. Please log in and try again.",
292
+ "error_403_title": "Access Forbidden",
293
+ "error_403_desc": "You don't have permission to access this resource. Contact your administrator if you need help.",
294
+ "error_404_title": "Page Not Found",
295
+ "error_404_desc": "The page you are looking for doesn't exist or has been moved. Please check the URL.",
296
+ "error_500_title": "Internal Server Error",
297
+ "error_500_desc": "Something went wrong on our end. We're working on it, please try again later.",
298
+ "error_503_title": "Under Maintenance",
299
+ "error_503_desc": "The system is currently undergoing maintenance. Please check back soon."
281
300
  }
@@ -160,6 +160,8 @@
160
160
  "pagination_jump_to": "跳转到",
161
161
  "pagination_page": "页",
162
162
  "pagination_page_size": "{size} 条/页",
163
+ "pagination_rows_per_page": "每页",
164
+ "pagination_page_info": "第 {page} / {total} 页",
163
165
  "severity_critical": "严重",
164
166
  "severity_high": "高",
165
167
  "severity_medium": "中",
@@ -275,7 +277,24 @@
275
277
  "crud_delete_failed": "删除失败",
276
278
  "crud_network_error": "网络错误,请稍后重试",
277
279
  "crud_filter_all": "全部",
280
+ "crud_filter_yes": "是",
281
+ "crud_filter_no": "否",
282
+ "crud_filter_from": "开始日期",
283
+ "crud_filter_to": "结束日期",
284
+ "crud_reset": "重置",
278
285
  "crud_total": "共 {count} 条",
279
286
  "crud_detail_empty": "-",
280
- "crud_field_required": "此字段为必填项"
287
+ "crud_field_required": "此字段为必填项",
288
+ "error_go_back": "返回上一页",
289
+ "error_back_home": "返回首页",
290
+ "error_401_title": "未授权访问",
291
+ "error_401_desc": "您需要登录后才能访问此页面。请登录后重试。",
292
+ "error_403_title": "禁止访问",
293
+ "error_403_desc": "您没有权限访问此资源。如需帮助请联系管理员。",
294
+ "error_404_title": "页面不存在",
295
+ "error_404_desc": "您访问的页面不存在或已被移除。请检查地址是否正确。",
296
+ "error_500_title": "服务器错误",
297
+ "error_500_desc": "服务器出现了一些问题。我们正在处理,请稍后重试。",
298
+ "error_503_title": "服务维护中",
299
+ "error_503_desc": "系统正在维护升级,请稍后再访问。"
281
300
  }
@@ -57,7 +57,10 @@ input::-webkit-credentials-auto-fill-button {
57
57
  }
58
58
 
59
59
  /* ─── Focus ring ─── */
60
- :focus-visible {
60
+ /* 全局 focus-visible 仅用于非表单交互元素(按钮、链接等)
61
+ 表单组件 (.select, .radio, .checkbox, .toggle, .textarea, input, textarea)
62
+ 由 DaisyUI 或组件自身处理焦点样式 */
63
+ :focus-visible:not(input, select, textarea, .select, .radio, .checkbox, .toggle, .textarea) {
61
64
  outline: 2px solid oklch(0.6 0.15 250);
62
65
  outline-offset: 2px;
63
66
  border-radius: 4px;
@@ -52,6 +52,10 @@ export declare const THEME_GROUPS: ThemeGroup[];
52
52
  * 暗色主题列表
53
53
  */
54
54
  export declare const DARK_THEMES: string[];
55
+ /**
56
+ * 支持的主题 ID 列表
57
+ */
58
+ export declare const SUPPORTED_THEME_IDS: string[];
55
59
  /**
56
60
  * 默认主题色
57
61
  */
@@ -64,6 +68,14 @@ export declare const DEFAULT_THEME_COLOR_CSS_VAR = "--hai-theme-color-default";
64
68
  * 内置主题色预设
65
69
  */
66
70
  export declare const THEME_COLOR_PRESETS: ThemeColorPreset[];
71
+ /**
72
+ * 默认主题
73
+ */
74
+ export declare const DEFAULT_THEME = "light";
75
+ /**
76
+ * 主题存储键名
77
+ */
78
+ export declare const THEME_STORAGE_KEY = "theme";
67
79
  /**
68
80
  * 获取主题信息
69
81
  */
@@ -73,13 +85,57 @@ export declare function getThemeInfo(themeId: string): ThemeInfo | undefined;
73
85
  */
74
86
  export declare function isDarkTheme(themeId: string): boolean;
75
87
  /**
76
- * 默认主题
88
+ * 归一化主题 ID,不支持的值回退到默认主题。
77
89
  */
78
- export declare const DEFAULT_THEME = "light";
90
+ export declare function normalizeThemeId(themeId: string | null | undefined, fallbackThemeId?: string): string;
79
91
  /**
80
- * 主题存储键名
92
+ * 归一化 Hex 颜色;无效值返回 null。
81
93
  */
82
- export declare const THEME_STORAGE_KEY = "theme";
94
+ export declare function normalizeHexColor(value: string | null | undefined): string | null;
95
+ /**
96
+ * 解析主题对应的浏览器色调。
97
+ */
98
+ export declare function resolveThemeTone(themeId: string): 'light' | 'dark';
99
+ /**
100
+ * 主题首屏恢复脚本里可选的语言字段配置。
101
+ *
102
+ * 业务侧如果把语言和主题一起落到同一个偏好对象里,可用这组配置在首屏阶段同步恢复语言值,
103
+ * 避免 HTML shell 和 hydrate 后的语言状态短暂不一致。
104
+ */
105
+ export interface ThemeBootstrapLocaleConfig {
106
+ /** 偏好对象中的语言字段名。 */
107
+ key: string;
108
+ /** 默认语言。 */
109
+ defaultValue: string;
110
+ /** 允许的语言列表。 */
111
+ supportedValues: string[];
112
+ }
113
+ /**
114
+ * 生成首屏主题恢复脚本时使用的宿主侧配置。
115
+ *
116
+ * 这组配置刻意只保留“HTML shell 首屏恢复”所需的信息,避免把运行时 helper 或 UI 组件状态
117
+ * 泄漏到脚本字符串里,方便不同应用在 `app.html` / 服务端模板中复用同一套恢复逻辑。
118
+ */
119
+ export interface ThemeBootstrapScriptOptions {
120
+ /** 偏好存储 key。 */
121
+ storageKey: string;
122
+ /** 历史主题 key;传 null 表示不做迁移。 */
123
+ legacyThemeStorageKey?: string | null;
124
+ /** 默认主题 ID。 */
125
+ defaultThemeId?: string;
126
+ /** 默认主题色;传 null 表示不处理主题色。 */
127
+ defaultThemeColor?: string | null;
128
+ /** 主题色写入的 CSS 变量;传 null 表示不写。 */
129
+ colorCssVar?: string | null;
130
+ /** 主题 tone 写入的 dataset key;传 null 表示不写。 */
131
+ toneDatasetKey?: string | null;
132
+ /** 可选的语言字段配置。 */
133
+ locale?: ThemeBootstrapLocaleConfig | null;
134
+ }
135
+ /**
136
+ * 生成首屏主题恢复脚本,适合注入到 SvelteKit `app.html` 的 `<script>` 中。
137
+ */
138
+ export declare function createThemeBootstrapScript(options: ThemeBootstrapScriptOptions): string;
83
139
  /**
84
140
  * 获取主题初始化脚本(用于 HTML shell 防闪烁)
85
141
  *
@@ -46,6 +46,10 @@ export const THEME_GROUPS = [
46
46
  * 暗色主题列表
47
47
  */
48
48
  export const DARK_THEMES = THEMES.filter(t => t.dark).map(t => t.id);
49
+ /**
50
+ * 支持的主题 ID 列表
51
+ */
52
+ export const SUPPORTED_THEME_IDS = THEMES.map(theme => theme.id);
49
53
  /**
50
54
  * 默认主题色
51
55
  */
@@ -62,6 +66,17 @@ export const THEME_COLOR_PRESETS = [
62
66
  { value: '#13b981', labelKey: 'theme_color_grid_green' },
63
67
  { value: '#1f5eff', labelKey: 'theme_color_ocean' },
64
68
  ];
69
+ const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/i;
70
+ const SHORT_HEX_COLOR_REGEX = /^#[0-9a-f]{3}$/i;
71
+ const SUPPORTED_THEME_ID_SET = new Set(SUPPORTED_THEME_IDS);
72
+ /**
73
+ * 默认主题
74
+ */
75
+ export const DEFAULT_THEME = 'light';
76
+ /**
77
+ * 主题存储键名
78
+ */
79
+ export const THEME_STORAGE_KEY = 'theme';
65
80
  /**
66
81
  * 获取主题信息
67
82
  */
@@ -74,15 +89,68 @@ export function getThemeInfo(themeId) {
74
89
  export function isDarkTheme(themeId) {
75
90
  return DARK_THEMES.includes(themeId);
76
91
  }
77
- // ─── 主题初始化工具 ───
78
92
  /**
79
- * 默认主题
93
+ * 归一化主题 ID,不支持的值回退到默认主题。
80
94
  */
81
- export const DEFAULT_THEME = 'light';
95
+ export function normalizeThemeId(themeId, fallbackThemeId = DEFAULT_THEME) {
96
+ const normalized = `${themeId ?? ''}`;
97
+ if (SUPPORTED_THEME_ID_SET.has(normalized)) {
98
+ return normalized;
99
+ }
100
+ return SUPPORTED_THEME_ID_SET.has(fallbackThemeId) ? fallbackThemeId : DEFAULT_THEME;
101
+ }
82
102
  /**
83
- * 主题存储键名
103
+ * 归一化 Hex 颜色;无效值返回 null。
84
104
  */
85
- export const THEME_STORAGE_KEY = 'theme';
105
+ export function normalizeHexColor(value) {
106
+ const normalized = value?.trim().toLowerCase() ?? '';
107
+ if (HEX_COLOR_REGEX.test(normalized)) {
108
+ return normalized;
109
+ }
110
+ if (SHORT_HEX_COLOR_REGEX.test(normalized)) {
111
+ return `#${normalized[1]}${normalized[1]}${normalized[2]}${normalized[2]}${normalized[3]}${normalized[3]}`;
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * 解析主题对应的浏览器色调。
117
+ */
118
+ export function resolveThemeTone(themeId) {
119
+ return isDarkTheme(themeId) ? 'dark' : 'light';
120
+ }
121
+ function escapeJsonForScript(value) {
122
+ return JSON.stringify(value)
123
+ .replace(/</g, '\\u003C')
124
+ .replace(/>/g, '\\u003E')
125
+ .replace(/\u2028/g, '\\u2028')
126
+ .replace(/\u2029/g, '\\u2029');
127
+ }
128
+ /**
129
+ * 生成首屏主题恢复脚本,适合注入到 SvelteKit `app.html` 的 `<script>` 中。
130
+ */
131
+ export function createThemeBootstrapScript(options) {
132
+ const defaultThemeId = normalizeThemeId(options.defaultThemeId ?? DEFAULT_THEME);
133
+ const defaultThemeColor = options.defaultThemeColor == null
134
+ ? null
135
+ : normalizeHexColor(options.defaultThemeColor) ?? DEFAULT_THEME_COLOR;
136
+ const locale = options.locale
137
+ ? {
138
+ key: options.locale.key,
139
+ defaultValue: options.locale.defaultValue,
140
+ supportedValues: [...options.locale.supportedValues],
141
+ }
142
+ : null;
143
+ const defaultPreferences = {
144
+ themeId: defaultThemeId,
145
+ };
146
+ if (defaultThemeColor) {
147
+ defaultPreferences.themeColor = defaultThemeColor;
148
+ }
149
+ if (locale) {
150
+ defaultPreferences[locale.key] = locale.defaultValue;
151
+ }
152
+ return `(function(){var d=${escapeJsonForScript(defaultPreferences)};var s=${escapeJsonForScript(options.storageKey)};var l=${escapeJsonForScript(options.legacyThemeStorageKey ?? null)};var u=new Set(${escapeJsonForScript(SUPPORTED_THEME_IDS)});var k=new Set(${escapeJsonForScript(DARK_THEMES)});var c=${escapeJsonForScript(options.colorCssVar ?? null)};var y=${escapeJsonForScript(options.toneDatasetKey ?? null)};var i=${escapeJsonForScript(locale)};function n(e){var r=''+(e??'');return u.has(r)?r:d.themeId}function h(e){var r=(''+(e??'')).trim().toLowerCase();if(/^#[0-9a-f]{6}$/i.test(r))return r;if(/^#[0-9a-f]{3}$/i.test(r))return '#'+r[1]+r[1]+r[2]+r[2]+r[3]+r[3];return Object.prototype.hasOwnProperty.call(d,'themeColor')?d.themeColor:null}function g(e){var r={...d,themeId:n(e?.themeId)};if(Object.prototype.hasOwnProperty.call(d,'themeColor'))r.themeColor=h(e?.themeColor);if(i)r[i.key]=i.supportedValues.includes(e?.[i.key])?e[i.key]:d[i.key];return r}function a(e){var r=document.documentElement;var o=k.has(e.themeId)?'dark':'light';r.setAttribute('data-theme',e.themeId);if(y)r.dataset[y]=o;if(c){if(e.themeColor){r.style.setProperty(c,e.themeColor)}else{r.style.removeProperty(c)}}r.style.setProperty('color-scheme',o)}var p=d;try{var f=window.localStorage.getItem(s);if(f){p=g(JSON.parse(f))}else if(l){p=g({themeId:window.localStorage.getItem(l)})}else{p=g(d)}a(p);window.localStorage.setItem(s,JSON.stringify(p));if(l)window.localStorage.removeItem(l)}catch{a(d)}})()`;
153
+ }
86
154
  /**
87
155
  * 获取主题初始化脚本(用于 HTML shell 防闪烁)
88
156
  *
@@ -90,7 +158,10 @@ export const THEME_STORAGE_KEY = 'theme';
90
158
  * 对于 SvelteKit `app.html`,请直接粘贴这段脚本字符串的内容,不能写 `{@html getThemeInitScript()}`。
91
159
  */
92
160
  export function getThemeInitScript() {
93
- return `(function(){var t='${DEFAULT_THEME}';try{var s=localStorage.getItem('${THEME_STORAGE_KEY}');if(s)t=s}catch{}document.documentElement.setAttribute('data-theme',t)})()`;
161
+ return createThemeBootstrapScript({
162
+ storageKey: THEME_STORAGE_KEY,
163
+ defaultThemeId: DEFAULT_THEME,
164
+ });
94
165
  }
95
166
  /**
96
167
  * 应用主题
package/dist/types.d.ts CHANGED
@@ -438,6 +438,8 @@ export interface ModalProps {
438
438
  title?: string;
439
439
  /** 尺寸 */
440
440
  size?: Size | 'full';
441
+ /** 面板圆角(任意合法 CSS border-radius 值,默认 1rem) */
442
+ radius?: string;
441
443
  /** 是否可通过点击遮罩关闭 */
442
444
  closeOnBackdrop?: boolean;
443
445
  /** 是否可通过 ESC 键关闭 */
@@ -601,6 +603,10 @@ export interface PaginationProps {
601
603
  page?: string;
602
604
  /** 每页条数文案,{size} 会被替换为实际数字 */
603
605
  pageSize?: string;
606
+ /** 每页条数前缀文案(如「每页」/「Rows per page」) */
607
+ rowsPerPage?: string;
608
+ /** 页码信息文案,{page} / {total} 会被替换(如「第 {page} / {total} 页」) */
609
+ pageInfo?: string;
604
610
  };
605
611
  /** 自定义类名 */
606
612
  class?: string;
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.24",
4
+ "version": "0.1.0-alpha.26",
5
5
  "description": "Hai Framework UI component library for Svelte 5 Runes.",
6
6
  "license": "Apache-2.0",
7
7
  "svelte": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  "mermaid": "^11.12.0",
46
46
  "shiki": "^4.0.2",
47
47
  "tailwind-merge": "^3.5.0",
48
- "@h-ai/core": "0.1.0-alpha.24"
48
+ "@h-ai/core": "0.1.0-alpha.26"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@sveltejs/package": "^2.5.7",