@ticatec/topside-elements 1.0.6 → 1.1.0
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/CHANGELOG.md +16 -0
- package/dist/data_table/DataTable.svelte +153 -12
- package/dist/data_table/DataTable.svelte.d.ts +22 -2
- package/dist/data_table/README.md +119 -38
- package/dist/data_table/README_CN.md +119 -38
- package/dist/data_table/index.d.ts +5 -0
- package/dist/data_table/index.js +2 -0
- package/dist/data_table/lib/IndicatorColumn.d.ts +2 -1
- package/dist/data_table/lib/RowExpansion.d.ts +84 -0
- package/dist/data_table/lib/RowExpansion.js +43 -0
- package/dist/data_table/parts/ActionsPanel.svelte +4 -0
- package/dist/data_table/parts/ActionsPanel.svelte.d.ts +2 -0
- package/dist/data_table/parts/ActionsRow.svelte +8 -1
- package/dist/data_table/parts/ActionsRow.svelte.d.ts +2 -0
- package/dist/data_table/parts/ContentPanel.svelte +9 -1
- package/dist/data_table/parts/ContentPanel.svelte.d.ts +4 -0
- package/dist/data_table/parts/DataRow.svelte +13 -2
- package/dist/data_table/parts/DataRow.svelte.d.ts +2 -0
- package/dist/data_table/parts/FixedColumnsPanel.svelte +11 -2
- package/dist/data_table/parts/FixedColumnsPanel.svelte.d.ts +6 -1
- package/dist/data_table/parts/FixedRow.svelte +19 -2
- package/dist/data_table/parts/FixedRow.svelte.d.ts +4 -0
- package/docs/components/README.md +1 -1
- package/docs/components/data-table/README.md +53 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
- **DataTable 行展开重做(`rowExpansion` 属性)**:展开区组件、触发方式与控制方式分离。`showToggle`(默认 true)控制指示列的展开图标;`trigger: 'click' | 'dblclick'` 整行单击 / 双击切换(点到按钮、链接、输入框不触发);`isExpandable` 限制可展开行;`onExpandChange` 回调。
|
|
7
|
+
- **组件方法**:`expandRow(data)` / `collapseRow(data?)` / `toggleRowExpand(data)` / `isRowExpanded(data)` / `getExpandedRow()`,操作列按钮、外部按钮都可以借此控制展开。
|
|
8
|
+
- **行内事件请求**:自定义 `render` 单元格 / `ActionCell` / 展开区内部在任意元素上调用 `requestRowExpand(el)` / `requestRowCollapse(el)` / `requestRowToggle(el)`(或自行派发 `ROW_EXPAND_EVENT`),由 DataTable 定位所在行处理,不需要拿到表格实例。
|
|
9
|
+
- **展开区上下文**:展开区组件除 `data` 外还收到 `context: RowExpandContext`——`collapse()` 关闭本行、`actions` 为本行行动作(来自 `actionsColumn.getActions`)、`invoke(label)` 按标签执行行动作,可在展开区里直接做“编辑明细”等操作。
|
|
10
|
+
- 异步 `build` 期间切换到别的行时,过期的结果不再覆盖当前展开行。
|
|
11
|
+
|
|
12
|
+
### 变更
|
|
13
|
+
- `indicatorColumn.buildInlineComponent` 标记为 `@deprecated`,保留兼容(等价于 `rowExpansion = { build }`)。
|
|
14
|
+
|
|
15
|
+
### 测试与文档
|
|
16
|
+
- `topside-test-suite`:新增 `tests/elements/data_table/DataTable.expand.test.ts`(17 个用例);`/test/data/data-table/expand` 演示页扩充为图标 / 双击 + 展开区行动作 / 自定义单元格 + 组件方法三个示例。
|
|
17
|
+
- `data_table/README(_CN).md` 新增「行展开」章节与 `RowExpansion` / `RowExpandContext` 类型说明。
|
|
18
|
+
|
|
3
19
|
## 1.0.6
|
|
4
20
|
|
|
5
21
|
### 变更
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
import { buildTableSummary } from "./lib/summary";
|
|
16
16
|
import { isSameColumn } from "./lib/TableUtils";
|
|
17
17
|
import i18nRes from "../i18nRes";
|
|
18
|
+
import type RowExpansion from "./lib/RowExpansion";
|
|
19
|
+
import {
|
|
20
|
+
type RowExpandContext,
|
|
21
|
+
type RowExpandRequest,
|
|
22
|
+
type RowEventHandlers,
|
|
23
|
+
isInteractiveTarget
|
|
24
|
+
} from "./lib/RowExpansion";
|
|
18
25
|
|
|
19
26
|
interface Props {
|
|
20
27
|
columns: Array<DataColumn>;
|
|
@@ -45,6 +52,13 @@
|
|
|
45
52
|
* 分页场景下用它显示服务端算好的全量合计(数字串会按数字格式化)。
|
|
46
53
|
*/
|
|
47
54
|
summaryValues?: Record<string, any> | null;
|
|
55
|
+
/**
|
|
56
|
+
* 行展开配置:展开区组件、是否显示指示列图标(默认显示)、整行单击 / 双击触发等。
|
|
57
|
+
* 不配置图标时可以用组件方法 expandRow / collapseRow / toggleRowExpand,
|
|
58
|
+
* 或在行内(自定义单元格、操作列、展开区)派发 ROW_EXPAND_EVENT 事件来控制。
|
|
59
|
+
* 同一时间只展开一行。
|
|
60
|
+
*/
|
|
61
|
+
rowExpansion?: RowExpansion<T> | null;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
let {
|
|
@@ -65,6 +79,7 @@
|
|
|
65
79
|
overscan = 8,
|
|
66
80
|
summaryLabel,
|
|
67
81
|
summaryValues = null,
|
|
82
|
+
rowExpansion = null,
|
|
68
83
|
}: Props = $props();
|
|
69
84
|
|
|
70
85
|
const id: string = Math.round(new Date().getTime() + Math.random() * 1000).toString(36);
|
|
@@ -78,13 +93,24 @@
|
|
|
78
93
|
let frozenWidth = $state(0);
|
|
79
94
|
let styleEl: HTMLStyleElement | undefined;
|
|
80
95
|
|
|
81
|
-
let
|
|
96
|
+
let expandedRow = $state<TableRow | null>(null);
|
|
82
97
|
let scrollTop = $state(0);
|
|
83
98
|
let inlineComponent = $state<any>(null);
|
|
84
99
|
|
|
100
|
+
// 有效的行展开配置:rowExpansion 优先,兼容旧的 indicatorColumn.buildInlineComponent
|
|
101
|
+
let expansion = $derived.by<RowExpansion<T> | null>(() => {
|
|
102
|
+
if (rowExpansion) return rowExpansion;
|
|
103
|
+
const legacy = indicatorColumn?.buildInlineComponent;
|
|
104
|
+
return legacy ? { build: legacy } : null;
|
|
105
|
+
});
|
|
106
|
+
let expandable = $derived(expansion != null);
|
|
107
|
+
// 指示列图标:默认显示;需要有 indicatorColumn 才有地方放
|
|
108
|
+
let showExpandToggle = $derived(expandable && indicatorColumn != null && expansion?.showToggle !== false);
|
|
109
|
+
let expandTrigger = $derived(expansion?.trigger ?? null);
|
|
110
|
+
|
|
85
111
|
// Measured by ContentPanel (its scrollable data area); drives the virtual window.
|
|
86
112
|
let viewportHeight = $state(0);
|
|
87
|
-
let expandIndex = $derived(
|
|
113
|
+
let expandIndex = $derived(expandedRow ? rows.indexOf(expandedRow) : -1);
|
|
88
114
|
// One shared window for all three panels (frozen / content / actions), so the
|
|
89
115
|
// slices they render always cover the same absolute row indices.
|
|
90
116
|
let vwindow = $derived(virtual
|
|
@@ -163,7 +189,7 @@
|
|
|
163
189
|
|
|
164
190
|
const sortRows = (compareFun: CompareFn, descending: boolean) => {
|
|
165
191
|
if (!table) return;
|
|
166
|
-
|
|
192
|
+
closeExpanded();
|
|
167
193
|
let oList = list.sort(compareFun);
|
|
168
194
|
table.data = descending ? oList.reverse() : oList;
|
|
169
195
|
rows = table.rows;
|
|
@@ -183,15 +209,125 @@
|
|
|
183
209
|
}
|
|
184
210
|
};
|
|
185
211
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
const isExpandableRow = (row: TableRow): boolean =>
|
|
213
|
+
expandable && (expansion?.isExpandable?.(row.data) ?? true);
|
|
214
|
+
|
|
215
|
+
const closeExpanded = () => {
|
|
216
|
+
if (expandedRow == null) return;
|
|
217
|
+
expandedRow = null;
|
|
218
|
+
inlineComponent = null;
|
|
219
|
+
expansion?.onExpandChange?.(null);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// 异步 build 期间用户可能又点了别的行:只接受最后一次请求的结果
|
|
223
|
+
let expandSeq = 0;
|
|
224
|
+
const openRow = async (row: TableRow) => {
|
|
225
|
+
if (!isExpandableRow(row)) return;
|
|
226
|
+
const seq = ++expandSeq;
|
|
227
|
+
const comp = await expansion!.build(row.data);
|
|
228
|
+
if (seq !== expandSeq) return;
|
|
229
|
+
inlineComponent = comp;
|
|
230
|
+
expandedRow = row;
|
|
231
|
+
expansion?.onExpandChange?.(row.data);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/** 切换某行展开状态;expanded 指定目标状态,不传则切换 */
|
|
235
|
+
const setRowExpanded = async (row: TableRow, expanded?: boolean) => {
|
|
236
|
+
const isOpen = row === expandedRow;
|
|
237
|
+
const target = expanded ?? !isOpen;
|
|
238
|
+
if (target === isOpen) return;
|
|
239
|
+
if (target) {
|
|
240
|
+
await openRow(row);
|
|
190
241
|
} else {
|
|
191
|
-
|
|
192
|
-
|
|
242
|
+
expandSeq++;
|
|
243
|
+
closeExpanded();
|
|
193
244
|
}
|
|
194
245
|
};
|
|
246
|
+
|
|
247
|
+
const onRowExpand = (row: TableRow) => setRowExpanded(row);
|
|
248
|
+
|
|
249
|
+
// 对外方法(bind:this 后调用)用它定位行:按数据对象同一性匹配
|
|
250
|
+
const findRow = (data: T): TableRow | undefined => rows.find(r => r.data === data);
|
|
251
|
+
|
|
252
|
+
/** 展开指定行(按行数据对象匹配);行不存在或不允许展开时不做任何事 */
|
|
253
|
+
export async function expandRow(data: T): Promise<void> {
|
|
254
|
+
const row = findRow(data);
|
|
255
|
+
if (row) await setRowExpanded(row, true);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** 收起指定行;不传参数则收起当前展开的行 */
|
|
259
|
+
export function collapseRow(data?: T): void {
|
|
260
|
+
if (data === undefined) {
|
|
261
|
+
expandSeq++;
|
|
262
|
+
closeExpanded();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const row = findRow(data);
|
|
266
|
+
if (row) void setRowExpanded(row, false);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 切换指定行的展开状态 */
|
|
270
|
+
export async function toggleRowExpand(data: T): Promise<void> {
|
|
271
|
+
const row = findRow(data);
|
|
272
|
+
if (row) await setRowExpanded(row);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** 某行当前是否展开 */
|
|
276
|
+
export function isRowExpanded(data: T): boolean {
|
|
277
|
+
return expandedRow != null && expandedRow.data === data;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** 当前展开行的数据,没有则为 null */
|
|
281
|
+
export function getExpandedRow(): T | null {
|
|
282
|
+
return expandedRow?.data ?? null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---- 行级触发(整行单击 / 双击) ----
|
|
286
|
+
|
|
287
|
+
const handleRowClick = (row: TableRow, e: MouseEvent) => {
|
|
288
|
+
if (expandTrigger !== 'click' || isInteractiveTarget(e)) return;
|
|
289
|
+
void setRowExpanded(row);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const handleRowDblClick = (row: TableRow, e: MouseEvent) => {
|
|
293
|
+
if (expandTrigger !== 'dblclick' || isInteractiveTarget(e)) return;
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
void setRowExpanded(row);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// ---- 行内自定义事件:render cell / ActionCell / 操作列 / 展开区内部都可以派发 ----
|
|
299
|
+
|
|
300
|
+
const handleExpandRequest = (row: TableRow, e: Event) => {
|
|
301
|
+
if (!expandable) return;
|
|
302
|
+
e.stopPropagation();
|
|
303
|
+
const detail = (e as CustomEvent<RowExpandRequest>).detail;
|
|
304
|
+
void setRowExpanded(row, detail?.expanded);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// 传给展开区组件的上下文
|
|
308
|
+
let expandContext = $derived.by<RowExpandContext<T> | null>(() => {
|
|
309
|
+
const row = expandedRow;
|
|
310
|
+
if (!row) return null;
|
|
311
|
+
const data = row.data as T;
|
|
312
|
+
const actions = actionsColumn?.getActions(data) ?? [];
|
|
313
|
+
return {
|
|
314
|
+
data,
|
|
315
|
+
collapse: () => { void setRowExpanded(row, false); },
|
|
316
|
+
actions,
|
|
317
|
+
invoke: async (label: string) => {
|
|
318
|
+
const action = actions.find(a => a.label === label);
|
|
319
|
+
if (!action || action.disabled) return false;
|
|
320
|
+
await action.handler(data);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const rowEvents: RowEventHandlers<TableRow> = {
|
|
327
|
+
onRowClick: handleRowClick,
|
|
328
|
+
onRowDblClick: handleRowDblClick,
|
|
329
|
+
onExpandRequest: handleExpandRequest,
|
|
330
|
+
};
|
|
195
331
|
</script>
|
|
196
332
|
|
|
197
333
|
<div id="tab-{id}" {style} class="topside-data-table" class:cell-border={colBorder} class:row-border={rowBorder}>
|
|
@@ -204,7 +340,10 @@
|
|
|
204
340
|
{orderColumn}
|
|
205
341
|
{onCellClick}
|
|
206
342
|
{indicatorColumn}
|
|
207
|
-
|
|
343
|
+
expandRow={expandedRow}
|
|
344
|
+
expandable={showExpandToggle}
|
|
345
|
+
isExpandable={isExpandableRow}
|
|
346
|
+
{rowEvents}
|
|
208
347
|
{rowHeight}
|
|
209
348
|
{headerHeight}
|
|
210
349
|
{fixedCols}
|
|
@@ -227,7 +366,9 @@
|
|
|
227
366
|
{tabWidth}
|
|
228
367
|
bind:rows
|
|
229
368
|
bind:scrollTop
|
|
230
|
-
{
|
|
369
|
+
expandRow={expandedRow}
|
|
370
|
+
{expandContext}
|
|
371
|
+
{rowEvents}
|
|
231
372
|
{rowHeight}
|
|
232
373
|
{headerHeight}
|
|
233
374
|
{list}
|
|
@@ -245,6 +386,6 @@
|
|
|
245
386
|
{/if}
|
|
246
387
|
|
|
247
388
|
{#if actionsColumn}
|
|
248
|
-
<ActionPanel {
|
|
389
|
+
<ActionPanel expandRow={expandedRow} {rowEvents} {rowHeight} {headerHeight} {actionsColumn} {rows} {inlineRowHeight} bind:scrollTop {vwindow} showSummary={tableSummary != null} />
|
|
249
390
|
{/if}
|
|
250
391
|
</div>
|
|
@@ -2,6 +2,7 @@ import type DataColumn from "./lib/DataColumn";
|
|
|
2
2
|
import type ActionsColumn from "./lib/ActionsColumn";
|
|
3
3
|
import type IndicatorColumn from "./lib/IndicatorColumn";
|
|
4
4
|
import type { GetRowFontStyle } from "../types";
|
|
5
|
+
import type RowExpansion from "./lib/RowExpansion";
|
|
5
6
|
declare function $$render<T = any>(): {
|
|
6
7
|
props: {
|
|
7
8
|
columns: Array<DataColumn>;
|
|
@@ -32,8 +33,21 @@ declare function $$render<T = any>(): {
|
|
|
32
33
|
* 分页场景下用它显示服务端算好的全量合计(数字串会按数字格式化)。
|
|
33
34
|
*/
|
|
34
35
|
summaryValues?: Record<string, any> | null;
|
|
36
|
+
/**
|
|
37
|
+
* 行展开配置:展开区组件、是否显示指示列图标(默认显示)、整行单击 / 双击触发等。
|
|
38
|
+
* 不配置图标时可以用组件方法 expandRow / collapseRow / toggleRowExpand,
|
|
39
|
+
* 或在行内(自定义单元格、操作列、展开区)派发 ROW_EXPAND_EVENT 事件来控制。
|
|
40
|
+
* 同一时间只展开一行。
|
|
41
|
+
*/
|
|
42
|
+
rowExpansion?: RowExpansion<T> | null;
|
|
43
|
+
};
|
|
44
|
+
exports: {
|
|
45
|
+
expandRow: (data: T) => Promise<void>;
|
|
46
|
+
collapseRow: (data?: T) => void;
|
|
47
|
+
toggleRowExpand: (data: T) => Promise<void>;
|
|
48
|
+
isRowExpanded: (data: T) => boolean;
|
|
49
|
+
getExpandedRow: () => T | null;
|
|
35
50
|
};
|
|
36
|
-
exports: {};
|
|
37
51
|
bindings: "inlineRowHeight" | "selectedRows";
|
|
38
52
|
slots: {};
|
|
39
53
|
events: {};
|
|
@@ -43,7 +57,13 @@ declare class __sveltets_Render<T = any> {
|
|
|
43
57
|
events(): ReturnType<typeof $$render<T>>['events'];
|
|
44
58
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
45
59
|
bindings(): "inlineRowHeight" | "selectedRows";
|
|
46
|
-
exports(): {
|
|
60
|
+
exports(): {
|
|
61
|
+
expandRow: (data: T) => Promise<void>;
|
|
62
|
+
collapseRow: (data?: T | undefined) => void;
|
|
63
|
+
toggleRowExpand: (data: T) => Promise<void>;
|
|
64
|
+
isRowExpanded: (data: T) => boolean;
|
|
65
|
+
getExpandedRow: () => T | null;
|
|
66
|
+
};
|
|
47
67
|
}
|
|
48
68
|
interface $$IsomorphicComponent {
|
|
49
69
|
new <T = any>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
@@ -55,6 +55,7 @@ import type { DataColumn, ActionsColumn, IndicatorColumn, RowAction } from "@tic
|
|
|
55
55
|
| `style` | `string` | `""` | Additional CSS styles |
|
|
56
56
|
| `summaryLabel` | `string` | i18n `dataTable.summaryLabel` ("Total") | Label of the summary row; empty string hides it |
|
|
57
57
|
| `summaryValues` | `Record<string, any> \| null` | `null` | External totals keyed by field; override local aggregation (server-side totals for paged data) |
|
|
58
|
+
| `rowExpansion` | `RowExpansion \| null` | `null` | Row expansion config (inline component, toggle icon, click / double-click trigger…), see [Row Expansion](#row-expansion) |
|
|
58
59
|
|
|
59
60
|
## Types
|
|
60
61
|
|
|
@@ -134,7 +135,28 @@ interface IndicatorColumn {
|
|
|
134
135
|
width: number; // Column width in pixels
|
|
135
136
|
selectable?: boolean; // Whether rows are selectable
|
|
136
137
|
showRowNo?: boolean; // Whether to show row numbers
|
|
137
|
-
|
|
138
|
+
/** @deprecated use the DataTable `rowExpansion` prop instead */
|
|
139
|
+
buildInlineComponent?: (data: any) => Promise<any>;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### RowExpansion
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface RowExpansion<T = any> {
|
|
147
|
+
build: (data: T) => Promise<Component> | Component; // inline component builder (may be async)
|
|
148
|
+
showToggle?: boolean; // show the expand icon in the indicator column, default true (needs indicatorColumn)
|
|
149
|
+
trigger?: 'click' | 'dblclick'; // toggle by clicking / double-clicking the row; unset by default
|
|
150
|
+
isExpandable?: (data: T) => boolean; // which rows may expand
|
|
151
|
+
onExpandChange?: (data: T | null) => void; // fired on expand / collapse (null when collapsed)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Props received by the inline component: data (row data) and context
|
|
155
|
+
interface RowExpandContext<T = any> {
|
|
156
|
+
data: T;
|
|
157
|
+
collapse: () => void; // collapse the expanded row
|
|
158
|
+
actions: Array<RowAction<T>>; // the row's actions (from actionsColumn.getActions)
|
|
159
|
+
invoke: (label: string) => Promise<boolean>; // run a row action by label
|
|
138
160
|
}
|
|
139
161
|
```
|
|
140
162
|
|
|
@@ -486,60 +508,36 @@ Using the custom component in column definitions:
|
|
|
486
508
|
import OrderDetails from "./OrderDetails.svelte";
|
|
487
509
|
|
|
488
510
|
const columns = [
|
|
489
|
-
{
|
|
490
|
-
{
|
|
491
|
-
{
|
|
511
|
+
{ title: "Order ID", field: "orderId", width: 120 },
|
|
512
|
+
{ title: "Customer", field: "customer", width: 150 },
|
|
513
|
+
{ title: "Total", field: "total", width: 100, align: "right",
|
|
492
514
|
formatter: (value) => `$${value?.toFixed(2) ?? '0.00'}`
|
|
493
515
|
},
|
|
494
|
-
{
|
|
495
|
-
{ text: "Date", field: "orderDate", width: 120 }
|
|
516
|
+
{ title: "Status", field: "status", width: 120, align: "center" }
|
|
496
517
|
];
|
|
497
518
|
|
|
498
|
-
const indicatorColumn = {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
519
|
+
const indicatorColumn = { width: 50 };
|
|
520
|
+
|
|
521
|
+
const rowExpansion = {
|
|
522
|
+
build: async (orderData) => {
|
|
523
|
+
// e.g. fetch the details first, then pick the component
|
|
524
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
505
525
|
return OrderDetails;
|
|
506
526
|
}
|
|
507
527
|
};
|
|
508
|
-
|
|
509
|
-
const orders = [
|
|
510
|
-
{
|
|
511
|
-
orderId: "ORD-001",
|
|
512
|
-
customer: "John Doe",
|
|
513
|
-
total: 299.99,
|
|
514
|
-
status: "Shipped",
|
|
515
|
-
orderDate: "2024-01-15",
|
|
516
|
-
items: [
|
|
517
|
-
{ product: "Laptop", quantity: 1, price: 299.99 }
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
orderId: "ORD-002",
|
|
522
|
-
customer: "Jane Smith",
|
|
523
|
-
total: 149.98,
|
|
524
|
-
status: "Processing",
|
|
525
|
-
orderDate: "2024-01-14",
|
|
526
|
-
items: [
|
|
527
|
-
{ product: "Mouse", quantity: 2, price: 24.99 },
|
|
528
|
-
{ product: "Keyboard", quantity: 1, price: 99.99 }
|
|
529
|
-
]
|
|
530
|
-
}
|
|
531
|
-
];
|
|
532
528
|
</script>
|
|
533
529
|
|
|
534
530
|
<DataTable
|
|
535
531
|
{columns}
|
|
536
532
|
list={orders}
|
|
537
533
|
{indicatorColumn}
|
|
538
|
-
|
|
534
|
+
{rowExpansion}
|
|
539
535
|
emptyText="No orders found"
|
|
540
536
|
/>
|
|
541
537
|
```
|
|
542
538
|
|
|
539
|
+
See [Row Expansion](#row-expansion) for other triggers and for closing / running row actions from inside the expanded panel.
|
|
540
|
+
|
|
543
541
|
### Table with Frozen Columns
|
|
544
542
|
|
|
545
543
|
```svelte
|
|
@@ -837,6 +835,89 @@ Set `summary` on any column (or pass `summaryValues` for a column's field) and a
|
|
|
837
835
|
- **Paging**: pass server totals via `summaryValues` (keyed by field); they win over local aggregation and work on columns without `summary`.
|
|
838
836
|
- Background: CSS variable `--topside-data-table-summary-bg` (defaults to the header background).
|
|
839
837
|
|
|
838
|
+
## Row Expansion
|
|
839
|
+
|
|
840
|
+
Configured through `rowExpansion`. Only one row is expanded at a time; the inline component receives `data` (the row data) and `context` (a `RowExpandContext`).
|
|
841
|
+
|
|
842
|
+
### Triggers
|
|
843
|
+
|
|
844
|
+
```svelte
|
|
845
|
+
<script>
|
|
846
|
+
import DataTable from "@ticatec/topside-elements/DataTable";
|
|
847
|
+
import OrderDetails from "./OrderDetails.svelte";
|
|
848
|
+
|
|
849
|
+
let table;
|
|
850
|
+
|
|
851
|
+
const rowExpansion = {
|
|
852
|
+
build: async (order) => OrderDetails,
|
|
853
|
+
showToggle: false, // no expand icon in the indicator column (default true)
|
|
854
|
+
trigger: 'dblclick', // double-click the row to toggle; 'click' for single click (clicks on buttons / links / inputs are ignored)
|
|
855
|
+
isExpandable: (order) => order.items?.length > 0,
|
|
856
|
+
onExpandChange: (order) => console.log('expanded:', order) // null when collapsed
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
const actionsColumn = {
|
|
860
|
+
width: 120,
|
|
861
|
+
getActions: (order) => [
|
|
862
|
+
{ label: 'Edit', handler: () => edit(order) },
|
|
863
|
+
// an action button can drive expansion through the component methods
|
|
864
|
+
{ label: 'Details', handler: () => table.toggleRowExpand(order) }
|
|
865
|
+
]
|
|
866
|
+
};
|
|
867
|
+
</script>
|
|
868
|
+
|
|
869
|
+
<DataTable bind:this={table} {columns} {list} {rowExpansion} {actionsColumn} />
|
|
870
|
+
<button onclick={() => table.expandRow(list[0])}>Expand first row</button>
|
|
871
|
+
<button onclick={() => table.collapseRow()}>Collapse</button>
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
Component methods (call them on the `bind:this` instance; rows are matched by data object identity):
|
|
875
|
+
|
|
876
|
+
| Method | Description |
|
|
877
|
+
|--------|-------------|
|
|
878
|
+
| `expandRow(data)` | Expand the row (ignored when already expanded, not allowed by `isExpandable`, or not found); returns a Promise |
|
|
879
|
+
| `collapseRow(data?)` | Collapse the row; without an argument collapses whatever is expanded |
|
|
880
|
+
| `toggleRowExpand(data)` | Toggle the row; returns a Promise |
|
|
881
|
+
| `isRowExpanded(data)` | Whether the row is expanded |
|
|
882
|
+
| `getExpandedRow()` | Data of the expanded row, or `null` |
|
|
883
|
+
|
|
884
|
+
### Triggering from a custom cell
|
|
885
|
+
|
|
886
|
+
A custom `render` component or an `ActionCell` does not need the table instance: dispatch a request from any element inside the row and the DataTable resolves the row itself.
|
|
887
|
+
|
|
888
|
+
```svelte
|
|
889
|
+
<!-- OrderNoCell.svelte -->
|
|
890
|
+
<script>
|
|
891
|
+
import { requestRowToggle } from "@ticatec/topside-elements/DataTable";
|
|
892
|
+
let { data } = $props();
|
|
893
|
+
</script>
|
|
894
|
+
|
|
895
|
+
<a href="#" onclick={(e) => { e.preventDefault(); requestRowToggle(e.currentTarget); }}>{data.orderId}</a>
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
Helpers: `requestRowExpand(el)`, `requestRowCollapse(el)`, `requestRowToggle(el)`. You can also dispatch `ROW_EXPAND_EVENT` (`topside-row-expand`, `detail: { expanded?: boolean }`, `bubbles: true`) yourself. Requests are ignored when no `rowExpansion` is configured.
|
|
899
|
+
|
|
900
|
+
### Closing / running row actions from the expanded panel
|
|
901
|
+
|
|
902
|
+
```svelte
|
|
903
|
+
<!-- OrderDetails.svelte -->
|
|
904
|
+
<script>
|
|
905
|
+
let { data, context } = $props();
|
|
906
|
+
</script>
|
|
907
|
+
|
|
908
|
+
<div>
|
|
909
|
+
{#each context.actions as action}
|
|
910
|
+
<button disabled={action.disabled} onclick={() => action.handler(data)}>{action.label}</button>
|
|
911
|
+
{/each}
|
|
912
|
+
<button onclick={() => context.invoke('Edit')}>Edit</button>
|
|
913
|
+
<button onclick={() => context.collapse()}>Close</button>
|
|
914
|
+
</div>
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
`context.actions` is the result of `actionsColumn.getActions(data)` (an empty array without an `actionsColumn`); deeply nested children can also call `requestRowCollapse(el)` to close their row.
|
|
918
|
+
|
|
919
|
+
> Compatibility: the legacy `indicatorColumn.buildInlineComponent` still works and is equivalent to `rowExpansion = { build }`.
|
|
920
|
+
|
|
840
921
|
## Styling
|
|
841
922
|
|
|
842
923
|
The DataTable component supports extensive styling customization through CSS custom properties and classes:
|