stk-table-vue 0.4.15 → 0.5.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/lib/style.css CHANGED
@@ -32,6 +32,7 @@
32
32
  --col-resize-indicator-color:#87879c;
33
33
  --fixed-col-shadow-color-from:rgba(0, 0, 0, 0.1);
34
34
  --fixed-col-shadow-color-to:rgba(0, 0, 0, 0);
35
+ --drag-handle-hover-color:#d0d1e0;
35
36
  position:relative;
36
37
  overflow:auto;
37
38
  display:flex;
@@ -61,6 +62,7 @@
61
62
  --col-resize-indicator-color:#5d6064;
62
63
  --fixed-col-shadow-color-from:rgba(135, 135, 156, 0.1);
63
64
  --fixed-col-shadow-color-to:rgba(135, 135, 156, 0);
65
+ --drag-handle-hover-color:#5d6064;
64
66
  color:#d1d1e0;
65
67
  }
66
68
  .stk-table.headless{
@@ -134,7 +136,8 @@
134
136
  }
135
137
  .stk-table.virtual .table-header-cell-wrapper{
136
138
  overflow:hidden;
137
- max-height:calc(var(--header-row-height) * var(--row-span));
139
+ max-height:calc(var(--header-row-height) * 1);
140
+ max-height:calc(var(--header-row-height) * var(--row-span, 1));
138
141
  }
139
142
  .stk-table.virtual tbody td{
140
143
  height:var(--row-height);
@@ -146,6 +149,9 @@
146
149
  .stk-table.virtual .padding-top-tr td{
147
150
  height:0;
148
151
  }
152
+ .stk-table.virtual .expand-cell .table-cell-wrapper{
153
+ white-space:nowrap;
154
+ }
149
155
  .stk-table.fixed-relative-mode th{
150
156
  position:relative;
151
157
  }
@@ -236,6 +242,49 @@
236
242
  .stk-table .seq-column{
237
243
  text-align:center;
238
244
  }
245
+ .stk-table .expand-cell{
246
+ cursor:pointer;
247
+ }
248
+ .stk-table .expand-cell .table-cell-wrapper.expanded-cell-wrapper::before{
249
+ content:'';
250
+ display:inline-block;
251
+ margin:0 2px;
252
+ width:0;
253
+ height:0;
254
+ border-left:5px solid #757699;
255
+ border-top:4px solid transparent;
256
+ border-bottom:4px solid transparent;
257
+ transition:transform 0.2s ease;
258
+ }
259
+ .stk-table .expand-cell .table-cell-wrapper.expanded-cell-wrapper > span{
260
+ margin-left:var(--cell-padding-x);
261
+ }
262
+ .stk-table .expand-cell.expanded .table-cell-wrapper::before{
263
+ transform:rotate(90deg);
264
+ }
265
+ .stk-table .drag-row-cell .table-cell-wrapper{
266
+ display:inline-flex;
267
+ align-items:center;
268
+ }
269
+ .stk-table .drag-row-cell .drag-row-handle{
270
+ cursor:grab;
271
+ border-radius:4px;
272
+ }
273
+ .stk-table .drag-row-cell .drag-row-handle:hover{
274
+ background-color:var(--drag-handle-hover-color);
275
+ }
276
+ .stk-table .drag-row-cell .drag-row-handle:active{
277
+ cursor:grabbing;
278
+ }
279
+ .stk-table .drag-row-cell .drag-row-handle > svg{
280
+ vertical-align:-2px;
281
+ }
282
+ .stk-table .tr-dragging{
283
+ opacity:0.5;
284
+ }
285
+ .stk-table .tr-dragging-over{
286
+ background-color:var(--tr-hover-bgc);
287
+ }
239
288
  .stk-table .fixed-cell--left{
240
289
  --shadow-rotate:90deg;
241
290
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stk-table-vue",
3
- "version": "0.4.15",
3
+ "version": "0.5.0",
4
4
  "description": "Simple realtime virtual table for vue3 and vue2.7",
5
5
  "main": "./lib/stk-table-vue.js",
6
6
  "types": "./lib/src/StkTable/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  @drop="onThDrop"
85
85
  @dragover="onThDragOver"
86
86
  >
87
- <div class="table-header-cell-wrapper" :style="`--row-span:${virtualX_on ? 1 : col.rowSpan}`">
87
+ <div class="table-header-cell-wrapper" :style="{ '--row-span': virtualX_on ? 1 : col.rowSpan }">
88
88
  <component :is="col.customHeaderCell" v-if="col.customHeaderCell" :col="col" :colIndex="colIndex" :rowIndex="rowIndex" />
89
89
  <template v-else-if="col.type === 'seq'">
90
90
  <span class="table-header-title">{{ col.title }}</span>
@@ -97,14 +97,7 @@
97
97
 
98
98
  <!-- 排序图图标 -->
99
99
  <span v-if="col.sorter" class="table-header-sorter">
100
- <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 16 16">
101
- <polygon class="arrow-up" fill="#757699" points="8 2 4.8 6 11.2 6"></polygon>
102
- <polygon
103
- class="arrow-down"
104
- transform="translate(8, 12) rotate(-180) translate(-8, -12) "
105
- points="8 10 4.8 14 11.2 14"
106
- ></polygon>
107
- </svg>
100
+ <SortIcon />
108
101
  </span>
109
102
  <!-- 列宽拖动handler -->
110
103
  <div
@@ -123,7 +116,7 @@
123
116
  <!-- 用于虚拟滚动表格内容定位 @deprecated 有兼容问题-->
124
117
  <!-- <tbody v-if="virtual_on" :style="{ height: `${virtualScroll.offsetTop}px` }"></tbody> -->
125
118
  <!-- <tbody :style="{ transform: `translateY(${virtualScroll.offsetTop}px)` }"> -->
126
- <tbody class="stk-tbody-main">
119
+ <tbody class="stk-tbody-main" @dragover="onTrDragOver" @dragenter="onTrDragEnter" @dragend="onTrDragEnd">
127
120
  <tr v-if="virtual_on" :style="`height:${virtualScroll.offsetTop}px`" class="padding-top-tr">
128
121
  <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
129
122
  <td v-if="virtualX_on && fixedMode && headless" class="vt-x-left"></td>
@@ -133,58 +126,103 @@
133
126
  </tr>
134
127
  <tr
135
128
  v-for="(row, rowIndex) in virtual_dataSourcePart"
136
- :id="stkTableId + '-' + (rowKey ? rowKeyGen(row) : rowIndex)"
137
- :key="rowKey ? rowKeyGen(row) : rowIndex"
138
- :data-row-key="rowKey ? rowKeyGen(row) : rowIndex"
129
+ :id="stkTableId + '-' + (rowKey ? rowKeyGen(row) : virtualScroll.startIndex + rowIndex)"
130
+ :key="rowKey ? rowKeyGen(row) : virtualScroll.startIndex + rowIndex"
131
+ :data-row-key="rowKey ? rowKeyGen(row) : virtualScroll.startIndex + rowIndex"
139
132
  :class="{
140
133
  active: rowKey ? rowKeyGen(row) === currentRowKey : row === currentRow,
141
134
  hover: props.showTrHoverClass && (rowKey ? rowKeyGen(row) === currentHoverRowKey : row === currentHoverRowKey),
142
- [rowClassName(row, rowIndex)]: true,
135
+ [rowClassName(row, virtualScroll.startIndex + rowIndex)]: true,
136
+ expanded: row?.__EXPANDED__,
137
+ 'expanded-row': row && (row as ExpandedRow).__EXPANDED_ROW__,
138
+ }"
139
+ :style="{
140
+ '--row-height':
141
+ row &&
142
+ (row as ExpandedRow).__EXPANDED_ROW__ &&
143
+ virtual_on &&
144
+ props.expandConfig?.height &&
145
+ props.expandConfig?.height + 'px',
143
146
  }"
144
147
  @click="e => onRowClick(e, row)"
145
148
  @dblclick="e => onRowDblclick(e, row)"
146
149
  @contextmenu="e => onRowMenu(e, row)"
147
150
  @mouseover="e => onTrMouseOver(e, row)"
151
+ @drop="e => onTrDrop(e, virtualScroll.startIndex + rowIndex)"
148
152
  >
149
153
  <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
150
154
  <td v-if="virtualX_on" class="vt-x-left"></td>
151
- <td
152
- v-for="(col, colIndex) in virtualX_columnPart"
153
- :key="col.dataIndex"
154
- :data-index="col.dataIndex"
155
- :cell-key="rowKeyGen(row) + '--' + colKeyGen(col)"
156
- :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"
157
- :class="[
158
- col.className,
159
- fixedColClassMap.get(colKeyGen(col)),
160
- {
161
- 'seq-column': col.type === 'seq',
162
- active: currentSelectedCellKey === cellKeyGen(row, col),
163
- },
164
- ]"
165
- @click="e => onCellClick(e, row, col)"
166
- @mouseenter="e => onCellMouseEnter(e, row, col)"
167
- @mouseleave="e => onCellMouseLeave(e, row, col)"
168
- @mouseover="e => onCellMouseOver(e, row, col)"
169
- >
170
- <component
171
- :is="col.customCell"
172
- v-if="col.customCell"
173
- :col="col"
174
- :row="row"
175
- :rowIndex="rowIndex"
176
- :colIndex="colIndex"
177
- :cellValue="row?.[col.dataIndex]"
178
- />
179
- <div v-else class="table-cell-wrapper" :title="!col.type ? row?.[col.dataIndex] : ''">
180
- <template v-if="col.type === 'seq'">
181
- {{ (props.seqConfig.startIndex || 0) + rowIndex + 1 }}
182
- </template>
183
- <template v-else>
184
- {{ row?.[col.dataIndex] ?? getEmptyCellText(col, row) }}
185
- </template>
155
+ <td v-if="row && (row as ExpandedRow).__EXPANDED_ROW__" :colspan="virtualX_columnPart.length">
156
+ <!-- TODO: support wheel -->
157
+ <div class="table-cell-wrapper">
158
+ <slot name="expand" :row="(row as ExpandedRow).__EXPANDED_ROW__" :col="(row as ExpandedRow).__EXPANDED_COL__">
159
+ {{ (row as ExpandedRow).__EXPANDED_ROW__?.[(row as ExpandedRow).__EXPANDED_COL__.dataIndex] ?? '' }}
160
+ </slot>
186
161
  </div>
187
162
  </td>
163
+ <template v-else>
164
+ <td
165
+ v-for="(col, colIndex) in virtualX_columnPart"
166
+ :key="col.dataIndex"
167
+ :data-index="col.dataIndex"
168
+ :cell-key="rowKeyGen(row) + '--' + colKeyGen(col)"
169
+ :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"
170
+ :class="[
171
+ col.className,
172
+ fixedColClassMap.get(colKeyGen(col)),
173
+ {
174
+ 'seq-column': col.type === 'seq',
175
+ active: currentSelectedCellKey === cellKeyGen(row, col),
176
+ 'expand-cell': col.type === 'expand',
177
+ expanded: col.type === 'expand' && colKeyGen(row?.__EXPANDED__) === colKeyGen(col),
178
+ 'drag-row-cell': col.type === 'dragRow',
179
+ },
180
+ ]"
181
+ @click="
182
+ e => {
183
+ col.type === 'expand' && toggleExpandRow(row, col);
184
+ onCellClick(e, row, col);
185
+ }
186
+ "
187
+ @mouseenter="e => onCellMouseEnter(e, row, col)"
188
+ @mouseleave="e => onCellMouseLeave(e, row, col)"
189
+ @mouseover="e => onCellMouseOver(e, row, col)"
190
+ >
191
+ <component
192
+ :is="col.customCell"
193
+ v-if="col.customCell"
194
+ class="table-cell-wrapper"
195
+ :col="col"
196
+ :row="row"
197
+ :rowIndex="virtualScroll.startIndex + rowIndex"
198
+ :colIndex="colIndex"
199
+ :cellValue="row?.[col.dataIndex]"
200
+ :expanded="row?.__EXPANDED__ || null"
201
+ />
202
+ <div
203
+ v-else
204
+ class="table-cell-wrapper"
205
+ :class="{ 'expanded-cell-wrapper': col.type === 'expand' }"
206
+ :title="col.type !== 'seq' ? row?.[col.dataIndex] : ''"
207
+ >
208
+ <template v-if="col.type === 'seq'">
209
+ {{ (props.seqConfig.startIndex || 0) + virtualScroll.startIndex + rowIndex + 1 }}
210
+ </template>
211
+ <span v-else-if="col.type === 'expand'">
212
+ {{ row?.[col.dataIndex] ?? '' }}
213
+ </span>
214
+ <template v-else-if="col.type === 'dragRow'">
215
+ <DragHandle @dragstart="e => onTrDragStart(e, virtualScroll.startIndex + rowIndex)" />
216
+ <span>
217
+ {{ row?.[col.dataIndex] ?? '' }}
218
+ </span>
219
+ </template>
220
+ <template v-else>
221
+ {{ row?.[col.dataIndex] ?? getEmptyCellText(col, row) }}
222
+ </template>
223
+ </div>
224
+ </td>
225
+ </template>
188
226
  </tr>
189
227
  <tr v-if="virtual_on" :style="`height: ${virtual_offsetBottom}px`"></tr>
190
228
  </tbody>
@@ -197,13 +235,29 @@
197
235
 
198
236
  <script setup lang="ts">
199
237
  /**
200
- * @author JA+
201
- * TODO:存在的问题:
202
- * [] column.dataIndex 作为唯一键,不能重复
238
+ * @author japlus
203
239
  */
204
240
  import { CSSProperties, computed, nextTick, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
205
- import { DEFAULT_ROW_HEIGHT, IS_LEGACY_MODE, DEFAULT_SMOOTH_SCROLL } from './const';
206
- import { HighlightConfig, Order, SeqConfig, SortConfig, SortOption, SortState, StkTableColumn, TagType, UniqKeyProp } from './types/index';
241
+ import DragHandle from './components/DragHandle.vue';
242
+ import SortIcon from './components/SortIcon.vue';
243
+ import { DEFAULT_ROW_HEIGHT, DEFAULT_SMOOTH_SCROLL, EXPANDED_ROW_KEY_PREFIX, IS_LEGACY_MODE } from './const';
244
+ import {
245
+ DragRowConfig,
246
+ ExpandConfig,
247
+ ExpandedRow,
248
+ HeaderDragConfig,
249
+ HighlightConfig,
250
+ Order,
251
+ PrivateRowDT,
252
+ PrivateStkTableColumn,
253
+ SeqConfig,
254
+ SortConfig,
255
+ SortOption,
256
+ SortState,
257
+ StkTableColumn,
258
+ TagType,
259
+ UniqKeyProp,
260
+ } from './types/index';
207
261
  import { useAutoResize } from './useAutoResize';
208
262
  import { useColResize } from './useColResize';
209
263
  import { useFixedCol } from './useFixedCol';
@@ -212,15 +266,18 @@ import { useGetFixedColPosition } from './useGetFixedColPosition';
212
266
  import { useHighlight } from './useHighlight';
213
267
  import { useKeyboardArrowScroll } from './useKeyboardArrowScroll';
214
268
  import { useThDrag } from './useThDrag';
269
+ import { useTrDrag } from './useTrDrag';
215
270
  import { useVirtualScroll } from './useVirtualScroll';
216
271
  import { createStkTableId, getCalculatedColWidth, getColWidth, howDeepTheHeader, tableSort, transformWidthToStr } from './utils/index';
217
272
 
218
273
  /** Generic stands for DataType */
219
- type DT = any;
220
- /** 自己生成实例id */
274
+ type DT = any & PrivateRowDT;
275
+
276
+ /** generate table instance id */
221
277
  const stkTableId = createStkTableId();
278
+
222
279
  /**
223
- * props 不能放在单独的文件中。vue2.7 compiler 构建会出错。
280
+ * props cannot be placed in a separate file. It will cause compilation errors with vue 2.7 compiler.
224
281
  */
225
282
  const props = withDefaults(
226
283
  defineProps<{
@@ -280,16 +337,17 @@ const props = withDefaults(
280
337
  /** 单元格再次点击否可以取消选中 (cellActive=true)*/
281
338
  selectedCellRevokable?: boolean;
282
339
  /** 表头是否可拖动。支持回调函数。 */
283
- headerDrag?: boolean | ((col: StkTableColumn<DT>) => boolean);
340
+ headerDrag?: HeaderDragConfig;
284
341
  /**
285
342
  * 给行附加className<br>
286
343
  * FIXME: 是否需要优化,因为不传此prop会使表格行一直执行空函数,是否有影响
287
344
  */
288
345
  rowClassName?: (row: DT, i: number) => string;
289
346
  /**
290
- * 列宽是否可拖动<br>
347
+ * 列宽是否可拖动(需要设置v-model:columns)<br>
291
348
  * **不要设置**列minWidth,**必须**设置width<br>
292
349
  * 列宽拖动时,每一列都必须要有width,且minWidth/maxWidth不生效。table width会变为"fit-content"。
350
+ * - 会自动更新props.columns中的with属性
293
351
  */
294
352
  colResizable?: boolean;
295
353
  /** 可拖动至最小的列宽 */
@@ -320,6 +378,10 @@ const props = withDefaults(
320
378
  highlightConfig?: HighlightConfig;
321
379
  /** 序号列配置 */
322
380
  seqConfig?: SeqConfig;
381
+ /** 展开行配置 */
382
+ expandConfig?: ExpandConfig;
383
+ /** 列拖动配置 */
384
+ dragRowConfig?: DragRowConfig;
323
385
  /**
324
386
  * 固定头,固定列实现方式。(非响应式)
325
387
  *
@@ -381,6 +443,8 @@ const props = withDefaults(
381
443
  hideHeaderTitle: false,
382
444
  highlightConfig: () => ({}),
383
445
  seqConfig: () => ({}),
446
+ expandConfig: () => ({}),
447
+ dragRowConfig: () => ({}),
384
448
  cellFixedMode: 'sticky',
385
449
  smoothScroll: DEFAULT_SMOOTH_SCROLL,
386
450
  },
@@ -472,11 +536,24 @@ const emits = defineEmits<{
472
536
  * ```(targetColKey: string)```
473
537
  */
474
538
  (e: 'th-drop', targetColKey: string): void;
539
+ /**
540
+ * 行拖动事件
541
+ * ```(dragStartKey: string, targetRowKey: string)```
542
+ */
543
+ (e: 'row-order-change', dragStartKey: string, targetRowKey: string): void;
475
544
  /**
476
545
  * 列宽变动时触发
546
+ * ```(cols: StkTableColumn<DT>)```
477
547
  */
478
548
  (e: 'col-resize', cols: StkTableColumn<DT>): void;
479
- /** v-model:columns col resize 时更新宽度*/
549
+ /**
550
+ * 展开行触发
551
+ * ```( data: { expanded: boolean; row: DT; col: StkTableColumn<DT> })```
552
+ */
553
+ (e: 'toggle-row-expand', data: { expanded: boolean; row: DT; col: StkTableColumn<DT> | null }): void;
554
+ /**
555
+ * v-model:columns col resize 时更新宽度
556
+ */
480
557
  (e: 'update:columns', cols: StkTableColumn<DT>[]): void;
481
558
  }>();
482
559
 
@@ -542,17 +619,14 @@ const dataSourceCopy = shallowRef<DT[]>([...props.dataSource]);
542
619
  * 列唯一键
543
620
  * @param col
544
621
  */
545
- const colKeyGen = computed(() => {
622
+ const colKeyGen = computed<(col: StkTableColumn<DT>) => string>(() => {
546
623
  if (typeof props.colKey === 'function') {
547
- return (col: StkTableColumn<DT>) => (props.colKey as (col: StkTableColumn<DT>) => string)(col);
624
+ return col => (props.colKey as (col: StkTableColumn<DT>) => string)(col);
548
625
  } else {
549
- return (col: StkTableColumn<DT>) => (col as any)[props.colKey as string];
626
+ return col => (col ? (col as any)[props.colKey as string] : null);
550
627
  }
551
628
  });
552
629
 
553
- /**高亮帧间隔
554
- const highlightStepDuration = Highlight_Color_Change_Freq / 1000 + 's';*/
555
-
556
630
  /** 空单元格占位字符 */
557
631
  const getEmptyCellText = computed(() => {
558
632
  if (typeof props.emptyCellText === 'string') {
@@ -565,7 +639,9 @@ const getEmptyCellText = computed(() => {
565
639
  /** rowKey缓存 */
566
640
  const rowKeyGenStore = new WeakMap();
567
641
 
568
- const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits });
642
+ const { onThDragStart, onThDragOver, onThDrop, isHeaderDraggable } = useThDrag({ props, emits, colKeyGen });
643
+
644
+ const { onTrDragStart, onTrDrop, onTrDragOver, onTrDragEnd, onTrDragEnter } = useTrDrag({ props, emits, dataSourceCopy });
569
645
 
570
646
  const {
571
647
  virtualScroll,
@@ -581,7 +657,7 @@ const {
581
657
  initVirtualScrollX,
582
658
  updateVirtualScrollY,
583
659
  updateVirtualScrollX,
584
- } = useVirtualScroll({ tableContainerRef, theadRef, props, dataSourceCopy, tableHeaderLast, tableHeaders });
660
+ } = useVirtualScroll({ tableContainerRef, props, dataSourceCopy, tableHeaderLast, tableHeaders });
585
661
 
586
662
  /** 获取固定列的位置 */
587
663
  const getFixedColPosition = useGetFixedColPosition({ colKeyGen, tableHeaders });
@@ -753,7 +829,11 @@ function dealColumns() {
753
829
  * @param parent 父节点引用,用于构建双向链表。
754
830
  * @param parentFixed 父节点固定列继承。
755
831
  */
756
- function flat(arr: StkTableColumn<DT>[], parent: StkTableColumn<DT> | null, depth = 0 /* , parentFixed: 'left' | 'right' | null = null */) {
832
+ function flat(
833
+ arr: PrivateStkTableColumn<DT>[],
834
+ parent: PrivateStkTableColumn<DT> | null,
835
+ depth = 0 /* , parentFixed: 'left' | 'right' | null = null */,
836
+ ) {
757
837
  if (!tableHeadersTemp[depth]) {
758
838
  tableHeadersTemp[depth] = [];
759
839
  }
@@ -810,7 +890,11 @@ function rowKeyGen(row: DT | null | undefined) {
810
890
  if (!row) return row;
811
891
  let key = rowKeyGenStore.get(row);
812
892
  if (!key) {
813
- key = typeof props.rowKey === 'function' ? props.rowKey(row) : row[props.rowKey];
893
+ if ((row as PrivateRowDT).__ROW_KEY__) {
894
+ key = (row as PrivateRowDT).__ROW_KEY__;
895
+ } else {
896
+ key = typeof props.rowKey === 'function' ? props.rowKey(row) : row[props.rowKey];
897
+ }
814
898
 
815
899
  if (key === void 0) {
816
900
  // key为undefined时,不应该高亮行。因此重新生成key
@@ -1069,7 +1153,7 @@ function onTableScroll(e: Event) {
1069
1153
  }
1070
1154
  }
1071
1155
 
1072
- /** tr hover事件 */
1156
+ /** tr hover */
1073
1157
  function onTrMouseOver(_e: MouseEvent, row: DT) {
1074
1158
  if (currentHoverRow === row) return;
1075
1159
  currentHoverRow = row;
@@ -1079,7 +1163,7 @@ function onTrMouseOver(_e: MouseEvent, row: DT) {
1079
1163
  /**
1080
1164
  * 选中一行
1081
1165
  * @param {string} rowKeyOrRow selected rowKey, undefined to unselect
1082
- * @param {boolean} option.silent if emit current-change. default:false(not emit `current-change`)
1166
+ * @param {boolean} option.silent if set true not emit `current-change`. default:false
1083
1167
  */
1084
1168
  function setCurrentRow(rowKeyOrRow: string | undefined | DT, option = { silent: false }) {
1085
1169
  if (!dataSourceCopy.value.length) return;
@@ -1142,7 +1226,6 @@ function setSorter(dataIndex: string, order: Order, option: { sortOption?: SortO
1142
1226
  return dataSourceCopy.value;
1143
1227
  }
1144
1228
 
1145
- /** 重置排序 */
1146
1229
  function resetSorter() {
1147
1230
  sortCol.value = void 0;
1148
1231
  sortOrderIndex.value = 0;
@@ -1150,9 +1233,9 @@ function resetSorter() {
1150
1233
  }
1151
1234
 
1152
1235
  /**
1153
- * 设置滚动条位置
1154
- * @param top null 则不变动位置
1155
- * @param left null 则不变动位置
1236
+ * set scroll bar position
1237
+ * @param top null to not change
1238
+ * @param left null to not change
1156
1239
  */
1157
1240
  function scrollTo(top: number | null = 0, left: number | null = 0) {
1158
1241
  if (!tableContainerRef.value) return;
@@ -1160,18 +1243,80 @@ function scrollTo(top: number | null = 0, left: number | null = 0) {
1160
1243
  if (left !== null) tableContainerRef.value.scrollLeft = left;
1161
1244
  }
1162
1245
 
1163
- /** 获取当前状态的表格数据 */
1246
+ /** get current table data */
1164
1247
  function getTableData() {
1165
1248
  return toRaw(dataSourceCopy.value);
1166
1249
  }
1167
1250
 
1168
- /** 获取当前排序列的信息 */
1251
+ /** get current sort info */
1169
1252
  function getSortColumns(): Partial<SortState<DT>>[] {
1170
1253
  const sortOrder = sortSwitchOrder[sortOrderIndex.value];
1171
1254
  if (!sortOrder) return [];
1172
1255
  return [{ dataIndex: sortCol.value, order: sortOrder }];
1173
1256
  }
1174
1257
 
1258
+ /** click expended icon to toggleg expand row */
1259
+ function toggleExpandRow(row: DT, col: StkTableColumn<DT>) {
1260
+ const isExpand = row?.__EXPANDED__ === col ? !row?.__EXPANDED__ : true;
1261
+ setRowExpand(row, isExpand, { col });
1262
+ }
1263
+
1264
+ /**
1265
+ *
1266
+ * @param rowKeyOrRow rowKey or row
1267
+ * @param expand expand or collapse
1268
+ * @param data { col?: StkTableColumn<DT> }
1269
+ * @param data.silent if set true, not emit `toggle-row-expand`, default:false
1270
+ */
1271
+ function setRowExpand(rowKeyOrRow: string | undefined | DT, expand?: boolean, data?: { col?: StkTableColumn<DT>; silent?: boolean }) {
1272
+ let rowKey: string;
1273
+ if (typeof rowKeyOrRow === 'string') {
1274
+ rowKey = rowKeyOrRow;
1275
+ } else {
1276
+ rowKey = rowKeyGen(rowKeyOrRow);
1277
+ }
1278
+ const tempData = [...dataSourceCopy.value];
1279
+ const index = tempData.findIndex(it => rowKeyGen(it) === rowKey);
1280
+ if (index === -1) {
1281
+ console.warn('expandRow failed.rowKey:', rowKey);
1282
+ return;
1283
+ }
1284
+
1285
+ // delete other expanded row below the target row
1286
+ for (let i = index + 1; i < tempData.length; i++) {
1287
+ const item: PrivateRowDT = tempData[i];
1288
+ const rowKey = item.__ROW_KEY__;
1289
+ if (rowKey?.startsWith(EXPANDED_ROW_KEY_PREFIX)) {
1290
+ tempData.splice(i, 1);
1291
+ i--;
1292
+ } else {
1293
+ break;
1294
+ }
1295
+ }
1296
+
1297
+ const row = tempData[index];
1298
+ const col = data?.col || null;
1299
+
1300
+ if (expand) {
1301
+ // insert new expanded row
1302
+ const newExpandRow: ExpandedRow = {
1303
+ __ROW_KEY__: EXPANDED_ROW_KEY_PREFIX + rowKey,
1304
+ __EXPANDED_ROW__: row,
1305
+ __EXPANDED_COL__: col,
1306
+ };
1307
+ tempData.splice(index + 1, 0, newExpandRow);
1308
+ }
1309
+
1310
+ if (row) {
1311
+ row.__EXPANDED__ = expand ? col : null;
1312
+ }
1313
+
1314
+ dataSourceCopy.value = tempData;
1315
+ if (!data?.silent) {
1316
+ emits('toggle-row-expand', { expanded: Boolean(expand), row, col });
1317
+ }
1318
+ }
1319
+
1175
1320
  defineExpose({
1176
1321
  /** @see {@link initVirtualScroll} */
1177
1322
  initVirtualScroll,
@@ -1199,5 +1344,7 @@ defineExpose({
1199
1344
  scrollTo,
1200
1345
  /** @see {@link getTableData} */
1201
1346
  getTableData,
1347
+ /** @see {@link setRowExpand} */
1348
+ setRowExpand,
1202
1349
  });
1203
1350
  </script>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <span class="drag-row-handle" draggable="true">
3
+ <svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor">
4
+ <path
5
+ d="M640 853.3a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3z m-256 0a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3z m256-256a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3z m-256 0a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3z m256-256a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3zM384 341.3a85.3 85.3 0 1 1 85.3-85.3 85.3 85.3 0 0 1-85.3 85.3z"
6
+ ></path>
7
+ </svg>
8
+ </span>
9
+ </template>
@@ -0,0 +1,6 @@
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 16 16">
3
+ <polygon class="arrow-up" fill="#757699" points="8 2 4.8 6 11.2 6"></polygon>
4
+ <polygon class="arrow-down" transform="translate(8, 12) rotate(-180) translate(-8, -12) " points="8 10 4.8 14 11.2 14"></polygon>
5
+ </svg>
6
+ </template>
@@ -6,29 +6,29 @@ export const DEFAULT_TABLE_HEIGHT = 100;
6
6
  export const DEFAULT_TABLE_WIDTH = 200;
7
7
  export const DEFAULT_ROW_HEIGHT = 28;
8
8
 
9
- /** 高亮背景色 */
9
+ /** highlight background */
10
10
  export const HIGHLIGHT_COLOR = {
11
11
  light: { from: '#71a2fd', to: '#fff' },
12
12
  dark: { from: '#1e4c99', to: '#181c21' },
13
13
  };
14
- /** 高亮持续时间 */
15
14
  export const HIGHLIGHT_DURATION = 2000;
16
15
 
17
- /** 高亮变更频率 */
16
+ /** highlight change frequency 1000/30 -> 30FPS */
18
17
  export const HIGHLIGHT_FREQ = 1000 / 30;
19
18
 
20
- /** 高亮行class */
21
19
  export const HIGHLIGHT_ROW_CLASS = 'highlight-row';
22
- /** 高连单元格class */
23
20
  export const HIGHLIGHT_CELL_CLASS = 'highlight-cell';
24
21
 
25
22
  const _chromeVersion = getBrowsersVersion('chrome');
26
23
  const _firefoxVersion = getBrowsersVersion('firefox');
27
24
 
28
- /** 低版本sticky兼容模式 */
25
+ /** legacy sticky compatible mode */
29
26
  export const IS_LEGACY_MODE = _chromeVersion < 56 || _firefoxVersion < 59;
30
27
 
31
- /** 默认props.smoothDefault */
28
+ /** default props.smoothDefault */
32
29
  export const DEFAULT_SMOOTH_SCROLL = _chromeVersion < 85;
33
30
 
34
31
  export const STK_ID_PREFIX = 'stk';
32
+
33
+ /** expanded row key prefix */
34
+ export const EXPANDED_ROW_KEY_PREFIX = 'expanded-';