@ticatec/topside-elements 1.0.5 → 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 CHANGED
@@ -1,5 +1,29 @@
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
+
19
+ ## 1.0.6
20
+
21
+ ### 变更
22
+ - **应用布局组件样式调整**:`SidebarLayout`、`HeaderLayout` 与 `ClassicLayout` 移除多余的容器外间距 `gap` 与面板硬编码边框,统一配合内置拖拽分割线(`Split`)实现无间距贴合与平滑拖拽。
23
+
24
+ ### 测试与文档
25
+ - **AppLayout 测试演示套件完善**:在 `topside-test-suite` 中补充「应用布局」完整演示路由(包含 `/test/app-layout` 总览,以及 `sidebar-layout`、`header-layout`、`classic-layout`、`columns-layout`、`rows-layout` 独立演示页面),完善测试站首页与侧边栏导航入口。
26
+
3
27
  ## 1.0.5
4
28
 
5
29
  ### 修复
@@ -115,7 +115,6 @@
115
115
  flex: 0 0 auto;
116
116
  overflow: auto;
117
117
  width: 100%;
118
- border-right: 1px solid var(--topside-color-border-secondary);
119
118
  }
120
119
 
121
120
  .content-area {
@@ -127,7 +126,6 @@
127
126
  flex: 0 0 auto;
128
127
  overflow: auto;
129
128
  width: 100%;
130
- border-left: 1px solid var(--topside-color-border-secondary);
131
129
  }
132
130
 
133
131
  .bottom-area {
@@ -135,6 +133,5 @@
135
133
  position: relative;
136
134
  width: 100%;
137
135
  overflow: hidden;
138
- border-top: 1px solid var(--topside-color-border-secondary);
139
136
  }
140
137
  </style>
@@ -82,7 +82,6 @@
82
82
  height: 100%;
83
83
  flex: 0 0 auto;
84
84
  overflow: auto;
85
- border-right: 1px solid var(--topside-color-border-secondary);
86
85
  }
87
86
 
88
87
  .main-content {
@@ -61,14 +61,12 @@
61
61
  box-sizing: border-box;
62
62
  display: flex;
63
63
  flex-direction: row;
64
- gap: var(--topside-spacing-2);
65
64
  }
66
65
 
67
66
  .sidebar {
68
67
  height: 100%;
69
68
  flex: 0 0 auto;
70
69
  overflow: auto;
71
- border-right: 1px solid var(--topside-color-border-secondary);
72
70
  }
73
71
 
74
72
  .main-content {
@@ -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 expandRow = $state<TableRow | null>(null);
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(expandRow ? rows.indexOf(expandRow) : -1);
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
- expandRow = null;
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 onRowExpand = async (row: TableRow) => {
187
- if (row == expandRow) {
188
- expandRow = null;
189
- inlineComponent = null;
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
- inlineComponent = await indicatorColumn?.buildInlineComponent?.(row.data);
192
- expandRow = row;
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
- bind:expandRow
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
- {expandRow}
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 {expandRow} {rowHeight} {headerHeight} {actionsColumn} {rows} {inlineRowHeight} bind:scrollTop {vwindow} showSummary={tableSummary != null} />
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
- buildInlineComponent?: (data: any) => Promise<any>; // Inline component builder
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
- { text: "Order ID", field: "orderId", width: 120 },
490
- { text: "Customer", field: "customer", width: 150 },
491
- { text: "Total", field: "total", width: 100, align: "right",
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
- { text: "Status", field: "status", width: 120, align: "center" },
495
- { text: "Date", field: "orderDate", width: 120 }
516
+ { title: "Status", field: "status", width: 120, align: "center" }
496
517
  ];
497
518
 
498
- const indicatorColumn = {
499
- width: 50,
500
- selectable: false,
501
- showRowNo: false,
502
- buildInlineComponent: async (orderData) => {
503
- // Simulate API call to fetch order details
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
- inlineRowHeight={200}
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: