@h-ai/ui 0.1.0-alpha.23 → 0.1.0-alpha.25
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.md +23 -5
- package/dist/components/compounds/Card.svelte +1 -1
- package/dist/components/compounds/DataTable.svelte +29 -7
- package/dist/components/compounds/Modal.svelte +79 -57
- package/dist/components/compounds/Pagination.svelte +30 -16
- package/dist/components/primitives/Input.svelte +23 -1
- package/dist/components/primitives/Select.svelte +2 -2
- package/dist/components/scenes/ai/markdown-parse.js +12 -4
- package/dist/components/scenes/ai/table/AiTableEditor.svelte +4 -2
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte +196 -0
- package/dist/components/scenes/crud/CrudDetailDrawer.svelte.d.ts +44 -0
- package/dist/components/scenes/crud/CrudDetailPanel.svelte +23 -11
- package/dist/components/scenes/crud/CrudDetailPanel.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudEditPanel.svelte +28 -12
- package/dist/components/scenes/crud/CrudEditPanel.svelte.d.ts +2 -1
- package/dist/components/scenes/crud/CrudFilterBar.svelte +22 -18
- package/dist/components/scenes/crud/CrudPage.svelte +23 -13
- package/dist/components/scenes/crud/crud-types.d.ts +1 -1
- package/dist/theme-config.d.ts +60 -4
- package/dist/theme-config.js +77 -6
- package/package.json +2 -2
|
@@ -137,6 +137,14 @@
|
|
|
137
137
|
const canUpdate = $derived(permissions.update !== false && Boolean(crud.api.update))
|
|
138
138
|
const canDelete = $derived(permissions.delete !== false && Boolean(crud.api.remove))
|
|
139
139
|
const searchable = $derived(crud.resource.searchable !== false)
|
|
140
|
+
const isCompact = $derived(density === 'compact')
|
|
141
|
+
const pageGapClass = 'space-y-5'
|
|
142
|
+
const actionButtonSize = $derived(isCompact ? 'xs' : 'sm')
|
|
143
|
+
const actionIconClass = $derived(isCompact ? 'size-3.5' : 'size-4')
|
|
144
|
+
const paginationSize = $derived(isCompact ? 'xs' : 'sm')
|
|
145
|
+
const filterCardClass = 'overflow-hidden rounded-[1.25rem] border-base-content/7 shadow-sm'
|
|
146
|
+
const listCardClass = 'overflow-hidden rounded-[1.25rem] border-base-content/7 shadow-sm'
|
|
147
|
+
const paginationBarClass = 'flex justify-center border-t border-base-content/5 px-4 py-2'
|
|
140
148
|
|
|
141
149
|
// 表单展示配置(抽屉 / 弹窗)
|
|
142
150
|
const formVariant = $derived(form.variant ?? 'drawer')
|
|
@@ -395,7 +403,7 @@
|
|
|
395
403
|
)
|
|
396
404
|
</script>
|
|
397
405
|
|
|
398
|
-
<div class='
|
|
406
|
+
<div class='{pageGapClass} {className}'>
|
|
399
407
|
<!-- 页面标题 -->
|
|
400
408
|
<PageHeader title={resourceLabel}>
|
|
401
409
|
{#snippet actions()}
|
|
@@ -403,8 +411,8 @@
|
|
|
403
411
|
{@render headerActions()}
|
|
404
412
|
{/if}
|
|
405
413
|
{#if canCreate}
|
|
406
|
-
<Button variant='primary' onclick={openCreate}>
|
|
407
|
-
<span class='icon-[tabler--plus] size-4.5 mr-1'></span>
|
|
414
|
+
<Button variant='primary' size='sm' onclick={openCreate} class='shadow-sm'>
|
|
415
|
+
<span class='icon-[tabler--plus] size-4.5 mr-1.5'></span>
|
|
408
416
|
{uiM('crud_create')}
|
|
409
417
|
</Button>
|
|
410
418
|
{/if}
|
|
@@ -413,7 +421,7 @@
|
|
|
413
421
|
|
|
414
422
|
<!-- 搜索 + 过滤栏 -->
|
|
415
423
|
{#if searchable || filterFields.length > 0}
|
|
416
|
-
<Card>
|
|
424
|
+
<Card padding='sm' class={filterCardClass}>
|
|
417
425
|
<CrudFilterBar
|
|
418
426
|
{searchable}
|
|
419
427
|
searchPlaceholder={searchPlaceholderText}
|
|
@@ -428,7 +436,7 @@
|
|
|
428
436
|
{/if}
|
|
429
437
|
|
|
430
438
|
<!-- 数据列表 -->
|
|
431
|
-
<Card>
|
|
439
|
+
<Card padding='none' class={listCardClass}>
|
|
432
440
|
<DataTable
|
|
433
441
|
data={data.items}
|
|
434
442
|
columns={dtColumns}
|
|
@@ -440,32 +448,32 @@
|
|
|
440
448
|
{#if rowClickDetail}
|
|
441
449
|
<IconButton
|
|
442
450
|
variant='ghost'
|
|
443
|
-
size=
|
|
451
|
+
size={actionButtonSize}
|
|
444
452
|
ariaLabel={uiM('crud_detail')}
|
|
445
453
|
onclick={() => openDetail(item)}
|
|
446
454
|
>
|
|
447
|
-
<span class='icon-[tabler--eye]
|
|
455
|
+
<span class='icon-[tabler--eye] {actionIconClass}'></span>
|
|
448
456
|
</IconButton>
|
|
449
457
|
{/if}
|
|
450
458
|
{#if canUpdate}
|
|
451
459
|
<IconButton
|
|
452
460
|
variant='ghost'
|
|
453
|
-
size=
|
|
461
|
+
size={actionButtonSize}
|
|
454
462
|
ariaLabel={uiM('crud_edit')}
|
|
455
463
|
onclick={() => openEdit(item)}
|
|
456
464
|
>
|
|
457
|
-
<span class='icon-[tabler--edit]
|
|
465
|
+
<span class='icon-[tabler--edit] {actionIconClass}'></span>
|
|
458
466
|
</IconButton>
|
|
459
467
|
{/if}
|
|
460
468
|
{#if canDelete}
|
|
461
469
|
<IconButton
|
|
462
470
|
variant='ghost'
|
|
463
|
-
size=
|
|
471
|
+
size={actionButtonSize}
|
|
464
472
|
ariaLabel={uiM('crud_delete')}
|
|
465
473
|
onclick={() => requestDelete(item)}
|
|
466
474
|
class='hover:text-error'
|
|
467
475
|
>
|
|
468
|
-
<span class='icon-[tabler--trash]
|
|
476
|
+
<span class='icon-[tabler--trash] {actionIconClass}'></span>
|
|
469
477
|
</IconButton>
|
|
470
478
|
{/if}
|
|
471
479
|
{#if listItemActions}
|
|
@@ -475,12 +483,12 @@
|
|
|
475
483
|
</DataTable>
|
|
476
484
|
|
|
477
485
|
<!-- 分页栏:始终显示,支持每页条数选择与跳页 -->
|
|
478
|
-
<div class=
|
|
486
|
+
<div class={paginationBarClass}>
|
|
479
487
|
<Pagination
|
|
480
488
|
page={data.page}
|
|
481
489
|
total={data.total}
|
|
482
490
|
pageSize={data.pageSize}
|
|
483
|
-
size=
|
|
491
|
+
size={paginationSize}
|
|
484
492
|
showTotal={paginationShowTotal}
|
|
485
493
|
showJumper={paginationShowJumper}
|
|
486
494
|
showSizeChanger={paginationShowSizeChanger}
|
|
@@ -497,6 +505,7 @@
|
|
|
497
505
|
open={detailOpen}
|
|
498
506
|
item={selectedItem}
|
|
499
507
|
fields={detailFields}
|
|
508
|
+
{density}
|
|
500
509
|
title={panelTitle}
|
|
501
510
|
variant={formVariant}
|
|
502
511
|
size={formDrawerSize}
|
|
@@ -515,6 +524,7 @@
|
|
|
515
524
|
open={editOpen}
|
|
516
525
|
mode={panelMode === 'create' ? 'create' : 'edit'}
|
|
517
526
|
fields={panelMode === 'create' ? createFields : editFields}
|
|
527
|
+
{density}
|
|
518
528
|
bind:formData
|
|
519
529
|
title={panelTitle}
|
|
520
530
|
variant={formVariant}
|
|
@@ -142,7 +142,7 @@ export interface CrudPageProps<T = Record<string, unknown>> {
|
|
|
142
142
|
form?: CrudFormConfig;
|
|
143
143
|
/** 分页栏展示配置(每页条数选择、跳页、总数) */
|
|
144
144
|
pagination?: CrudPaginationConfig;
|
|
145
|
-
/**
|
|
145
|
+
/** 列表显示密度:影响列表行、行内操作、分页与详情/编辑表单;不影响页头与筛选栏 */
|
|
146
146
|
density?: CrudDensity;
|
|
147
147
|
/** 列表行点击是否打开详情(默认 true) */
|
|
148
148
|
rowClickDetail?: boolean;
|
package/dist/theme-config.d.ts
CHANGED
|
@@ -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
|
|
90
|
+
export declare function normalizeThemeId(themeId: string | null | undefined, fallbackThemeId?: string): string;
|
|
79
91
|
/**
|
|
80
|
-
*
|
|
92
|
+
* 归一化 Hex 颜色;无效值返回 null。
|
|
81
93
|
*/
|
|
82
|
-
export declare
|
|
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
|
*
|
package/dist/theme-config.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
161
|
+
return createThemeBootstrapScript({
|
|
162
|
+
storageKey: THEME_STORAGE_KEY,
|
|
163
|
+
defaultThemeId: DEFAULT_THEME,
|
|
164
|
+
});
|
|
94
165
|
}
|
|
95
166
|
/**
|
|
96
167
|
* 应用主题
|
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.25",
|
|
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.
|
|
48
|
+
"@h-ai/core": "0.1.0-alpha.25"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@sveltejs/package": "^2.5.7",
|