stk-table-vue 0.3.2 → 0.3.4

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
@@ -16,7 +16,7 @@ repo:
16
16
  - [x] 虚拟滚动默认通过js计算行高亮背景色,可通过 `useCss` 设置为css @keyframe实现 。
17
17
  - [x] 支持配置高亮参数(持续时间,颜色,频率(虚拟滚动))。(`v0.2.9`)
18
18
  - [x] `setHighlightDimRow`/`setHighlightCellRow`支持自定义高亮css类名。(`v0.2.9`)
19
- - [x] 使用 `Web Animations API` 实现高亮。
19
+ - [x] 使用 `Web Animations API` 实现高亮。(`v0.3.4` 变更为默认值)
20
20
  * [x] 虚拟滚动。
21
21
  - [x] 纵向。
22
22
  - [x] 横向(必须设置列宽)。
@@ -223,7 +223,7 @@ export type StkProps = {
223
223
  highlightConfig?: {
224
224
  /** 高亮持续时间(s) */
225
225
  duration?: number;
226
- /** 高亮帧率(虚拟滚动生效) */
226
+ /** 高亮帧率*/
227
227
  fps?: number;
228
228
  };
229
229
  /** 序号列配置 */
@@ -231,6 +231,14 @@ export type StkProps = {
231
231
  /** 序号列起始下标 用于适配分页 */
232
232
  startIndex?: number;
233
233
  };
234
+ /**
235
+ * 固定头,固定列实现方式。
236
+ *
237
+ * relative:固定列只能放在props.columns的两侧。如果列宽会变动则谨慎使用。
238
+ *
239
+ * 低版本浏览器只能为'relative',
240
+ */
241
+ cellFixedMode?: 'sticky' | 'relative';
234
242
  };
235
243
  ```
236
244
 
@@ -541,10 +549,20 @@ export type SortConfig<T extends Record<string, any>> = {
541
549
  ### 鼠标悬浮表头时,不展示title
542
550
  * 将 `StkTableColumn` 中的 `title` 字段置为 "" 空字符串。这样th中就没有title了。
543
551
  * 使用 `StkTableColumn` 中的 `customHeaderCell` 属性中,自定义表头渲染。
544
- ### 高亮性能
545
- * 在虚拟滚动下高亮强制使用css @keyframes 实现动画。`setHighlightDimRow`/`setHighlightCellRow` 最后一个参数传入 `{method: 'css'}` 即可。(滚动后动画会中断)
546
- * 指定 `{method:'animation'}` 在虚拟滚动下使用animation api实现动画。好处是动画流畅,且滚动后动画不中断。
552
+ ### 性能
553
+ #### highlight
554
+ * 在虚拟滚动下高亮强制使用 `css @keyframes` 实现动画。`setHighlightDimRow`/`setHighlightCellRow` 最后一个参数传入 `{method: 'css'}` 即可。(滚动后动画会中断)
555
+ * 指定 `{method:'animation'}` 在虚拟滚动下使用animation api实现动画。好处是动画流畅,且滚动后动画不中断。坏处是不支持帧率配置。
547
556
  * 配置 `props.highlightConfig.fps` 指定高亮帧率。降低帧率有利于性能。
557
+ #### relative fixed
558
+ * 配置 `props.cellFixedMode` 为 `relative` 时,将使用相对定位实现固定列与固定表头,相较于`sticky`的实现,渲染合成层更少。
559
+ * 问题:若开启了纵向虚拟滚动,不开启横向虚拟滚动,且不设置某些列宽时。如果纵向滚动导致某些列宽变化,则会导致右侧固定列计算错误。
560
+ #### tr 分层
561
+ * 通过css选择器将 stk-table tbody tr 配置 `transform:translateZ(0)` 对每行 tr 进行分层。在 `customCell` 较多且复杂时,尝试开启此功能可能对性能有帮助。
548
562
 
549
563
  ## Other
550
564
  * `$*$` 兼容注释
565
+
566
+
567
+ ### Planed removal APi
568
+ * `setHighlightDimRow` 中的 `method="js"`。观察animation Api 是否足够满足使用场景。若足够满足计划在后期移除,并且可以移除 `d3-interpolate` 依赖。
@@ -1,4 +1,4 @@
1
- import { HighlightConfig, Order, SeqConfig, SortConfig, SortOption, SortState, StkTableColumn, UniqKeyProp } from './types/index';
1
+ import { HighlightConfig, Order, SeqConfig, SortConfig, SortOption, SortState, StkTableColumn, UniqKey, UniqKeyProp } from './types/index';
2
2
  /** Generic stands for DataType */
3
3
  type DT = any;
4
4
  /**
@@ -126,6 +126,14 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
126
126
  highlightConfig?: HighlightConfig | undefined;
127
127
  /** 序号列配置 */
128
128
  seqConfig?: SeqConfig | undefined;
129
+ /**
130
+ * 固定头,固定列实现方式。
131
+ *
132
+ * relative:固定列只能放在props.columns的两侧。如果列宽会变动则谨慎使用。
133
+ *
134
+ * 低版本浏览器只能为'relative',
135
+ */
136
+ cellFixedMode?: "sticky" | "relative" | undefined;
129
137
  }>, {
130
138
  width: string;
131
139
  fixedMode: boolean;
@@ -165,6 +173,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
165
173
  hideHeaderTitle: boolean;
166
174
  highlightConfig: () => {};
167
175
  seqConfig: () => {};
176
+ cellFixedMode: string;
168
177
  }>, {
169
178
  /** 初始化横向纵向虚拟滚动 */
170
179
  initVirtualScroll: (height?: number | undefined) => void;
@@ -177,13 +186,13 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
177
186
  /** 设置高亮渐暗单元格 */
178
187
  setHighlightDimCell: (rowKeyValue: string, dataIndex: string, option?: {
179
188
  className?: string | undefined;
180
- method?: "css" | "animation" | undefined;
189
+ method?: "animation" | "css" | undefined;
181
190
  keyframe?: Keyframe[] | PropertyIndexedKeyframes | null | undefined;
182
191
  duration?: number | undefined;
183
192
  }) => void;
184
193
  /** 设置高亮渐暗行 */
185
- setHighlightDimRow: (rowKeyValues: import("./types/index").UniqKey[], option?: {
186
- method?: "css" | "animation" | "js" | undefined;
194
+ setHighlightDimRow: (rowKeyValues: UniqKey[], option?: {
195
+ method?: "animation" | "css" | "js" | undefined;
187
196
  useCss?: boolean | undefined;
188
197
  className?: string | undefined;
189
198
  keyframe?: Keyframe[] | PropertyIndexedKeyframes | null | undefined;
@@ -314,6 +323,14 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
314
323
  highlightConfig?: HighlightConfig | undefined;
315
324
  /** 序号列配置 */
316
325
  seqConfig?: SeqConfig | undefined;
326
+ /**
327
+ * 固定头,固定列实现方式。
328
+ *
329
+ * relative:固定列只能放在props.columns的两侧。如果列宽会变动则谨慎使用。
330
+ *
331
+ * 低版本浏览器只能为'relative',
332
+ */
333
+ cellFixedMode?: "sticky" | "relative" | undefined;
317
334
  }>, {
318
335
  width: string;
319
336
  fixedMode: boolean;
@@ -353,6 +370,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
353
370
  hideHeaderTitle: boolean;
354
371
  highlightConfig: () => {};
355
372
  seqConfig: () => {};
373
+ cellFixedMode: string;
356
374
  }>>> & {
357
375
  onScroll?: ((ev: Event, data: {
358
376
  startIndex: number;
@@ -415,6 +433,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<__
415
433
  hideHeaderTitle: boolean | string[];
416
434
  highlightConfig: HighlightConfig;
417
435
  seqConfig: SeqConfig;
436
+ cellFixedMode: "sticky" | "relative";
418
437
  }, {}>, {
419
438
  tableHeader?(_: {
420
439
  col: StkTableColumn<any>;
@@ -1,12 +1,12 @@
1
- import { Ref, ShallowRef } from 'vue';
2
- import { StkTableColumn } from './types';
1
+ import { ComputedRef, Ref, ShallowRef } from 'vue';
2
+ import { StkTableColumn, UniqKey } from './types';
3
3
  type Params<DT extends Record<string, any>> = {
4
4
  props: any;
5
5
  emits: any;
6
6
  tableContainerRef: Ref<HTMLElement | undefined>;
7
7
  tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>;
8
8
  colResizeIndicatorRef: Ref<HTMLElement | undefined>;
9
- colKeyGen: (p: any) => string;
9
+ colKeyGen: ComputedRef<(p: any) => UniqKey>;
10
10
  };
11
11
  /** 列宽拖动 */
12
12
  export declare function useColResize<DT extends Record<string, any>>({ tableContainerRef, tableHeaderLast, colResizeIndicatorRef, props, emits, colKeyGen, }: Params<DT>): {
@@ -1,8 +1,8 @@
1
- import { Ref, ShallowRef } from 'vue';
2
- import { StkTableColumn } from './types';
1
+ import { ComputedRef, Ref, ShallowRef } from 'vue';
2
+ import { StkTableColumn, UniqKey } from './types';
3
3
  type Params<T extends Record<string, any>> = {
4
4
  props: any;
5
- colKeyGen: (col: StkTableColumn<T>) => string;
5
+ colKeyGen: ComputedRef<(col: StkTableColumn<T>) => UniqKey>;
6
6
  tableHeaders: ShallowRef<StkTableColumn<T>[][]>;
7
7
  tableHeaderLast: ShallowRef<StkTableColumn<T>[]>;
8
8
  tableContainerRef: Ref<HTMLDivElement | undefined>;
@@ -13,7 +13,7 @@ type Params<T extends Record<string, any>> = {
13
13
  */
14
14
  export declare function useFixedCol<DT extends Record<string, any>>({ props, colKeyGen, tableHeaders, tableHeaderLast, tableContainerRef }: Params<DT>): {
15
15
  /** 固定列class */
16
- fixedColClassMap: import("vue").ComputedRef<Map<any, any>>;
16
+ fixedColClassMap: ComputedRef<Map<any, any>>;
17
17
  /** 处理固定列阴影 */
18
18
  dealFixedColShadow: () => void;
19
19
  /** 滚动条变化时,更新需要展示阴影的列 */
@@ -9,9 +9,9 @@ type Params = {
9
9
  * 高亮单元格,行
10
10
  */
11
11
  export declare function useHighlight({ props, stkTableId, tableContainerRef }: Params): {
12
- highlightSteps: number;
12
+ highlightSteps: number | null;
13
13
  setHighlightDimRow: (rowKeyValues: UniqKey[], option?: {
14
- method?: 'css' | 'animation' | 'js';
14
+ method?: 'animation' | 'css' | 'js';
15
15
  /** @deprecated 请使用method */
16
16
  useCss?: boolean;
17
17
  className?: string;
@@ -20,7 +20,7 @@ export declare function useHighlight({ props, stkTableId, tableContainerRef }: P
20
20
  }) => void;
21
21
  setHighlightDimCell: (rowKeyValue: string, dataIndex: string, option?: {
22
22
  className?: string;
23
- method?: 'css' | 'animation';
23
+ method?: 'animation' | 'css';
24
24
  keyframe?: Parameters<Animatable['animate']>['0'];
25
25
  duration?: number;
26
26
  }) => void;
@@ -26,8 +26,10 @@ export type VirtualScrollStore = {
26
26
  };
27
27
  /** 暂存横向虚拟滚动的数据 */
28
28
  export type VirtualScrollXStore = {
29
- /** 容器宽度 */
29
+ /** 父容器宽度 */
30
30
  containerWidth: number;
31
+ /** 滚动容器的宽度 */
32
+ scrollWidth: number;
31
33
  /** 开始位置 */
32
34
  startIndex: number;
33
35
  /** 结束始位置 */
@@ -54,6 +56,7 @@ export declare function useVirtualScroll<DT extends Record<string, any>>({ props
54
56
  }>;
55
57
  virtualScrollX: Ref<{
56
58
  containerWidth: number;
59
+ scrollWidth: number;
57
60
  startIndex: number;
58
61
  endIndex: number;
59
62
  offsetLeft: number;
@@ -1,4 +1,4 @@
1
- import { onMounted, onBeforeUnmount, watch, ref, shallowRef, computed, defineComponent, toRaw, openBlock, createElementBlock, normalizeClass, unref, normalizeStyle, createCommentVNode, createElementVNode, Fragment, renderList, createBlock, resolveDynamicComponent, toDisplayString, renderSlot, createTextVNode } from "vue";
1
+ import { onMounted, onBeforeUnmount, watch, ref, shallowRef, computed, defineComponent, nextTick, toRaw, openBlock, createElementBlock, normalizeClass, unref, normalizeStyle, createCommentVNode, createElementVNode, Fragment, renderList, createBlock, resolveDynamicComponent, toDisplayString, renderSlot, createTextVNode } from "vue";
2
2
  import { interpolateRgb } from "d3-interpolate";
3
3
  const DEFAULT_COL_WIDTH = "100";
4
4
  const DEFAULT_TABLE_HEIGHT = 100;
@@ -260,7 +260,7 @@ function useColResize({
260
260
  const { clientX } = e;
261
261
  const { scrollLeft, scrollTop } = tableContainerRef.value;
262
262
  const { left } = tableContainerRef.value.getBoundingClientRect();
263
- let colIndex = tableHeaderLast.value.findIndex((it) => colKeyGen(it) === colKeyGen(col));
263
+ let colIndex = tableHeaderLast.value.findIndex((it) => colKeyGen.value(it) === colKeyGen.value(col));
264
264
  if (isPrev) {
265
265
  colIndex -= 1;
266
266
  col = tableHeaderLast.value[colIndex];
@@ -307,7 +307,7 @@ function useColResize({
307
307
  let width = getCalculatedColWidth(lastCol) + moveX;
308
308
  if (width < props.colMinWidth)
309
309
  width = props.colMinWidth;
310
- const curCol = tableHeaderLast.value.find((it) => colKeyGen(it) === colKeyGen(lastCol));
310
+ const curCol = tableHeaderLast.value.find((it) => colKeyGen.value(it) === colKeyGen.value(lastCol));
311
311
  if (!curCol)
312
312
  return;
313
313
  curCol.width = width + "px";
@@ -359,7 +359,7 @@ function useFixedCol({ props, colKeyGen, tableHeaders, tableHeaderLast, tableCon
359
359
  ["fixed-cell--" + col.fixed]: col.fixed,
360
360
  "fixed-cell--shadow": showShadow
361
361
  };
362
- colMap.set(colKeyGen(col), classObj);
362
+ colMap.set(colKeyGen.value(col), classObj);
363
363
  });
364
364
  });
365
365
  return colMap;
@@ -453,33 +453,45 @@ function useFixedStyle({
453
453
  const { fixed } = col;
454
454
  if (tagType === TagType.TD && !fixed)
455
455
  return null;
456
- const isFixedLeft = fixed === "left";
457
456
  const style = {};
458
457
  const { colKeyStore, refStore } = fixedColumnsPositionStore.value;
458
+ let isRelativeMode = true;
459
+ if (props.cellFixedMode === "sticky") {
460
+ isRelativeMode = false;
461
+ }
459
462
  if (IS_LEGACY_MODE) {
463
+ isRelativeMode = true;
464
+ }
465
+ const { scrollLeft, scrollWidth, offsetLeft, containerWidth } = virtualScrollX.value;
466
+ const scrollRight = scrollWidth - containerWidth - scrollLeft;
467
+ if (virtualScrollX.value.scrollLeft === 0 && fixed === "left" && tagType === TagType.TD) {
468
+ style.position = void 0;
469
+ } else if (scrollRight === 0 && fixed === "right" && tagType === TagType.TD) {
470
+ style.position = void 0;
471
+ } else if (isRelativeMode) {
460
472
  style.position = "relative";
461
473
  } else {
462
474
  style.position = "sticky";
463
475
  }
476
+ const isFixedLeft = fixed === "left";
464
477
  if (tagType === TagType.TH) {
465
- if (IS_LEGACY_MODE) {
478
+ if (isRelativeMode) {
466
479
  style.top = virtualScroll.value.scrollTop + "px";
467
480
  } else {
468
481
  style.top = depth * props.rowHeight + "px";
469
482
  }
470
483
  style.zIndex = isFixedLeft ? "3" : "2";
471
484
  } else {
472
- style.zIndex = isFixedLeft ? "2" : "1";
485
+ if (isFixedLeft) {
486
+ style.zIndex = "2";
487
+ }
473
488
  }
474
489
  if (fixed === "left" || fixed === "right") {
475
- if (IS_LEGACY_MODE) {
490
+ if (isRelativeMode) {
476
491
  if (isFixedLeft) {
477
- if (virtualX_on.value)
478
- style.left = virtualScrollX.value.scrollLeft - virtualScrollX.value.offsetLeft + "px";
479
- else
480
- style.left = virtualScrollX.value.scrollLeft + "px";
492
+ style.left = scrollLeft - (virtualX_on.value ? offsetLeft : 0) + "px";
481
493
  } else {
482
- style.right = `${virtualX_offsetRight.value}px`;
494
+ style.right = Math.max(scrollRight - (virtualX_on.value ? virtualX_offsetRight.value : 0), 0) + "px";
483
495
  }
484
496
  } else {
485
497
  const lr = (col.dataIndex ? colKeyStore[col.dataIndex] : refStore.get(col)) + "px";
@@ -498,13 +510,13 @@ function useFixedStyle({
498
510
  }
499
511
  function useHighlight({ props, stkTableId, tableContainerRef }) {
500
512
  const config = props.highlightConfig;
501
- const highlightDuration = config.duration ? config.duration * 1e3 : HIGHLIGHT_DURATION;
502
- const highlightFrequency = config.fps ? 1e3 / config.fps : HIGHLIGHT_FREQ;
503
513
  const highlightColor = {
504
514
  light: HIGHLIGHT_COLOR.light,
505
515
  dark: HIGHLIGHT_COLOR.dark
506
516
  };
507
- const highlightSteps = highlightDuration / highlightFrequency;
517
+ const highlightDuration = config.duration ? config.duration * 1e3 : HIGHLIGHT_DURATION;
518
+ const highlightFrequency = config.fps && config.fps > 0 ? 1e3 / config.fps : null;
519
+ const highlightSteps = highlightFrequency ? Math.round(highlightDuration / highlightFrequency) : null;
508
520
  const highlightFrom = computed(() => highlightColor[props.theme].from);
509
521
  const highlightTo = computed(() => highlightColor[props.theme].to);
510
522
  const highlightInter = computed(() => interpolateRgb(highlightFrom.value, highlightTo.value));
@@ -514,6 +526,13 @@ function useHighlight({ props, stkTableId, tableContainerRef }) {
514
526
  let calcHighlightDimLoopAnimation = false;
515
527
  const highlightDimRowsTimeout = /* @__PURE__ */ new Map();
516
528
  const highlightDimCellsTimeout = /* @__PURE__ */ new Map();
529
+ const defaultHighlightDimOption = (() => {
530
+ const keyframe = { backgroundColor: [highlightFrom.value, highlightTo.value] };
531
+ if (highlightSteps) {
532
+ keyframe.easing = `steps(${highlightSteps})`;
533
+ }
534
+ return { duration: highlightDuration, keyframe };
535
+ })();
517
536
  function calcRowHighlightLoop() {
518
537
  if (calcHighlightDimLoopAnimation)
519
538
  return;
@@ -565,20 +584,16 @@ function useHighlight({ props, stkTableId, tableContainerRef }) {
565
584
  calcHighlightDimLoopJs = false;
566
585
  highlightDimRowsJs.clear();
567
586
  }
568
- }, highlightFrequency);
587
+ }, highlightFrequency || HIGHLIGHT_FREQ);
569
588
  };
570
589
  recursion();
571
590
  }
572
- const defaultHighlightDimOption = {
573
- keyframe: [{ backgroundColor: highlightFrom.value }, { backgroundColor: highlightTo.value }],
574
- duration: highlightDuration
575
- };
576
591
  function setHighlightDimCell(rowKeyValue, dataIndex, option = {}) {
577
592
  var _a;
578
593
  const cellEl = (_a = tableContainerRef.value) == null ? void 0 : _a.querySelector(`[data-row-key="${rowKeyValue}"]>[data-index="${dataIndex}"]`);
579
594
  const { className, method, duration, keyframe } = {
580
595
  className: HIGHLIGHT_CELL_CLASS,
581
- method: "css",
596
+ method: "animation",
582
597
  ...defaultHighlightDimOption,
583
598
  ...option
584
599
  };
@@ -595,7 +610,7 @@ function useHighlight({ props, stkTableId, tableContainerRef }) {
595
610
  rowKeyValues = [rowKeyValues];
596
611
  const { className, method, useCss, keyframe, duration } = {
597
612
  className: HIGHLIGHT_ROW_CLASS,
598
- method: props.virtual ? "js" : "css",
613
+ method: "animation",
599
614
  ...defaultHighlightDimOption,
600
615
  ...option
601
616
  };
@@ -837,6 +852,7 @@ function useVirtualScroll({
837
852
  });
838
853
  const virtualScrollX = ref({
839
854
  containerWidth: 0,
855
+ scrollWidth: 0,
840
856
  startIndex: 0,
841
857
  endIndex: 0,
842
858
  offsetLeft: 0,
@@ -897,12 +913,7 @@ function useVirtualScroll({
897
913
  return;
898
914
  const { offsetHeight, scrollTop } = tableContainerRef.value || {};
899
915
  const { rowHeight } = virtualScroll.value;
900
- let containerHeight;
901
- if (typeof height === "number") {
902
- containerHeight = height;
903
- } else {
904
- containerHeight = offsetHeight || DEFAULT_TABLE_HEIGHT;
905
- }
916
+ const containerHeight = height ?? (offsetHeight || DEFAULT_TABLE_HEIGHT);
906
917
  const { headless, headerRowHeight } = props;
907
918
  let pageSize = Math.ceil(containerHeight / rowHeight);
908
919
  if (!headless) {
@@ -913,10 +924,9 @@ function useVirtualScroll({
913
924
  updateVirtualScrollY(scrollTop);
914
925
  }
915
926
  function initVirtualScrollX() {
916
- if (!props.virtualX)
917
- return;
918
- const { offsetWidth, scrollLeft } = tableContainerRef.value || {};
919
- virtualScrollX.value.containerWidth = offsetWidth || DEFAULT_TABLE_WIDTH;
927
+ const { clientWidth, scrollLeft, scrollWidth } = tableContainerRef.value || {};
928
+ virtualScrollX.value.containerWidth = clientWidth || DEFAULT_TABLE_WIDTH;
929
+ virtualScrollX.value.scrollWidth = scrollWidth || DEFAULT_TABLE_WIDTH;
920
930
  updateVirtualScrollX(scrollLeft);
921
931
  }
922
932
  function initVirtualScroll(height) {
@@ -930,17 +940,12 @@ function useVirtualScroll({
930
940
  if (!virtual_on.value)
931
941
  return;
932
942
  let startIndex = Math.floor(sTop / rowHeight);
933
- if (props.stripe) {
934
- startIndex -= 1;
935
- }
936
943
  if (startIndex < 0) {
937
944
  startIndex = 0;
938
945
  }
939
946
  if (props.stripe && startIndex !== 0) {
940
947
  const scrollRows = Math.abs(oldStartIndex - startIndex);
941
- if (scrollRows < 2) {
942
- return;
943
- } else if (scrollRows % 2) {
948
+ if (scrollRows % 2) {
944
949
  startIndex -= 1;
945
950
  }
946
951
  }
@@ -969,18 +974,21 @@ function useVirtualScroll({
969
974
  let vue2ScrollXTimeout = null;
970
975
  function updateVirtualScrollX(sLeft = 0) {
971
976
  var _a;
977
+ if (!props.virtualX)
978
+ return;
972
979
  const headerLength = (_a = tableHeaderLast.value) == null ? void 0 : _a.length;
973
- const { scrollLeft } = virtualScrollX.value;
974
980
  if (!headerLength)
975
981
  return;
982
+ const { scrollLeft } = virtualScrollX.value;
976
983
  let startIndex = 0;
977
984
  let offsetLeft = 0;
978
985
  let colWidthSum = 0;
979
986
  let leftColWidthSum = 0;
987
+ let leftFirstColRestWidth = 0;
980
988
  for (let colIndex = 0; colIndex < headerLength; colIndex++) {
981
- startIndex++;
982
989
  const col = tableHeaderLast.value[colIndex];
983
990
  const colWidth = getCalculatedColWidth(col);
991
+ startIndex++;
984
992
  if (col.fixed === "left") {
985
993
  leftColWidthSum += colWidth;
986
994
  continue;
@@ -989,10 +997,11 @@ function useVirtualScroll({
989
997
  if (colWidthSum >= sLeft) {
990
998
  offsetLeft = colWidthSum - colWidth;
991
999
  startIndex--;
1000
+ leftFirstColRestWidth = colWidthSum - sLeft;
992
1001
  break;
993
1002
  }
994
1003
  }
995
- colWidthSum = 0;
1004
+ colWidthSum = leftFirstColRestWidth;
996
1005
  const containerWidth = virtualScrollX.value.containerWidth - leftColWidthSum;
997
1006
  let endIndex = headerLength;
998
1007
  for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {
@@ -1068,14 +1077,12 @@ const _hoisted_9 = ["onMousedown"];
1068
1077
  const _hoisted_10 = ["onMousedown"];
1069
1078
  const _hoisted_11 = {
1070
1079
  key: 0,
1071
- class: "virtual-x-left",
1072
- style: { "padding": "0" }
1080
+ class: "virtual-x-left"
1073
1081
  };
1074
1082
  const _hoisted_12 = ["id", "data-row-key", "onClick", "onDblclick", "onContextmenu", "onMouseover"];
1075
1083
  const _hoisted_13 = {
1076
1084
  key: 0,
1077
- class: "virtual-x-left",
1078
- style: { "padding": "0" }
1085
+ class: "virtual-x-left"
1079
1086
  };
1080
1087
  const _hoisted_14 = ["data-index", "onClick", "onMouseenter", "onMouseleave", "onMouseover"];
1081
1088
  const _hoisted_15 = ["title"];
@@ -1119,7 +1126,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1119
1126
  }) },
1120
1127
  hideHeaderTitle: { type: [Boolean, Array], default: false },
1121
1128
  highlightConfig: { default: () => ({}) },
1122
- seqConfig: { default: () => ({}) }
1129
+ seqConfig: { default: () => ({}) },
1130
+ cellFixedMode: { default: "sticky" }
1123
1131
  },
1124
1132
  emits: ["sort-change", "row-click", "current-change", "row-dblclick", "header-row-menu", "row-menu", "cell-click", "cell-mouseenter", "cell-mouseleave", "cell-mouseover", "header-cell-click", "scroll", "scroll-x", "col-order-change", "th-drag-start", "th-drop", "update:columns"],
1125
1133
  setup(__props, { expose: __expose, emit: __emit }) {
@@ -1138,6 +1146,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1138
1146
  const tableHeaders = shallowRef([]);
1139
1147
  const tableHeaderLast = shallowRef([]);
1140
1148
  const dataSourceCopy = shallowRef([...props.dataSource]);
1149
+ const colKeyGen = computed(() => {
1150
+ if (typeof props.colKey === "function") {
1151
+ return (col) => props.colKey(col);
1152
+ } else {
1153
+ return (col) => col[props.colKey];
1154
+ }
1155
+ });
1141
1156
  const getEmptyCellText = computed(() => {
1142
1157
  if (typeof props.emptyCellText === "string") {
1143
1158
  return () => props.emptyCellText;
@@ -1201,14 +1216,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1201
1216
  () => props.columns,
1202
1217
  () => {
1203
1218
  dealColumns();
1204
- initVirtualScrollX();
1219
+ nextTick(initVirtualScrollX);
1205
1220
  }
1206
1221
  );
1207
1222
  watch(
1208
1223
  () => props.virtualX,
1209
- () => dealColumns()
1224
+ () => {
1225
+ dealColumns();
1226
+ nextTick(initVirtualScrollX);
1227
+ }
1210
1228
  );
1211
- dealColumns();
1212
1229
  watch(
1213
1230
  () => props.dataSource,
1214
1231
  (val) => {
@@ -1234,6 +1251,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1234
1251
  }
1235
1252
  );
1236
1253
  watch(() => props.fixedColShadow, dealFixedColShadow);
1254
+ dealColumns();
1237
1255
  onMounted(() => {
1238
1256
  initVirtualScroll();
1239
1257
  updateFixedShadow();
@@ -1305,15 +1323,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1305
1323
  }
1306
1324
  return key;
1307
1325
  }
1308
- function colKeyGen(col) {
1309
- return typeof props.colKey === "function" ? props.colKey(col) : col[props.colKey];
1310
- }
1311
1326
  const cellStyleMap = computed(() => {
1312
1327
  const thMap = /* @__PURE__ */ new Map();
1313
1328
  const tdMap = /* @__PURE__ */ new Map();
1314
1329
  tableHeaders.value.forEach((cols, depth) => {
1315
1330
  cols.forEach((col) => {
1316
- const colKey = colKeyGen(col);
1331
+ const colKey = colKeyGen.value(col);
1317
1332
  const width = props.virtualX ? getCalculatedColWidth(col) + "px" : transformWidthToStr(col.width);
1318
1333
  const style = {
1319
1334
  width
@@ -1550,16 +1565,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1550
1565
  "text-overflow": props.showOverflow,
1551
1566
  "header-text-overflow": props.showHeaderOverflow
1552
1567
  }]),
1553
- style: normalizeStyle([
1554
- _ctx.virtual && {
1555
- "--row-height": unref(virtualScroll).rowHeight + "px",
1556
- "--header-row-height": (props.headerRowHeight || props.rowHeight) + "px"
1557
- },
1558
- {
1559
- "--highlight-duration": props.highlightConfig.duration && props.highlightConfig.duration + "s",
1560
- "--highlight-timing-function": unref(highlightSteps) ? `steps(${unref(highlightSteps)})` : ""
1561
- }
1562
- ]),
1568
+ style: normalizeStyle({
1569
+ "--row-height": unref(virtualScroll).rowHeight + "px",
1570
+ "--header-row-height": (props.headerRowHeight || props.rowHeight) + "px",
1571
+ "--highlight-duration": props.highlightConfig.duration && props.highlightConfig.duration + "s",
1572
+ "--highlight-timing-function": unref(highlightSteps) ? `steps(${unref(highlightSteps)})` : ""
1573
+ }),
1563
1574
  onScroll: onTableScroll,
1564
1575
  onWheel: onTableWheel
1565
1576
  }, [
@@ -1592,17 +1603,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1592
1603
  (openBlock(true), createElementBlock(Fragment, null, renderList(unref(virtualX_on) && rowIndex === tableHeaders.value.length - 1 ? unref(virtualX_columnPart) : row, (col, colIndex) => {
1593
1604
  return openBlock(), createElementBlock("th", {
1594
1605
  key: col.dataIndex,
1595
- "data-col-key": colKeyGen(col),
1606
+ "data-col-key": colKeyGen.value(col),
1596
1607
  draggable: unref(isHeaderDraggable)(col) ? "true" : "false",
1597
1608
  rowspan: unref(virtualX_on) ? 1 : col.rowSpan,
1598
1609
  colspan: col.colSpan,
1599
- style: normalizeStyle(cellStyleMap.value[unref(TagType).TH].get(colKeyGen(col))),
1610
+ style: normalizeStyle(cellStyleMap.value[unref(TagType).TH].get(colKeyGen.value(col))),
1600
1611
  title: getHeaderTitle(col),
1601
1612
  class: normalizeClass([
1602
1613
  col.sorter ? "sortable" : "",
1603
1614
  col.dataIndex === unref(sortCol) && unref(sortOrderIndex) !== 0 && "sorter-" + sortSwitchOrder[unref(sortOrderIndex)],
1604
1615
  col.headerClassName,
1605
- unref(fixedColClassMap).get(colKeyGen(col))
1616
+ unref(fixedColClassMap).get(colKeyGen.value(col))
1606
1617
  ]),
1607
1618
  onClick: (e) => {
1608
1619
  onColumnSort(col);
@@ -1660,7 +1671,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1660
1671
  _ctx.fixedMode && _ctx.headless ? (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(unref(virtualX_columnPart), (col) => {
1661
1672
  return openBlock(), createElementBlock("td", {
1662
1673
  key: col.dataIndex,
1663
- style: normalizeStyle(cellStyleMap.value[unref(TagType).TD].get(colKeyGen(col)))
1674
+ style: normalizeStyle(cellStyleMap.value[unref(TagType).TD].get(colKeyGen.value(col)))
1664
1675
  }, null, 4);
1665
1676
  }), 128)) : createCommentVNode("", true)
1666
1677
  ], 4)) : createCommentVNode("", true),
@@ -1684,8 +1695,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1684
1695
  return openBlock(), createElementBlock("td", {
1685
1696
  key: col.dataIndex,
1686
1697
  "data-index": col.dataIndex,
1687
- style: normalizeStyle(cellStyleMap.value[unref(TagType).TD].get(colKeyGen(col))),
1688
- class: normalizeClass([col.className, unref(fixedColClassMap).get(colKeyGen(col)), col.type === "seq" ? "seq-column" : ""]),
1698
+ style: normalizeStyle(cellStyleMap.value[unref(TagType).TD].get(colKeyGen.value(col))),
1699
+ class: normalizeClass([col.className, unref(fixedColClassMap).get(colKeyGen.value(col)), col.type === "seq" ? "seq-column" : ""]),
1689
1700
  onClick: (e) => onCellClick(e, row, col),
1690
1701
  onMouseenter: (e) => onCellMouseEnter(e, row, col),
1691
1702
  onMouseleave: (e) => onCellMouseLeave(e, row, col),
package/lib/style.css CHANGED
@@ -88,15 +88,6 @@
88
88
  .stk-table.border thead tr:first-child th{
89
89
  background-image:var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
90
90
  }
91
- .stk-table.border.virtual-x .virtual-x-left{
92
- background:none;
93
- pointer-events:none;
94
- }
95
- .stk-table.border.virtual-x .virtual-x-right{
96
- padding:0;
97
- background:none;
98
- pointer-events:none;
99
- }
100
91
  .stk-table.border-body-v tbody{
101
92
  --bg-border-bottom:linear-gradient(transparent, transparent);
102
93
  }
@@ -136,9 +127,6 @@
136
127
  .stk-table.virtual .padding-top-tr td{
137
128
  height:0;
138
129
  }
139
- .stk-table.virtual-x .virtual-x-left{
140
- padding:0;
141
- }
142
130
  .stk-table th,
143
131
  .stk-table td{
144
132
  z-index:1;
@@ -197,6 +185,12 @@
197
185
  .stk-table .stk-table-main tbody tr.active{
198
186
  background-color:var(--tr-active-bgc);
199
187
  }
188
+ .stk-table .virtual-x-left,
189
+ .stk-table .virtual-x-right{
190
+ padding:0;
191
+ background:none;
192
+ pointer-events:none;
193
+ }
200
194
  .stk-table .column-resize-indicator{
201
195
  width:0;
202
196
  height:100%;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stk-table-vue",
3
- "version": "0.3.2",
4
- "description": "Simple realtime virtual table for vue3&vue2.7",
3
+ "version": "0.3.4",
4
+ "description": "Simple realtime virtual table for vue3 and vue2.7",
5
5
  "main": "./lib/stk-table-vue.js",
6
6
  "types": "./lib/src/StkTable/index.d.ts",
7
7
  "packageManager": "pnpm@8.14.3",
@@ -17,8 +17,13 @@
17
17
  "keywords": [
18
18
  "virtual table",
19
19
  "vue",
20
+ "vue2",
21
+ "vue3",
20
22
  "highlight",
21
- "sticky"
23
+ "sticky",
24
+ "virtual",
25
+ "table",
26
+ "list"
22
27
  ],
23
28
  "files": [
24
29
  "lib",
@@ -18,16 +18,12 @@
18
18
  'text-overflow': props.showOverflow,
19
19
  'header-text-overflow': props.showHeaderOverflow,
20
20
  }"
21
- :style="[
22
- virtual && {
23
- '--row-height': virtualScroll.rowHeight + 'px',
24
- '--header-row-height': (props.headerRowHeight || props.rowHeight) + 'px',
25
- },
26
- {
27
- '--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
28
- '--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : '',
29
- },
30
- ]"
21
+ :style="{
22
+ '--row-height': virtualScroll.rowHeight + 'px',
23
+ '--header-row-height': (props.headerRowHeight || props.rowHeight) + 'px',
24
+ '--highlight-duration': props.highlightConfig.duration && props.highlightConfig.duration + 's',
25
+ '--highlight-timing-function': highlightSteps ? `steps(${highlightSteps})` : '',
26
+ }"
31
27
  @scroll="onTableScroll"
32
28
  @wheel="onTableWheel"
33
29
  >
@@ -142,7 +138,7 @@
142
138
  <tbody>
143
139
  <tr v-if="virtual_on" :style="{ height: `${virtualScroll.offsetTop}px` }" class="padding-top-tr">
144
140
  <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
145
- <td v-if="virtualX_on && fixedMode && headless" class="virtual-x-left" style="padding: 0"></td>
141
+ <td v-if="virtualX_on && fixedMode && headless" class="virtual-x-left"></td>
146
142
  <template v-if="fixedMode && headless">
147
143
  <td v-for="col in virtualX_columnPart" :key="col.dataIndex" :style="cellStyleMap[TagType.TD].get(colKeyGen(col))"></td
148
144
  ></template>
@@ -163,7 +159,7 @@
163
159
  @mouseover="e => onTrMouseOver(e, row)"
164
160
  >
165
161
  <!--这个td用于配合虚拟滚动的th对应,防止列错位-->
166
- <td v-if="virtualX_on" class="virtual-x-left" style="padding: 0"></td>
162
+ <td v-if="virtualX_on" class="virtual-x-left"></td>
167
163
  <td
168
164
  v-for="(col, colIndex) in virtualX_columnPart"
169
165
  :key="col.dataIndex"
@@ -212,9 +208,9 @@
212
208
  * [] 计算的高亮颜色,挂在数据源上对象上,若多个表格使用同一个数据源对象会有问题。需要深拷贝。(解决方案:获取组件uid)
213
209
  * [] highlight-row 颜色不能恢复到active的颜色
214
210
  */
215
- import { CSSProperties, computed, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
211
+ import { CSSProperties, computed, nextTick, onMounted, ref, shallowRef, toRaw, watch } from 'vue';
216
212
  import { DEFAULT_ROW_HEIGHT } from './const';
217
- import { HighlightConfig, Order, SeqConfig, SortConfig, SortOption, SortState, StkTableColumn, TagType, UniqKeyProp } from './types/index';
213
+ import { HighlightConfig, Order, SeqConfig, SortConfig, SortOption, SortState, StkTableColumn, TagType, UniqKey, UniqKeyProp } from './types/index';
218
214
  import { useAutoResize } from './useAutoResize';
219
215
  import { useColResize } from './useColResize';
220
216
  import { useFixedCol } from './useFixedCol';
@@ -320,6 +316,14 @@ const props = withDefaults(
320
316
  highlightConfig?: HighlightConfig;
321
317
  /** 序号列配置 */
322
318
  seqConfig?: SeqConfig;
319
+ /**
320
+ * 固定头,固定列实现方式。
321
+ *
322
+ * relative:固定列只能放在props.columns的两侧。如果列宽会变动则谨慎使用。
323
+ *
324
+ * 低版本浏览器只能为'relative',
325
+ */
326
+ cellFixedMode?: 'sticky' | 'relative';
323
327
  }>(),
324
328
  {
325
329
  width: '',
@@ -360,6 +364,7 @@ const props = withDefaults(
360
364
  hideHeaderTitle: false,
361
365
  highlightConfig: () => ({}),
362
366
  seqConfig: () => ({}),
367
+ cellFixedMode: 'sticky',
363
368
  },
364
369
  );
365
370
 
@@ -496,6 +501,18 @@ const tableHeaderLast = shallowRef<StkTableColumn<DT>[]>([]);
496
501
 
497
502
  const dataSourceCopy = shallowRef<DT[]>([...props.dataSource]);
498
503
 
504
+ /**
505
+ * 列唯一键
506
+ * @param col
507
+ */
508
+ const colKeyGen = computed(() => {
509
+ if (typeof props.colKey === 'function') {
510
+ return (col: StkTableColumn<DT>) => (props.colKey as (col: StkTableColumn<DT>) => string)(col);
511
+ } else {
512
+ return (col: StkTableColumn<DT>) => (col as any)[props.colKey as string];
513
+ }
514
+ });
515
+
499
516
  /**高亮帧间隔
500
517
  const highlightStepDuration = Highlight_Color_Change_Freq / 1000 + 's';*/
501
518
 
@@ -579,16 +596,17 @@ watch(
579
596
  () => props.columns,
580
597
  () => {
581
598
  dealColumns();
582
- initVirtualScrollX();
599
+ nextTick(initVirtualScrollX);
583
600
  },
584
601
  );
585
602
  watch(
586
603
  () => props.virtualX,
587
- () => dealColumns(),
604
+ () => {
605
+ dealColumns();
606
+ nextTick(initVirtualScrollX);
607
+ },
588
608
  );
589
609
 
590
- dealColumns();
591
-
592
610
  watch(
593
611
  () => props.dataSource,
594
612
  val => {
@@ -618,6 +636,8 @@ watch(
618
636
 
619
637
  watch(() => props.fixedColShadow, dealFixedColShadow);
620
638
 
639
+ dealColumns();
640
+
621
641
  onMounted(() => {
622
642
  initVirtualScroll();
623
643
  updateFixedShadow();
@@ -717,21 +737,13 @@ function rowKeyGen(row: DT) {
717
737
  return key;
718
738
  }
719
739
 
720
- /**
721
- * 列唯一键
722
- * @param col
723
- */
724
- function colKeyGen(col: StkTableColumn<DT>) {
725
- return typeof props.colKey === 'function' ? props.colKey(col) : (col as any)[props.colKey];
726
- }
727
-
728
740
  /** 单元格样式 */
729
741
  const cellStyleMap = computed(() => {
730
742
  const thMap = new Map();
731
743
  const tdMap = new Map();
732
744
  tableHeaders.value.forEach((cols, depth) => {
733
745
  cols.forEach(col => {
734
- const colKey = colKeyGen(col);
746
+ const colKey = colKeyGen.value(col);
735
747
  const width = props.virtualX ? getCalculatedColWidth(col) + 'px' : transformWidthToStr(col.width);
736
748
  const style: CSSProperties = {
737
749
  width,
@@ -11,6 +11,7 @@ export const HIGHLIGHT_COLOR = {
11
11
  };
12
12
  /** 高亮持续时间 */
13
13
  export const HIGHLIGHT_DURATION = 2000;
14
+
14
15
  /** 高亮变更频率 */
15
16
  export const HIGHLIGHT_FREQ = 1000 / 30;
16
17
 
@@ -116,28 +116,8 @@
116
116
  background-image: var(--bg-border-right), var(--bg-border-bottom);
117
117
  }
118
118
 
119
- thead {
120
- tr {
121
- &:first-child th {
122
- background-image: var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
123
- }
124
-
125
- }
126
- }
127
-
128
-
129
- &.virtual-x {
130
- .virtual-x-left {
131
- background: none;
132
- pointer-events: none;
133
- }
134
-
135
- .virtual-x-right {
136
- padding: 0;
137
- background: none;
138
- pointer-events: none;
139
- }
140
-
119
+ thead tr:first-child th {
120
+ background-image: var(--bg-border-top), var(--bg-border-right), var(--bg-border-bottom);
141
121
  }
142
122
  }
143
123
 
@@ -191,15 +171,13 @@
191
171
  max-height: var(--header-row-height);
192
172
  }
193
173
 
194
- tbody {
195
- td {
196
- height: var(--row-height);
197
- line-height: 1;
198
-
199
- .table-cell-wrapper {
200
- max-height: var(--row-height);
201
- overflow: hidden;
202
- }
174
+ tbody td {
175
+ height: var(--row-height);
176
+ line-height: 1;
177
+
178
+ .table-cell-wrapper {
179
+ max-height: var(--row-height);
180
+ overflow: hidden;
203
181
  }
204
182
  }
205
183
 
@@ -208,12 +186,6 @@
208
186
  }
209
187
  }
210
188
 
211
- &.virtual-x {
212
- .virtual-x-left {
213
- padding: 0;
214
- }
215
- }
216
-
217
189
  th,
218
190
  td {
219
191
  z-index: 1;
@@ -294,6 +266,13 @@
294
266
  }
295
267
  }
296
268
 
269
+ .virtual-x-left,
270
+ .virtual-x-right {
271
+ padding: 0;
272
+ background: none;
273
+ pointer-events: none;
274
+ }
275
+
297
276
 
298
277
  /** 列宽调整指示器 */
299
278
  .column-resize-indicator {
@@ -1,5 +1,5 @@
1
- import { Ref, ShallowRef, onBeforeUnmount, onMounted, ref } from 'vue';
2
- import { StkTableColumn } from './types';
1
+ import { ComputedRef, Ref, ShallowRef, onBeforeUnmount, onMounted, ref } from 'vue';
2
+ import { StkTableColumn, UniqKey } from './types';
3
3
  import { getCalculatedColWidth } from './utils';
4
4
 
5
5
  type ColResizeState<DT extends Record<string, any>> = {
@@ -21,7 +21,7 @@ type Params<DT extends Record<string, any>> = {
21
21
  tableContainerRef: Ref<HTMLElement | undefined>;
22
22
  tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>;
23
23
  colResizeIndicatorRef: Ref<HTMLElement | undefined>;
24
- colKeyGen: (p: any) => string;
24
+ colKeyGen: ComputedRef<(p: any) => UniqKey>;
25
25
  };
26
26
 
27
27
  /** 列宽拖动 */
@@ -78,7 +78,7 @@ export function useColResize<DT extends Record<string, any>>({
78
78
  const { scrollLeft, scrollTop } = tableContainerRef.value;
79
79
  const { left } = tableContainerRef.value.getBoundingClientRect();
80
80
  /** 列下标 */
81
- let colIndex = tableHeaderLast.value.findIndex(it => colKeyGen(it) === colKeyGen(col));
81
+ let colIndex = tableHeaderLast.value.findIndex(it => colKeyGen.value(it) === colKeyGen.value(col));
82
82
  if (isPrev) {
83
83
  // 上一列
84
84
  colIndex -= 1;
@@ -139,7 +139,7 @@ export function useColResize<DT extends Record<string, any>>({
139
139
  let width = getCalculatedColWidth(lastCol) + moveX;
140
140
  if (width < props.colMinWidth) width = props.colMinWidth;
141
141
 
142
- const curCol = tableHeaderLast.value.find(it => colKeyGen(it) === colKeyGen(lastCol));
142
+ const curCol = tableHeaderLast.value.find(it => colKeyGen.value(it) === colKeyGen.value(lastCol));
143
143
  if (!curCol) return;
144
144
  curCol.width = width + 'px';
145
145
 
@@ -1,9 +1,9 @@
1
- import { computed, ref, Ref, ShallowRef, shallowRef } from 'vue';
2
- import { StkTableColumn } from './types';
1
+ import { computed, ComputedRef, ref, Ref, ShallowRef, shallowRef } from 'vue';
2
+ import { StkTableColumn, UniqKey } from './types';
3
3
 
4
4
  type Params<T extends Record<string, any>> = {
5
5
  props: any;
6
- colKeyGen: (col: StkTableColumn<T>) => string;
6
+ colKeyGen: ComputedRef<(col: StkTableColumn<T>) => UniqKey>;
7
7
  tableHeaders: ShallowRef<StkTableColumn<T>[][]>;
8
8
  tableHeaderLast: ShallowRef<StkTableColumn<T>[]>;
9
9
  tableContainerRef: Ref<HTMLDivElement | undefined>;
@@ -42,7 +42,7 @@ export function useFixedCol<DT extends Record<string, any>>({ props, colKeyGen,
42
42
  ['fixed-cell--' + col.fixed]: col.fixed,
43
43
  'fixed-cell--shadow': showShadow,
44
44
  };
45
- colMap.set(colKeyGen(col), classObj);
45
+ colMap.set(colKeyGen.value(col), classObj);
46
46
  });
47
47
  });
48
48
  return colMap;
@@ -75,19 +75,39 @@ export function useFixedStyle<DT extends Record<string, any>>({
75
75
  const { fixed } = col;
76
76
  if (tagType === TagType.TD && !fixed) return null;
77
77
 
78
- const isFixedLeft = fixed === 'left';
79
78
  const style: CSSProperties = {};
80
79
  const { colKeyStore, refStore } = fixedColumnsPositionStore.value;
81
80
 
81
+ /** 是否是relative模式完成固定列 */
82
+ let isRelativeMode = true;
83
+ if (props.cellFixedMode === 'sticky') {
84
+ isRelativeMode = false;
85
+ }
86
+
82
87
  if (IS_LEGACY_MODE) {
88
+ // 低版本浏览器只能为固定列设置position: sticky
89
+ isRelativeMode = true;
90
+ }
91
+
92
+ const { scrollLeft, scrollWidth, offsetLeft, containerWidth } = virtualScrollX.value;
93
+ const scrollRight = scrollWidth - containerWidth - scrollLeft;
94
+
95
+ if (virtualScrollX.value.scrollLeft === 0 && fixed === 'left' && tagType === TagType.TD) {
96
+ // 滚动条在最左侧时,左侧固定列不需要,防止分层
97
+ style.position = void 0;
98
+ } else if (scrollRight === 0 && fixed === 'right' && tagType === TagType.TD) {
99
+ // 滚动条在最右侧时,右侧固定列不需要,防止分层
100
+ style.position = void 0;
101
+ } else if (isRelativeMode) {
83
102
  style.position = 'relative';
84
103
  } else {
85
104
  style.position = 'sticky';
86
105
  }
87
106
 
107
+ const isFixedLeft = fixed === 'left';
88
108
  if (tagType === TagType.TH) {
89
109
  // TH
90
- if (IS_LEGACY_MODE) {
110
+ if (isRelativeMode) {
91
111
  style.top = virtualScroll.value.scrollTop + 'px';
92
112
  } else {
93
113
  style.top = depth * props.rowHeight + 'px';
@@ -95,17 +115,18 @@ export function useFixedStyle<DT extends Record<string, any>>({
95
115
  style.zIndex = isFixedLeft ? '3' : '2'; // 保证固定列高于其他单元格
96
116
  } else {
97
117
  // TD
98
- style.zIndex = isFixedLeft ? '2' : '1';
118
+ if (isFixedLeft) {
119
+ style.zIndex = '2';
120
+ }
99
121
  }
100
122
 
101
123
  if (fixed === 'left' || fixed === 'right') {
102
- if (IS_LEGACY_MODE) {
124
+ if (isRelativeMode) {
103
125
  if (isFixedLeft) {
104
- if (virtualX_on.value) style.left = virtualScrollX.value.scrollLeft - virtualScrollX.value.offsetLeft + 'px';
105
- else style.left = virtualScrollX.value.scrollLeft + 'px';
126
+ style.left = scrollLeft - (virtualX_on.value ? offsetLeft : 0) + 'px';
106
127
  } else {
107
- // TODO:计算右侧距离
108
- style.right = `${virtualX_offsetRight.value}px`;
128
+ // fixed right
129
+ style.right = Math.max(scrollRight - (virtualX_on.value ? virtualX_offsetRight.value : 0), 0) + 'px';
109
130
  }
110
131
  } else {
111
132
  const lr = (col.dataIndex ? colKeyStore[col.dataIndex] : refStore.get(col)) + 'px';
@@ -26,18 +26,17 @@ type HighlightDimRowStore = {
26
26
  export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
27
27
  const config: HighlightConfig = props.highlightConfig;
28
28
 
29
- /** 持续时间 */
30
- const highlightDuration = config.duration ? config.duration * 1000 : HIGHLIGHT_DURATION;
31
- /** 高亮频率(仅虚拟滚动生效) */
32
- const highlightFrequency = config.fps ? 1000 / config.fps : HIGHLIGHT_FREQ;
33
29
  /** 高亮颜色 */
34
30
  const highlightColor = {
35
31
  light: HIGHLIGHT_COLOR.light,
36
32
  dark: HIGHLIGHT_COLOR.dark,
37
33
  };
38
-
39
- /** css 高亮的次数,用于css animation steps() */
40
- const highlightSteps = highlightDuration / highlightFrequency;
34
+ /** 持续时间 */
35
+ const highlightDuration = config.duration ? config.duration * 1000 : HIGHLIGHT_DURATION;
36
+ /** 高亮频率*/
37
+ const highlightFrequency = config.fps && config.fps > 0 ? 1000 / config.fps : null;
38
+ /** 高亮帧数(非帧率),用于 timing-function: steps() */
39
+ const highlightSteps = highlightFrequency ? Math.round(highlightDuration / highlightFrequency) : null;
41
40
  /** 高亮开始 */
42
41
  const highlightFrom = computed(() => highlightColor[props.theme as 'light' | 'dark'].from);
43
42
  /** 高亮结束 */
@@ -66,6 +65,15 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
66
65
  /** 高亮后渐暗的单元格定时器 */
67
66
  const highlightDimCellsTimeout = new Map();
68
67
 
68
+ /** 高亮函数的默认参数 */
69
+ const defaultHighlightDimOption = (() => {
70
+ const keyframe: PropertyIndexedKeyframes = { backgroundColor: [highlightFrom.value, highlightTo.value] };
71
+ if (highlightSteps) {
72
+ keyframe.easing = `steps(${highlightSteps})`;
73
+ }
74
+ return { duration: highlightDuration, keyframe };
75
+ })();
76
+
69
77
  /**
70
78
  * 计算高亮渐暗颜色的循环
71
79
  */
@@ -130,22 +138,16 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
130
138
  calcHighlightDimLoopJs = false;
131
139
  highlightDimRowsJs.clear(); // TODO: 是否需要 清除
132
140
  }
133
- }, highlightFrequency);
141
+ }, highlightFrequency || HIGHLIGHT_FREQ);
134
142
  };
135
143
  recursion();
136
144
  }
137
145
 
138
- /** 高亮函数的默认参数 */
139
- const defaultHighlightDimOption = {
140
- keyframe: [{ backgroundColor: highlightFrom.value }, { backgroundColor: highlightTo.value }],
141
- duration: highlightDuration,
142
- };
143
-
144
146
  /**
145
- * 高亮一个单元格
147
+ * 高亮一个单元格。暂不支持虚拟滚动高亮状态记忆。
146
148
  * @param rowKeyValue 一行的key
147
149
  * @param dataIndex 列key
148
- * @param options.method css-使用css渲染,animation-使用animation api。默认css;
150
+ * @param options.method css-使用css渲染,animation-使用animation api。默认animation;
149
151
  * @param option.className 自定义css动画的class。
150
152
  * @param option.keyframe 同Keyframe https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
151
153
  * @param option.duration 动画时长。method='css'状态下,用于移除class,如果传入了className则需要与自定义的动画时间一致。
@@ -153,13 +155,12 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
153
155
  function setHighlightDimCell(
154
156
  rowKeyValue: string,
155
157
  dataIndex: string,
156
- option: { className?: string; method?: 'css' | 'animation'; keyframe?: Parameters<Animatable['animate']>['0']; duration?: number } = {},
158
+ option: { className?: string; method?: 'animation' | 'css'; keyframe?: Parameters<Animatable['animate']>['0']; duration?: number } = {},
157
159
  ) {
158
- // TODO: 支持动态计算高亮颜色。不易实现。需记录每一个单元格的颜色情况。
159
160
  const cellEl = tableContainerRef.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]>[data-index="${dataIndex}"]`);
160
161
  const { className, method, duration, keyframe } = {
161
162
  className: HIGHLIGHT_CELL_CLASS,
162
- method: 'css',
163
+ method: 'animation',
163
164
  ...defaultHighlightDimOption,
164
165
  ...option,
165
166
  };
@@ -174,15 +175,15 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
174
175
  /**
175
176
  * 高亮一行
176
177
  * @param rowKeyValues 行唯一键的数组
177
- * @param option.method css-使用css渲染,animation-使用animation api,js-使用js计算颜色
178
+ * @param option.method css-使用css渲染,animation-使用animation api,js-使用js计算颜色。默认animation
178
179
  * @param option.className 自定义css动画的class。
179
- * @param option.keyframe 同Keyframe,无法控制帧率。 https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
180
+ * @param option.keyframe 同Keyframe https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API/Keyframe_Formats
180
181
  * @param option.duration 动画时长。method='css'状态下,用于移除class,如果传入了className则需要与自定义的动画时间一致。。
181
182
  */
182
183
  function setHighlightDimRow(
183
184
  rowKeyValues: UniqKey[],
184
185
  option: {
185
- method?: 'css' | 'animation' | 'js';
186
+ method?: 'animation' | 'css' | 'js';
186
187
  /** @deprecated 请使用method */
187
188
  useCss?: boolean;
188
189
  className?: string;
@@ -193,7 +194,7 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
193
194
  if (!Array.isArray(rowKeyValues)) rowKeyValues = [rowKeyValues];
194
195
  const { className, method, useCss, keyframe, duration } = {
195
196
  className: HIGHLIGHT_ROW_CLASS,
196
- method: props.virtual ? 'js' : 'css',
197
+ method: 'animation',
197
198
  ...defaultHighlightDimOption,
198
199
  ...option,
199
200
  };
@@ -234,7 +235,7 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
234
235
 
235
236
  /**
236
237
  * 使用css @keyframes动画,实现高亮行动画
237
- * 此方案作为兼容方式。v0.3.0 将使用Element.animate 接口实现动画。
238
+ * 此方案作为兼容方式。v0.3.4 将使用Element.animate 接口实现动画。
238
239
  */
239
240
  function highlightRowsInCssKeyframe(rowKeyValues: UniqKey[], className: string, duration: number) {
240
241
  /**是否需要重绘 */
@@ -267,7 +268,7 @@ export function useHighlight({ props, stkTableId, tableContainerRef }: Params) {
267
268
 
268
269
  /**
269
270
  * 使用css @keyframes动画,实现高亮单元格动画
270
- * 此方案作为兼容方式。v0.3.0 将使用Element.animate 接口实现动画。
271
+ * 此方案作为兼容方式。v0.3.4 将使用Element.animate 接口实现动画。
271
272
  */
272
273
  function highlightCellsInCssKeyFrame(cellEl: HTMLElement, rowKeyValue: UniqKey, className: string, duration: number) {
273
274
  if (cellEl.classList.contains(className)) {
@@ -30,8 +30,10 @@ export type VirtualScrollStore = {
30
30
  };
31
31
  /** 暂存横向虚拟滚动的数据 */
32
32
  export type VirtualScrollXStore = {
33
- /** 容器宽度 */
33
+ /** 父容器宽度 */
34
34
  containerWidth: number;
35
+ /** 滚动容器的宽度 */
36
+ scrollWidth: number;
35
37
  /** 开始位置 */
36
38
  startIndex: number;
37
39
  /** 结束始位置 */
@@ -69,6 +71,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
69
71
 
70
72
  const virtualScrollX = ref<VirtualScrollXStore>({
71
73
  containerWidth: 0,
74
+ scrollWidth: 0,
72
75
  startIndex: 0,
73
76
  endIndex: 0,
74
77
  offsetLeft: 0,
@@ -143,13 +146,7 @@ export function useVirtualScroll<DT extends Record<string, any>>({
143
146
  if (!virtual_on.value) return;
144
147
  const { offsetHeight, scrollTop } = tableContainerRef.value || {};
145
148
  const { rowHeight } = virtualScroll.value;
146
- let containerHeight: number;
147
- // FIXME: 可能多次获取offsetHeight 会导致浏览器频繁重排
148
- if (typeof height === 'number') {
149
- containerHeight = height;
150
- } else {
151
- containerHeight = offsetHeight || DEFAULT_TABLE_HEIGHT;
152
- }
149
+ const containerHeight = height ?? (offsetHeight || DEFAULT_TABLE_HEIGHT);
153
150
  const { headless, headerRowHeight } = props;
154
151
  let pageSize = Math.ceil(containerHeight / rowHeight);
155
152
  if (!headless) {
@@ -162,10 +159,9 @@ export function useVirtualScroll<DT extends Record<string, any>>({
162
159
  }
163
160
 
164
161
  function initVirtualScrollX() {
165
- if (!props.virtualX) return;
166
- const { offsetWidth, scrollLeft } = tableContainerRef.value || {};
167
- // scrollTo(null, 0);
168
- virtualScrollX.value.containerWidth = offsetWidth || DEFAULT_TABLE_WIDTH;
162
+ const { clientWidth, scrollLeft, scrollWidth } = tableContainerRef.value || {};
163
+ virtualScrollX.value.containerWidth = clientWidth || DEFAULT_TABLE_WIDTH;
164
+ virtualScrollX.value.scrollWidth = scrollWidth || DEFAULT_TABLE_WIDTH;
169
165
  updateVirtualScrollX(scrollLeft);
170
166
  }
171
167
  /**
@@ -189,25 +185,19 @@ export function useVirtualScroll<DT extends Record<string, any>>({
189
185
  if (!virtual_on.value) return;
190
186
 
191
187
  let startIndex = Math.floor(sTop / rowHeight);
192
- if (props.stripe) {
193
- startIndex -= 1; //预渲染1行
194
- }
195
188
  if (startIndex < 0) {
196
189
  startIndex = 0;
197
190
  }
198
191
  if (props.stripe && startIndex !== 0) {
199
192
  const scrollRows = Math.abs(oldStartIndex - startIndex);
200
193
  // 斑马纹情况下,每滚动偶数行才加载。防止斑马纹错位。
201
- if (scrollRows < 2) {
202
- return;
203
- } else if (scrollRows % 2) {
194
+ if (scrollRows % 2) {
204
195
  startIndex -= 1; // 奇数-1变成偶数
205
196
  }
206
197
  }
207
198
  let endIndex = startIndex + pageSize;
208
199
  if (props.stripe) {
209
- // 由于stripe上方预渲染-1行,这里也要预渲染1+1行
210
- endIndex += 1;
200
+ endIndex += 1; // 斑马纹下多渲染一些
211
201
  }
212
202
  const offsetTop = startIndex * rowHeight; // startIndex之前的高度
213
203
  endIndex = Math.min(endIndex, dataSourceCopy.value.length); // 溢出index修正
@@ -237,18 +227,24 @@ export function useVirtualScroll<DT extends Record<string, any>>({
237
227
 
238
228
  /** 通过横向滚动条位置,计算横向虚拟滚动的参数 */
239
229
  function updateVirtualScrollX(sLeft = 0) {
230
+ if (!props.virtualX) return;
240
231
  const headerLength = tableHeaderLast.value?.length;
241
- const { scrollLeft } = virtualScrollX.value;
242
232
  if (!headerLength) return;
233
+
234
+ const { scrollLeft } = virtualScrollX.value;
243
235
  let startIndex = 0;
244
236
  let offsetLeft = 0;
245
-
237
+ /** 列宽累加 */
246
238
  let colWidthSum = 0;
239
+ /** 固定左侧列宽 */
247
240
  let leftColWidthSum = 0;
241
+ /** 横向滚动时,第一列的剩余宽度 */
242
+ let leftFirstColRestWidth = 0;
243
+
248
244
  for (let colIndex = 0; colIndex < headerLength; colIndex++) {
249
- startIndex++;
250
245
  const col = tableHeaderLast.value[colIndex];
251
246
  const colWidth = getCalculatedColWidth(col);
247
+ startIndex++;
252
248
  // fixed left 不进入计算列宽
253
249
  if (col.fixed === 'left') {
254
250
  leftColWidthSum += colWidth;
@@ -259,11 +255,12 @@ export function useVirtualScroll<DT extends Record<string, any>>({
259
255
  if (colWidthSum >= sLeft) {
260
256
  offsetLeft = colWidthSum - colWidth;
261
257
  startIndex--;
258
+ leftFirstColRestWidth = colWidthSum - sLeft;
262
259
  break;
263
260
  }
264
261
  }
265
262
  // -----
266
- colWidthSum = 0;
263
+ colWidthSum = leftFirstColRestWidth;
267
264
  const containerWidth = virtualScrollX.value.containerWidth - leftColWidthSum;
268
265
  let endIndex = headerLength;
269
266
  for (let colIndex = startIndex + 1; colIndex < headerLength; colIndex++) {