stk-table-vue 0.11.7 → 0.11.8
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 +9 -8
- package/lib/index-C40Rz-HL.js +1 -1
- package/lib/src/StkTable/types/index.d.ts +6 -5
- package/lib/src/StkTable/useColResize.d.ts +1 -1
- package/lib/src/StkTable/useVirtualScroll.d.ts +1 -1
- package/lib/src/StkTable/utils/index.d.ts +1 -0
- package/lib/stk-table-vue.js +114 -72
- package/lib/style.css +1 -1
- package/package.json +18 -9
- package/src/StkTable/StkTable.vue +9 -1
- package/src/StkTable/types/index.ts +7 -5
- package/src/StkTable/useColResize.ts +7 -5
- package/src/StkTable/useHighlight.ts +3 -1
- package/src/StkTable/useKeyboardArrowScroll.ts +15 -13
- package/src/StkTable/useMergeCells.ts +2 -2
- package/src/StkTable/useScrollbar.ts +16 -6
- package/src/StkTable/useVirtualScroll.ts +69 -34
- package/src/StkTable/utils/index.ts +12 -2
package/README.md
CHANGED
|
@@ -12,21 +12,22 @@
|
|
|
12
12
|
</p>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Stk Table Vue(Sticky Table) is a high-performance virtual list component based on Vue.
|
|
16
|
+
|
|
17
|
+
Smooth performance with tens of thousands of rows
|
|
18
|
+
|
|
19
|
+
Used for real-time data display, with data highlighting and dynamic effects.
|
|
20
|
+
|
|
21
|
+
Support Vue3 and Vue2.7
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
## Documentation
|
|
23
|
-
### [Stk Table Vue Official
|
|
24
|
-
### [Stk Table Vue Official zh-CN](https://ja-plus.github.io/stk-table-vue/)
|
|
25
|
+
### [Stk Table Vue Official](https://ja-plus.github.io/stk-table-vue/)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
## Repo:
|
|
28
29
|
- [Github](https://github.com/ja-plus/stk-table-vue)
|
|
29
|
-
- [Gitee](https://gitee.com/japlus/stk-table-vue)
|
|
30
|
+
- [Gitee](https://gitee.com/japlus/stk-table-vue)
|
|
30
31
|
|
|
31
32
|
## Demo
|
|
32
33
|
[<span style="font-size: 16px;font-weight: bold;">Online Demo in stackblitz</span>](https://stackblitz.com/edit/vitejs-vite-ad91hh?file=src%2FDemo%2Findex.vue)
|
package/lib/index-C40Rz-HL.js
CHANGED
|
@@ -254,12 +254,13 @@ export type SortConfig<T extends Record<string, any>> = {
|
|
|
254
254
|
multiSortLimit?: number;
|
|
255
255
|
};
|
|
256
256
|
/** th td type */
|
|
257
|
-
export declare const
|
|
258
|
-
TH
|
|
259
|
-
TD
|
|
257
|
+
export declare const TagType: {
|
|
258
|
+
readonly TH: 0;
|
|
259
|
+
readonly TD: 1;
|
|
260
260
|
/** tfoot */
|
|
261
|
-
TF
|
|
262
|
-
}
|
|
261
|
+
readonly TF: 2;
|
|
262
|
+
};
|
|
263
|
+
export type TagType = (typeof TagType)[keyof typeof TagType];
|
|
263
264
|
export type HighlightConfig = {
|
|
264
265
|
/** Duration of the highlight in seconds */
|
|
265
266
|
duration?: number;
|
|
@@ -2,4 +2,4 @@ import { ComputedRef, Ref, ShallowRef } from 'vue';
|
|
|
2
2
|
import { StkTableColumn, UniqKey } from './types';
|
|
3
3
|
|
|
4
4
|
/** 列宽拖动 */
|
|
5
|
-
export declare function useColResize<DT extends Record<string, any>>(props: any, emits: any, tableContainerRef: Ref<HTMLElement | undefined>, tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>, colResizeIndicatorRef: Ref<HTMLElement | undefined>, colKeyGen: ComputedRef<(p: any) => UniqKey>, fixedCols: Ref<StkTableColumn<DT>[]
|
|
5
|
+
export declare function useColResize<DT extends Record<string, any>>(props: any, emits: any, tableContainerRef: Ref<HTMLElement | undefined>, tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>, colResizeIndicatorRef: Ref<HTMLElement | undefined>, colKeyGen: ComputedRef<(p: any) => UniqKey>, fixedCols: Ref<StkTableColumn<DT>[]>, onColWidthChange?: () => void): readonly [ComputedRef<((col: StkTableColumn<DT>) => boolean) | (() => any)>, Ref<boolean, boolean>, (e: MouseEvent, col: StkTableColumn<DT>, leftHandle?: boolean) => void];
|
|
@@ -75,4 +75,4 @@ export declare function useVirtualScroll(props: any, tableContainerRef: Ref<HTML
|
|
|
75
75
|
endIndex: number;
|
|
76
76
|
offsetLeft: number;
|
|
77
77
|
scrollLeft: number;
|
|
78
|
-
}>, import('vue').ComputedRef<any>, import('vue').ComputedRef<PrivateRowDT[]>, import('vue').ComputedRef<number>, import('vue').ComputedRef<any>, import('vue').ComputedRef<PrivateStkTableColumn<PrivateRowDT>[]>, import('vue').ComputedRef<number>, import('vue').ComputedRef<number>, (height?: number) => void, (height?: number) => void, () => void, (sTop?: number) => void, (sLeft?: number) => void, (rowKey: UniqKey, height?: number | null) => void, () => void];
|
|
78
|
+
}>, import('vue').ComputedRef<any>, import('vue').ComputedRef<PrivateRowDT[]>, import('vue').ComputedRef<number>, import('vue').ComputedRef<any>, import('vue').ComputedRef<PrivateStkTableColumn<PrivateRowDT>[]>, import('vue').ComputedRef<number>, import('vue').ComputedRef<number>, (height?: number) => void, (height?: number) => void, () => void, (sTop?: number) => void, (sLeft?: number) => void, (rowKey: UniqKey, height?: number | null) => void, () => void, () => void];
|
|
@@ -68,3 +68,4 @@ export declare function throttle<T extends (...args: any[]) => any>(fn: T, delay
|
|
|
68
68
|
* @returns A throttled function that executes on the next animation frame
|
|
69
69
|
*/
|
|
70
70
|
export declare function rafThrottle<T extends (...args: any[]) => any>(fn: T): (...args: Parameters<T>) => void;
|
|
71
|
+
export declare function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void;
|
package/lib/stk-table-vue.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* name: stk-table-vue
|
|
3
|
-
* version: v0.11.
|
|
3
|
+
* version: v0.11.8
|
|
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/
|
|
7
7
|
* license: MIT
|
|
8
8
|
*/
|
|
9
|
-
import { createElementBlock, openBlock, createElementVNode, defineComponent, normalizeStyle, createBlock, createCommentVNode, toDisplayString, ref, computed, onMounted, onBeforeUnmount, watch, shallowRef,
|
|
9
|
+
import { createElementBlock, openBlock, createElementVNode, defineComponent, normalizeStyle, createBlock, createCommentVNode, toDisplayString, ref, computed, onMounted, onBeforeUnmount, watch, shallowRef, nextTick, customRef, onUnmounted, toRaw, provide, toRef, normalizeClass, unref, renderSlot, Fragment, renderList, mergeProps, resolveDynamicComponent, withCtx, createTextVNode, createVNode, createApp, markRaw, getCurrentInstance, h } from "vue";
|
|
10
10
|
const _export_sfc = (sfc, props) => {
|
|
11
11
|
const target = sfc.__vccOpts || sfc;
|
|
12
12
|
for (const [key, val] of props) {
|
|
@@ -253,7 +253,7 @@ function getClosestColKey(target) {
|
|
|
253
253
|
function throttle(fn, delay) {
|
|
254
254
|
let timer;
|
|
255
255
|
let lastArgs = null;
|
|
256
|
-
const callFn =
|
|
256
|
+
const callFn = () => {
|
|
257
257
|
if (lastArgs) {
|
|
258
258
|
fn(...lastArgs);
|
|
259
259
|
lastArgs = null;
|
|
@@ -273,7 +273,7 @@ function throttle(fn, delay) {
|
|
|
273
273
|
function rafThrottle(fn) {
|
|
274
274
|
let rafId = null;
|
|
275
275
|
let lastArgs = null;
|
|
276
|
-
const callFn =
|
|
276
|
+
const callFn = () => {
|
|
277
277
|
if (lastArgs) {
|
|
278
278
|
fn(...lastArgs);
|
|
279
279
|
lastArgs = null;
|
|
@@ -886,12 +886,12 @@ function registerFeature(feature) {
|
|
|
886
886
|
ON_DEMAND_FEATURE[fnName] = f;
|
|
887
887
|
});
|
|
888
888
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
889
|
+
const TagType = {
|
|
890
|
+
TH: 0,
|
|
891
|
+
TD: 1,
|
|
892
|
+
/** tfoot */
|
|
893
|
+
TF: 2
|
|
894
|
+
};
|
|
895
895
|
function useAutoResize(tableContainerRef, initVirtualScroll, props, debounceMs) {
|
|
896
896
|
let resizeObserver = null;
|
|
897
897
|
let isObserved = false;
|
|
@@ -965,7 +965,7 @@ function useAutoResize(tableContainerRef, initVirtualScroll, props, debounceMs)
|
|
|
965
965
|
}, debounceMs);
|
|
966
966
|
}
|
|
967
967
|
}
|
|
968
|
-
function useColResize(props, emits, tableContainerRef, tableHeaderLast, colResizeIndicatorRef, colKeyGen, fixedCols) {
|
|
968
|
+
function useColResize(props, emits, tableContainerRef, tableHeaderLast, colResizeIndicatorRef, colKeyGen, fixedCols, onColWidthChange) {
|
|
969
969
|
const isColResizing = ref(false);
|
|
970
970
|
let colResizeState = {
|
|
971
971
|
currentCol: null,
|
|
@@ -1062,10 +1062,12 @@ function useColResize(props, emits, tableContainerRef, tableHeaderLast, colResiz
|
|
|
1062
1062
|
if (width < props.colMinWidth) width = props.colMinWidth;
|
|
1063
1063
|
const colKey = colKeyGen.value;
|
|
1064
1064
|
const curCol = tableHeaderLast.value.find((it) => colKey(it) === colKey(lastCol));
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1065
|
+
if (curCol) {
|
|
1066
|
+
curCol.width = width + "px";
|
|
1067
|
+
onColWidthChange == null ? void 0 : onColWidthChange();
|
|
1068
|
+
emits("update:columns", props.columns.slice());
|
|
1069
|
+
emits("col-resize", { ...curCol });
|
|
1070
|
+
}
|
|
1069
1071
|
if (colResizeIndicatorRef.value) {
|
|
1070
1072
|
const style = colResizeIndicatorRef.value.style;
|
|
1071
1073
|
style.display = "none";
|
|
@@ -1276,15 +1278,17 @@ function useHighlight(props, stkTableId, tableContainerRef) {
|
|
|
1276
1278
|
window.requestAnimationFrame(
|
|
1277
1279
|
() => {
|
|
1278
1280
|
const nowTs = performance.now();
|
|
1281
|
+
const keysToDelete = [];
|
|
1279
1282
|
highlightDimRowsAnimation.forEach((store, rowKeyValue) => {
|
|
1280
1283
|
const { ts, duration } = store;
|
|
1281
1284
|
const timeOffset = nowTs - ts;
|
|
1282
1285
|
if (timeOffset < duration) {
|
|
1283
1286
|
updateRowAnimation(rowKeyValue, store, timeOffset);
|
|
1284
1287
|
} else {
|
|
1285
|
-
|
|
1288
|
+
keysToDelete.push(rowKeyValue);
|
|
1286
1289
|
}
|
|
1287
1290
|
});
|
|
1291
|
+
keysToDelete.forEach((key) => highlightDimRowsAnimation.delete(key));
|
|
1288
1292
|
if (highlightDimRowsAnimation.size) {
|
|
1289
1293
|
recursion();
|
|
1290
1294
|
} else {
|
|
@@ -1408,24 +1412,22 @@ function useHighlight(props, stkTableId, tableContainerRef) {
|
|
|
1408
1412
|
}
|
|
1409
1413
|
return [highlightSteps, setHighlightDimRow, setHighlightDimCell];
|
|
1410
1414
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
})(ScrollCodes || {});
|
|
1415
|
+
const ScrollCodes = {
|
|
1416
|
+
ArrowUp: "ArrowUp",
|
|
1417
|
+
ArrowRight: "ArrowRight",
|
|
1418
|
+
ArrowDown: "ArrowDown",
|
|
1419
|
+
ArrowLeft: "ArrowLeft",
|
|
1420
|
+
PageUp: "PageUp",
|
|
1421
|
+
PageDown: "PageDown",
|
|
1422
|
+
Home: "Home",
|
|
1423
|
+
End: "End"
|
|
1424
|
+
};
|
|
1422
1425
|
const ScrollCodesValues = Object.values(ScrollCodes);
|
|
1423
1426
|
function useKeyboardArrowScroll(targetElement, props, scrollTo, virtualScroll, virtualScrollX, tableHeaders, virtual_on, areaSelectionConfig) {
|
|
1424
1427
|
let isMouseOver = false;
|
|
1425
1428
|
watch(virtual_on, (val) => {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
} else {
|
|
1429
|
+
removeListeners();
|
|
1430
|
+
if (val) {
|
|
1429
1431
|
addEventListeners();
|
|
1430
1432
|
}
|
|
1431
1433
|
});
|
|
@@ -1457,21 +1459,21 @@ function useKeyboardArrowScroll(targetElement, props, scrollTo, virtualScroll, v
|
|
|
1457
1459
|
const { headless, headerRowHeight } = props;
|
|
1458
1460
|
const headerHeight = headless ? 0 : tableHeaders.value.length * (headerRowHeight || rowHeight);
|
|
1459
1461
|
const bodyPageSize = Math.floor((containerHeight - headerHeight) / rowHeight);
|
|
1460
|
-
if (keyCode ===
|
|
1462
|
+
if (keyCode === ScrollCodes.ArrowUp) {
|
|
1461
1463
|
scrollTo(scrollTop - rowHeight, null);
|
|
1462
|
-
} else if (keyCode ===
|
|
1464
|
+
} else if (keyCode === ScrollCodes.ArrowRight) {
|
|
1463
1465
|
scrollTo(null, scrollLeft + 50);
|
|
1464
|
-
} else if (keyCode ===
|
|
1466
|
+
} else if (keyCode === ScrollCodes.ArrowDown) {
|
|
1465
1467
|
scrollTo(scrollTop + rowHeight, null);
|
|
1466
|
-
} else if (keyCode ===
|
|
1468
|
+
} else if (keyCode === ScrollCodes.ArrowLeft) {
|
|
1467
1469
|
scrollTo(null, scrollLeft - 50);
|
|
1468
|
-
} else if (keyCode ===
|
|
1470
|
+
} else if (keyCode === ScrollCodes.PageUp) {
|
|
1469
1471
|
scrollTo(scrollTop - rowHeight * bodyPageSize + headerHeight, null);
|
|
1470
|
-
} else if (keyCode ===
|
|
1472
|
+
} else if (keyCode === ScrollCodes.PageDown) {
|
|
1471
1473
|
scrollTo(scrollTop + rowHeight * bodyPageSize - headerHeight, null);
|
|
1472
|
-
} else if (keyCode ===
|
|
1474
|
+
} else if (keyCode === ScrollCodes.Home) {
|
|
1473
1475
|
scrollTo(0, null);
|
|
1474
|
-
} else if (keyCode ===
|
|
1476
|
+
} else if (keyCode === ScrollCodes.End) {
|
|
1475
1477
|
scrollTo(scrollHeight, null);
|
|
1476
1478
|
}
|
|
1477
1479
|
}
|
|
@@ -1560,7 +1562,7 @@ function useMergeCells(rowActiveProp, tableHeaderLast, rowKeyGen, colKeyGen, vir
|
|
|
1560
1562
|
if (colspan === 1 && rowspan === 1) return;
|
|
1561
1563
|
const rowKey = rowKeyGen(row);
|
|
1562
1564
|
const curRowIndex = virtual_dataSourcePart.value.findIndex((item) => rowKeyGen(item) === rowKey);
|
|
1563
|
-
if (curRowIndex
|
|
1565
|
+
if (curRowIndex < 0) return;
|
|
1564
1566
|
const colKey = colKeyGen.value(col);
|
|
1565
1567
|
const mergedCellKey = pureCellKeyGen(rowKey, colKey);
|
|
1566
1568
|
for (let i = curRowIndex; i < curRowIndex + rowspan; i++) {
|
|
@@ -1649,6 +1651,7 @@ function useScrollbar(props, containerRef, virtualScroll, virtualScrollX, update
|
|
|
1649
1651
|
let dragStartTop = 0;
|
|
1650
1652
|
let dragStartLeft = 0;
|
|
1651
1653
|
let resizeObserver = null;
|
|
1654
|
+
let currentDragHandler;
|
|
1652
1655
|
const throttledUpdateScrollbar = throttle(() => updateCustomScrollbar(), 200);
|
|
1653
1656
|
const rafUpdateVirtualScrollY = rafThrottle((scrollTop) => updateVirtualScrollY(scrollTop));
|
|
1654
1657
|
onMounted(() => {
|
|
@@ -1658,7 +1661,8 @@ function useScrollbar(props, containerRef, virtualScroll, virtualScrollX, update
|
|
|
1658
1661
|
}
|
|
1659
1662
|
initScrollbar();
|
|
1660
1663
|
});
|
|
1661
|
-
|
|
1664
|
+
onBeforeUnmount(() => {
|
|
1665
|
+
onDragEnd();
|
|
1662
1666
|
resizeObserver == null ? void 0 : resizeObserver.disconnect();
|
|
1663
1667
|
resizeObserver = null;
|
|
1664
1668
|
});
|
|
@@ -1697,6 +1701,8 @@ function useScrollbar(props, containerRef, virtualScroll, virtualScrollX, update
|
|
|
1697
1701
|
addDragListeners(onHorizontalDrag);
|
|
1698
1702
|
}
|
|
1699
1703
|
function addDragListeners(dragHandler) {
|
|
1704
|
+
removeCurrentDragHandlerListeners();
|
|
1705
|
+
currentDragHandler = dragHandler;
|
|
1700
1706
|
document.addEventListener("mousemove", dragHandler);
|
|
1701
1707
|
document.addEventListener("mouseup", onDragEnd);
|
|
1702
1708
|
document.addEventListener("touchmove", dragHandler, { passive: false });
|
|
@@ -1735,13 +1741,17 @@ function useScrollbar(props, containerRef, virtualScroll, virtualScrollX, update
|
|
|
1735
1741
|
function onDragEnd() {
|
|
1736
1742
|
isDraggingVertical = false;
|
|
1737
1743
|
isDraggingHorizontal = false;
|
|
1738
|
-
|
|
1739
|
-
document.removeEventListener("mousemove", onHorizontalDrag);
|
|
1744
|
+
removeCurrentDragHandlerListeners();
|
|
1740
1745
|
document.removeEventListener("mouseup", onDragEnd);
|
|
1741
|
-
document.removeEventListener("touchmove", onVerticalDrag);
|
|
1742
|
-
document.removeEventListener("touchmove", onHorizontalDrag);
|
|
1743
1746
|
document.removeEventListener("touchend", onDragEnd);
|
|
1744
1747
|
}
|
|
1748
|
+
function removeCurrentDragHandlerListeners() {
|
|
1749
|
+
if (currentDragHandler) {
|
|
1750
|
+
document.removeEventListener("mousemove", currentDragHandler);
|
|
1751
|
+
document.removeEventListener("touchmove", currentDragHandler);
|
|
1752
|
+
currentDragHandler = void 0;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1745
1755
|
function initScrollbar() {
|
|
1746
1756
|
nextTick(updateCustomScrollbar);
|
|
1747
1757
|
}
|
|
@@ -2309,6 +2319,34 @@ function useTree(props, dataSourceCopy, rowKeyGen, emits) {
|
|
|
2309
2319
|
}
|
|
2310
2320
|
return [toggleTreeNode, setTreeExpand, flatTreeData];
|
|
2311
2321
|
}
|
|
2322
|
+
function useColWidthCache(getColWidth2) {
|
|
2323
|
+
let colWidthCache = { cols: null, nonFixedCols: [], leftColWidthSum: 0 };
|
|
2324
|
+
function build(cols) {
|
|
2325
|
+
const nonFixedCols = [];
|
|
2326
|
+
let leftColWidthSum = 0;
|
|
2327
|
+
let cumWidth = 0;
|
|
2328
|
+
for (let i = 0; i < cols.length; i++) {
|
|
2329
|
+
const col = cols[i];
|
|
2330
|
+
const w = getColWidth2(col);
|
|
2331
|
+
if (col.fixed === "left") {
|
|
2332
|
+
leftColWidthSum += w;
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
cumWidth += w;
|
|
2336
|
+
nonFixedCols.push({ index: i, cumWidth });
|
|
2337
|
+
}
|
|
2338
|
+
colWidthCache = { cols, nonFixedCols, leftColWidthSum };
|
|
2339
|
+
return colWidthCache;
|
|
2340
|
+
}
|
|
2341
|
+
function get(cols) {
|
|
2342
|
+
if (colWidthCache.cols === cols) return colWidthCache;
|
|
2343
|
+
return build(cols);
|
|
2344
|
+
}
|
|
2345
|
+
function clear() {
|
|
2346
|
+
colWidthCache.cols = null;
|
|
2347
|
+
}
|
|
2348
|
+
return [get, clear];
|
|
2349
|
+
}
|
|
2312
2350
|
const VUE2_SCROLL_TIMEOUT_MS = 200;
|
|
2313
2351
|
function useVirtualScroll(props, tableContainerRef, trRef, dataSourceCopy, tableHeaderLast, tableHeaders, rowKeyGen, maxRowSpan, scrollbarOptions, isExperimentalScrollY) {
|
|
2314
2352
|
const tableHeaderHeight = computed(() => props.headerRowHeight * tableHeaders.value.length);
|
|
@@ -2331,6 +2369,7 @@ function useVirtualScroll(props, tableContainerRef, trRef, dataSourceCopy, table
|
|
|
2331
2369
|
offsetLeft: 0,
|
|
2332
2370
|
scrollLeft: 0
|
|
2333
2371
|
});
|
|
2372
|
+
const [getColWidthCache, clearColWidthCache] = useColWidthCache(getCalculatedColWidth);
|
|
2334
2373
|
const hasExpandCol = computed(() => {
|
|
2335
2374
|
return tableHeaderLast.value.some((col) => col.type === "expand");
|
|
2336
2375
|
});
|
|
@@ -2366,15 +2405,18 @@ function useVirtualScroll(props, tableContainerRef, trRef, dataSourceCopy, table
|
|
|
2366
2405
|
const leftCols = [];
|
|
2367
2406
|
const rightCols = [];
|
|
2368
2407
|
const { startIndex, endIndex } = virtualScrollX.value;
|
|
2369
|
-
|
|
2408
|
+
const maxIndex = tableHeaderLastValue.length;
|
|
2409
|
+
const validEndIndex = Math.min(endIndex, maxIndex);
|
|
2410
|
+
const validStartIndex = Math.min(startIndex, maxIndex);
|
|
2411
|
+
for (let i = 0; i < validStartIndex; i++) {
|
|
2370
2412
|
const col = tableHeaderLastValue[i];
|
|
2371
2413
|
if ((col == null ? void 0 : col.fixed) === "left") leftCols.push(col);
|
|
2372
2414
|
}
|
|
2373
|
-
for (let i =
|
|
2415
|
+
for (let i = validEndIndex; i < tableHeaderLastValue.length; i++) {
|
|
2374
2416
|
const col = tableHeaderLastValue[i];
|
|
2375
2417
|
if ((col == null ? void 0 : col.fixed) === "right") rightCols.push(col);
|
|
2376
2418
|
}
|
|
2377
|
-
const mainColumns = tableHeaderLastValue.slice(
|
|
2419
|
+
const mainColumns = tableHeaderLastValue.slice(validStartIndex, validEndIndex);
|
|
2378
2420
|
return leftCols.concat(mainColumns).concat(rightCols);
|
|
2379
2421
|
}
|
|
2380
2422
|
return tableHeaderLastValue;
|
|
@@ -2594,32 +2636,26 @@ function useVirtualScroll(props, tableContainerRef, trRef, dataSourceCopy, table
|
|
|
2594
2636
|
const { scrollLeft, containerWidth } = virtualScrollX.value;
|
|
2595
2637
|
let startIndex = 0;
|
|
2596
2638
|
let offsetLeft = 0;
|
|
2597
|
-
let colWidthSum = 0;
|
|
2598
|
-
let leftColWidthSum = 0;
|
|
2599
2639
|
let leftFirstColRestWidth = 0;
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
break;
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
colWidthSum = leftFirstColRestWidth;
|
|
2640
|
+
const { nonFixedCols, leftColWidthSum } = getColWidthCache(tableHeaderLastValue);
|
|
2641
|
+
if (nonFixedCols.length > 0 && sLeft > 0) {
|
|
2642
|
+
const found = binarySearch(nonFixedCols, (mid) => {
|
|
2643
|
+
return nonFixedCols[mid].cumWidth < sLeft ? -1 : 1;
|
|
2644
|
+
});
|
|
2645
|
+
const idx = Math.min(found, nonFixedCols.length - 1);
|
|
2646
|
+
startIndex = nonFixedCols[idx].index;
|
|
2647
|
+
offsetLeft = idx > 0 ? nonFixedCols[idx - 1].cumWidth : 0;
|
|
2648
|
+
leftFirstColRestWidth = nonFixedCols[idx].cumWidth - sLeft;
|
|
2649
|
+
} else if (nonFixedCols.length > 0) {
|
|
2650
|
+
startIndex = nonFixedCols[0].index;
|
|
2651
|
+
}
|
|
2652
|
+
let endColWidthSum = leftFirstColRestWidth;
|
|
2617
2653
|
const containerW = containerWidth - leftColWidthSum;
|
|
2618
2654
|
let endIndex = headerLength;
|
|
2619
2655
|
for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {
|
|
2620
2656
|
const col = tableHeaderLastValue[colIndex];
|
|
2621
|
-
|
|
2622
|
-
if (
|
|
2657
|
+
endColWidthSum += getCalculatedColWidth(col);
|
|
2658
|
+
if (endColWidthSum >= containerW) {
|
|
2623
2659
|
endIndex = colIndex + 1;
|
|
2624
2660
|
break;
|
|
2625
2661
|
}
|
|
@@ -2653,7 +2689,8 @@ function useVirtualScroll(props, tableContainerRef, trRef, dataSourceCopy, table
|
|
|
2653
2689
|
updateVirtualScrollY,
|
|
2654
2690
|
updateVirtualScrollX,
|
|
2655
2691
|
setAutoHeight,
|
|
2656
|
-
clearAllAutoHeight
|
|
2692
|
+
clearAllAutoHeight,
|
|
2693
|
+
clearColWidthCache
|
|
2657
2694
|
];
|
|
2658
2695
|
}
|
|
2659
2696
|
function useWheeling(resetDelay = 500) {
|
|
@@ -2889,7 +2926,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
2889
2926
|
updateVirtualScrollY,
|
|
2890
2927
|
updateVirtualScrollX,
|
|
2891
2928
|
setAutoHeight,
|
|
2892
|
-
clearAllAutoHeight
|
|
2929
|
+
clearAllAutoHeight,
|
|
2930
|
+
clearColWidthCache
|
|
2893
2931
|
] = useVirtualScroll(
|
|
2894
2932
|
props,
|
|
2895
2933
|
tableContainerRef,
|
|
@@ -2961,7 +2999,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
2961
2999
|
tableHeaderLast,
|
|
2962
3000
|
colResizeIndicatorRef,
|
|
2963
3001
|
colKeyGen,
|
|
2964
|
-
fixedCols
|
|
3002
|
+
fixedCols,
|
|
3003
|
+
clearColWidthCache
|
|
2965
3004
|
);
|
|
2966
3005
|
const [toggleExpandRow, setRowExpand] = useRowExpand(emits, dataSourceCopy, rowKeyGen);
|
|
2967
3006
|
const [toggleTreeNode, setTreeExpand, flatTreeData] = useTree(props, dataSourceCopy, rowKeyGen, emits);
|
|
@@ -3065,6 +3104,9 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
3065
3104
|
}
|
|
3066
3105
|
initDataSource(val);
|
|
3067
3106
|
updateMaxRowSpan();
|
|
3107
|
+
if (!val.length) {
|
|
3108
|
+
clearSelectedArea();
|
|
3109
|
+
}
|
|
3068
3110
|
if (needInitVirtualScrollY) {
|
|
3069
3111
|
nextTick(() => initVirtualScrollY());
|
|
3070
3112
|
}
|
|
@@ -3817,7 +3859,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
3817
3859
|
}, 8, ["style"])) : createCommentVNode("", true),
|
|
3818
3860
|
createElementVNode("tbody", {
|
|
3819
3861
|
class: "stk-tbody-main",
|
|
3820
|
-
style: normalizeStyle({ transform: `translateY(${unref(virtualScroll).translateY}px)` }),
|
|
3862
|
+
style: normalizeStyle({ transform: isExperimentalScrollY.value ? `translateY(${unref(virtualScroll).translateY}px)` : "" }),
|
|
3821
3863
|
onClick: onCellClick,
|
|
3822
3864
|
onMousedown: onCellMouseDown,
|
|
3823
3865
|
onMouseover: onCellMouseOver
|
package/lib/style.css
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stk-table-vue",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.8",
|
|
4
4
|
"description": "High performance 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",
|
|
7
7
|
"homepage": "https://ja-plus.github.io/stk-table-vue/",
|
|
8
|
+
"author": "japlus",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/ja-plus/stk-table-vue"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ja-plus/stk-table-vue/issues"
|
|
16
|
+
},
|
|
8
17
|
"packageManager": "pnpm@10.7.0",
|
|
9
18
|
"directories": {
|
|
10
19
|
"test": "test"
|
|
@@ -20,27 +29,27 @@
|
|
|
20
29
|
"docs:update": "cp -rf ./docs-src/.vitepress/dist/* ./docs"
|
|
21
30
|
},
|
|
22
31
|
"keywords": [
|
|
23
|
-
"virtual table",
|
|
24
32
|
"vue",
|
|
33
|
+
"table",
|
|
34
|
+
"vue-table",
|
|
35
|
+
"virtual table",
|
|
36
|
+
"virtual-scroll",
|
|
37
|
+
"data-table",
|
|
38
|
+
"vue-component",
|
|
39
|
+
"grid",
|
|
25
40
|
"vue2",
|
|
26
41
|
"vue3",
|
|
27
42
|
"highlight",
|
|
28
43
|
"sticky",
|
|
29
44
|
"virtual",
|
|
30
|
-
"table",
|
|
31
45
|
"list"
|
|
32
46
|
],
|
|
33
47
|
"files": [
|
|
34
48
|
"lib",
|
|
35
49
|
"src"
|
|
36
50
|
],
|
|
37
|
-
"author": "japlus",
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "https://github.com/ja-plus/stk-table-vue"
|
|
41
|
-
},
|
|
42
|
-
"license": "MIT",
|
|
43
51
|
"devDependencies": {
|
|
52
|
+
"@chenglou/pretext": "^0.0.3",
|
|
44
53
|
"@types/mockjs": "^1.0.10",
|
|
45
54
|
"@types/node": "^22.18.10",
|
|
46
55
|
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
|
|
132
132
|
<tbody
|
|
133
133
|
class="stk-tbody-main"
|
|
134
|
-
:style="{ transform: `translateY(${virtualScroll.translateY}px)` }"
|
|
134
|
+
:style="{ transform: isExperimentalScrollY ? `translateY(${virtualScroll.translateY}px)` : '' }"
|
|
135
135
|
@click="onCellClick"
|
|
136
136
|
@mousedown="onCellMouseDown"
|
|
137
137
|
@mouseover="onCellMouseOver"
|
|
@@ -850,6 +850,7 @@ const [
|
|
|
850
850
|
updateVirtualScrollX,
|
|
851
851
|
setAutoHeight,
|
|
852
852
|
clearAllAutoHeight,
|
|
853
|
+
clearColWidthCache,
|
|
853
854
|
] = useVirtualScroll(
|
|
854
855
|
props,
|
|
855
856
|
tableContainerRef,
|
|
@@ -936,6 +937,7 @@ const [colResizeOn, isColResizing, onThResizeMouseDown] = useColResize(
|
|
|
936
937
|
colResizeIndicatorRef,
|
|
937
938
|
colKeyGen,
|
|
938
939
|
fixedCols,
|
|
940
|
+
clearColWidthCache,
|
|
939
941
|
);
|
|
940
942
|
|
|
941
943
|
const [toggleExpandRow, setRowExpand] = useRowExpand(emits, dataSourceCopy, rowKeyGen);
|
|
@@ -1064,6 +1066,12 @@ function updateDataSource(val: DT[]) {
|
|
|
1064
1066
|
}
|
|
1065
1067
|
initDataSource(val);
|
|
1066
1068
|
updateMaxRowSpan();
|
|
1069
|
+
|
|
1070
|
+
// #47
|
|
1071
|
+
if (!val.length) {
|
|
1072
|
+
clearSelectedArea();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1067
1075
|
// if data length is not change, not init virtual scroll
|
|
1068
1076
|
if (needInitVirtualScrollY) {
|
|
1069
1077
|
// wait for table render,initVirtualScrollY has get `dom` operation.
|
|
@@ -267,12 +267,14 @@ export type SortConfig<T extends Record<string, any>> = {
|
|
|
267
267
|
};
|
|
268
268
|
|
|
269
269
|
/** th td type */
|
|
270
|
-
export const
|
|
271
|
-
TH,
|
|
272
|
-
TD,
|
|
270
|
+
export const TagType = {
|
|
271
|
+
TH: 0,
|
|
272
|
+
TD: 1,
|
|
273
273
|
/** tfoot */
|
|
274
|
-
TF,
|
|
275
|
-
}
|
|
274
|
+
TF: 2,
|
|
275
|
+
} as const;
|
|
276
|
+
|
|
277
|
+
export type TagType = (typeof TagType)[keyof typeof TagType];
|
|
276
278
|
|
|
277
279
|
export type HighlightConfig = {
|
|
278
280
|
/** Duration of the highlight in seconds */
|
|
@@ -24,6 +24,7 @@ export function useColResize<DT extends Record<string, any>>(
|
|
|
24
24
|
colResizeIndicatorRef: Ref<HTMLElement | undefined>,
|
|
25
25
|
colKeyGen: ComputedRef<(p: any) => UniqKey>,
|
|
26
26
|
fixedCols: Ref<StkTableColumn<DT>[]>,
|
|
27
|
+
onColWidthChange?: () => void,
|
|
27
28
|
) {
|
|
28
29
|
/** 列宽是否在拖动 */
|
|
29
30
|
const isColResizing = ref(false);
|
|
@@ -163,11 +164,12 @@ export function useColResize<DT extends Record<string, any>>(
|
|
|
163
164
|
const colKey = colKeyGen.value;
|
|
164
165
|
|
|
165
166
|
const curCol = tableHeaderLast.value.find(it => colKey(it) === colKey(lastCol));
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
if (curCol) {
|
|
168
|
+
curCol.width = width + 'px';
|
|
169
|
+
onColWidthChange?.();
|
|
170
|
+
emits('update:columns', props.columns.slice());
|
|
171
|
+
emits('col-resize', { ...curCol });
|
|
172
|
+
}
|
|
171
173
|
|
|
172
174
|
// 隐藏指示线
|
|
173
175
|
if (colResizeIndicatorRef.value) {
|
|
@@ -68,15 +68,17 @@ export function useHighlight(props: any, stkTableId: string, tableContainerRef:
|
|
|
68
68
|
window.requestAnimationFrame(
|
|
69
69
|
() => {
|
|
70
70
|
const nowTs = performance.now();
|
|
71
|
+
const keysToDelete: UniqKey[] = [];
|
|
71
72
|
highlightDimRowsAnimation.forEach((store, rowKeyValue) => {
|
|
72
73
|
const { ts, duration } = store;
|
|
73
74
|
const timeOffset = nowTs - ts;
|
|
74
75
|
if (timeOffset < duration) {
|
|
75
76
|
updateRowAnimation(rowKeyValue, store, timeOffset);
|
|
76
77
|
} else {
|
|
77
|
-
|
|
78
|
+
keysToDelete.push(rowKeyValue);
|
|
78
79
|
}
|
|
79
80
|
});
|
|
81
|
+
keysToDelete.forEach(key => highlightDimRowsAnimation.delete(key));
|
|
80
82
|
|
|
81
83
|
if (highlightDimRowsAnimation.size) {
|
|
82
84
|
recursion();
|
|
@@ -3,16 +3,19 @@ import { AreaSelectionConfig, StkTableColumn } from './types';
|
|
|
3
3
|
import { VirtualScrollStore, VirtualScrollXStore } from './useVirtualScroll';
|
|
4
4
|
|
|
5
5
|
/** 翻页按键 */
|
|
6
|
-
|
|
7
|
-
ArrowUp
|
|
8
|
-
ArrowRight
|
|
9
|
-
ArrowDown
|
|
10
|
-
ArrowLeft
|
|
11
|
-
PageUp
|
|
12
|
-
PageDown
|
|
13
|
-
Home
|
|
14
|
-
End
|
|
15
|
-
}
|
|
6
|
+
const ScrollCodes = {
|
|
7
|
+
ArrowUp: 'ArrowUp',
|
|
8
|
+
ArrowRight: 'ArrowRight',
|
|
9
|
+
ArrowDown: 'ArrowDown',
|
|
10
|
+
ArrowLeft: 'ArrowLeft',
|
|
11
|
+
PageUp: 'PageUp',
|
|
12
|
+
PageDown: 'PageDown',
|
|
13
|
+
Home: 'Home',
|
|
14
|
+
End: 'End',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
type ScrollCodes = (typeof ScrollCodes)[keyof typeof ScrollCodes];
|
|
18
|
+
|
|
16
19
|
/** 所有翻页按键数组 */
|
|
17
20
|
const ScrollCodesValues = Object.values(ScrollCodes);
|
|
18
21
|
|
|
@@ -34,9 +37,8 @@ export function useKeyboardArrowScroll<DT extends Record<string, any>>(
|
|
|
34
37
|
/** 检测鼠标是否悬浮在表格体上 */
|
|
35
38
|
let isMouseOver = false;
|
|
36
39
|
watch(virtual_on, val => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} else {
|
|
40
|
+
removeListeners();
|
|
41
|
+
if (val) {
|
|
40
42
|
addEventListeners();
|
|
41
43
|
}
|
|
42
44
|
});
|
|
@@ -6,7 +6,7 @@ export function useMergeCells(
|
|
|
6
6
|
tableHeaderLast: ShallowRef<PrivateStkTableColumn<any>[]>,
|
|
7
7
|
rowKeyGen: RowKeyGen,
|
|
8
8
|
colKeyGen: ColKeyGen,
|
|
9
|
-
virtual_dataSourcePart: ShallowRef<any[]
|
|
9
|
+
virtual_dataSourcePart: ShallowRef<any[]>,
|
|
10
10
|
) {
|
|
11
11
|
/**
|
|
12
12
|
* which cell need be hidden
|
|
@@ -99,7 +99,7 @@ export function useMergeCells(
|
|
|
99
99
|
const rowKey = rowKeyGen(row);
|
|
100
100
|
|
|
101
101
|
const curRowIndex = virtual_dataSourcePart.value.findIndex(item => rowKeyGen(item) === rowKey);
|
|
102
|
-
if (curRowIndex
|
|
102
|
+
if (curRowIndex < 0) return;
|
|
103
103
|
|
|
104
104
|
const colKey = colKeyGen.value(col);
|
|
105
105
|
const mergedCellKey = pureCellKeyGen(rowKey, colKey);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nextTick, onMounted,
|
|
1
|
+
import { nextTick, onMounted, onBeforeUnmount, ref, Ref } from 'vue';
|
|
2
2
|
import type { VirtualScrollStore, VirtualScrollXStore } from './useVirtualScroll';
|
|
3
3
|
import { rafThrottle, throttle } from './utils/index';
|
|
4
4
|
|
|
@@ -40,6 +40,7 @@ export function useScrollbar(
|
|
|
40
40
|
let dragStartLeft = 0;
|
|
41
41
|
|
|
42
42
|
let resizeObserver: ResizeObserver | null = null;
|
|
43
|
+
let currentDragHandler: ((e: MouseEvent | TouchEvent) => void) | undefined;
|
|
43
44
|
|
|
44
45
|
const throttledUpdateScrollbar = throttle(() => updateCustomScrollbar(), 200);
|
|
45
46
|
// Use requestAnimationFrame for smoother scrollbar dragging performance
|
|
@@ -53,7 +54,9 @@ export function useScrollbar(
|
|
|
53
54
|
initScrollbar();
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
onBeforeUnmount(() => {
|
|
58
|
+
// en: Clean up all event listeners to prevent memory leaks
|
|
59
|
+
onDragEnd();
|
|
57
60
|
resizeObserver?.disconnect();
|
|
58
61
|
resizeObserver = null;
|
|
59
62
|
});
|
|
@@ -99,6 +102,8 @@ export function useScrollbar(
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
function addDragListeners(dragHandler: (e: MouseEvent | TouchEvent) => void) {
|
|
105
|
+
removeCurrentDragHandlerListeners();
|
|
106
|
+
currentDragHandler = dragHandler;
|
|
102
107
|
document.addEventListener('mousemove', dragHandler);
|
|
103
108
|
document.addEventListener('mouseup', onDragEnd);
|
|
104
109
|
document.addEventListener('touchmove', dragHandler, { passive: false });
|
|
@@ -143,14 +148,19 @@ export function useScrollbar(
|
|
|
143
148
|
function onDragEnd() {
|
|
144
149
|
isDraggingVertical = false;
|
|
145
150
|
isDraggingHorizontal = false;
|
|
146
|
-
|
|
147
|
-
document.removeEventListener('mousemove', onHorizontalDrag);
|
|
151
|
+
removeCurrentDragHandlerListeners();
|
|
148
152
|
document.removeEventListener('mouseup', onDragEnd);
|
|
149
|
-
document.removeEventListener('touchmove', onVerticalDrag);
|
|
150
|
-
document.removeEventListener('touchmove', onHorizontalDrag);
|
|
151
153
|
document.removeEventListener('touchend', onDragEnd);
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
function removeCurrentDragHandlerListeners() {
|
|
157
|
+
if (currentDragHandler) {
|
|
158
|
+
document.removeEventListener('mousemove', currentDragHandler);
|
|
159
|
+
document.removeEventListener('touchmove', currentDragHandler);
|
|
160
|
+
currentDragHandler = void 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
function initScrollbar() {
|
|
155
165
|
nextTick(updateCustomScrollbar);
|
|
156
166
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Ref, ShallowRef, computed, ref } from 'vue';
|
|
2
2
|
import { DEFAULT_ROW_HEIGHT, DEFAULT_TABLE_HEIGHT, DEFAULT_TABLE_WIDTH } from './const';
|
|
3
|
-
import { AutoRowHeightConfig, PrivateRowDT, PrivateStkTableColumn, RowKeyGen, UniqKey } from './types';
|
|
3
|
+
import { AutoRowHeightConfig, PrivateRowDT, PrivateStkTableColumn, RowKeyGen, StkTableColumn, UniqKey } from './types';
|
|
4
4
|
import { ScrollbarOptions } from './useScrollbar';
|
|
5
|
+
import { binarySearch } from './utils';
|
|
5
6
|
import { getCalculatedColWidth } from './utils/constRefUtils';
|
|
6
7
|
|
|
7
8
|
/** 暂存纵向虚拟滚动的数据 */
|
|
@@ -40,6 +41,45 @@ export type VirtualScrollXStore = {
|
|
|
40
41
|
scrollLeft: number;
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
/** 列宽缓存项 */
|
|
45
|
+
type ColWidthCacheItem = { index: number; cumWidth: number };
|
|
46
|
+
/** 列宽缓存 */
|
|
47
|
+
type ColWidthCache<T> = { cols: T[] | null; nonFixedCols: ColWidthCacheItem[]; leftColWidthSum: number };
|
|
48
|
+
|
|
49
|
+
/** 横向虚拟滚动列宽缓存,避免每次滚动都 O(n) 构建 */
|
|
50
|
+
function useColWidthCache<T extends { fixed?: StkTableColumn<PrivateRowDT>['fixed'] }>(getColWidth: (col: T) => number) {
|
|
51
|
+
let colWidthCache: ColWidthCache<T> = { cols: null, nonFixedCols: [], leftColWidthSum: 0 };
|
|
52
|
+
|
|
53
|
+
function build(cols: T[]): ColWidthCache<T> {
|
|
54
|
+
const nonFixedCols: ColWidthCacheItem[] = [];
|
|
55
|
+
let leftColWidthSum = 0;
|
|
56
|
+
let cumWidth = 0;
|
|
57
|
+
for (let i = 0; i < cols.length; i++) {
|
|
58
|
+
const col = cols[i];
|
|
59
|
+
const w = getColWidth(col);
|
|
60
|
+
if (col.fixed === 'left') {
|
|
61
|
+
leftColWidthSum += w;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
cumWidth += w;
|
|
65
|
+
nonFixedCols.push({ index: i, cumWidth });
|
|
66
|
+
}
|
|
67
|
+
colWidthCache = { cols, nonFixedCols, leftColWidthSum };
|
|
68
|
+
return colWidthCache;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function get(cols: T[]): ColWidthCache<T> {
|
|
72
|
+
if (colWidthCache.cols === cols) return colWidthCache;
|
|
73
|
+
return build(cols);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clear() {
|
|
77
|
+
colWidthCache.cols = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [get, clear] as const;
|
|
81
|
+
}
|
|
82
|
+
|
|
43
83
|
/** vue2 优化滚动回收延时 */
|
|
44
84
|
const VUE2_SCROLL_TIMEOUT_MS = 200;
|
|
45
85
|
|
|
@@ -84,6 +124,8 @@ export function useVirtualScroll(
|
|
|
84
124
|
scrollLeft: 0,
|
|
85
125
|
});
|
|
86
126
|
|
|
127
|
+
const [getColWidthCache, clearColWidthCache] = useColWidthCache<PrivateStkTableColumn<PrivateRowDT>>(getCalculatedColWidth);
|
|
128
|
+
|
|
87
129
|
const hasExpandCol = computed(() => {
|
|
88
130
|
return tableHeaderLast.value.some(col => col.type === 'expand');
|
|
89
131
|
});
|
|
@@ -129,25 +171,24 @@ export function useVirtualScroll(
|
|
|
129
171
|
// 虚拟横向滚动,固定列要一直保持存在
|
|
130
172
|
const leftCols: PrivateStkTableColumn<PrivateRowDT>[] = [];
|
|
131
173
|
const rightCols: PrivateStkTableColumn<PrivateRowDT>[] = [];
|
|
132
|
-
/**
|
|
133
|
-
* 存在问题:
|
|
134
|
-
* table columns 从多到少时。比方原来的start=5,end=10,现在start=4,end=8。这时候endIndex就超出数组范围了。
|
|
135
|
-
* FIXME: 如果新列数 < endIndex,此时需要重新计算列startIndex和endIndex。
|
|
136
|
-
*/
|
|
137
174
|
const { startIndex, endIndex } = virtualScrollX.value;
|
|
175
|
+
// 将索引钳制到列数组范围内,防止列数减少时越界
|
|
176
|
+
const maxIndex = tableHeaderLastValue.length;
|
|
177
|
+
const validEndIndex = Math.min(endIndex, maxIndex);
|
|
178
|
+
const validStartIndex = Math.min(startIndex, maxIndex);
|
|
138
179
|
|
|
139
180
|
// 左侧固定列,如果在左边不可见区。则需要拿出来放在前面
|
|
140
|
-
for (let i = 0; i <
|
|
181
|
+
for (let i = 0; i < validStartIndex; i++) {
|
|
141
182
|
const col = tableHeaderLastValue[i];
|
|
142
183
|
if (col?.fixed === 'left') leftCols.push(col);
|
|
143
184
|
}
|
|
144
185
|
// 右侧固定列,如果在右边不可见区。则需要拿出来放在后面
|
|
145
|
-
for (let i =
|
|
186
|
+
for (let i = validEndIndex; i < tableHeaderLastValue.length; i++) {
|
|
146
187
|
const col = tableHeaderLastValue[i];
|
|
147
188
|
if (col?.fixed === 'right') rightCols.push(col);
|
|
148
189
|
}
|
|
149
190
|
|
|
150
|
-
const mainColumns = tableHeaderLastValue.slice(
|
|
191
|
+
const mainColumns = tableHeaderLastValue.slice(validStartIndex, validEndIndex);
|
|
151
192
|
|
|
152
193
|
return leftCols.concat(mainColumns).concat(rightCols);
|
|
153
194
|
}
|
|
@@ -301,7 +342,6 @@ export function useVirtualScroll(
|
|
|
301
342
|
let startIndex = 0;
|
|
302
343
|
let endIndex = dataLength;
|
|
303
344
|
let autoRowHeightTop = 0;
|
|
304
|
-
|
|
305
345
|
if (autoRowHeight || hasExpandCol.value) {
|
|
306
346
|
if (autoRowHeight && trRef.value) {
|
|
307
347
|
// Batch DOM measurements for better performance
|
|
@@ -433,39 +473,33 @@ export function useVirtualScroll(
|
|
|
433
473
|
const { scrollLeft, containerWidth } = virtualScrollX.value;
|
|
434
474
|
let startIndex = 0;
|
|
435
475
|
let offsetLeft = 0;
|
|
436
|
-
let colWidthSum = 0;
|
|
437
|
-
/** 固定左侧列宽 */
|
|
438
|
-
let leftColWidthSum = 0;
|
|
439
476
|
/** 横向滚动时,第一列的剩余宽度 */
|
|
440
477
|
let leftFirstColRestWidth = 0;
|
|
441
478
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
leftFirstColRestWidth = colWidthSum - sLeft;
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
479
|
+
// 使用缓存的累计宽度数组,列配置不变时直接复用
|
|
480
|
+
const { nonFixedCols, leftColWidthSum } = getColWidthCache(tableHeaderLastValue);
|
|
481
|
+
|
|
482
|
+
if (nonFixedCols.length > 0 && sLeft > 0) {
|
|
483
|
+
// 二分查找:找到第一个累计宽度 >= sLeft 的非固定列
|
|
484
|
+
const found = binarySearch(nonFixedCols, mid => {
|
|
485
|
+
return nonFixedCols[mid].cumWidth < sLeft ? -1 : 1;
|
|
486
|
+
});
|
|
487
|
+
const idx = Math.min(found, nonFixedCols.length - 1);
|
|
488
|
+
startIndex = nonFixedCols[idx].index;
|
|
489
|
+
offsetLeft = idx > 0 ? nonFixedCols[idx - 1].cumWidth : 0;
|
|
490
|
+
leftFirstColRestWidth = nonFixedCols[idx].cumWidth - sLeft;
|
|
491
|
+
} else if (nonFixedCols.length > 0) {
|
|
492
|
+
startIndex = nonFixedCols[0].index;
|
|
459
493
|
}
|
|
460
494
|
// -----
|
|
461
|
-
|
|
495
|
+
let endColWidthSum = leftFirstColRestWidth;
|
|
462
496
|
const containerW = containerWidth - leftColWidthSum;
|
|
463
497
|
let endIndex = headerLength;
|
|
464
498
|
for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {
|
|
465
499
|
const col = tableHeaderLastValue[colIndex];
|
|
466
|
-
|
|
500
|
+
endColWidthSum += getCalculatedColWidth(col);
|
|
467
501
|
// 列宽大于容器宽度则停止
|
|
468
|
-
if (
|
|
502
|
+
if (endColWidthSum >= containerW) {
|
|
469
503
|
endIndex = colIndex + 1; // slice endIndex + 1
|
|
470
504
|
break;
|
|
471
505
|
}
|
|
@@ -482,7 +516,7 @@ export function useVirtualScroll(
|
|
|
482
516
|
// 向左滚动
|
|
483
517
|
Object.assign(virtualScrollX.value, { startIndex, endIndex, offsetLeft, scrollLeft: sLeft });
|
|
484
518
|
} else {
|
|
485
|
-
//vue2
|
|
519
|
+
// vue2 向右滚动优化
|
|
486
520
|
Object.assign(virtualScrollX.value, { endIndex, scrollLeft: sLeft });
|
|
487
521
|
vue2ScrollXTimeout = window.setTimeout(() => {
|
|
488
522
|
Object.assign(virtualScrollX.value, { startIndex, offsetLeft });
|
|
@@ -507,5 +541,6 @@ export function useVirtualScroll(
|
|
|
507
541
|
updateVirtualScrollX,
|
|
508
542
|
setAutoHeight,
|
|
509
543
|
clearAllAutoHeight,
|
|
544
|
+
clearColWidthCache,
|
|
510
545
|
] as const;
|
|
511
546
|
}
|
|
@@ -266,7 +266,7 @@ export function throttle<T extends (...args: any[]) => any>(fn: T, delay: number
|
|
|
266
266
|
let timer: number;
|
|
267
267
|
let lastArgs: Parameters<T> | null = null;
|
|
268
268
|
|
|
269
|
-
const callFn =
|
|
269
|
+
const callFn = () => {
|
|
270
270
|
if (lastArgs) {
|
|
271
271
|
fn(...lastArgs);
|
|
272
272
|
lastArgs = null;
|
|
@@ -295,7 +295,7 @@ export function rafThrottle<T extends (...args: any[]) => any>(fn: T): (...args:
|
|
|
295
295
|
let rafId: number | null = null;
|
|
296
296
|
let lastArgs: Parameters<T> | null = null;
|
|
297
297
|
|
|
298
|
-
const callFn =
|
|
298
|
+
const callFn = () => {
|
|
299
299
|
if (lastArgs) {
|
|
300
300
|
fn(...lastArgs);
|
|
301
301
|
lastArgs = null;
|
|
@@ -312,3 +312,13 @@ export function rafThrottle<T extends (...args: any[]) => any>(fn: T): (...args:
|
|
|
312
312
|
}
|
|
313
313
|
};
|
|
314
314
|
}
|
|
315
|
+
export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
|
|
316
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
317
|
+
return function (...args: Parameters<T>) {
|
|
318
|
+
if (timer) clearTimeout(timer);
|
|
319
|
+
timer = setTimeout(() => {
|
|
320
|
+
fn(...args);
|
|
321
|
+
timer = null;
|
|
322
|
+
}, delay);
|
|
323
|
+
};
|
|
324
|
+
}
|