stk-table-vue 0.11.10 → 0.11.11
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/lib/index-C40Rz-HL.js +1 -1
- package/lib/src/StkTable/types/index.d.ts +12 -0
- package/lib/stk-table-vue.js +100 -66
- package/lib/style.css +2 -2
- package/package.json +1 -1
- package/src/StkTable/StkTable.vue +14 -7
- package/src/StkTable/features/useAreaSelection.ts +81 -52
- package/src/StkTable/registerFeature.ts +6 -3
- package/src/StkTable/style.less +1 -1
- package/src/StkTable/types/index.ts +6 -0
- package/src/StkTable/useFixedCol.ts +7 -4
- package/src/StkTable/useHighlight.ts +9 -5
package/README.md
CHANGED
|
@@ -44,18 +44,18 @@ const stkTableRef = ref<InstanceType<typeof StkTable>>();
|
|
|
44
44
|
const stkTableRef = useTemplateRef('stkTableRef');
|
|
45
45
|
|
|
46
46
|
// highlight row
|
|
47
|
-
stkTableRef.value.setHighlightDimRow([rowKey]
|
|
48
|
-
method: 'css'|'animation'
|
|
49
|
-
className: 'custom-class-name', // method css
|
|
50
|
-
keyframe: [{backgroundColor:'#aaa'}, {backgroundColor: '#222'}]
|
|
51
|
-
duration: 2000
|
|
47
|
+
stkTableRef.value.setHighlightDimRow([rowKey], {
|
|
48
|
+
method: 'css' | 'animation', // default animation
|
|
49
|
+
className: 'custom-class-name', // for method 'css'
|
|
50
|
+
keyframe: [{ backgroundColor: '#aaa' }, { backgroundColor: '#222' }], // same as https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
|
|
51
|
+
duration: 2000,
|
|
52
52
|
});
|
|
53
|
-
|
|
53
|
+
// highlight cell
|
|
54
54
|
stkTableRef.value.setHighlightDimCell(rowKey, colDataIndex, {
|
|
55
|
-
method: 'css'|'animation',
|
|
56
|
-
className:'custom-class-name', // method css
|
|
57
|
-
keyframe: [{backgroundColor:'#aaa'}, {backgroundColor: '#222'}], // method animation
|
|
58
|
-
duration: 2000
|
|
55
|
+
method: 'css' | 'animation',
|
|
56
|
+
className: 'custom-class-name', // for method 'css'
|
|
57
|
+
keyframe: [{ backgroundColor: '#aaa' }, { backgroundColor: '#222' }], // for method 'animation'
|
|
58
|
+
duration: 2000,
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const columns = [
|
package/lib/index-C40Rz-HL.js
CHANGED
|
@@ -320,8 +320,20 @@ export type RowActiveOption<DT> = {
|
|
|
320
320
|
/** 单元格选区范围 */
|
|
321
321
|
export type AreaSelectionRange = {
|
|
322
322
|
index: {
|
|
323
|
+
/** column index range @deprecated */
|
|
323
324
|
x: [number, number];
|
|
325
|
+
/** row index range @deprecated */
|
|
324
326
|
y: [number, number];
|
|
327
|
+
/** start point index*/
|
|
328
|
+
begin: {
|
|
329
|
+
row: number;
|
|
330
|
+
col: number;
|
|
331
|
+
};
|
|
332
|
+
/** end point index */
|
|
333
|
+
end: {
|
|
334
|
+
row: number;
|
|
335
|
+
col: number;
|
|
336
|
+
};
|
|
325
337
|
};
|
|
326
338
|
};
|
|
327
339
|
/** 单元格选区配置 */
|
package/lib/stk-table-vue.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* name: stk-table-vue
|
|
3
|
-
* version: v0.11.
|
|
3
|
+
* version: v0.11.11
|
|
4
4
|
* description: High performance realtime virtual table for vue3 and vue2.7
|
|
5
5
|
* author: japlus
|
|
6
6
|
* homepage: https://ja-plus.github.io/stk-table-vue/
|
|
@@ -458,6 +458,44 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
458
458
|
onBeforeUnmount(() => {
|
|
459
459
|
removeListener();
|
|
460
460
|
});
|
|
461
|
+
watch([() => dataSourceCopy.value.length, () => tableHeaderLast.value.length], ([rowCount, colCount]) => {
|
|
462
|
+
if (!config.value.enabled) return;
|
|
463
|
+
if (anchorCell) {
|
|
464
|
+
if (rowCount === 0 || colCount === 0) {
|
|
465
|
+
anchorCell = null;
|
|
466
|
+
} else {
|
|
467
|
+
anchorCell.rowIndex = clamp(anchorCell.rowIndex, 0, rowCount - 1);
|
|
468
|
+
anchorCell.colIndex = clamp(anchorCell.colIndex, 0, colCount - 1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!selectionRanges.value.length) return;
|
|
472
|
+
if (rowCount === 0 || colCount === 0) {
|
|
473
|
+
clearSelectedArea();
|
|
474
|
+
emitSelectionChange();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const maxRow = rowCount - 1;
|
|
478
|
+
const maxCol = colCount - 1;
|
|
479
|
+
let changed = false;
|
|
480
|
+
const newRanges = [];
|
|
481
|
+
for (const range of selectionRanges.value) {
|
|
482
|
+
const { begin, end } = range.index;
|
|
483
|
+
const nbRow = clamp(begin.row, 0, maxRow);
|
|
484
|
+
const nbCol = clamp(begin.col, 0, maxCol);
|
|
485
|
+
const neRow = clamp(end.row, 0, maxRow);
|
|
486
|
+
const neCol = clamp(end.col, 0, maxCol);
|
|
487
|
+
if (nbRow !== begin.row || nbCol !== begin.col || neRow !== end.row || neCol !== end.col) {
|
|
488
|
+
changed = true;
|
|
489
|
+
newRanges.push(makeRange(nbRow, nbCol, neRow, neCol));
|
|
490
|
+
} else {
|
|
491
|
+
newRanges.push(range);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (changed) {
|
|
495
|
+
selectionRanges.value = newRanges;
|
|
496
|
+
emitSelectionChange();
|
|
497
|
+
}
|
|
498
|
+
});
|
|
461
499
|
function addListener() {
|
|
462
500
|
removeListener();
|
|
463
501
|
const el = tableContainerRef.value;
|
|
@@ -475,14 +513,22 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
475
513
|
stopAutoScroll();
|
|
476
514
|
}
|
|
477
515
|
function normalizeRange(range) {
|
|
478
|
-
const {
|
|
479
|
-
|
|
480
|
-
|
|
516
|
+
const { begin, end } = range.index;
|
|
517
|
+
return {
|
|
518
|
+
minRow: Math.min(begin.row, end.row),
|
|
519
|
+
maxRow: Math.max(begin.row, end.row),
|
|
520
|
+
minCol: Math.min(begin.col, end.col),
|
|
521
|
+
maxCol: Math.max(begin.col, end.col)
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function makeRange(beginRow, beginCol, endRow, endCol) {
|
|
481
525
|
return {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
526
|
+
index: {
|
|
527
|
+
x: [beginCol, endCol],
|
|
528
|
+
y: [beginRow, endRow],
|
|
529
|
+
begin: { row: beginRow, col: beginCol },
|
|
530
|
+
end: { row: endRow, col: endCol }
|
|
531
|
+
}
|
|
486
532
|
};
|
|
487
533
|
}
|
|
488
534
|
function getColIndexByKey(colKey) {
|
|
@@ -568,20 +614,10 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
568
614
|
const colIndex = getColIndexByKey(colKey);
|
|
569
615
|
if (rowIndex < 0 || colIndex < 0) return;
|
|
570
616
|
const ctrlKey = e.ctrlKey || e.metaKey;
|
|
571
|
-
const range =
|
|
572
|
-
index: {
|
|
573
|
-
x: [colIndex, colIndex],
|
|
574
|
-
y: [rowIndex, rowIndex]
|
|
575
|
-
}
|
|
576
|
-
};
|
|
617
|
+
const range = makeRange(rowIndex, colIndex, rowIndex, colIndex);
|
|
577
618
|
if (e.shiftKey && anchorCell && shiftEnabled.value) {
|
|
578
619
|
const ranges = selectionRanges.value.slice();
|
|
579
|
-
const shiftRange =
|
|
580
|
-
index: {
|
|
581
|
-
x: [anchorCell.colIndex, colIndex],
|
|
582
|
-
y: [anchorCell.rowIndex, rowIndex]
|
|
583
|
-
}
|
|
584
|
-
};
|
|
620
|
+
const shiftRange = makeRange(anchorCell.rowIndex, anchorCell.colIndex, rowIndex, colIndex);
|
|
585
621
|
if (ranges.length) {
|
|
586
622
|
ranges[ranges.length - 1] = shiftRange;
|
|
587
623
|
} else {
|
|
@@ -621,12 +657,7 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
621
657
|
}
|
|
622
658
|
function updateSelectionEnd(endRowIndex, endColIndex) {
|
|
623
659
|
if (!anchorCell) return;
|
|
624
|
-
const newRange =
|
|
625
|
-
index: {
|
|
626
|
-
x: [anchorCell.colIndex, endColIndex],
|
|
627
|
-
y: [anchorCell.rowIndex, endRowIndex]
|
|
628
|
-
}
|
|
629
|
-
};
|
|
660
|
+
const newRange = makeRange(anchorCell.rowIndex, anchorCell.colIndex, endRowIndex, endColIndex);
|
|
630
661
|
const ranges = [...selectionRanges.value];
|
|
631
662
|
if (ranges.length > 0) {
|
|
632
663
|
ranges[ranges.length - 1] = newRange;
|
|
@@ -762,14 +793,7 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
762
793
|
if (rowCount === 0 || colCount === 0) return;
|
|
763
794
|
if (!selectionRanges.value.length) {
|
|
764
795
|
anchorCell = { rowIndex: 0, colIndex: 0 };
|
|
765
|
-
selectionRanges.value = [
|
|
766
|
-
{
|
|
767
|
-
index: {
|
|
768
|
-
x: [0, 0],
|
|
769
|
-
y: [0, 0]
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
];
|
|
796
|
+
selectionRanges.value = [makeRange(0, 0, 0, 0)];
|
|
773
797
|
emitSelectionChange();
|
|
774
798
|
scrollToCell(0, 0);
|
|
775
799
|
return;
|
|
@@ -779,17 +803,12 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
779
803
|
const ranges = [...selectionRanges.value];
|
|
780
804
|
const range = ranges.length > 0 ? ranges[ranges.length - 1] : null;
|
|
781
805
|
if (!range) return;
|
|
782
|
-
const {
|
|
783
|
-
let newEndRow =
|
|
784
|
-
let newEndCol =
|
|
806
|
+
const { begin, end } = range.index;
|
|
807
|
+
let newEndRow = end.row + rowDelta;
|
|
808
|
+
let newEndCol = end.col + colDelta;
|
|
785
809
|
newEndRow = clamp(newEndRow, 0, rowCount - 1);
|
|
786
810
|
newEndCol = clamp(newEndCol, 0, colCount - 1);
|
|
787
|
-
ranges[ranges.length - 1] =
|
|
788
|
-
index: {
|
|
789
|
-
x: [index.x[0], newEndCol],
|
|
790
|
-
y: [index.y[0], newEndRow]
|
|
791
|
-
}
|
|
792
|
-
};
|
|
811
|
+
ranges[ranges.length - 1] = makeRange(begin.row, begin.col, newEndRow, newEndCol);
|
|
793
812
|
selectionRanges.value = ranges;
|
|
794
813
|
scrollToCell(newEndRow, newEndCol);
|
|
795
814
|
} else {
|
|
@@ -808,14 +827,7 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
808
827
|
newCol = tabCol;
|
|
809
828
|
}
|
|
810
829
|
anchorCell = { rowIndex: newRow, colIndex: newCol };
|
|
811
|
-
selectionRanges.value = [
|
|
812
|
-
{
|
|
813
|
-
index: {
|
|
814
|
-
x: [newCol, newCol],
|
|
815
|
-
y: [newRow, newRow]
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
];
|
|
830
|
+
selectionRanges.value = [makeRange(newRow, newCol, newRow, newCol)];
|
|
819
831
|
scrollToCell(newRow, newCol);
|
|
820
832
|
}
|
|
821
833
|
emitSelectionChange();
|
|
@@ -928,8 +940,10 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
|
928
940
|
const useAreaSelectionName = "useAreaSelection";
|
|
929
941
|
useAreaSelection[MY_FN_NAME] = useAreaSelectionName;
|
|
930
942
|
const ON_DEMAND_FEATURE = {
|
|
931
|
-
[useAreaSelectionName]: () => {
|
|
932
|
-
|
|
943
|
+
[useAreaSelectionName]: (props) => {
|
|
944
|
+
if ("useAreaSelection" in props) {
|
|
945
|
+
console.warn("useAreaSelection is not registered");
|
|
946
|
+
}
|
|
933
947
|
return {
|
|
934
948
|
config: computed(() => ({ enabled: false })),
|
|
935
949
|
isSelecting: ref(false),
|
|
@@ -947,7 +961,7 @@ const ON_DEMAND_FEATURE = {
|
|
|
947
961
|
function registerFeature(feature) {
|
|
948
962
|
const features = Array.isArray(feature) ? feature : [feature];
|
|
949
963
|
features.forEach((f) => {
|
|
950
|
-
const fnName = f
|
|
964
|
+
const fnName = f[MY_FN_NAME];
|
|
951
965
|
if (!fnName) {
|
|
952
966
|
console.warn("invalid feature");
|
|
953
967
|
return;
|
|
@@ -1209,9 +1223,12 @@ function useFixedCol(props, colKeyGen, getFixedColPosition, tableHeaders, tableH
|
|
|
1209
1223
|
}
|
|
1210
1224
|
const leftShadowCol = [];
|
|
1211
1225
|
const rightShadowCol = [];
|
|
1212
|
-
tableHeadersForCalc.value.
|
|
1226
|
+
const len = tableHeadersForCalc.value.length;
|
|
1227
|
+
for (let level = 0; level < len; level++) {
|
|
1228
|
+
const row = tableHeadersForCalc.value[level];
|
|
1213
1229
|
let left = 0;
|
|
1214
|
-
row.
|
|
1230
|
+
for (let i = 0, rowLen = row.length; i < rowLen; i++) {
|
|
1231
|
+
const col = row[i];
|
|
1215
1232
|
const position = getFixedColPositionValue(col);
|
|
1216
1233
|
const isFixedLeft = col.fixed === "left";
|
|
1217
1234
|
const isFixedRight = col.fixed === "right";
|
|
@@ -1226,8 +1243,8 @@ function useFixedCol(props, colKeyGen, getFixedColPosition, tableHeaders, tableH
|
|
|
1226
1243
|
rightShadowCol[level] = col;
|
|
1227
1244
|
}
|
|
1228
1245
|
}
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1231
1248
|
if (props.fixedColShadow) {
|
|
1232
1249
|
fixedShadowCols.value = leftShadowCol.concat(rightShadowCol).filter(Boolean);
|
|
1233
1250
|
}
|
|
@@ -1382,7 +1399,7 @@ function useHighlight(props, stkTableId, tableContainerRef) {
|
|
|
1382
1399
|
if (method === "animation") {
|
|
1383
1400
|
cellEl.animate(keyframe, duration);
|
|
1384
1401
|
} else {
|
|
1385
|
-
highlightCellsInCssKeyFrame(cellEl, rowKeyValue, className, duration);
|
|
1402
|
+
highlightCellsInCssKeyFrame(cellEl, rowKeyValue, colKeyValue, className, duration);
|
|
1386
1403
|
}
|
|
1387
1404
|
}
|
|
1388
1405
|
function setHighlightDimRow(rowKeyValues, option = {}) {
|
|
@@ -1442,18 +1459,22 @@ function useHighlight(props, stkTableId, tableContainerRef) {
|
|
|
1442
1459
|
}
|
|
1443
1460
|
rowElTemp.forEach((el) => el.classList.add(className));
|
|
1444
1461
|
}
|
|
1445
|
-
function highlightCellsInCssKeyFrame(cellEl, rowKeyValue, className, duration) {
|
|
1462
|
+
function highlightCellsInCssKeyFrame(cellEl, rowKeyValue, colKeyValue, className, duration) {
|
|
1446
1463
|
if (cellEl.classList.contains(className)) {
|
|
1447
1464
|
cellEl.classList.remove(className);
|
|
1448
1465
|
void cellEl.offsetHeight;
|
|
1449
1466
|
}
|
|
1450
1467
|
cellEl.classList.add(className);
|
|
1451
|
-
|
|
1468
|
+
const cellKey = `${rowKeyValue}-${colKeyValue}`;
|
|
1469
|
+
window.clearTimeout(highlightDimCellsTimeout.get(cellKey));
|
|
1470
|
+
if (!duration) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1452
1473
|
highlightDimCellsTimeout.set(
|
|
1453
|
-
|
|
1474
|
+
cellKey,
|
|
1454
1475
|
window.setTimeout(() => {
|
|
1455
1476
|
cellEl.classList.remove(className);
|
|
1456
|
-
highlightDimCellsTimeout.delete(
|
|
1477
|
+
highlightDimCellsTimeout.delete(cellKey);
|
|
1457
1478
|
}, duration)
|
|
1458
1479
|
);
|
|
1459
1480
|
}
|
|
@@ -2819,11 +2840,15 @@ const _hoisted_14 = {
|
|
|
2819
2840
|
class: "vt-x-left"
|
|
2820
2841
|
};
|
|
2821
2842
|
const _hoisted_15 = ["colspan"];
|
|
2822
|
-
const _hoisted_16 = {
|
|
2843
|
+
const _hoisted_16 = {
|
|
2844
|
+
class: "table-cell-wrapper",
|
|
2845
|
+
tabindex: "-1"
|
|
2846
|
+
};
|
|
2823
2847
|
const _hoisted_17 = ["title"];
|
|
2824
2848
|
const _hoisted_18 = {
|
|
2825
2849
|
key: 2,
|
|
2826
|
-
class: "table-cell-wrapper"
|
|
2850
|
+
class: "table-cell-wrapper",
|
|
2851
|
+
tabindex: "-1"
|
|
2827
2852
|
};
|
|
2828
2853
|
const _hoisted_19 = ["title"];
|
|
2829
2854
|
const _hoisted_20 = { key: 2 };
|
|
@@ -3495,9 +3520,12 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
3495
3520
|
dom.scrollLeft += distance;
|
|
3496
3521
|
}
|
|
3497
3522
|
}
|
|
3523
|
+
let scrollRAFScheduled = false;
|
|
3498
3524
|
function onTableScroll(e) {
|
|
3499
|
-
if (!(e == null ? void 0 : e.target)) return;
|
|
3525
|
+
if (!(e == null ? void 0 : e.target) || scrollRAFScheduled) return;
|
|
3526
|
+
scrollRAFScheduled = true;
|
|
3500
3527
|
requestAnimationFrame(() => {
|
|
3528
|
+
scrollRAFScheduled = false;
|
|
3501
3529
|
const { scrollTop, scrollLeft } = e.target;
|
|
3502
3530
|
const { scrollTop: vScrollTop } = virtualScroll.value;
|
|
3503
3531
|
const { scrollLeft: vScrollLeft } = virtualScrollX.value;
|
|
@@ -3928,6 +3956,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
3928
3956
|
col.customFooterCell ? (openBlock(), createBlock(resolveDynamicComponent(col.customFooterCell), {
|
|
3929
3957
|
key: 0,
|
|
3930
3958
|
class: "table-cell-wrapper",
|
|
3959
|
+
tabindex: "-1",
|
|
3931
3960
|
col,
|
|
3932
3961
|
row: footRow,
|
|
3933
3962
|
rowIndex: footRowIndex,
|
|
@@ -3936,6 +3965,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
3936
3965
|
}, null, 8, ["col", "row", "rowIndex", "colIndex", "cellValue"])) : createCommentVNode("", true),
|
|
3937
3966
|
createElementVNode("div", {
|
|
3938
3967
|
class: "table-cell-wrapper",
|
|
3968
|
+
tabindex: "-1",
|
|
3939
3969
|
title: footRow[col.dataIndex] || ""
|
|
3940
3970
|
}, [
|
|
3941
3971
|
footRow[col.dataIndex] != null ? (openBlock(), createElementBlock("span", _hoisted_10, toDisplayString(footRow[col.dataIndex]), 1)) : createCommentVNode("", true)
|
|
@@ -4002,6 +4032,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
4002
4032
|
col.customCell ? (openBlock(), createBlock(resolveDynamicComponent(col.customCell), {
|
|
4003
4033
|
key: 0,
|
|
4004
4034
|
class: "table-cell-wrapper",
|
|
4035
|
+
tabindex: "-1",
|
|
4005
4036
|
col,
|
|
4006
4037
|
row,
|
|
4007
4038
|
rowIndex: getRowIndex(rowIndex),
|
|
@@ -4024,16 +4055,19 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
4024
4055
|
}, 1032, ["col", "row", "rowIndex", "colIndex", "cellValue", "expanded", "tree-expanded"])) : !col.type ? (openBlock(), createElementBlock("div", {
|
|
4025
4056
|
key: 1,
|
|
4026
4057
|
class: "table-cell-wrapper",
|
|
4058
|
+
tabindex: "-1",
|
|
4027
4059
|
title: row[col.dataIndex] || ""
|
|
4028
4060
|
}, toDisplayString((row && row[col.dataIndex]) !== void 0 ? row && row[col.dataIndex] : getEmptyCellText.value(col, row)), 9, _hoisted_17)) : col.type === "seq" ? (openBlock(), createElementBlock("div", _hoisted_18, toDisplayString((props.seqConfig.startIndex || 0) + getRowIndex(rowIndex) + 1), 1)) : col.type === "tree-node" ? (openBlock(), createBlock(_sfc_main$2, {
|
|
4029
4061
|
key: 3,
|
|
4030
4062
|
class: "table-cell-wrapper",
|
|
4063
|
+
tabindex: "-1",
|
|
4031
4064
|
col,
|
|
4032
4065
|
row,
|
|
4033
4066
|
onClick: ($event) => triangleClick($event, row, col)
|
|
4034
4067
|
}, null, 8, ["col", "row", "onClick"])) : (openBlock(), createElementBlock("div", {
|
|
4035
4068
|
key: 4,
|
|
4036
4069
|
class: "table-cell-wrapper",
|
|
4070
|
+
tabindex: "-1",
|
|
4037
4071
|
title: row[col.dataIndex] || ""
|
|
4038
4072
|
}, [
|
|
4039
4073
|
col.type === "dragRow" ? (openBlock(), createBlock(DragHandle, {
|
package/lib/style.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* name: stk-table-vue
|
|
3
|
-
* version: v0.11.
|
|
3
|
+
* version: v0.11.11
|
|
4
4
|
* description: High performance realtime virtual table for vue3 and vue2.7
|
|
5
5
|
* author: japlus
|
|
6
6
|
* homepage: https://ja-plus.github.io/stk-table-vue/
|
|
@@ -395,7 +395,7 @@
|
|
|
395
395
|
.stk-table .stk-footer{
|
|
396
396
|
position:sticky;
|
|
397
397
|
bottom:0;
|
|
398
|
-
z-index:
|
|
398
|
+
z-index:2;
|
|
399
399
|
}
|
|
400
400
|
.stk-table .stk-footer tr{
|
|
401
401
|
background-color:var(--tf-bgc);
|
package/package.json
CHANGED
|
@@ -116,13 +116,14 @@
|
|
|
116
116
|
:is="col.customFooterCell"
|
|
117
117
|
v-if="col.customFooterCell"
|
|
118
118
|
class="table-cell-wrapper"
|
|
119
|
+
tabindex="-1"
|
|
119
120
|
:col="col"
|
|
120
121
|
:row="footRow"
|
|
121
122
|
:rowIndex="footRowIndex"
|
|
122
123
|
:colIndex="colIndex"
|
|
123
124
|
:cellValue="footRow[col.dataIndex]"
|
|
124
125
|
/>
|
|
125
|
-
<div class="table-cell-wrapper" :title="footRow[col.dataIndex] || ''">
|
|
126
|
+
<div class="table-cell-wrapper" tabindex="-1" :title="footRow[col.dataIndex] || ''">
|
|
126
127
|
<span v-if="footRow[col.dataIndex] != null">{{ footRow[col.dataIndex] }}</span>
|
|
127
128
|
</div>
|
|
128
129
|
</td>
|
|
@@ -153,7 +154,7 @@
|
|
|
153
154
|
>
|
|
154
155
|
<td v-if="virtualX_on" class="vt-x-left"></td>
|
|
155
156
|
<td v-if="row && row.__EXP_R__" :colspan="virtualX_columnPart.length">
|
|
156
|
-
<div class="table-cell-wrapper">
|
|
157
|
+
<div class="table-cell-wrapper" tabindex="-1">
|
|
157
158
|
<slot name="expand" :row="row.__EXP_R__" :col="row.__EXP_C__">
|
|
158
159
|
{{ (row.__EXP_R__ && row.__EXP_C__ && row.__EXP_R__[row.__EXP_C__.dataIndex]) || '' }}
|
|
159
160
|
</slot>
|
|
@@ -172,6 +173,7 @@
|
|
|
172
173
|
:is="col.customCell"
|
|
173
174
|
v-if="col.customCell"
|
|
174
175
|
class="table-cell-wrapper"
|
|
176
|
+
tabindex="-1"
|
|
175
177
|
:col="col"
|
|
176
178
|
:row="row"
|
|
177
179
|
:rowIndex="getRowIndex(rowIndex)"
|
|
@@ -187,20 +189,21 @@
|
|
|
187
189
|
<DragHandle @dragstart="onTrDragStart($event, getRowIndex(rowIndex))" />
|
|
188
190
|
</template>
|
|
189
191
|
</component>
|
|
190
|
-
<div v-else-if="!col.type" class="table-cell-wrapper" :title="row[col.dataIndex] || ''">
|
|
192
|
+
<div v-else-if="!col.type" class="table-cell-wrapper" tabindex="-1" :title="row[col.dataIndex] || ''">
|
|
191
193
|
{{ (row && row[col.dataIndex]) !== void 0 ? row && row[col.dataIndex] : getEmptyCellText(col, row) }}
|
|
192
194
|
</div>
|
|
193
|
-
<div v-else-if="col.type === 'seq'" class="table-cell-wrapper">
|
|
195
|
+
<div v-else-if="col.type === 'seq'" class="table-cell-wrapper" tabindex="-1">
|
|
194
196
|
{{ (props.seqConfig.startIndex || 0) + getRowIndex(rowIndex) + 1 }}
|
|
195
197
|
</div>
|
|
196
198
|
<TreeNodeCell
|
|
197
199
|
v-else-if="col.type === 'tree-node'"
|
|
198
200
|
class="table-cell-wrapper"
|
|
201
|
+
tabindex="-1"
|
|
199
202
|
:col="col"
|
|
200
203
|
:row="row"
|
|
201
204
|
@click="triangleClick($event, row, col)"
|
|
202
205
|
></TreeNodeCell>
|
|
203
|
-
<div v-else class="table-cell-wrapper" :title="row[col.dataIndex] || ''">
|
|
206
|
+
<div v-else class="table-cell-wrapper" tabindex="-1" :title="row[col.dataIndex] || ''">
|
|
204
207
|
<DragHandle v-if="col.type === 'dragRow'" @dragstart="onTrDragStart($event, getRowIndex(rowIndex))" />
|
|
205
208
|
<TriangleIcon v-else-if="col.type === 'expand'" @click="triangleClick($event, row, col)" />
|
|
206
209
|
<span v-if="row[col.dataIndex] != null">{{ row[col.dataIndex] }}</span>
|
|
@@ -1450,13 +1453,17 @@ function onTableWheel(e: WheelEvent) {
|
|
|
1450
1453
|
}
|
|
1451
1454
|
}
|
|
1452
1455
|
|
|
1456
|
+
/** Prevent re-entrant requestAnimationFrame in onTableScroll */
|
|
1457
|
+
let scrollRAFScheduled = false;
|
|
1458
|
+
|
|
1453
1459
|
/**
|
|
1454
1460
|
* @param e scrollEvent
|
|
1455
1461
|
*/
|
|
1456
1462
|
function onTableScroll(e: Event) {
|
|
1457
|
-
if (!e?.target) return;
|
|
1458
|
-
|
|
1463
|
+
if (!e?.target || scrollRAFScheduled) return;
|
|
1464
|
+
scrollRAFScheduled = true;
|
|
1459
1465
|
requestAnimationFrame(() => {
|
|
1466
|
+
scrollRAFScheduled = false;
|
|
1460
1467
|
const { scrollTop, scrollLeft } = e.target as HTMLElement;
|
|
1461
1468
|
const { scrollTop: vScrollTop } = virtualScroll.value;
|
|
1462
1469
|
const { scrollLeft: vScrollLeft } = virtualScrollX.value;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ref, ShallowRef, computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
1
|
+
import { Ref, ShallowRef, computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
2
2
|
import { AreaSelectionConfig, AreaSelectionRange, CellKeyGen, ColKeyGen, StkTableColumn, UniqKey } from '../types';
|
|
3
3
|
import { VirtualScrollStore, VirtualScrollXStore } from '../useVirtualScroll';
|
|
4
4
|
import { getClosestColKey, getClosestTrIndex } from '../utils';
|
|
@@ -193,6 +193,56 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
193
193
|
removeListener();
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* 监听数据行数/列数变化,当行列变少时钳制选区与锚点,避免越界
|
|
198
|
+
* en: Watch row/col count changes, clamp selection ranges and anchor to avoid out-of-bounds
|
|
199
|
+
*/
|
|
200
|
+
watch([() => dataSourceCopy.value.length, () => tableHeaderLast.value.length], ([rowCount, colCount]) => {
|
|
201
|
+
if (!config.value.enabled) return;
|
|
202
|
+
|
|
203
|
+
// 钳制锚点
|
|
204
|
+
if (anchorCell) {
|
|
205
|
+
if (rowCount === 0 || colCount === 0) {
|
|
206
|
+
anchorCell = null;
|
|
207
|
+
} else {
|
|
208
|
+
anchorCell.rowIndex = clamp(anchorCell.rowIndex, 0, rowCount - 1);
|
|
209
|
+
anchorCell.colIndex = clamp(anchorCell.colIndex, 0, colCount - 1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!selectionRanges.value.length) return;
|
|
214
|
+
|
|
215
|
+
// 行或列为 0 时清空选区
|
|
216
|
+
if (rowCount === 0 || colCount === 0) {
|
|
217
|
+
clearSelectedArea();
|
|
218
|
+
emitSelectionChange();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const maxRow = rowCount - 1;
|
|
223
|
+
const maxCol = colCount - 1;
|
|
224
|
+
let changed = false;
|
|
225
|
+
const newRanges: AreaSelectionRange[] = [];
|
|
226
|
+
for (const range of selectionRanges.value) {
|
|
227
|
+
const { begin, end } = range.index;
|
|
228
|
+
const nbRow = clamp(begin.row, 0, maxRow);
|
|
229
|
+
const nbCol = clamp(begin.col, 0, maxCol);
|
|
230
|
+
const neRow = clamp(end.row, 0, maxRow);
|
|
231
|
+
const neCol = clamp(end.col, 0, maxCol);
|
|
232
|
+
if (nbRow !== begin.row || nbCol !== begin.col || neRow !== end.row || neCol !== end.col) {
|
|
233
|
+
changed = true;
|
|
234
|
+
newRanges.push(makeRange(nbRow, nbCol, neRow, neCol));
|
|
235
|
+
} else {
|
|
236
|
+
newRanges.push(range);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (changed) {
|
|
241
|
+
selectionRanges.value = newRanges;
|
|
242
|
+
emitSelectionChange();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
196
246
|
function addListener() {
|
|
197
247
|
removeListener();
|
|
198
248
|
const el = tableContainerRef.value;
|
|
@@ -213,14 +263,27 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
213
263
|
|
|
214
264
|
/** 获取归一化(min/max)后的选区范围 */
|
|
215
265
|
function normalizeRange(range: AreaSelectionRange) {
|
|
216
|
-
const {
|
|
217
|
-
const [x1, x2] = index.x;
|
|
218
|
-
const [y1, y2] = index.y;
|
|
266
|
+
const { begin, end } = range.index;
|
|
219
267
|
return {
|
|
220
|
-
minRow: Math.min(
|
|
221
|
-
maxRow: Math.max(
|
|
222
|
-
minCol: Math.min(
|
|
223
|
-
maxCol: Math.max(
|
|
268
|
+
minRow: Math.min(begin.row, end.row),
|
|
269
|
+
maxRow: Math.max(begin.row, end.row),
|
|
270
|
+
minCol: Math.min(begin.col, end.col),
|
|
271
|
+
maxCol: Math.max(begin.col, end.col),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 构造选区范围。begin = 拖拽起点,end = 拖拽终点。
|
|
277
|
+
* 同时填充已废弃的 x/y 字段以保证向后兼容。
|
|
278
|
+
*/
|
|
279
|
+
function makeRange(beginRow: number, beginCol: number, endRow: number, endCol: number): AreaSelectionRange {
|
|
280
|
+
return {
|
|
281
|
+
index: {
|
|
282
|
+
x: [beginCol, endCol],
|
|
283
|
+
y: [beginRow, endRow],
|
|
284
|
+
begin: { row: beginRow, col: beginCol },
|
|
285
|
+
end: { row: endRow, col: endCol },
|
|
286
|
+
},
|
|
224
287
|
};
|
|
225
288
|
}
|
|
226
289
|
|
|
@@ -334,21 +397,11 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
334
397
|
|
|
335
398
|
const ctrlKey = e.ctrlKey || e.metaKey;
|
|
336
399
|
|
|
337
|
-
const range: AreaSelectionRange =
|
|
338
|
-
index: {
|
|
339
|
-
x: [colIndex, colIndex],
|
|
340
|
-
y: [rowIndex, rowIndex],
|
|
341
|
-
},
|
|
342
|
-
};
|
|
400
|
+
const range: AreaSelectionRange = makeRange(rowIndex, colIndex, rowIndex, colIndex);
|
|
343
401
|
// Shift 扩选:从锚点扩展到当前位置,更新最后一个区域
|
|
344
402
|
if (e.shiftKey && anchorCell && shiftEnabled.value) {
|
|
345
403
|
const ranges = selectionRanges.value.slice();
|
|
346
|
-
const shiftRange: AreaSelectionRange =
|
|
347
|
-
index: {
|
|
348
|
-
x: [anchorCell.colIndex, colIndex],
|
|
349
|
-
y: [anchorCell.rowIndex, rowIndex],
|
|
350
|
-
},
|
|
351
|
-
};
|
|
404
|
+
const shiftRange: AreaSelectionRange = makeRange(anchorCell.rowIndex, anchorCell.colIndex, rowIndex, colIndex);
|
|
352
405
|
if (ranges.length) {
|
|
353
406
|
ranges[ranges.length - 1] = shiftRange;
|
|
354
407
|
} else {
|
|
@@ -408,12 +461,7 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
408
461
|
/** 更新最后一个选区的终点(拖拽过程中) */
|
|
409
462
|
function updateSelectionEnd(endRowIndex: number, endColIndex: number) {
|
|
410
463
|
if (!anchorCell) return;
|
|
411
|
-
const newRange: AreaSelectionRange =
|
|
412
|
-
index: {
|
|
413
|
-
x: [anchorCell.colIndex, endColIndex],
|
|
414
|
-
y: [anchorCell.rowIndex, endRowIndex],
|
|
415
|
-
},
|
|
416
|
-
};
|
|
464
|
+
const newRange: AreaSelectionRange = makeRange(anchorCell.rowIndex, anchorCell.colIndex, endRowIndex, endColIndex);
|
|
417
465
|
const ranges = [...selectionRanges.value];
|
|
418
466
|
if (ranges.length > 0) {
|
|
419
467
|
ranges[ranges.length - 1] = newRange;
|
|
@@ -617,14 +665,7 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
617
665
|
// en: If no selection, start from the first cell
|
|
618
666
|
if (!selectionRanges.value.length) {
|
|
619
667
|
anchorCell = { rowIndex: 0, colIndex: 0 };
|
|
620
|
-
selectionRanges.value = [
|
|
621
|
-
{
|
|
622
|
-
index: {
|
|
623
|
-
x: [0, 0],
|
|
624
|
-
y: [0, 0],
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
];
|
|
668
|
+
selectionRanges.value = [makeRange(0, 0, 0, 0)];
|
|
628
669
|
emitSelectionChange();
|
|
629
670
|
scrollToCell(0, 0);
|
|
630
671
|
return;
|
|
@@ -635,24 +676,19 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
635
676
|
|
|
636
677
|
// Shift 扩展选区,否则移动单格选区
|
|
637
678
|
if (e.shiftKey && isArrowKey && shiftEnabled.value) {
|
|
638
|
-
//
|
|
679
|
+
// 扩展选区:保留 begin,更新最后一个区域的 end
|
|
639
680
|
const ranges = [...selectionRanges.value];
|
|
640
681
|
const range = ranges.length > 0 ? ranges[ranges.length - 1] : null;
|
|
641
682
|
if (!range) return;
|
|
642
|
-
const {
|
|
643
|
-
let newEndRow =
|
|
644
|
-
let newEndCol =
|
|
683
|
+
const { begin, end } = range.index;
|
|
684
|
+
let newEndRow = end.row + rowDelta;
|
|
685
|
+
let newEndCol = end.col + colDelta;
|
|
645
686
|
|
|
646
687
|
// 边界检查
|
|
647
688
|
newEndRow = clamp(newEndRow, 0, rowCount - 1);
|
|
648
689
|
newEndCol = clamp(newEndCol, 0, colCount - 1);
|
|
649
690
|
|
|
650
|
-
ranges[ranges.length - 1] =
|
|
651
|
-
index: {
|
|
652
|
-
x: [index.x[0], newEndCol],
|
|
653
|
-
y: [index.y[0], newEndRow],
|
|
654
|
-
},
|
|
655
|
-
};
|
|
691
|
+
ranges[ranges.length - 1] = makeRange(begin.row, begin.col, newEndRow, newEndCol);
|
|
656
692
|
selectionRanges.value = ranges;
|
|
657
693
|
|
|
658
694
|
scrollToCell(newEndRow, newEndCol);
|
|
@@ -681,14 +717,7 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
681
717
|
|
|
682
718
|
// 更新锚点和选区(移动单格时清空其他区域,仅保留新位置)
|
|
683
719
|
anchorCell = { rowIndex: newRow, colIndex: newCol };
|
|
684
|
-
selectionRanges.value = [
|
|
685
|
-
{
|
|
686
|
-
index: {
|
|
687
|
-
x: [newCol, newCol],
|
|
688
|
-
y: [newRow, newRow],
|
|
689
|
-
},
|
|
690
|
-
},
|
|
691
|
-
];
|
|
720
|
+
selectionRanges.value = [makeRange(newRow, newCol, newRow, newCol)];
|
|
692
721
|
|
|
693
722
|
scrollToCell(newRow, newCol);
|
|
694
723
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { computed, ref } from 'vue';
|
|
2
2
|
import { useAreaSelection, useAreaSelectionName } from './features';
|
|
3
|
+
import { MY_FN_NAME } from './features/const';
|
|
3
4
|
|
|
4
5
|
type OnDemandFeature = {
|
|
5
6
|
[useAreaSelectionName]: typeof useAreaSelection<any>;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
export const ON_DEMAND_FEATURE: OnDemandFeature = {
|
|
9
|
-
[useAreaSelectionName]: (
|
|
10
|
-
|
|
10
|
+
[useAreaSelectionName]: (props => {
|
|
11
|
+
if ('useAreaSelection' in props) {
|
|
12
|
+
console.warn('useAreaSelection is not registered');
|
|
13
|
+
}
|
|
11
14
|
return {
|
|
12
15
|
config: computed(() => ({ enabled: false })),
|
|
13
16
|
isSelecting: ref(false),
|
|
@@ -26,7 +29,7 @@ type Feature = OnDemandFeature[keyof OnDemandFeature];
|
|
|
26
29
|
export function registerFeature(feature: Feature | Feature[]) {
|
|
27
30
|
const features = Array.isArray(feature) ? feature : [feature];
|
|
28
31
|
features.forEach(f => {
|
|
29
|
-
const fnName = (f as any)
|
|
32
|
+
const fnName = (f as any)[MY_FN_NAME];
|
|
30
33
|
if (!fnName) {
|
|
31
34
|
console.warn('invalid feature');
|
|
32
35
|
return;
|
package/src/StkTable/style.less
CHANGED
|
@@ -349,8 +349,14 @@ export type RowActiveOption<DT> = {
|
|
|
349
349
|
/** 单元格选区范围 */
|
|
350
350
|
export type AreaSelectionRange = {
|
|
351
351
|
index: {
|
|
352
|
+
/** column index range @deprecated */
|
|
352
353
|
x: [number, number];
|
|
354
|
+
/** row index range @deprecated */
|
|
353
355
|
y: [number, number];
|
|
356
|
+
/** start point index*/
|
|
357
|
+
begin: { row: number; col: number };
|
|
358
|
+
/** end point index */
|
|
359
|
+
end: { row: number; col: number };
|
|
354
360
|
};
|
|
355
361
|
};
|
|
356
362
|
|
|
@@ -101,12 +101,15 @@ export function useFixedCol<DT extends Record<string, any>>(
|
|
|
101
101
|
const leftShadowCol: StkTableColumn<DT>[] = [];
|
|
102
102
|
/** 右侧展示阴影的列 */
|
|
103
103
|
const rightShadowCol: StkTableColumn<DT>[] = [];
|
|
104
|
-
tableHeadersForCalc.value.
|
|
104
|
+
const len = tableHeadersForCalc.value.length;
|
|
105
|
+
for (let level = 0; level < len; level++) {
|
|
106
|
+
const row = tableHeadersForCalc.value[level];
|
|
105
107
|
/**
|
|
106
108
|
* 左侧第n个fixed:left 计算要加上前面所有left 的列宽。
|
|
107
109
|
*/
|
|
108
110
|
let left = 0;
|
|
109
|
-
row.
|
|
111
|
+
for (let i = 0, rowLen = row.length; i < rowLen; i++) {
|
|
112
|
+
const col = row[i];
|
|
110
113
|
const position = getFixedColPositionValue(col);
|
|
111
114
|
const isFixedLeft = col.fixed === 'left';
|
|
112
115
|
const isFixedRight = col.fixed === 'right';
|
|
@@ -125,8 +128,8 @@ export function useFixedCol<DT extends Record<string, any>>(
|
|
|
125
128
|
rightShadowCol[level] = col;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
130
133
|
|
|
131
134
|
if (props.fixedColShadow) {
|
|
132
135
|
fixedShadowCols.value = leftShadowCol.concat(rightShadowCol).filter(Boolean) as StkTableColumn<DT>[];
|
|
@@ -114,7 +114,7 @@ export function useHighlight(props: any, stkTableId: string, tableContainerRef:
|
|
|
114
114
|
if (method === 'animation') {
|
|
115
115
|
cellEl.animate(keyframe, duration);
|
|
116
116
|
} else {
|
|
117
|
-
highlightCellsInCssKeyFrame(cellEl, rowKeyValue, className, duration);
|
|
117
|
+
highlightCellsInCssKeyFrame(cellEl, rowKeyValue, colKeyValue, className, duration);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -198,18 +198,22 @@ export function useHighlight(props: any, stkTableId: string, tableContainerRef:
|
|
|
198
198
|
* 使用css @keyframes动画,实现高亮单元格动画
|
|
199
199
|
* 此方案作为兼容方式。v0.3.4 将使用Element.animate 接口实现动画。
|
|
200
200
|
*/
|
|
201
|
-
function highlightCellsInCssKeyFrame(cellEl: HTMLElement, rowKeyValue: UniqKey, className: string, duration: number) {
|
|
201
|
+
function highlightCellsInCssKeyFrame(cellEl: HTMLElement, rowKeyValue: UniqKey, colKeyValue: string, className: string, duration: number) {
|
|
202
202
|
if (cellEl.classList.contains(className)) {
|
|
203
203
|
cellEl.classList.remove(className);
|
|
204
204
|
void cellEl.offsetHeight; // 通知浏览器重绘
|
|
205
205
|
}
|
|
206
206
|
cellEl.classList.add(className);
|
|
207
|
-
|
|
207
|
+
const cellKey = `${rowKeyValue}-${colKeyValue}`;
|
|
208
|
+
window.clearTimeout(highlightDimCellsTimeout.get(cellKey));
|
|
209
|
+
if (!duration) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
208
212
|
highlightDimCellsTimeout.set(
|
|
209
|
-
|
|
213
|
+
cellKey,
|
|
210
214
|
window.setTimeout(() => {
|
|
211
215
|
cellEl.classList.remove(className);
|
|
212
|
-
highlightDimCellsTimeout.delete(
|
|
216
|
+
highlightDimCellsTimeout.delete(cellKey);
|
|
213
217
|
}, duration),
|
|
214
218
|
);
|
|
215
219
|
}
|