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 CHANGED
@@ -12,21 +12,22 @@
12
12
  </p>
13
13
  </p>
14
14
 
15
- > Stk Table Vue(Sticky Table) is a high-performance virtual list component based on Vue.
16
- >
17
- > Used for real-time data display, with data highlighting and dynamic effects
18
- >
19
- > Support Vue3 and Vue2.7
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 en](https://ja-plus.github.io/stk-table-vue/en/)
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)
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * name: stk-table-vue
3
- * version: v0.11.6
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/
@@ -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 enum TagType {
258
- TH = 0,
259
- TD = 1,
257
+ export declare const TagType: {
258
+ readonly TH: 0;
259
+ readonly TD: 1;
260
260
  /** tfoot */
261
- TF = 2
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>[]>): readonly [ComputedRef<((col: StkTableColumn<DT>) => boolean) | (() => any)>, Ref<boolean, boolean>, (e: MouseEvent, col: StkTableColumn<DT>, leftHandle?: boolean) => void];
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;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * name: stk-table-vue
3
- * version: v0.11.6
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, onUnmounted, nextTick, customRef, toRaw, provide, toRef, normalizeClass, unref, renderSlot, Fragment, renderList, mergeProps, resolveDynamicComponent, withCtx, createTextVNode, createVNode, createApp, markRaw, getCurrentInstance, h } from "vue";
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 = function() {
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 = function() {
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
- var TagType = /* @__PURE__ */ ((TagType2) => {
890
- TagType2[TagType2["TH"] = 0] = "TH";
891
- TagType2[TagType2["TD"] = 1] = "TD";
892
- TagType2[TagType2["TF"] = 2] = "TF";
893
- return TagType2;
894
- })(TagType || {});
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 (!curCol) return;
1066
- curCol.width = width + "px";
1067
- emits("update:columns", props.columns.slice());
1068
- emits("col-resize", { ...curCol });
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
- highlightDimRowsAnimation.delete(rowKeyValue);
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
- var ScrollCodes = /* @__PURE__ */ ((ScrollCodes2) => {
1412
- ScrollCodes2["ArrowUp"] = "ArrowUp";
1413
- ScrollCodes2["ArrowRight"] = "ArrowRight";
1414
- ScrollCodes2["ArrowDown"] = "ArrowDown";
1415
- ScrollCodes2["ArrowLeft"] = "ArrowLeft";
1416
- ScrollCodes2["PageUp"] = "PageUp";
1417
- ScrollCodes2["PageDown"] = "PageDown";
1418
- ScrollCodes2["Home"] = "Home";
1419
- ScrollCodes2["End"] = "End";
1420
- return ScrollCodes2;
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
- if (!val) {
1427
- removeListeners();
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 === "ArrowUp") {
1462
+ if (keyCode === ScrollCodes.ArrowUp) {
1461
1463
  scrollTo(scrollTop - rowHeight, null);
1462
- } else if (keyCode === "ArrowRight") {
1464
+ } else if (keyCode === ScrollCodes.ArrowRight) {
1463
1465
  scrollTo(null, scrollLeft + 50);
1464
- } else if (keyCode === "ArrowDown") {
1466
+ } else if (keyCode === ScrollCodes.ArrowDown) {
1465
1467
  scrollTo(scrollTop + rowHeight, null);
1466
- } else if (keyCode === "ArrowLeft") {
1468
+ } else if (keyCode === ScrollCodes.ArrowLeft) {
1467
1469
  scrollTo(null, scrollLeft - 50);
1468
- } else if (keyCode === "PageUp") {
1470
+ } else if (keyCode === ScrollCodes.PageUp) {
1469
1471
  scrollTo(scrollTop - rowHeight * bodyPageSize + headerHeight, null);
1470
- } else if (keyCode === "PageDown") {
1472
+ } else if (keyCode === ScrollCodes.PageDown) {
1471
1473
  scrollTo(scrollTop + rowHeight * bodyPageSize - headerHeight, null);
1472
- } else if (keyCode === "Home") {
1474
+ } else if (keyCode === ScrollCodes.Home) {
1473
1475
  scrollTo(0, null);
1474
- } else if (keyCode === "End") {
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 === -1) return;
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
- onUnmounted(() => {
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
- document.removeEventListener("mousemove", onVerticalDrag);
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
- for (let i = 0; i < startIndex; i++) {
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 = endIndex; i < tableHeaderLastValue.length; 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(startIndex, endIndex);
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
- for (let colIndex = 0; colIndex < headerLength; colIndex++) {
2601
- const col = tableHeaderLastValue[colIndex];
2602
- const colWidth = getCalculatedColWidth(col);
2603
- startIndex++;
2604
- if (col.fixed === "left") {
2605
- leftColWidthSum += colWidth;
2606
- continue;
2607
- }
2608
- colWidthSum += colWidth;
2609
- if (colWidthSum >= sLeft) {
2610
- offsetLeft = colWidthSum - colWidth;
2611
- startIndex--;
2612
- leftFirstColRestWidth = colWidthSum - sLeft;
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
- colWidthSum += getCalculatedColWidth(col);
2622
- if (colWidthSum >= containerW) {
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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * name: stk-table-vue
3
- * version: v0.11.6
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/
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "stk-table-vue",
3
- "version": "0.11.7",
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 enum TagType {
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 (!curCol) return;
167
- curCol.width = width + 'px';
168
-
169
- emits('update:columns', props.columns.slice());
170
- emits('col-resize', { ...curCol });
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
- highlightDimRowsAnimation.delete(rowKeyValue);
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
- enum 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
- }
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
- if (!val) {
38
- removeListeners();
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 === -1) return;
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, onUnmounted, ref, Ref } from 'vue';
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
- onUnmounted(() => {
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
- document.removeEventListener('mousemove', onVerticalDrag);
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 < startIndex; 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 = endIndex; i < tableHeaderLastValue.length; 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(startIndex, endIndex);
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
- for (let colIndex = 0; colIndex < headerLength; colIndex++) {
443
- const col = tableHeaderLastValue[colIndex];
444
- const colWidth = getCalculatedColWidth(col);
445
- startIndex++;
446
- // fixed left 不进入计算列宽
447
- if (col.fixed === 'left') {
448
- leftColWidthSum += colWidth;
449
- continue;
450
- }
451
- colWidthSum += colWidth;
452
- // 列宽(非固定列)加到超过scrollLeft的时候,表示startIndex从上一个开始下标
453
- if (colWidthSum >= sLeft) {
454
- offsetLeft = colWidthSum - colWidth;
455
- startIndex--;
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
- colWidthSum = leftFirstColRestWidth;
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
- colWidthSum += getCalculatedColWidth(col);
500
+ endColWidthSum += getCalculatedColWidth(col);
467
501
  // 列宽大于容器宽度则停止
468
- if (colWidthSum >= containerW) {
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 = function () {
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 = function () {
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
+ }