@canvas-components/pivot-table 0.1.8 → 0.1.9

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/dist/index.cjs CHANGED
@@ -995,7 +995,7 @@ function computePivotScrollbarLayout(params) {
995
995
  }
996
996
  if (hasVertical) {
997
997
  const fullHeight = Math.max(
998
- params.bodyHeight - VERTICAL_TOP_INSET - (hasHorizontal ? SCROLLBAR_THICKNESS : 0),
998
+ params.bodyHeight - VERTICAL_TOP_INSET,
999
999
  0
1000
1000
  );
1001
1001
  const buttonSize = Math.min(SCROLLBAR_THICKNESS, fullHeight / 3);
@@ -1051,6 +1051,7 @@ var DEFAULT_ROW_HEIGHT = 40;
1051
1051
  var DEFAULT_COLUMN_WIDTH = 120;
1052
1052
  var DEFAULT_HEADER_SIZE = 40;
1053
1053
  var DEFAULT_ROW_DIMENSION_WIDTH2 = 140;
1054
+ var SCROLLBAR_THICKNESS2 = 10;
1054
1055
  function buildPivotLayoutSnapshot(params) {
1055
1056
  const { model, options, visibleRowNodes, visibleColumnNodes, width, height } = params;
1056
1057
  const indicatorLayout = options.indicatorLayout ?? "columns";
@@ -1083,8 +1084,8 @@ function buildPivotLayoutSnapshot(params) {
1083
1084
  const columnHeaderHeight = columnHeaderLevels * DEFAULT_HEADER_SIZE;
1084
1085
  const reportFilterHeight = (options.filterDimensions?.length ?? 0) * rowHeight;
1085
1086
  const tableHeaderHeight = reportFilterHeight + columnHeaderHeight;
1086
- const bodyWidth = Math.max(width - rowHeaderWidth, 0);
1087
- const availableBodyHeight = Math.max(height - tableHeaderHeight, 0);
1087
+ const rawBodyWidth = Math.max(width - rowHeaderWidth, 0);
1088
+ const rawAvailableBodyHeight = Math.max(height - tableHeaderHeight, 0);
1088
1089
  const rowSlots = createPivotAxisSlots({
1089
1090
  nodes: visibleRowNodes,
1090
1091
  indicators: options.indicators.map((indicator) => indicator.key),
@@ -1107,19 +1108,52 @@ function buildPivotLayoutSnapshot(params) {
1107
1108
  const columnOffsets = createOffsets(columnWidths);
1108
1109
  const totalBodyWidth = columnOffsets[columnOffsets.length - 1] ?? 0;
1109
1110
  const totalBodyHeight = rowSlots.length * rowHeight;
1111
+ let hasHorizontalScrollbar = totalBodyWidth > rawBodyWidth;
1112
+ let hasVerticalScrollbar = totalBodyHeight > rawAvailableBodyHeight;
1113
+ for (let index = 0; index < 2; index += 1) {
1114
+ hasHorizontalScrollbar = totalBodyWidth > Math.max(
1115
+ rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
1116
+ 0
1117
+ );
1118
+ hasVerticalScrollbar = totalBodyHeight > Math.max(
1119
+ rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
1120
+ 0
1121
+ );
1122
+ }
1123
+ const bodyWidth = Math.max(
1124
+ rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
1125
+ 0
1126
+ );
1127
+ const availableBodyHeight = Math.max(
1128
+ rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
1129
+ 0
1130
+ );
1131
+ const frozenBottomRowRange = resolveFrozenBottomRowRange(rowSlots);
1132
+ const frozenBottomRowCount = frozenBottomRowRange ? frozenBottomRowRange.end - frozenBottomRowRange.start : 0;
1133
+ const frozenBottomHeight = frozenBottomRowCount * rowHeight;
1134
+ const scrollableRowCount = frozenBottomRowRange?.start ?? rowSlots.length;
1135
+ const totalScrollableBodyHeight = scrollableRowCount * rowHeight;
1110
1136
  const tableWidth = rowHeaderWidth + Math.min(bodyWidth, totalBodyWidth);
1111
1137
  const bodyHeight = Math.min(availableBodyHeight, totalBodyHeight);
1112
- const fittedHeight = tableHeaderHeight + bodyHeight;
1138
+ const visibleFrozenBottomHeight = Math.min(frozenBottomHeight, bodyHeight);
1139
+ const scrollableBodyHeight = Math.max(
1140
+ bodyHeight - visibleFrozenBottomHeight,
1141
+ 0
1142
+ );
1143
+ const fittedHeight = tableHeaderHeight + bodyHeight + (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0);
1113
1144
  const maxScrollLeft = Math.max(totalBodyWidth - bodyWidth, 0);
1114
- const maxScrollTop = Math.max(totalBodyHeight - bodyHeight, 0);
1145
+ const maxScrollTop = Math.max(
1146
+ totalScrollableBodyHeight - scrollableBodyHeight,
1147
+ 0
1148
+ );
1115
1149
  const scroll = {
1116
1150
  left: utils.clamp(params.scroll.left, 0, maxScrollLeft),
1117
1151
  top: utils.clamp(params.scroll.top, 0, maxScrollTop)
1118
1152
  };
1119
1153
  const visibleRowRange = resolveVisibleRange({
1120
- count: rowSlots.length,
1154
+ count: scrollableRowCount,
1121
1155
  itemSize: rowHeight,
1122
- viewportSize: bodyHeight,
1156
+ viewportSize: scrollableBodyHeight,
1123
1157
  offset: scroll.top,
1124
1158
  overscan: options.overscanRowCount ?? 1
1125
1159
  });
@@ -1154,6 +1188,23 @@ function buildPivotLayoutSnapshot(params) {
1154
1188
  dimensionCount: options.rows.length,
1155
1189
  indicators: options.indicators
1156
1190
  });
1191
+ if (frozenBottomRowRange) {
1192
+ rowHeaderCells.push(
1193
+ ...buildRowHeaderCells({
1194
+ model,
1195
+ options,
1196
+ slots: rowSlots,
1197
+ range: frozenBottomRowRange,
1198
+ rowHeight,
1199
+ rowDimensionWidths,
1200
+ columnHeaderHeight: tableHeaderHeight,
1201
+ scrollTop: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight,
1202
+ expandedNodeIds: params.expandedRowNodeIds,
1203
+ dimensionCount: options.rows.length,
1204
+ indicators: options.indicators
1205
+ })
1206
+ );
1207
+ }
1157
1208
  const columnHeaderCells = buildColumnHeaderCells({
1158
1209
  model,
1159
1210
  options,
@@ -1180,14 +1231,34 @@ function buildPivotLayoutSnapshot(params) {
1180
1231
  columnHeaderHeight: tableHeaderHeight,
1181
1232
  scroll
1182
1233
  });
1234
+ if (frozenBottomRowRange) {
1235
+ bodyCells.push(
1236
+ ...buildBodyCells({
1237
+ model,
1238
+ rowSlots,
1239
+ columnSlots,
1240
+ rowRange: frozenBottomRowRange,
1241
+ columnRange: visibleColumnRange,
1242
+ rowHeight,
1243
+ columnWidths,
1244
+ columnOffsets,
1245
+ rowHeaderWidth,
1246
+ columnHeaderHeight: tableHeaderHeight,
1247
+ scroll: {
1248
+ left: scroll.left,
1249
+ top: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight
1250
+ }
1251
+ })
1252
+ );
1253
+ }
1183
1254
  const scrollbar = computePivotScrollbarLayout({
1184
- width,
1255
+ width: tableWidth + (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
1185
1256
  height: fittedHeight,
1186
1257
  bodyTop: tableHeaderHeight,
1187
- bodyHeight,
1258
+ bodyHeight: scrollableBodyHeight,
1188
1259
  bodyWidth,
1189
1260
  totalBodyWidth,
1190
- totalBodyHeight,
1261
+ totalBodyHeight: totalScrollableBodyHeight,
1191
1262
  scrollLeft: scroll.left,
1192
1263
  scrollTop: scroll.top,
1193
1264
  maxScrollLeft,
@@ -1239,6 +1310,7 @@ function buildPivotLayoutSnapshot(params) {
1239
1310
  rowSlots,
1240
1311
  columnSlots,
1241
1312
  visibleRowRange,
1313
+ frozenBottomRowRange,
1242
1314
  visibleColumnRange,
1243
1315
  reportFilterCells,
1244
1316
  rowFieldHeaderCells,
@@ -1373,6 +1445,13 @@ function createAxisSlot(node, indicatorKey) {
1373
1445
  kind: node?.kind ?? "empty"
1374
1446
  };
1375
1447
  }
1448
+ function resolveFrozenBottomRowRange(slots) {
1449
+ let start = slots.length;
1450
+ while (start > 0 && slots[start - 1]?.kind === "grandTotal") {
1451
+ start -= 1;
1452
+ }
1453
+ return start < slots.length ? { start, end: slots.length } : null;
1454
+ }
1376
1455
  function buildRowHeaderCells(params) {
1377
1456
  const cells = [];
1378
1457
  for (let rowIndex = params.range.start; rowIndex < params.range.end; rowIndex += 1) {
@@ -2635,8 +2714,8 @@ function resolvePivotSelectionRect(layout, selection) {
2635
2714
  const kind = resolvePivotSelectionKind(selection);
2636
2715
  const startX = kind === "row" || kind === "row-cell" ? layout.rowHeaderRect.x : layout.bodyRect.x + (layout.columnOffsets[indexes.startColumnIndex] ?? 0) - layout.scroll.left;
2637
2716
  const endX = layout.bodyRect.x + (layout.columnOffsets[indexes.endColumnIndex] ?? 0) + (layout.columnWidths[indexes.endColumnIndex] ?? layout.columnWidth) - layout.scroll.left;
2638
- const startY = kind === "column" ? layout.columnHeaderRect.y : layout.bodyRect.y + indexes.startRowIndex * layout.rowHeight - layout.scroll.top;
2639
- const endY = layout.bodyRect.y + (indexes.endRowIndex + 1) * layout.rowHeight - layout.scroll.top;
2717
+ const startY = kind === "column" ? layout.columnHeaderRect.y : resolvePivotRowTop(layout, indexes.startRowIndex);
2718
+ const endY = resolvePivotRowTop(layout, indexes.endRowIndex) + layout.rowHeight;
2640
2719
  const clipRect = resolvePivotSelectionClipRect(layout, selection);
2641
2720
  const visibleStartX = Math.max(startX, clipRect.x);
2642
2721
  const visibleEndX = Math.min(endX, clipRect.x + clipRect.width);
@@ -2650,6 +2729,13 @@ function resolvePivotSelectionRect(layout, selection) {
2650
2729
  height: visibleEndY - visibleStartY
2651
2730
  };
2652
2731
  }
2732
+ function resolvePivotRowTop(layout, rowIndex) {
2733
+ const frozenRange = layout.frozenBottomRowRange;
2734
+ if (frozenRange && rowIndex >= frozenRange.start && rowIndex < frozenRange.end) {
2735
+ return layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - rowIndex) * layout.rowHeight;
2736
+ }
2737
+ return layout.bodyRect.y + rowIndex * layout.rowHeight - layout.scroll.top;
2738
+ }
2653
2739
  function resolvePivotSelectionClipRect(layout, selection) {
2654
2740
  const kind = resolvePivotSelectionKind(selection);
2655
2741
  if (kind === "row" || kind === "row-cell") {
@@ -2794,26 +2880,29 @@ var defaultPivotTableTheme = {
2794
2880
  };
2795
2881
 
2796
2882
  // src/rendering/draw-scrollbars.ts
2797
- function drawPivotScrollbars(context, layout) {
2883
+ function drawPivotScrollbars(context, layout, borderColor) {
2798
2884
  context.save();
2799
2885
  if (layout.horizontal) {
2800
- drawAxis(context, layout.horizontal, "horizontal");
2886
+ drawAxis(context, layout.horizontal, "horizontal", borderColor);
2801
2887
  }
2802
2888
  if (layout.vertical) {
2803
- drawAxis(context, layout.vertical, "vertical");
2889
+ drawAxis(context, layout.vertical, "vertical", borderColor);
2804
2890
  }
2805
2891
  context.restore();
2806
2892
  }
2807
- function drawAxis(context, layout, axis) {
2808
- drawTrack(context, layout.track);
2893
+ function drawAxis(context, layout, axis, borderColor) {
2894
+ drawTrack(context, layout.track, borderColor);
2809
2895
  drawButton(context, layout.decreaseButton, axis, "decrease");
2810
2896
  drawButton(context, layout.increaseButton, axis, "increase");
2811
2897
  drawThumb(context, layout.thumb);
2812
2898
  }
2813
- function drawTrack(context, rect) {
2899
+ function drawTrack(context, rect, borderColor) {
2814
2900
  context.fillStyle = "rgba(148, 163, 184, 0.16)";
2901
+ context.strokeStyle = borderColor;
2902
+ context.lineWidth = 1;
2815
2903
  roundRect(context, rect, rect.height / 2);
2816
2904
  context.fill();
2905
+ context.stroke();
2817
2906
  }
2818
2907
  function drawThumb(context, rect) {
2819
2908
  context.fillStyle = "rgba(100, 116, 139, 0.8)";
@@ -3081,7 +3170,7 @@ function renderPivotTable(params) {
3081
3170
  theme.borderColor
3082
3171
  );
3083
3172
  if (options.debug) drawDebugOverlay(context, layout, theme);
3084
- drawPivotScrollbars(context, layout.scrollbar);
3173
+ drawPivotScrollbars(context, layout.scrollbar, theme.borderColor);
3085
3174
  drawPivotViewportRightBorder(
3086
3175
  context,
3087
3176
  { x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
@@ -3599,7 +3688,7 @@ function hitTestPivotLayout(layout, x, y) {
3599
3688
  onSortIcon: false
3600
3689
  };
3601
3690
  }
3602
- const bodyCell = layout.bodyCells.find((cell) => contains(cell.rect, x, y));
3691
+ const bodyCell = findLastContaining(layout.bodyCells, x, y);
3603
3692
  if (bodyCell) {
3604
3693
  return {
3605
3694
  region: "body",
@@ -3609,9 +3698,7 @@ function hitTestPivotLayout(layout, x, y) {
3609
3698
  onSortIcon: false
3610
3699
  };
3611
3700
  }
3612
- const rowHeaderCell = layout.rowHeaderCells.find(
3613
- (cell) => contains(cell.rect, x, y)
3614
- );
3701
+ const rowHeaderCell = findLastContaining(layout.rowHeaderCells, x, y);
3615
3702
  if (rowHeaderCell) {
3616
3703
  return {
3617
3704
  region: "rowHeader",
@@ -3664,6 +3751,13 @@ function isSortIconHit(cell, x) {
3664
3751
  function contains(rect, x, y) {
3665
3752
  return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
3666
3753
  }
3754
+ function findLastContaining(cells, x, y) {
3755
+ for (let index = cells.length - 1; index >= 0; index -= 1) {
3756
+ const cell = cells[index];
3757
+ if (cell && contains(cell.rect, x, y)) return cell;
3758
+ }
3759
+ return void 0;
3760
+ }
3667
3761
 
3668
3762
  // src/interaction/text-selection.ts
3669
3763
  function resolvePivotTextOffset(params) {
@@ -3900,6 +3994,16 @@ function createAddress(rowSlot, columnSlot, indicatorKey) {
3900
3994
  // src/domain/interaction/row-selection.ts
3901
3995
  function resolvePivotRowIndexAtY(layout, y) {
3902
3996
  if (layout.rowSlots.length === 0 || layout.rowHeight <= 0) return null;
3997
+ const frozenRange = layout.frozenBottomRowRange;
3998
+ if (frozenRange) {
3999
+ const frozenTop = layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - frozenRange.start) * layout.rowHeight;
4000
+ if (y >= frozenTop) {
4001
+ return Math.min(
4002
+ frozenRange.start + Math.floor((y - frozenTop) / layout.rowHeight),
4003
+ frozenRange.end - 1
4004
+ );
4005
+ }
4006
+ }
3903
4007
  const index = Math.floor(
3904
4008
  (y - layout.bodyRect.y + layout.scroll.top) / layout.rowHeight
3905
4009
  );
@@ -4605,11 +4709,16 @@ var PivotTable = class {
4605
4709
  (slot) => (slot.nodeId ?? "") === address.columnNodeId && (!slot.indicatorKey || slot.indicatorKey === address.indicatorKey)
4606
4710
  );
4607
4711
  if (rowIndex < 0 || columnIndex < 0) return false;
4608
- const top = resolveVisibleOffset({
4712
+ const frozenRange = this.layout.frozenBottomRowRange;
4713
+ const frozenBottomHeight = frozenRange ? (frozenRange.end - frozenRange.start) * this.layout.rowHeight : 0;
4714
+ const top = frozenRange && rowIndex >= frozenRange.start ? this.scroll.top : resolveVisibleOffset({
4609
4715
  current: this.scroll.top,
4610
4716
  itemStart: rowIndex * this.layout.rowHeight,
4611
4717
  itemSize: this.layout.rowHeight,
4612
- viewportSize: this.layout.bodyRect.height
4718
+ viewportSize: Math.max(
4719
+ this.layout.bodyRect.height - frozenBottomHeight,
4720
+ 0
4721
+ )
4613
4722
  });
4614
4723
  const left = resolveVisibleOffset({
4615
4724
  current: this.scroll.left,