@h-ai/ui 0.1.0-alpha.48 → 0.1.0-alpha.50
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 +20 -3
- package/dist/components/compounds/Pagination.svelte +12 -6
- package/dist/components/compounds/Popover.svelte +104 -18
- package/dist/components/compounds/Tabs.svelte +20 -1
- package/dist/components/compounds/Tooltip.svelte +127 -6
- package/dist/components/scenes/crud/CrudPage.svelte +167 -90
- package/dist/components/scenes/crud/CrudPage.svelte.d.ts +13 -0
- package/dist/components/scenes/crud/crud-controller.svelte.d.ts +213 -0
- package/dist/components/scenes/crud/crud-controller.svelte.js +265 -0
- package/dist/components/scenes/crud/index.d.ts +5 -1
- package/dist/components/scenes/crud/index.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/internal/browser-safety.d.ts +3 -2
- package/dist/internal/browser-safety.js +26 -5
- package/dist/messages/en-US.json +3 -1
- package/dist/messages/zh-CN.json +3 -1
- package/dist/toast.svelte.d.ts +46 -0
- package/dist/toast.svelte.js +61 -0
- package/dist/types.d.ts +27 -4
- package/package.json +2 -2
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @h-ai/ui — CRUD 控制器(SPA 数据 + 导航托管)
|
|
3
|
+
*
|
|
4
|
+
* 为 `CrudPage` 提供两个框架级适配器,业务应用无需在各自项目里重复实现:
|
|
5
|
+
* 1. `defineCrud`:由声明式资源定义生成 `CrudPage` 所需的 `crud` 对象(字段筛选、列映射、默认值),
|
|
6
|
+
* 不依赖 `@h-ai/kit`,纯 SPA / Tauri / Capacitor 均可直接使用。
|
|
7
|
+
* 2. `createCrudController`:在纯客户端(无 SvelteKit SSR)下托管 `CrudPage` 的 `data` 与 `nav`,
|
|
8
|
+
* 把 `CrudPage` 通过 URL query 同步的搜索/过滤/分页参数解析回本地状态并重新拉取列表。
|
|
9
|
+
*
|
|
10
|
+
* 控制器特性:latest-wins(seq + AbortSignal 双保险)、失败保留旧数据、惰性加载、并发去重、
|
|
11
|
+
* 查询串回填地址栏(保留 pathname 与 hash,不整页跳转)。
|
|
12
|
+
*
|
|
13
|
+
* @module crud-controller
|
|
14
|
+
*/
|
|
15
|
+
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
16
|
+
/**
|
|
17
|
+
* 解析可能是 i18n 函数的文本。
|
|
18
|
+
*/
|
|
19
|
+
function resolveText(text) {
|
|
20
|
+
return typeof text === 'function' ? text() : text;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 映射分页结果的 `items`,保留 total / page / pageSize。
|
|
24
|
+
*
|
|
25
|
+
* @description 标准 `PaginatedResult<T>` 适配器:业务页面只需转换条目本身,
|
|
26
|
+
* 无需重复搬运分页元数据,配合 `CrudApi.list` 消除 page/pageSize/total 样板。
|
|
27
|
+
*
|
|
28
|
+
* @param result - 后端返回的分页结果。
|
|
29
|
+
* @param map - 单条映射函数。
|
|
30
|
+
* @returns 条目被映射、分页元数据不变的新分页结果。
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* list: async (params) => mapPaginated(await api.listUsers(params), toRow)
|
|
34
|
+
*/
|
|
35
|
+
export function mapPaginated(result, map) {
|
|
36
|
+
return {
|
|
37
|
+
items: result.items.map(map),
|
|
38
|
+
total: result.total,
|
|
39
|
+
page: result.page,
|
|
40
|
+
pageSize: result.pageSize,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 由声明式资源定义生成 `CrudPage` 所需的 `crud` 对象。
|
|
45
|
+
*
|
|
46
|
+
* @description 仅做字段筛选、列映射与默认值收集,不依赖 `@h-ai/kit`,可在纯 SPA 直接使用。
|
|
47
|
+
*
|
|
48
|
+
* @param resource - 资源定义(字段、标签、API)。
|
|
49
|
+
* @returns `CrudPage` 可直接消费的 `crud` 对象。
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const userCrud = defineCrud<User>({
|
|
53
|
+
* name: 'user',
|
|
54
|
+
* label: () => m.users_title(),
|
|
55
|
+
* fields: [{ id: 'name', label: () => m.user_name(), type: 'text', filterable: true }],
|
|
56
|
+
* api: { list: params => listUsers(params) },
|
|
57
|
+
* })
|
|
58
|
+
*/
|
|
59
|
+
export function defineCrud(resource) {
|
|
60
|
+
const fields = [...resource.fields].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
61
|
+
return {
|
|
62
|
+
resource,
|
|
63
|
+
getListFields: () => fields.filter(f => f.inList !== false),
|
|
64
|
+
getFilterFields: () => fields.filter(f => f.filterable === true),
|
|
65
|
+
// readonly 仅表示“编辑态禁用”(由 CrudEditPanel 在 edit 模式 disable),新建仍可填写;
|
|
66
|
+
// 因此创建表单只按 inCreate 过滤,不排除 readonly 字段。
|
|
67
|
+
getCreateFields: () => fields.filter(f => f.inCreate !== false),
|
|
68
|
+
getEditFields: () => fields.filter(f => f.inEdit !== false),
|
|
69
|
+
getDetailFields: () => fields.filter(f => f.inDetail !== false),
|
|
70
|
+
toTableColumns: () => fields
|
|
71
|
+
.filter(f => f.inList !== false)
|
|
72
|
+
.map(f => ({
|
|
73
|
+
key: f.id,
|
|
74
|
+
label: resolveText(f.label),
|
|
75
|
+
width: f.width,
|
|
76
|
+
align: f.align,
|
|
77
|
+
render: f.render ? (item) => f.render(item[f.id], item) : undefined,
|
|
78
|
+
})),
|
|
79
|
+
getDefaultValues: () => Object.fromEntries(fields.filter(f => f.defaultValue !== undefined).map(f => [f.id, f.defaultValue])),
|
|
80
|
+
api: resource.api,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 在纯客户端(无 SvelteKit SSR)下为 `CrudPage` 创建数据 + 导航控制器。
|
|
85
|
+
*
|
|
86
|
+
* @description `CrudPage` 通过 `nav.navigate(url)` 把搜索/过滤/分页写进 URL query,再调用 `nav.refresh()`
|
|
87
|
+
* 触发列表刷新。本控制器把 query 解析回参数、调用 `crud.api.list` 更新本地 `data`,并把查询串回填地址栏,
|
|
88
|
+
* 不触发整页跳转(避免刷新与登录态丢失)。连续请求只允许最新一轮更新状态(seq + AbortSignal 双保险),
|
|
89
|
+
* 失败时保留已有数据并暴露错误供页面重试。
|
|
90
|
+
*
|
|
91
|
+
* @param crud - `defineCrud` 的返回值。
|
|
92
|
+
* @param options - 加载策略;默认立即加载、失败保留旧数据、回填地址栏。
|
|
93
|
+
* @returns 含响应式 `data`、`nav` 适配器与 `load` / `reload` 的控制器。
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const controller = createCrudController(userCrud)
|
|
97
|
+
* // <CrudPage crud={userCrud} data={controller.data} nav={controller.nav}
|
|
98
|
+
* // loading={controller.loading} loaded={controller.loaded}
|
|
99
|
+
* // error={controller.error} onRetry={controller.reload} />
|
|
100
|
+
*/
|
|
101
|
+
export function createCrudController(crud, options = {}) {
|
|
102
|
+
const defaultPageSize = crud.resource.defaultPageSize ?? 10;
|
|
103
|
+
const keepPreviousData = options.keepPreviousData !== false;
|
|
104
|
+
const syncUrl = options.syncUrl !== false;
|
|
105
|
+
const filterIds = crud.getFilterFields().map(f => f.id);
|
|
106
|
+
const data = $state({
|
|
107
|
+
items: [],
|
|
108
|
+
total: 0,
|
|
109
|
+
page: 1,
|
|
110
|
+
pageSize: defaultPageSize,
|
|
111
|
+
filters: {},
|
|
112
|
+
});
|
|
113
|
+
// 最近一次解析出的查询参数(供 refresh 使用)。
|
|
114
|
+
let pending = { search: '', page: 1, pageSize: defaultPageSize };
|
|
115
|
+
// 单调递增请求号;只有最新一轮请求可写回状态(latest-wins)。
|
|
116
|
+
let listRequestSeq = 0;
|
|
117
|
+
// 最新一轮请求的取消控制器;新请求发起前 abort 上一轮。
|
|
118
|
+
let abortController;
|
|
119
|
+
// 并发去重:同参数请求在途时复用同一 Promise。
|
|
120
|
+
let inFlightKey;
|
|
121
|
+
let inFlightPromise;
|
|
122
|
+
let loading = $state(false);
|
|
123
|
+
let loaded = $state(false);
|
|
124
|
+
let error = $state(undefined);
|
|
125
|
+
/** 生成参数的稳定 key(用于并发去重)。 */
|
|
126
|
+
function paramsKey(params) {
|
|
127
|
+
return JSON.stringify(Object.entries(params).sort(([a], [b]) => a.localeCompare(b)));
|
|
128
|
+
}
|
|
129
|
+
/** 把成功响应写回响应式 data,并回填 filters 供筛选栏回显。 */
|
|
130
|
+
function applyResult(result, params) {
|
|
131
|
+
data.items = result.items;
|
|
132
|
+
data.total = result.total;
|
|
133
|
+
data.page = result.page;
|
|
134
|
+
data.pageSize = result.pageSize;
|
|
135
|
+
const filters = { search: params.search ?? '' };
|
|
136
|
+
for (const id of filterIds) {
|
|
137
|
+
if (params[id] !== undefined)
|
|
138
|
+
filters[id] = params[id];
|
|
139
|
+
}
|
|
140
|
+
data.filters = filters;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 按参数拉取列表并写回响应式 data。
|
|
144
|
+
*
|
|
145
|
+
* @description 暴露查询中、已成功加载和错误三种状态;连续请求只允许最新响应更新状态与数据,
|
|
146
|
+
* 旧请求被 abort 且其结果被丢弃;失败时按 keepPreviousData 决定是否保留旧列表。
|
|
147
|
+
*
|
|
148
|
+
* @param params - 搜索、过滤与分页参数。
|
|
149
|
+
* @returns 最新请求结算后完成;错误被保存在控制器中,避免未处理 Promise 拒绝。
|
|
150
|
+
*/
|
|
151
|
+
function fetchList(params) {
|
|
152
|
+
const key = paramsKey(params);
|
|
153
|
+
// 并发去重:同参数请求在途时直接复用,避免重复打后端。
|
|
154
|
+
if (inFlightPromise && inFlightKey === key)
|
|
155
|
+
return inFlightPromise;
|
|
156
|
+
const requestSeq = ++listRequestSeq;
|
|
157
|
+
// 取消上一轮在途请求,实现 latest-wins 的第一重保险。
|
|
158
|
+
abortController?.abort();
|
|
159
|
+
const ac = new AbortController();
|
|
160
|
+
abortController = ac;
|
|
161
|
+
loading = true;
|
|
162
|
+
error = undefined;
|
|
163
|
+
// 不保留旧数据时立即清空,展示查询中占位;保留时维持旧列表避免闪烁。
|
|
164
|
+
if (!keepPreviousData) {
|
|
165
|
+
data.items = [];
|
|
166
|
+
data.total = 0;
|
|
167
|
+
}
|
|
168
|
+
inFlightKey = key;
|
|
169
|
+
const promise = (async () => {
|
|
170
|
+
try {
|
|
171
|
+
const result = await crud.api.list(params, { signal: ac.signal });
|
|
172
|
+
// 旧请求晚返回:seq 已被新请求覆盖时丢弃,绝不污染用户当前看到的结果。
|
|
173
|
+
if (requestSeq !== listRequestSeq)
|
|
174
|
+
return;
|
|
175
|
+
applyResult(result, params);
|
|
176
|
+
loaded = true;
|
|
177
|
+
}
|
|
178
|
+
catch (cause) {
|
|
179
|
+
// 被新请求取代而 abort 的旧请求:静默丢弃,不写错误。
|
|
180
|
+
if (ac.signal.aborted || (cause instanceof Error && cause.name === 'AbortError'))
|
|
181
|
+
return;
|
|
182
|
+
// 列表加载失败(慢响应/重启/超时)时保留上一次结果,绝不用空列表覆盖,避免误显示“0 条记录”;
|
|
183
|
+
// 同时暴露错误给页面:首次失败不能被当成一次成功的空结果。
|
|
184
|
+
if (requestSeq === listRequestSeq)
|
|
185
|
+
error = cause instanceof Error ? cause : new Error(String(cause));
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
if (requestSeq === listRequestSeq) {
|
|
189
|
+
loading = false;
|
|
190
|
+
inFlightPromise = undefined;
|
|
191
|
+
inFlightKey = undefined;
|
|
192
|
+
abortController = undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
inFlightPromise = promise;
|
|
197
|
+
return promise;
|
|
198
|
+
}
|
|
199
|
+
/** 从 CrudPage 生成的 URL 中解析查询参数。 */
|
|
200
|
+
function parseParams(url) {
|
|
201
|
+
const qs = url.includes('?') ? url.slice(url.indexOf('?') + 1) : '';
|
|
202
|
+
const sp = new SvelteURLSearchParams(qs);
|
|
203
|
+
const params = {
|
|
204
|
+
search: sp.get('search') ?? '',
|
|
205
|
+
page: sp.get('page') ? Number(sp.get('page')) : 1,
|
|
206
|
+
pageSize: sp.get('pageSize') ? Number(sp.get('pageSize')) : defaultPageSize,
|
|
207
|
+
};
|
|
208
|
+
for (const key of ['sortBy', 'sortDirection']) {
|
|
209
|
+
const v = sp.get(key);
|
|
210
|
+
if (v !== null && v !== '')
|
|
211
|
+
params[key] = v;
|
|
212
|
+
}
|
|
213
|
+
for (const id of filterIds) {
|
|
214
|
+
const v = sp.get(id);
|
|
215
|
+
if (v !== null && v !== '')
|
|
216
|
+
params[id] = v;
|
|
217
|
+
}
|
|
218
|
+
return params;
|
|
219
|
+
}
|
|
220
|
+
/** 把查询串回填地址栏,保留 pathname 与 hash,不触发跳转;SSR / 无 history 环境静默跳过。 */
|
|
221
|
+
function writeUrl(url) {
|
|
222
|
+
if (!syncUrl)
|
|
223
|
+
return;
|
|
224
|
+
const his = globalThis.history;
|
|
225
|
+
const loc = globalThis.location;
|
|
226
|
+
if (!his?.replaceState || !loc)
|
|
227
|
+
return;
|
|
228
|
+
const qs = url.includes('?') ? url.slice(url.indexOf('?')) : '';
|
|
229
|
+
try {
|
|
230
|
+
his.replaceState(his.state, '', `${loc.pathname}${qs}${loc.hash}`);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// 隐私模式 / 跨域等限制下忽略,不影响数据加载。
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const nav = {
|
|
237
|
+
pathname: '',
|
|
238
|
+
navigate: (url) => {
|
|
239
|
+
// 仅解析参数并回填地址栏,不做真实跳转。
|
|
240
|
+
pending = parseParams(url);
|
|
241
|
+
writeUrl(url);
|
|
242
|
+
},
|
|
243
|
+
refresh: () => fetchList(pending),
|
|
244
|
+
};
|
|
245
|
+
// 默认立即加载;惰性页签显式关闭,避免挂载时抢占后端资源,在首次激活时调用 load()。
|
|
246
|
+
if (options.autoLoad !== false)
|
|
247
|
+
void fetchList(pending);
|
|
248
|
+
return {
|
|
249
|
+
get data() {
|
|
250
|
+
return data;
|
|
251
|
+
},
|
|
252
|
+
nav,
|
|
253
|
+
load: () => fetchList(pending),
|
|
254
|
+
reload: () => fetchList(pending),
|
|
255
|
+
get loading() {
|
|
256
|
+
return loading;
|
|
257
|
+
},
|
|
258
|
+
get loaded() {
|
|
259
|
+
return loaded;
|
|
260
|
+
},
|
|
261
|
+
get error() {
|
|
262
|
+
return error;
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @h-ai/ui — CRUD 场景组件
|
|
3
3
|
*
|
|
4
|
-
* 提供声明式 CRUD
|
|
4
|
+
* 提供声明式 CRUD 页面组件与框架级控制器:
|
|
5
|
+
* - `CrudPage` 组件:自动渲染列表、搜索过滤、分页、详情/编辑面板、删除确认,以及查询中/失败/空态。
|
|
6
|
+
* - `defineCrud` / `createCrudController`:纯客户端(无 SvelteKit SSR)下托管数据与导航,
|
|
7
|
+
* 业务应用无需自建 CRUD 适配层。
|
|
5
8
|
* @module crud
|
|
6
9
|
*/
|
|
10
|
+
export * from './crud-controller.svelte.js';
|
|
7
11
|
export type * from './crud-types.js';
|
|
8
12
|
export { default as CrudDeleteConfirm } from './CrudDeleteConfirm.svelte';
|
|
9
13
|
export { default as CrudDetailPanel } from './CrudDetailPanel.svelte';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @h-ai/ui — CRUD 场景组件
|
|
3
3
|
*
|
|
4
|
-
* 提供声明式 CRUD
|
|
4
|
+
* 提供声明式 CRUD 页面组件与框架级控制器:
|
|
5
|
+
* - `CrudPage` 组件:自动渲染列表、搜索过滤、分页、详情/编辑面板、删除确认,以及查询中/失败/空态。
|
|
6
|
+
* - `defineCrud` / `createCrudController`:纯客户端(无 SvelteKit SSR)下托管数据与导航,
|
|
7
|
+
* 业务应用无需自建 CRUD 适配层。
|
|
5
8
|
* @module crud
|
|
6
9
|
*/
|
|
10
|
+
export * from './crud-controller.svelte.js';
|
|
7
11
|
export { default as CrudDeleteConfirm } from './CrudDeleteConfirm.svelte';
|
|
8
12
|
export { default as CrudDetailPanel } from './CrudDetailPanel.svelte';
|
|
9
13
|
export { default as CrudEditPanel } from './CrudEditPanel.svelte';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
export * from './components/index.js';
|
|
9
9
|
// ─── i18n 国际化(Paraglide 辅助工具) ───
|
|
10
10
|
export * from './i18n.svelte.js';
|
|
11
|
+
// ─── 浏览器能力安全辅助(剪贴板、localStorage 等降级方案) ───
|
|
12
|
+
export * from './internal/browser-safety.js';
|
|
11
13
|
// ─── Message 轻量消息提示(类似 ElementUI Message) ───
|
|
12
14
|
export * from './message.svelte.js';
|
|
13
15
|
// ─── MessageBox 命令式弹框(类似 ElementUI MessageBox) ───
|
|
@@ -20,7 +20,8 @@ export declare function writeStoredValue(key: string, value: string): boolean;
|
|
|
20
20
|
/**
|
|
21
21
|
* 安全写入剪贴板。
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* 优先使用现代 Clipboard API;在不安全上下文(HTTP)、旧 WebView
|
|
24
|
+
* 或浏览器策略禁用时回退到 `document.execCommand('copy')`,
|
|
25
|
+
* 确保尽可能多的环境下复制操作可用。
|
|
25
26
|
*/
|
|
26
27
|
export declare function writeTextToClipboard(text: string): Promise<boolean>;
|
|
@@ -41,16 +41,37 @@ export function writeStoredValue(key, value) {
|
|
|
41
41
|
/**
|
|
42
42
|
* 安全写入剪贴板。
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* 优先使用现代 Clipboard API;在不安全上下文(HTTP)、旧 WebView
|
|
45
|
+
* 或浏览器策略禁用时回退到 `document.execCommand('copy')`,
|
|
46
|
+
* 确保尽可能多的环境下复制操作可用。
|
|
46
47
|
*/
|
|
47
48
|
export async function writeTextToClipboard(text) {
|
|
48
|
-
if (typeof navigator === 'undefined'
|
|
49
|
+
if (typeof navigator === 'undefined') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// 现代 Clipboard API(仅安全上下文可用)
|
|
53
|
+
if (navigator.clipboard?.writeText) {
|
|
54
|
+
try {
|
|
55
|
+
await navigator.clipboard.writeText(text);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Clipboard API 失败时继续尝试 fallback
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// fallback:textarea + execCommand(兼容 HTTP、旧浏览器)
|
|
63
|
+
if (typeof document === 'undefined') {
|
|
49
64
|
return false;
|
|
50
65
|
}
|
|
51
66
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
const textarea = document.createElement('textarea');
|
|
68
|
+
textarea.value = text;
|
|
69
|
+
textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
|
70
|
+
document.body.appendChild(textarea);
|
|
71
|
+
textarea.select();
|
|
72
|
+
const ok = document.execCommand('copy');
|
|
73
|
+
document.body.removeChild(textarea);
|
|
74
|
+
return ok;
|
|
54
75
|
}
|
|
55
76
|
catch {
|
|
56
77
|
return false;
|
package/dist/messages/en-US.json
CHANGED
|
@@ -262,12 +262,14 @@
|
|
|
262
262
|
"crud_edit": "Edit",
|
|
263
263
|
"crud_detail": "Detail",
|
|
264
264
|
"crud_delete": "Delete",
|
|
265
|
+
"crud_load_failed": "Failed to load",
|
|
266
|
+
"crud_retry": "Retry",
|
|
265
267
|
"crud_save": "Save",
|
|
266
268
|
"crud_cancel": "Cancel",
|
|
267
269
|
"crud_close": "Close",
|
|
268
270
|
"crud_search_placeholder": "Search...",
|
|
269
271
|
"crud_no_data": "No data",
|
|
270
|
-
"crud_loading": "
|
|
272
|
+
"crud_loading": "Querying…",
|
|
271
273
|
"crud_delete_title": "Confirm Delete",
|
|
272
274
|
"crud_delete_message": "Are you sure you want to delete this record? This action cannot be undone.",
|
|
273
275
|
"crud_submit_success": "Operation successful",
|
package/dist/messages/zh-CN.json
CHANGED
|
@@ -262,12 +262,14 @@
|
|
|
262
262
|
"crud_edit": "编辑",
|
|
263
263
|
"crud_detail": "详情",
|
|
264
264
|
"crud_delete": "删除",
|
|
265
|
+
"crud_load_failed": "加载失败",
|
|
266
|
+
"crud_retry": "重试",
|
|
265
267
|
"crud_save": "保存",
|
|
266
268
|
"crud_cancel": "取消",
|
|
267
269
|
"crud_close": "关闭",
|
|
268
270
|
"crud_search_placeholder": "搜索...",
|
|
269
271
|
"crud_no_data": "暂无数据",
|
|
270
|
-
"crud_loading": "
|
|
272
|
+
"crud_loading": "查询中…",
|
|
271
273
|
"crud_delete_title": "确认删除",
|
|
272
274
|
"crud_delete_message": "确定要删除此记录吗?此操作不可恢复。",
|
|
273
275
|
"crud_submit_success": "操作成功",
|
package/dist/toast.svelte.d.ts
CHANGED
|
@@ -42,4 +42,50 @@ declare class ToastState {
|
|
|
42
42
|
* Toast 单例
|
|
43
43
|
*/
|
|
44
44
|
export declare const toast: ToastState;
|
|
45
|
+
/**
|
|
46
|
+
* 弹出操作成功提示(右上角)。
|
|
47
|
+
*
|
|
48
|
+
* @description 统一封装 toast 单例,业务应用无需自建 toast 工具文件即可获得一致的成功反馈。
|
|
49
|
+
*
|
|
50
|
+
* @param message - 用户可见的成功文案(已本地化)。
|
|
51
|
+
* @param duration - 可选停留时长(毫秒),默认 3000。
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* notifySuccess(m.submit_submitted())
|
|
55
|
+
*/
|
|
56
|
+
export declare function notifySuccess(message: string, duration?: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* 弹出操作失败提示(右上角)。
|
|
59
|
+
*
|
|
60
|
+
* @description 接受 `Error` 或字符串;`Error` 取其 message,其余回退到 fallback,
|
|
61
|
+
* 避免各业务页面重复实现错误文案兜底逻辑。
|
|
62
|
+
*
|
|
63
|
+
* @param error - 错误对象或已本地化的错误文案。
|
|
64
|
+
* @param fallback - 当 error 非 Error 且为空时使用的兜底文案。
|
|
65
|
+
* @param duration - 可选停留时长(毫秒),默认 5000。
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* notifyError(error, m.submit_failed())
|
|
69
|
+
*/
|
|
70
|
+
export declare function notifyError(error: unknown, fallback?: string, duration?: number): string;
|
|
71
|
+
/**
|
|
72
|
+
* 弹出信息提示(右上角)。
|
|
73
|
+
*
|
|
74
|
+
* @param message - 用户可见文案(已本地化)。
|
|
75
|
+
* @param duration - 可选停留时长(毫秒),默认 3000。
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* notifyInfo(m.common_saved())
|
|
79
|
+
*/
|
|
80
|
+
export declare function notifyInfo(message: string, duration?: number): string;
|
|
81
|
+
/**
|
|
82
|
+
* 弹出警告提示(右上角)。
|
|
83
|
+
*
|
|
84
|
+
* @param message - 用户可见文案(已本地化)。
|
|
85
|
+
* @param duration - 可选停留时长(毫秒),默认 5000。
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* notifyWarning(m.quota_almost_reached())
|
|
89
|
+
*/
|
|
90
|
+
export declare function notifyWarning(message: string, duration?: number): string;
|
|
45
91
|
export {};
|
package/dist/toast.svelte.js
CHANGED
|
@@ -75,3 +75,64 @@ class ToastState {
|
|
|
75
75
|
* Toast 单例
|
|
76
76
|
*/
|
|
77
77
|
export const toast = new ToastState();
|
|
78
|
+
/** 成功提示默认停留时长(毫秒)。 */
|
|
79
|
+
const SUCCESS_DURATION = 3000;
|
|
80
|
+
/** 失败提示默认停留时长(毫秒),比成功略长便于阅读错误信息。 */
|
|
81
|
+
const ERROR_DURATION = 5000;
|
|
82
|
+
/**
|
|
83
|
+
* 弹出操作成功提示(右上角)。
|
|
84
|
+
*
|
|
85
|
+
* @description 统一封装 toast 单例,业务应用无需自建 toast 工具文件即可获得一致的成功反馈。
|
|
86
|
+
*
|
|
87
|
+
* @param message - 用户可见的成功文案(已本地化)。
|
|
88
|
+
* @param duration - 可选停留时长(毫秒),默认 3000。
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* notifySuccess(m.submit_submitted())
|
|
92
|
+
*/
|
|
93
|
+
export function notifySuccess(message, duration = SUCCESS_DURATION) {
|
|
94
|
+
return toast.success(message, duration);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 弹出操作失败提示(右上角)。
|
|
98
|
+
*
|
|
99
|
+
* @description 接受 `Error` 或字符串;`Error` 取其 message,其余回退到 fallback,
|
|
100
|
+
* 避免各业务页面重复实现错误文案兜底逻辑。
|
|
101
|
+
*
|
|
102
|
+
* @param error - 错误对象或已本地化的错误文案。
|
|
103
|
+
* @param fallback - 当 error 非 Error 且为空时使用的兜底文案。
|
|
104
|
+
* @param duration - 可选停留时长(毫秒),默认 5000。
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* notifyError(error, m.submit_failed())
|
|
108
|
+
*/
|
|
109
|
+
export function notifyError(error, fallback = '', duration = ERROR_DURATION) {
|
|
110
|
+
const message = error instanceof Error
|
|
111
|
+
? error.message
|
|
112
|
+
: (typeof error === 'string' ? error : fallback);
|
|
113
|
+
return toast.error(message || fallback, duration);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 弹出信息提示(右上角)。
|
|
117
|
+
*
|
|
118
|
+
* @param message - 用户可见文案(已本地化)。
|
|
119
|
+
* @param duration - 可选停留时长(毫秒),默认 3000。
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* notifyInfo(m.common_saved())
|
|
123
|
+
*/
|
|
124
|
+
export function notifyInfo(message, duration = SUCCESS_DURATION) {
|
|
125
|
+
return toast.info(message, duration);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 弹出警告提示(右上角)。
|
|
129
|
+
*
|
|
130
|
+
* @param message - 用户可见文案(已本地化)。
|
|
131
|
+
* @param duration - 可选停留时长(毫秒),默认 5000。
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* notifyWarning(m.quota_almost_reached())
|
|
135
|
+
*/
|
|
136
|
+
export function notifyWarning(message, duration = ERROR_DURATION) {
|
|
137
|
+
return toast.warning(message, duration);
|
|
138
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -516,6 +516,11 @@ export interface TooltipProps extends DataAttributes {
|
|
|
516
516
|
class?: string;
|
|
517
517
|
/** 触发元素插槽 */
|
|
518
518
|
children?: Snippet;
|
|
519
|
+
/**
|
|
520
|
+
* 启用 portal 模式:提示内容通过 document.body 渲染,
|
|
521
|
+
* 不受父容器 overflow 裁剪。适用于表格、滚动容器等场景。
|
|
522
|
+
*/
|
|
523
|
+
portal?: boolean;
|
|
519
524
|
}
|
|
520
525
|
/**
|
|
521
526
|
* 加载属性
|
|
@@ -666,6 +671,18 @@ export interface TabItem {
|
|
|
666
671
|
/** 图标 */
|
|
667
672
|
icon?: string;
|
|
668
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* 标签页内容上下文
|
|
676
|
+
*
|
|
677
|
+
* 传给 `Tabs` 的 `children` 插槽,支持惰性挂载:只渲染已激活过的页签内容,
|
|
678
|
+
* 不预加载不可见页签。
|
|
679
|
+
*/
|
|
680
|
+
export interface TabsContentContext {
|
|
681
|
+
/** 当前激活的标签 key */
|
|
682
|
+
active: string;
|
|
683
|
+
/** 指定标签是否已被激活过(`lazy` 为 false 时恒为 true) */
|
|
684
|
+
isActivated: (key: string) => boolean;
|
|
685
|
+
}
|
|
669
686
|
/**
|
|
670
687
|
* 标签页属性
|
|
671
688
|
*/
|
|
@@ -682,8 +699,12 @@ export interface TabsProps extends DataAttributes {
|
|
|
682
699
|
class?: string;
|
|
683
700
|
/** 变化事件 */
|
|
684
701
|
onchange?: (key: string) => void;
|
|
685
|
-
/**
|
|
686
|
-
|
|
702
|
+
/** 页签首次激活时触发一次;用于惰性加载对应内容(如调用控制器的 load) */
|
|
703
|
+
onactivate?: (key: string) => void;
|
|
704
|
+
/** 是否惰性挂载页签内容:为 true 时未激活过的页签不渲染,`isActivated` 据此判断 */
|
|
705
|
+
lazy?: boolean;
|
|
706
|
+
/** 内容插槽;参数提供当前激活项与惰性挂载判断 */
|
|
707
|
+
children?: Snippet<[TabsContentContext]>;
|
|
687
708
|
}
|
|
688
709
|
/**
|
|
689
710
|
* 头像属性
|
|
@@ -727,8 +748,8 @@ export interface DropdownItem {
|
|
|
727
748
|
export interface DropdownProps extends DataAttributes {
|
|
728
749
|
/** 菜单项 */
|
|
729
750
|
items: DropdownItem[];
|
|
730
|
-
/**
|
|
731
|
-
trigger?: 'click' | 'hover';
|
|
751
|
+
/** 触发方式;`manual` 由外部完全控制 open 状态。 */
|
|
752
|
+
trigger?: 'click' | 'hover' | 'manual';
|
|
732
753
|
/** 位置 */
|
|
733
754
|
position?: Position;
|
|
734
755
|
/** 对齐 */
|
|
@@ -977,6 +998,8 @@ export interface PopoverProps extends DataAttributes {
|
|
|
977
998
|
trigger?: 'click' | 'hover';
|
|
978
999
|
/** 偏移量 */
|
|
979
1000
|
offset?: number;
|
|
1001
|
+
/** 是否将内容挂到 body,以逃逸祖先 overflow 和堆叠上下文。 */
|
|
1002
|
+
portal?: boolean;
|
|
980
1003
|
/** 自定义类名 */
|
|
981
1004
|
class?: string;
|
|
982
1005
|
/** 打开事件 */
|
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.50",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"mermaid": "^11.12.0",
|
|
53
53
|
"shiki": "^4.0.2",
|
|
54
54
|
"tailwind-merge": "^3.5.0",
|
|
55
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
55
|
+
"@h-ai/core": "0.1.0-alpha.50"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@sveltejs/package": "^2.5.7",
|