@ticatec/uniface-element 5.0.3 → 5.0.5
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 +10 -10
- package/README_CN.md +13 -12
- package/dist/base-calendar/DateContext.d.ts +1 -0
- package/dist/base-calendar/DateContext.js +5 -8
- package/dist/composite/pagination_panel/PaginationPanel.svelte +5 -2
- package/dist/composite/transfer/Transfer.svelte +0 -1
- package/dist/data_display/card/Card.svelte +12 -3
- package/dist/data_display/card/Card.svelte.d.ts +6 -0
- package/dist/data_display/card/CardAction.d.ts +8 -0
- package/dist/data_display/card/CardHeader.svelte +12 -1
- package/dist/data_display/card/CommonCardActionBar.svelte +31 -3
- package/dist/data_display/card/CommonCardActionBar.svelte.d.ts +5 -0
- package/dist/data_display/card/README.md +18 -1
- package/dist/data_display/card/README_CN.md +18 -1
- package/dist/data_table/DataTable.svelte +31 -1
- package/dist/data_table/DataTable.svelte.d.ts +7 -0
- package/dist/data_table/lib/virtualWindow.d.ts +37 -0
- package/dist/data_table/lib/virtualWindow.js +41 -0
- package/dist/data_table/parts/ActionsPanel.svelte +17 -2
- package/dist/data_table/parts/ActionsPanel.svelte.d.ts +3 -0
- package/dist/data_table/parts/ContentPanel.svelte +26 -1
- package/dist/data_table/parts/ContentPanel.svelte.d.ts +6 -1
- package/dist/data_table/parts/FixedColumnsPanel.svelte +17 -1
- package/dist/data_table/parts/FixedColumnsPanel.svelte.d.ts +4 -1
- package/dist/data_table/parts/FixedHeaderPanel.svelte +1 -2
- package/dist/form/attachment_files/FileUploadBar.svelte +3 -3
- package/dist/form/attachment_files/FileUploadPanel.svelte +1 -1
- package/dist/form/color_picker/ColorPickerPanel.svelte +1 -1
- package/dist/form/date_picker/DateTimePicker.svelte +1 -1
- package/dist/form/options_select/OptionsSelect.svelte +2 -2
- package/dist/i18nRes/i18nRes.d.ts +41 -2
- package/dist/i18nRes/i18nRes.js +2 -1
- package/dist/overlay/dialog/CommonDialog.svelte +1 -1
- package/dist/overlay/dialog/Dialog.svelte +1 -1
- package/dist/overlay/message-box/MessageBox.svelte.js +3 -3
- package/package.json +1 -1
- package/dist/layout/flex/FlexBlock.svelte +0 -17
- package/dist/layout/flex/FlexBlock.svelte.d.ts +0 -38
- package/dist/layout/flex/README.md +0 -148
- package/dist/layout/flex/README_CN.md +0 -148
- package/dist/layout/flex/index.d.ts +0 -2
- package/dist/layout/flex/index.js +0 -2
- package/dist/layout/index.d.ts +0 -0
- package/dist/layout/index.js +0 -1
package/README.md
CHANGED
|
@@ -185,9 +185,9 @@ The library uses CSS custom properties for theming. You can customize the appear
|
|
|
185
185
|
|
|
186
186
|
The library includes built-in internationalization support:
|
|
187
187
|
|
|
188
|
-
default language is
|
|
188
|
+
The default language is English. Other languages are loaded dynamically at runtime, so adding a new language never requires a code change.
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
Create a language resource json file in the static web directory, example: `/assets/uniface_zh-CN.json` (this is the complete key list):
|
|
191
191
|
```json
|
|
192
192
|
{
|
|
193
193
|
"uniface": {
|
|
@@ -195,7 +195,9 @@ create a language resource json file in the static web directory, example: `/ass
|
|
|
195
195
|
"btnClose": "关闭",
|
|
196
196
|
"btnCancel": "取消",
|
|
197
197
|
"btnConfirm": "确定",
|
|
198
|
-
"textMore": "加载更多"
|
|
198
|
+
"textMore": "加载更多",
|
|
199
|
+
"filterPlaceholder": "搜索...",
|
|
200
|
+
"noResultsText": "无匹配结果..."
|
|
199
201
|
},
|
|
200
202
|
"colorPicker": "选择颜色",
|
|
201
203
|
"calendar": {
|
|
@@ -218,14 +220,12 @@ create a language resource json file in the static web directory, example: `/ass
|
|
|
218
220
|
"btnRemove": "删除",
|
|
219
221
|
"btnPickup": "选择文件"
|
|
220
222
|
},
|
|
221
|
-
"propertyEditor": {
|
|
222
|
-
"colName": "属性",
|
|
223
|
-
"colValue": "值"
|
|
224
|
-
},
|
|
225
223
|
"dataTable": {
|
|
226
224
|
"rowNo": "#",
|
|
227
225
|
"actions": "操作",
|
|
228
|
-
"emptyDataSet": "暂无数据"
|
|
226
|
+
"emptyDataSet": "暂无数据",
|
|
227
|
+
"totalInfo": "共 <b>{{total}}</b> 条",
|
|
228
|
+
"rowsPerPage": "行/页"
|
|
229
229
|
},
|
|
230
230
|
"transfer": {
|
|
231
231
|
"selectIndicator": "已选择: {{selected}}/{{total}}"
|
|
@@ -234,14 +234,14 @@ create a language resource json file in the static web directory, example: `/ass
|
|
|
234
234
|
}
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
Load the resource file in `+page.svelte` or another startup page:
|
|
238
238
|
|
|
239
239
|
```typescript
|
|
240
240
|
import i18n, {i18nUtils} from "@ticatec/i18n";
|
|
241
241
|
|
|
242
242
|
// 设置语言
|
|
243
243
|
i18n.setLanguage('zh-CN'); // 或 'en'
|
|
244
|
-
i18nUtils.loadResources('/assets/uniface.json'); //the language code will insert before .json
|
|
244
|
+
i18nUtils.loadResources('/assets/uniface.json'); // the language code will insert before .json => /assets/uniface_zh-CN.json
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
## Available Imports
|
package/README_CN.md
CHANGED
|
@@ -261,10 +261,11 @@ await messageBox.showInfo("操作完成", { title: "提示" });
|
|
|
261
261
|
|
|
262
262
|
## 国际化
|
|
263
263
|
|
|
264
|
-
|
|
264
|
+
组件库内置国际化支持:
|
|
265
265
|
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
默认语言为英文。其他语言在运行时动态加载,未来新增语言无需修改代码。
|
|
267
|
+
|
|
268
|
+
在静态资源目录下创建语言资源 json 文件,例如 `/assets/uniface_zh-CN.json`(以下为完整键列表):
|
|
268
269
|
```json
|
|
269
270
|
{
|
|
270
271
|
"uniface": {
|
|
@@ -272,7 +273,9 @@ await messageBox.showInfo("操作完成", { title: "提示" });
|
|
|
272
273
|
"btnClose": "关闭",
|
|
273
274
|
"btnCancel": "取消",
|
|
274
275
|
"btnConfirm": "确定",
|
|
275
|
-
"textMore": "加载更多"
|
|
276
|
+
"textMore": "加载更多",
|
|
277
|
+
"filterPlaceholder": "搜索...",
|
|
278
|
+
"noResultsText": "无匹配结果..."
|
|
276
279
|
},
|
|
277
280
|
"colorPicker": "选择颜色",
|
|
278
281
|
"calendar": {
|
|
@@ -295,14 +298,12 @@ await messageBox.showInfo("操作完成", { title: "提示" });
|
|
|
295
298
|
"btnRemove": "删除",
|
|
296
299
|
"btnPickup": "选择文件"
|
|
297
300
|
},
|
|
298
|
-
"propertyEditor": {
|
|
299
|
-
"colName": "属性",
|
|
300
|
-
"colValue": "值"
|
|
301
|
-
},
|
|
302
301
|
"dataTable": {
|
|
303
302
|
"rowNo": "#",
|
|
304
303
|
"actions": "操作",
|
|
305
|
-
"emptyDataSet": "暂无数据"
|
|
304
|
+
"emptyDataSet": "暂无数据",
|
|
305
|
+
"totalInfo": "共 <b>{{total}}</b> 条",
|
|
306
|
+
"rowsPerPage": "行/页"
|
|
306
307
|
},
|
|
307
308
|
"transfer": {
|
|
308
309
|
"selectIndicator": "已选择: {{selected}}/{{total}}"
|
|
@@ -311,16 +312,16 @@ await messageBox.showInfo("操作完成", { title: "提示" });
|
|
|
311
312
|
}
|
|
312
313
|
```
|
|
313
314
|
|
|
315
|
+
在 `+page.svelte` 或其他启动页加载资源文件:
|
|
316
|
+
|
|
314
317
|
```typescript
|
|
315
318
|
import i18n, {i18nUtils} from "@ticatec/i18n";
|
|
316
319
|
|
|
317
320
|
// 设置语言
|
|
318
321
|
i18n.setLanguage('zh-CN'); // 或 'en'
|
|
319
|
-
i18nUtils.loadResources('/assets/uniface.json');
|
|
322
|
+
i18nUtils.loadResources('/assets/uniface.json'); // 语言代码会插入到 .json 之前 => /assets/uniface_zh-CN.json
|
|
320
323
|
```
|
|
321
324
|
|
|
322
|
-
|
|
323
|
-
|
|
324
325
|
## 可用导入
|
|
325
326
|
|
|
326
327
|
### 模块化导入
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import i18nRes from "../i18nRes";
|
|
2
1
|
import i18n from "@ticatec/i18n";
|
|
2
|
+
import "../i18nRes"; // side effect: seeds the uniface namespace defaults used by i18n.get() below
|
|
3
3
|
export default class DateContext {
|
|
4
4
|
_weekBegin = 1;
|
|
5
5
|
_dateFmt = "YYYY-MM-DD";
|
|
@@ -14,19 +14,16 @@ export default class DateContext {
|
|
|
14
14
|
return DateContext.instance;
|
|
15
15
|
}
|
|
16
16
|
get months() {
|
|
17
|
-
return
|
|
17
|
+
return i18n.get('uniface.calendar.months');
|
|
18
18
|
}
|
|
19
19
|
get monthsAbbr() {
|
|
20
|
-
return
|
|
21
|
-
;
|
|
20
|
+
return i18n.get('uniface.calendar.monthsAbbr');
|
|
22
21
|
}
|
|
23
22
|
get weekTitle() {
|
|
24
|
-
return
|
|
25
|
-
;
|
|
23
|
+
return i18n.get('uniface.calendar.weekTitle');
|
|
26
24
|
}
|
|
27
25
|
get weekTitleAbbr() {
|
|
28
|
-
return
|
|
29
|
-
;
|
|
26
|
+
return i18n.get('uniface.calendar.weekTitleAbbr');
|
|
30
27
|
}
|
|
31
28
|
set weekBegin(value) {
|
|
32
29
|
if (value < 0 || value > 6) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Pagination from "../../navigation/pagination";
|
|
3
3
|
import OptionsSelect from "../../form/options_select";
|
|
4
|
+
import i18nRes from "../../i18nRes";
|
|
4
5
|
import type {GenerateTotalInfo, OnPageChange, OnPageSizeChange} from "../../types";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
total = 0,
|
|
30
31
|
generateInfo,
|
|
31
32
|
rowCount = $bindable(25),
|
|
32
|
-
rowCountLabel =
|
|
33
|
+
rowCountLabel = i18nRes.dataTable.rowsPerPage(),
|
|
33
34
|
align = '',
|
|
34
35
|
onPageSizeChange,
|
|
35
36
|
onchange,
|
|
@@ -40,10 +41,12 @@
|
|
|
40
41
|
);
|
|
41
42
|
// Rendered via {@html} below: generateInfo is a trusted, caller-supplied
|
|
42
43
|
// renderer (not user input) — same opt-in-HTML boundary as DataColumn#renderHTML.
|
|
44
|
+
// The default text comes from the i18n resources (dataTable.totalInfo), so it
|
|
45
|
+
// follows the active language instead of being hardcoded English.
|
|
43
46
|
let totalText = $derived(
|
|
44
47
|
generateInfo
|
|
45
48
|
? generateInfo(total, pageCount, pageNo, rowCount)
|
|
46
|
-
:
|
|
49
|
+
: i18nRes.dataTable.totalInfo({total})
|
|
47
50
|
);
|
|
48
51
|
|
|
49
52
|
const handleRowCountChange = () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type {Snippet} from 'svelte';
|
|
3
3
|
import type CardAction from "./CardAction";
|
|
4
|
+
import type {CardActionsAlign} from "./CardAction";
|
|
4
5
|
import CommonCardActionBar from "./CommonCardActionBar.svelte";
|
|
5
6
|
import CardHeader from "./CardHeader.svelte";
|
|
6
7
|
|
|
@@ -13,6 +14,11 @@
|
|
|
13
14
|
headerBar?: Snippet;
|
|
14
15
|
children?: Snippet;
|
|
15
16
|
theme?: string;
|
|
17
|
+
/** 操作栏图标排布方式,默认 'distribute'(按数量平分宽度、每个居中,不会被拉到两端) */
|
|
18
|
+
actionsAlign?: CardActionsAlign;
|
|
19
|
+
/** 'center'/'left'/'right' 模式下相邻图标的固定间距,例如 '16px' */
|
|
20
|
+
actionsGap?: string;
|
|
21
|
+
contentStyle?: string;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
let {
|
|
@@ -23,7 +29,10 @@
|
|
|
23
29
|
data = null,
|
|
24
30
|
headerBar,
|
|
25
31
|
children,
|
|
26
|
-
theme = ""
|
|
32
|
+
theme = "",
|
|
33
|
+
actionsAlign = 'distribute',
|
|
34
|
+
actionsGap,
|
|
35
|
+
contentStyle = ''
|
|
27
36
|
}: Props = $props();
|
|
28
37
|
</script>
|
|
29
38
|
|
|
@@ -38,13 +47,13 @@
|
|
|
38
47
|
{@render headerBar()}
|
|
39
48
|
</div>
|
|
40
49
|
{/if}
|
|
41
|
-
<div class="card-content">
|
|
50
|
+
<div class="card-content" style={contentStyle}>
|
|
42
51
|
{#if children}
|
|
43
52
|
{@render children()}
|
|
44
53
|
{/if}
|
|
45
54
|
</div>
|
|
46
55
|
{#if actions && actions.length > 0}
|
|
47
|
-
<CommonCardActionBar {actions} {data}/>
|
|
56
|
+
<CommonCardActionBar {actions} {data} align={actionsAlign} gap={actionsGap}/>
|
|
48
57
|
{/if}
|
|
49
58
|
</div>
|
|
50
59
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type CardAction from "./CardAction";
|
|
3
|
+
import type { CardActionsAlign } from "./CardAction";
|
|
3
4
|
interface Props {
|
|
4
5
|
style?: string;
|
|
5
6
|
title?: string;
|
|
@@ -9,6 +10,11 @@ interface Props {
|
|
|
9
10
|
headerBar?: Snippet;
|
|
10
11
|
children?: Snippet;
|
|
11
12
|
theme?: string;
|
|
13
|
+
/** 操作栏图标排布方式,默认 'distribute'(按数量平分宽度、每个居中,不会被拉到两端) */
|
|
14
|
+
actionsAlign?: CardActionsAlign;
|
|
15
|
+
/** 'center'/'left'/'right' 模式下相邻图标的固定间距,例如 '16px' */
|
|
16
|
+
actionsGap?: string;
|
|
17
|
+
contentStyle?: string;
|
|
12
18
|
}
|
|
13
19
|
declare const Card: import("svelte").Component<Props, {}, "">;
|
|
14
20
|
type Card = ReturnType<typeof Card>;
|
|
@@ -21,3 +21,11 @@ export default interface CardAction {
|
|
|
21
21
|
*/
|
|
22
22
|
callback: ActionCallback;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 操作栏图标的排布方式:
|
|
26
|
+
* - distribute(默认):按图标数量平分整行宽度,每个图标在自己的等分格子里居中
|
|
27
|
+
* (等价于 justify-content: space-around;数量少时不会被拉到两端)
|
|
28
|
+
* - center / left / right:固定间距(见 CommonCardActionBar 的 gap),整组图标
|
|
29
|
+
* 在操作栏里居中 / 左对齐 / 右对齐
|
|
30
|
+
*/
|
|
31
|
+
export type CardActionsAlign = 'distribute' | 'center' | 'left' | 'right';
|
|
@@ -6,4 +6,15 @@
|
|
|
6
6
|
let {title}: Props = $props();
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
|
-
<span>{title}</span>
|
|
9
|
+
<span class="card-header-title" title={title}>{title}</span>
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.card-header-title {
|
|
13
|
+
display: block;
|
|
14
|
+
flex: 1 1 auto;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
text-overflow: ellipsis;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type CardAction from "./CardAction";
|
|
3
|
+
import type {CardActionsAlign} from "./CardAction";
|
|
3
4
|
import utils from "../../overlay/common/utils";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -7,15 +8,23 @@
|
|
|
7
8
|
color?: string | null;
|
|
8
9
|
data?: any;
|
|
9
10
|
theme?: string;
|
|
11
|
+
/** 图标排布方式,默认 'distribute'(按数量平分宽度、每个居中) */
|
|
12
|
+
align?: CardActionsAlign;
|
|
13
|
+
/** 'center'/'left'/'right' 模式下相邻图标的固定间距;不设置则用 --uniface-spacing-actionbar */
|
|
14
|
+
gap?: string;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
let {
|
|
13
18
|
actions,
|
|
14
19
|
color = null,
|
|
15
20
|
data,
|
|
16
|
-
theme = ""
|
|
21
|
+
theme = "",
|
|
22
|
+
align = 'distribute',
|
|
23
|
+
gap
|
|
17
24
|
}: Props = $props();
|
|
18
25
|
|
|
26
|
+
let barStyle = $derived(gap ? `--card-action-gap: ${gap}` : '');
|
|
27
|
+
|
|
19
28
|
let clickEnable = $state(true);
|
|
20
29
|
|
|
21
30
|
const handleActionClick = (action: CardAction) => async (e: MouseEvent) => {
|
|
@@ -35,7 +44,7 @@
|
|
|
35
44
|
};
|
|
36
45
|
</script>
|
|
37
46
|
|
|
38
|
-
<div class="card-action-bar">
|
|
47
|
+
<div class="card-action-bar align-{align}" style={barStyle}>
|
|
39
48
|
{#each actions as action}
|
|
40
49
|
{#if action.icon}
|
|
41
50
|
<i class="action-icon {action.icon}"
|
|
@@ -69,13 +78,32 @@
|
|
|
69
78
|
flex-direction: row;
|
|
70
79
|
width: 100%;
|
|
71
80
|
align-items: center;
|
|
72
|
-
justify-content: space-between;
|
|
73
81
|
text-align: center;
|
|
74
82
|
font-size: var(--uniface-card-bar-font-size);
|
|
75
83
|
border-top: 1px solid var(--uniface-card-color-inside-border);
|
|
76
84
|
color: var(--uniface-card-color-action-bar);
|
|
77
85
|
}
|
|
78
86
|
|
|
87
|
+
/* 按数量平分整行宽度,每个图标在自己的等分格子里居中 — 数量少时不会被拉到两端 */
|
|
88
|
+
.card-action-bar.align-distribute {
|
|
89
|
+
justify-content: space-around;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.card-action-bar.align-center {
|
|
93
|
+
justify-content: center;
|
|
94
|
+
gap: var(--card-action-gap, var(--uniface-spacing-actionbar, 8px));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.card-action-bar.align-left {
|
|
98
|
+
justify-content: flex-start;
|
|
99
|
+
gap: var(--card-action-gap, var(--uniface-spacing-actionbar, 8px));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.card-action-bar.align-right {
|
|
103
|
+
justify-content: flex-end;
|
|
104
|
+
gap: var(--card-action-gap, var(--uniface-spacing-actionbar, 8px));
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
.action-icon {
|
|
80
108
|
display: block;
|
|
81
109
|
cursor: pointer;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type CardAction from "./CardAction";
|
|
2
|
+
import type { CardActionsAlign } from "./CardAction";
|
|
2
3
|
interface Props {
|
|
3
4
|
actions: Array<CardAction>;
|
|
4
5
|
color?: string | null;
|
|
5
6
|
data?: any;
|
|
6
7
|
theme?: string;
|
|
8
|
+
/** 图标排布方式,默认 'distribute'(按数量平分宽度、每个居中) */
|
|
9
|
+
align?: CardActionsAlign;
|
|
10
|
+
/** 'center'/'left'/'right' 模式下相邻图标的固定间距;不设置则用 --uniface-spacing-actionbar */
|
|
11
|
+
gap?: string;
|
|
7
12
|
}
|
|
8
13
|
declare const CommonCardActionBar: import("svelte").Component<Props, {}, "">;
|
|
9
14
|
type CommonCardActionBar = ReturnType<typeof CommonCardActionBar>;
|
|
@@ -23,12 +23,15 @@ npm install @ticatec/uniface-element
|
|
|
23
23
|
|
|
24
24
|
| Prop | Type | Default | Description |
|
|
25
25
|
|------|------|---------|-------------|
|
|
26
|
-
| `title` | `string` | `''` | Plain-text title rendered by `CardHeader`. If empty and `headerBar` is provided, the custom snippet is used instead |
|
|
26
|
+
| `title` | `string` | `''` | Plain-text title rendered by `CardHeader`. Single-line, truncated with an ellipsis (native tooltip shows the full text) when it overflows. If empty and `headerBar` is provided, the custom snippet is used instead |
|
|
27
27
|
| `headerBar` | `Snippet` | `undefined` | Custom header content (takes over when `title` is empty) |
|
|
28
28
|
| `actions` | `CardAction[]` | `[]` | Action bar items rendered at the bottom. Empty array → no footer |
|
|
29
29
|
| `data` | `any` | `null` | Passed as the argument to each action's `callback(data)` |
|
|
30
|
+
| `actionsAlign` | `'distribute' \| 'center' \| 'left' \| 'right'` | `'distribute'` | How action icons are laid out — see below |
|
|
31
|
+
| `actionsGap` | `string` | `undefined` | Fixed spacing between icons for `'center'`/`'left'`/`'right'` (e.g. `'16px'`); falls back to `--uniface-spacing-actionbar` |
|
|
30
32
|
| `variant` | `'plain' \| '3d'` | `'plain'` | `'3d'` adds a box-shadow to lift the card |
|
|
31
33
|
| `style` | `string` | `''` | Inline CSS on the root |
|
|
34
|
+
| `contentStyle` | `string` | `''` | Inline CSS on the body wrapper (`.card-content`) |
|
|
32
35
|
| `theme` | `string` | `''` | Theme value, exposed as `data-theme` on the root for scoped CSS |
|
|
33
36
|
| `children` | `Snippet` | `undefined` | Card body |
|
|
34
37
|
|
|
@@ -92,6 +95,19 @@ interface CardAction {
|
|
|
92
95
|
</Card>
|
|
93
96
|
```
|
|
94
97
|
|
|
98
|
+
### Action bar layout
|
|
99
|
+
|
|
100
|
+
With few actions, `justify-content: space-between` (the old, fixed behavior) pushes them to opposite edges — two icons end up one at the far left, one at the far right. `actionsAlign` controls this:
|
|
101
|
+
|
|
102
|
+
- `'distribute'` (default) — the row is split into as many equal-width slots as there are actions, each icon centered in its own slot. Equivalent to `justify-content: space-around`; icons never get pushed all the way to the edges regardless of count.
|
|
103
|
+
- `'center'` / `'left'` / `'right'` — icons keep a fixed gap (`actionsGap`, default `--uniface-spacing-actionbar`) and the whole group is aligned as a block within the footer.
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<Card title="Item Details" {actions} data={cardData} actionsAlign="center" actionsGap="24px">
|
|
107
|
+
...
|
|
108
|
+
</Card>
|
|
109
|
+
```
|
|
110
|
+
|
|
95
111
|
## Styling
|
|
96
112
|
|
|
97
113
|
### Card body
|
|
@@ -118,6 +134,7 @@ interface CardAction {
|
|
|
118
134
|
| `--uniface-card-color-action-bar-hover` | Icon hover color |
|
|
119
135
|
| `--uniface-card-color-action-bar-disabled` | Icon disabled color |
|
|
120
136
|
| `--action-image-size` / `--uniface-action-image-size` | Per-card override for action icon/image size |
|
|
137
|
+
| `--uniface-spacing-actionbar` | Default `actionsGap` for `'center'`/`'left'`/`'right'` (falls back to `8px`) |
|
|
121
138
|
|
|
122
139
|
## Variants
|
|
123
140
|
|
|
@@ -23,12 +23,15 @@ npm install @ticatec/uniface-element
|
|
|
23
23
|
|
|
24
24
|
| 属性 | 类型 | 默认值 | 描述 |
|
|
25
25
|
|------|------|--------|------|
|
|
26
|
-
| `title` | `string` | `''` | 纯文本标题,由 `CardHeader`
|
|
26
|
+
| `title` | `string` | `''` | 纯文本标题,由 `CardHeader` 渲染。默认单行显示,超出宽度会用省略号截断(原生 tooltip 显示完整文字)。为空且传了 `headerBar` 时改用自定义 snippet |
|
|
27
27
|
| `headerBar` | `Snippet` | `undefined` | 自定义标题内容(`title` 为空时生效) |
|
|
28
28
|
| `actions` | `CardAction[]` | `[]` | 底部操作栏。空数组 → 不渲染底栏 |
|
|
29
29
|
| `data` | `any` | `null` | 作为参数传给每个 action 的 `callback(data)` |
|
|
30
|
+
| `actionsAlign` | `'distribute' \| 'center' \| 'left' \| 'right'` | `'distribute'` | 操作图标的排布方式,见下文 |
|
|
31
|
+
| `actionsGap` | `string` | `undefined` | `'center'`/`'left'`/`'right'` 模式下图标之间的固定间距(比如 `'16px'`),不设则回退到 `--uniface-spacing-actionbar` |
|
|
30
32
|
| `variant` | `'plain' \| '3d'` | `'plain'` | `'3d'` 加盒阴影让卡片浮起来 |
|
|
31
33
|
| `style` | `string` | `''` | 根节点的内联 CSS |
|
|
34
|
+
| `contentStyle` | `string` | `''` | 内容区域包裹层(`.card-content`)的内联 CSS |
|
|
32
35
|
| `theme` | `string` | `''` | 主题值,作为 `data-theme` 写到根节点上方便做作用域 CSS |
|
|
33
36
|
| `children` | `Snippet` | `undefined` | 卡片内容体 |
|
|
34
37
|
|
|
@@ -92,6 +95,19 @@ interface CardAction {
|
|
|
92
95
|
</Card>
|
|
93
96
|
```
|
|
94
97
|
|
|
98
|
+
### 操作栏排布方式
|
|
99
|
+
|
|
100
|
+
当图标数量比较少时,`justify-content: space-between`(旧的固定行为)会把它们推到两端 —— 两个图标就变成一个在最左,一个在最右。`actionsAlign` 用来控制这个行为:
|
|
101
|
+
|
|
102
|
+
- `'distribute'`(默认)—— 把整行按图标数量平分成若干等宽格子,每个图标在自己的格子里居中。等价于 `justify-content: space-around`;不管图标数量多少,都不会被拉到两端。
|
|
103
|
+
- `'center'` / `'left'` / `'right'` —— 图标之间保持固定间距(`actionsGap`,默认回退到 `--uniface-spacing-actionbar`),整组图标作为一个整体在底栏里居中 / 左对齐 / 右对齐。
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<Card title="项目详情" {actions} data={cardData} actionsAlign="center" actionsGap="24px">
|
|
107
|
+
...
|
|
108
|
+
</Card>
|
|
109
|
+
```
|
|
110
|
+
|
|
95
111
|
## 样式定制
|
|
96
112
|
|
|
97
113
|
### 卡片本体
|
|
@@ -118,6 +134,7 @@ interface CardAction {
|
|
|
118
134
|
| `--uniface-card-color-action-bar-hover` | 图标悬停颜色 |
|
|
119
135
|
| `--uniface-card-color-action-bar-disabled` | 图标禁用颜色 |
|
|
120
136
|
| `--action-image-size` / `--uniface-action-image-size` | 单卡片覆盖操作图标/图片的尺寸 |
|
|
137
|
+
| `--uniface-spacing-actionbar` | `'center'`/`'left'`/`'right'` 模式下 `actionsGap` 的默认值(缺省回退到 `8px`) |
|
|
121
138
|
|
|
122
139
|
## variant
|
|
123
140
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { OrderDirection } from "./lib/OrderDirection";
|
|
12
12
|
import type { CompareFunction } from "./lib/CompareFunction";
|
|
13
13
|
import type { GetRowFontStyle } from "../types";
|
|
14
|
+
import { computeVirtualWindow } from "./lib/virtualWindow";
|
|
14
15
|
|
|
15
16
|
interface Props {
|
|
16
17
|
columns: Array<DataColumn>;
|
|
@@ -26,6 +27,13 @@
|
|
|
26
27
|
emptyIndicator?: string;
|
|
27
28
|
getRowFontStyle?: GetRowFontStyle<T>;
|
|
28
29
|
style?: string;
|
|
30
|
+
/**
|
|
31
|
+
* 行虚拟化(窗口化渲染):仅渲染可视区域附近的行,大数据集(数千行以上)
|
|
32
|
+
* 建议开启。要求所有行高度一致(即统一的 rowHeight),因此默认关闭。
|
|
33
|
+
*/
|
|
34
|
+
virtual?: boolean;
|
|
35
|
+
/** 虚拟化时可视区上下额外渲染的行数。 */
|
|
36
|
+
overscan?: number;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
let {
|
|
@@ -42,6 +50,8 @@
|
|
|
42
50
|
emptyIndicator = undefined,
|
|
43
51
|
getRowFontStyle = undefined,
|
|
44
52
|
style = '',
|
|
53
|
+
virtual = false,
|
|
54
|
+
overscan = 8,
|
|
45
55
|
}: Props = $props();
|
|
46
56
|
|
|
47
57
|
const id: string = Math.round(new Date().getTime() + Math.random() * 1000).toString(36);
|
|
@@ -59,6 +69,23 @@
|
|
|
59
69
|
let scrollTop = $state(0);
|
|
60
70
|
let inlineComponent = $state<any>(null);
|
|
61
71
|
|
|
72
|
+
// Measured by ContentPanel (its scrollable data area); drives the virtual window.
|
|
73
|
+
let viewportHeight = $state(0);
|
|
74
|
+
let expandIndex = $derived(expandRow ? rows.indexOf(expandRow) : -1);
|
|
75
|
+
// One shared window for all three panels (frozen / content / actions), so the
|
|
76
|
+
// slices they render always cover the same absolute row indices.
|
|
77
|
+
let vwindow = $derived(virtual
|
|
78
|
+
? computeVirtualWindow({
|
|
79
|
+
scrollTop,
|
|
80
|
+
viewportHeight,
|
|
81
|
+
rowHeight,
|
|
82
|
+
count: rows.length,
|
|
83
|
+
overscan,
|
|
84
|
+
expandIndex,
|
|
85
|
+
inlineRowHeight
|
|
86
|
+
})
|
|
87
|
+
: null);
|
|
88
|
+
|
|
62
89
|
let orderColumn = $state<DataColumn | null>(null);
|
|
63
90
|
let orderDirection = $state<OrderDirection>(OrderDirection.Ascending);
|
|
64
91
|
|
|
@@ -162,6 +189,7 @@
|
|
|
162
189
|
{handleWidthChange}
|
|
163
190
|
width={frozenWidth}
|
|
164
191
|
bind:scrollTop
|
|
192
|
+
{vwindow}
|
|
165
193
|
/>
|
|
166
194
|
{/if}
|
|
167
195
|
|
|
@@ -185,10 +213,12 @@
|
|
|
185
213
|
displayHorizontalScroll={actionsColumn != null || indicatorColumn != null || fixedCols.length > 0}
|
|
186
214
|
showVerticalScroll={actionsColumn == null}
|
|
187
215
|
{getRowFontStyle}
|
|
216
|
+
{vwindow}
|
|
217
|
+
bind:viewportHeight
|
|
188
218
|
/>
|
|
189
219
|
{/if}
|
|
190
220
|
|
|
191
221
|
{#if actionsColumn}
|
|
192
|
-
<ActionPanel {expandRow} {rowHeight} {headerHeight} {actionsColumn} {rows} {inlineRowHeight} bind:scrollTop />
|
|
222
|
+
<ActionPanel {expandRow} {rowHeight} {headerHeight} {actionsColumn} {rows} {inlineRowHeight} bind:scrollTop {vwindow} />
|
|
193
223
|
{/if}
|
|
194
224
|
</div>
|
|
@@ -17,6 +17,13 @@ declare function $$render<T = any>(): {
|
|
|
17
17
|
emptyIndicator?: string;
|
|
18
18
|
getRowFontStyle?: GetRowFontStyle<T>;
|
|
19
19
|
style?: string;
|
|
20
|
+
/**
|
|
21
|
+
* 行虚拟化(窗口化渲染):仅渲染可视区域附近的行,大数据集(数千行以上)
|
|
22
|
+
* 建议开启。要求所有行高度一致(即统一的 rowHeight),因此默认关闭。
|
|
23
|
+
*/
|
|
24
|
+
virtual?: boolean;
|
|
25
|
+
/** 虚拟化时可视区上下额外渲染的行数。 */
|
|
26
|
+
overscan?: number;
|
|
20
27
|
};
|
|
21
28
|
exports: {};
|
|
22
29
|
bindings: "inlineRowHeight" | "selectedRows";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row-windowing math for the DataTable's opt-in virtual scrolling.
|
|
3
|
+
*
|
|
4
|
+
* The table renders three vertically synced panels (frozen columns, data
|
|
5
|
+
* content, actions). Each panel renders only the rows inside the computed
|
|
6
|
+
* window, with a top and bottom spacer so the scroll container keeps its
|
|
7
|
+
* full natural scrollHeight (the native scrollbar and the existing
|
|
8
|
+
* wheel/scroll sync logic keep working unchanged).
|
|
9
|
+
*
|
|
10
|
+
* All rows share one fixed `rowHeight`; at most one row (`expandIndex`) has
|
|
11
|
+
* an inline expansion panel of `inlineRowHeight` rendered directly below it.
|
|
12
|
+
*/
|
|
13
|
+
export interface VirtualWindow {
|
|
14
|
+
/** Index of the first rendered row (inclusive). */
|
|
15
|
+
start: number;
|
|
16
|
+
/** Index just past the last rendered row (exclusive). */
|
|
17
|
+
end: number;
|
|
18
|
+
/** Height (px) of the spacer above the rendered rows. */
|
|
19
|
+
topPad: number;
|
|
20
|
+
/** Height (px) of the spacer below the rendered rows. */
|
|
21
|
+
bottomPad: number;
|
|
22
|
+
}
|
|
23
|
+
export interface VirtualWindowInput {
|
|
24
|
+
scrollTop: number;
|
|
25
|
+
/** Height (px) of the scrollable viewport. 0 is tolerated (renders overscan only). */
|
|
26
|
+
viewportHeight: number;
|
|
27
|
+
rowHeight: number;
|
|
28
|
+
/** Total number of rows. */
|
|
29
|
+
count: number;
|
|
30
|
+
/** Extra rows rendered above and below the visible range. */
|
|
31
|
+
overscan?: number;
|
|
32
|
+
/** Index of the expanded row, or -1 when none. */
|
|
33
|
+
expandIndex?: number;
|
|
34
|
+
/** Height (px) of the inline expansion panel below the expanded row. */
|
|
35
|
+
inlineRowHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function computeVirtualWindow(input: VirtualWindowInput): VirtualWindow;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row-windowing math for the DataTable's opt-in virtual scrolling.
|
|
3
|
+
*
|
|
4
|
+
* The table renders three vertically synced panels (frozen columns, data
|
|
5
|
+
* content, actions). Each panel renders only the rows inside the computed
|
|
6
|
+
* window, with a top and bottom spacer so the scroll container keeps its
|
|
7
|
+
* full natural scrollHeight (the native scrollbar and the existing
|
|
8
|
+
* wheel/scroll sync logic keep working unchanged).
|
|
9
|
+
*
|
|
10
|
+
* All rows share one fixed `rowHeight`; at most one row (`expandIndex`) has
|
|
11
|
+
* an inline expansion panel of `inlineRowHeight` rendered directly below it.
|
|
12
|
+
*/
|
|
13
|
+
/** Top offset (px) of row `i`, accounting for an expansion panel above it. */
|
|
14
|
+
const rowTop = (i, rowHeight, expandIndex, inlineRowHeight) => i * rowHeight + (expandIndex >= 0 && i > expandIndex ? inlineRowHeight : 0);
|
|
15
|
+
export function computeVirtualWindow(input) {
|
|
16
|
+
const { scrollTop, viewportHeight, rowHeight, count } = input;
|
|
17
|
+
const overscan = input.overscan ?? 8;
|
|
18
|
+
const expandIndex = input.expandIndex ?? -1;
|
|
19
|
+
const inlineRowHeight = expandIndex >= 0 ? (input.inlineRowHeight ?? 0) : 0;
|
|
20
|
+
if (count === 0 || rowHeight <= 0) {
|
|
21
|
+
return { start: 0, end: 0, topPad: 0, bottomPad: 0 };
|
|
22
|
+
}
|
|
23
|
+
const st = Math.max(0, scrollTop);
|
|
24
|
+
// First row whose bottom edge is below the viewport top.
|
|
25
|
+
let start = Math.floor(st / rowHeight);
|
|
26
|
+
if (expandIndex >= 0 && start > expandIndex) {
|
|
27
|
+
// The viewport top sits below the expanded row: shift the estimate by the
|
|
28
|
+
// expansion panel's height. Clamping to expandIndex keeps the expanded
|
|
29
|
+
// row (and with it the inline panel, which is rendered inside the #each
|
|
30
|
+
// body next to its row) in the window while any part of the panel is
|
|
31
|
+
// visible.
|
|
32
|
+
start = Math.max(expandIndex, Math.floor((st - inlineRowHeight) / rowHeight));
|
|
33
|
+
}
|
|
34
|
+
start = Math.max(0, Math.min(start, count - 1) - overscan);
|
|
35
|
+
const visibleCount = Math.ceil(viewportHeight / rowHeight) + 1;
|
|
36
|
+
let end = Math.min(count, start + visibleCount + overscan * 2);
|
|
37
|
+
const totalHeight = count * rowHeight + inlineRowHeight;
|
|
38
|
+
const topPad = rowTop(start, rowHeight, expandIndex, inlineRowHeight);
|
|
39
|
+
const bottomPad = totalHeight - rowTop(end, rowHeight, expandIndex, inlineRowHeight);
|
|
40
|
+
return { start, end, topPad, bottomPad };
|
|
41
|
+
}
|