@canvas-components/pivot-table 0.1.8 → 0.2.1
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 +351 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +351 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -749,14 +749,11 @@ function matchesSearchKeyword(value, filter) {
|
|
|
749
749
|
return filter.searchExact ? text === keyword : text.includes(keyword);
|
|
750
750
|
}
|
|
751
751
|
function matchesFilterConditions(value, filter) {
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (!firstActive) return evaluateCondition(value, second);
|
|
758
|
-
if (!secondActive) return evaluateCondition(value, first);
|
|
759
|
-
return filter.conditionRelation === "or" ? evaluateCondition(value, first) || evaluateCondition(value, second) : evaluateCondition(value, first) && evaluateCondition(value, second);
|
|
752
|
+
const conditions = (filter.conditions?.length ? filter.conditions : [filter.condition, filter.secondCondition]).filter(
|
|
753
|
+
(condition) => condition != null && condition.operator !== "none"
|
|
754
|
+
);
|
|
755
|
+
if (conditions.length === 0) return true;
|
|
756
|
+
return filter.conditionRelation === "or" ? conditions.some((condition) => evaluateCondition(value, condition)) : conditions.every((condition) => evaluateCondition(value, condition));
|
|
760
757
|
}
|
|
761
758
|
function evaluateCondition(value, condition) {
|
|
762
759
|
const text = value == null ? "" : String(value);
|
|
@@ -995,7 +992,7 @@ function computePivotScrollbarLayout(params) {
|
|
|
995
992
|
}
|
|
996
993
|
if (hasVertical) {
|
|
997
994
|
const fullHeight = Math.max(
|
|
998
|
-
params.bodyHeight - VERTICAL_TOP_INSET
|
|
995
|
+
params.bodyHeight - VERTICAL_TOP_INSET,
|
|
999
996
|
0
|
|
1000
997
|
);
|
|
1001
998
|
const buttonSize = Math.min(SCROLLBAR_THICKNESS, fullHeight / 3);
|
|
@@ -1051,6 +1048,7 @@ var DEFAULT_ROW_HEIGHT = 40;
|
|
|
1051
1048
|
var DEFAULT_COLUMN_WIDTH = 120;
|
|
1052
1049
|
var DEFAULT_HEADER_SIZE = 40;
|
|
1053
1050
|
var DEFAULT_ROW_DIMENSION_WIDTH2 = 140;
|
|
1051
|
+
var SCROLLBAR_THICKNESS2 = 10;
|
|
1054
1052
|
function buildPivotLayoutSnapshot(params) {
|
|
1055
1053
|
const { model, options, visibleRowNodes, visibleColumnNodes, width, height } = params;
|
|
1056
1054
|
const indicatorLayout = options.indicatorLayout ?? "columns";
|
|
@@ -1083,8 +1081,8 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1083
1081
|
const columnHeaderHeight = columnHeaderLevels * DEFAULT_HEADER_SIZE;
|
|
1084
1082
|
const reportFilterHeight = (options.filterDimensions?.length ?? 0) * rowHeight;
|
|
1085
1083
|
const tableHeaderHeight = reportFilterHeight + columnHeaderHeight;
|
|
1086
|
-
const
|
|
1087
|
-
const
|
|
1084
|
+
const rawBodyWidth = Math.max(width - rowHeaderWidth, 0);
|
|
1085
|
+
const rawAvailableBodyHeight = Math.max(height - tableHeaderHeight, 0);
|
|
1088
1086
|
const rowSlots = createPivotAxisSlots({
|
|
1089
1087
|
nodes: visibleRowNodes,
|
|
1090
1088
|
indicators: options.indicators.map((indicator) => indicator.key),
|
|
@@ -1107,19 +1105,52 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1107
1105
|
const columnOffsets = createOffsets(columnWidths);
|
|
1108
1106
|
const totalBodyWidth = columnOffsets[columnOffsets.length - 1] ?? 0;
|
|
1109
1107
|
const totalBodyHeight = rowSlots.length * rowHeight;
|
|
1108
|
+
let hasHorizontalScrollbar = totalBodyWidth > rawBodyWidth;
|
|
1109
|
+
let hasVerticalScrollbar = totalBodyHeight > rawAvailableBodyHeight;
|
|
1110
|
+
for (let index = 0; index < 2; index += 1) {
|
|
1111
|
+
hasHorizontalScrollbar = totalBodyWidth > Math.max(
|
|
1112
|
+
rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1113
|
+
0
|
|
1114
|
+
);
|
|
1115
|
+
hasVerticalScrollbar = totalBodyHeight > Math.max(
|
|
1116
|
+
rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1117
|
+
0
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
const bodyWidth = Math.max(
|
|
1121
|
+
rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1122
|
+
0
|
|
1123
|
+
);
|
|
1124
|
+
const availableBodyHeight = Math.max(
|
|
1125
|
+
rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1126
|
+
0
|
|
1127
|
+
);
|
|
1128
|
+
const frozenBottomRowRange = resolveFrozenBottomRowRange(rowSlots);
|
|
1129
|
+
const frozenBottomRowCount = frozenBottomRowRange ? frozenBottomRowRange.end - frozenBottomRowRange.start : 0;
|
|
1130
|
+
const frozenBottomHeight = frozenBottomRowCount * rowHeight;
|
|
1131
|
+
const scrollableRowCount = frozenBottomRowRange?.start ?? rowSlots.length;
|
|
1132
|
+
const totalScrollableBodyHeight = scrollableRowCount * rowHeight;
|
|
1110
1133
|
const tableWidth = rowHeaderWidth + Math.min(bodyWidth, totalBodyWidth);
|
|
1111
1134
|
const bodyHeight = Math.min(availableBodyHeight, totalBodyHeight);
|
|
1112
|
-
const
|
|
1135
|
+
const visibleFrozenBottomHeight = Math.min(frozenBottomHeight, bodyHeight);
|
|
1136
|
+
const scrollableBodyHeight = Math.max(
|
|
1137
|
+
bodyHeight - visibleFrozenBottomHeight,
|
|
1138
|
+
0
|
|
1139
|
+
);
|
|
1140
|
+
const fittedHeight = tableHeaderHeight + bodyHeight + (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0);
|
|
1113
1141
|
const maxScrollLeft = Math.max(totalBodyWidth - bodyWidth, 0);
|
|
1114
|
-
const maxScrollTop = Math.max(
|
|
1142
|
+
const maxScrollTop = Math.max(
|
|
1143
|
+
totalScrollableBodyHeight - scrollableBodyHeight,
|
|
1144
|
+
0
|
|
1145
|
+
);
|
|
1115
1146
|
const scroll = {
|
|
1116
1147
|
left: utils.clamp(params.scroll.left, 0, maxScrollLeft),
|
|
1117
1148
|
top: utils.clamp(params.scroll.top, 0, maxScrollTop)
|
|
1118
1149
|
};
|
|
1119
1150
|
const visibleRowRange = resolveVisibleRange({
|
|
1120
|
-
count:
|
|
1151
|
+
count: scrollableRowCount,
|
|
1121
1152
|
itemSize: rowHeight,
|
|
1122
|
-
viewportSize:
|
|
1153
|
+
viewportSize: scrollableBodyHeight,
|
|
1123
1154
|
offset: scroll.top,
|
|
1124
1155
|
overscan: options.overscanRowCount ?? 1
|
|
1125
1156
|
});
|
|
@@ -1154,6 +1185,23 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1154
1185
|
dimensionCount: options.rows.length,
|
|
1155
1186
|
indicators: options.indicators
|
|
1156
1187
|
});
|
|
1188
|
+
if (frozenBottomRowRange) {
|
|
1189
|
+
rowHeaderCells.push(
|
|
1190
|
+
...buildRowHeaderCells({
|
|
1191
|
+
model,
|
|
1192
|
+
options,
|
|
1193
|
+
slots: rowSlots,
|
|
1194
|
+
range: frozenBottomRowRange,
|
|
1195
|
+
rowHeight,
|
|
1196
|
+
rowDimensionWidths,
|
|
1197
|
+
columnHeaderHeight: tableHeaderHeight,
|
|
1198
|
+
scrollTop: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight,
|
|
1199
|
+
expandedNodeIds: params.expandedRowNodeIds,
|
|
1200
|
+
dimensionCount: options.rows.length,
|
|
1201
|
+
indicators: options.indicators
|
|
1202
|
+
})
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1157
1205
|
const columnHeaderCells = buildColumnHeaderCells({
|
|
1158
1206
|
model,
|
|
1159
1207
|
options,
|
|
@@ -1180,14 +1228,34 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1180
1228
|
columnHeaderHeight: tableHeaderHeight,
|
|
1181
1229
|
scroll
|
|
1182
1230
|
});
|
|
1231
|
+
if (frozenBottomRowRange) {
|
|
1232
|
+
bodyCells.push(
|
|
1233
|
+
...buildBodyCells({
|
|
1234
|
+
model,
|
|
1235
|
+
rowSlots,
|
|
1236
|
+
columnSlots,
|
|
1237
|
+
rowRange: frozenBottomRowRange,
|
|
1238
|
+
columnRange: visibleColumnRange,
|
|
1239
|
+
rowHeight,
|
|
1240
|
+
columnWidths,
|
|
1241
|
+
columnOffsets,
|
|
1242
|
+
rowHeaderWidth,
|
|
1243
|
+
columnHeaderHeight: tableHeaderHeight,
|
|
1244
|
+
scroll: {
|
|
1245
|
+
left: scroll.left,
|
|
1246
|
+
top: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight
|
|
1247
|
+
}
|
|
1248
|
+
})
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1183
1251
|
const scrollbar = computePivotScrollbarLayout({
|
|
1184
|
-
width,
|
|
1252
|
+
width: tableWidth + (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1185
1253
|
height: fittedHeight,
|
|
1186
1254
|
bodyTop: tableHeaderHeight,
|
|
1187
|
-
bodyHeight,
|
|
1255
|
+
bodyHeight: scrollableBodyHeight,
|
|
1188
1256
|
bodyWidth,
|
|
1189
1257
|
totalBodyWidth,
|
|
1190
|
-
totalBodyHeight,
|
|
1258
|
+
totalBodyHeight: totalScrollableBodyHeight,
|
|
1191
1259
|
scrollLeft: scroll.left,
|
|
1192
1260
|
scrollTop: scroll.top,
|
|
1193
1261
|
maxScrollLeft,
|
|
@@ -1239,6 +1307,7 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1239
1307
|
rowSlots,
|
|
1240
1308
|
columnSlots,
|
|
1241
1309
|
visibleRowRange,
|
|
1310
|
+
frozenBottomRowRange,
|
|
1242
1311
|
visibleColumnRange,
|
|
1243
1312
|
reportFilterCells,
|
|
1244
1313
|
rowFieldHeaderCells,
|
|
@@ -1373,6 +1442,13 @@ function createAxisSlot(node, indicatorKey) {
|
|
|
1373
1442
|
kind: node?.kind ?? "empty"
|
|
1374
1443
|
};
|
|
1375
1444
|
}
|
|
1445
|
+
function resolveFrozenBottomRowRange(slots) {
|
|
1446
|
+
let start = slots.length;
|
|
1447
|
+
while (start > 0 && slots[start - 1]?.kind === "grandTotal") {
|
|
1448
|
+
start -= 1;
|
|
1449
|
+
}
|
|
1450
|
+
return start < slots.length ? { start, end: slots.length } : null;
|
|
1451
|
+
}
|
|
1376
1452
|
function buildRowHeaderCells(params) {
|
|
1377
1453
|
const cells = [];
|
|
1378
1454
|
for (let rowIndex = params.range.start; rowIndex < params.range.end; rowIndex += 1) {
|
|
@@ -2573,6 +2649,7 @@ function createPivotDomHost(container) {
|
|
|
2573
2649
|
right: "0",
|
|
2574
2650
|
height: "100%",
|
|
2575
2651
|
overflow: "hidden",
|
|
2652
|
+
borderRadius: "8px",
|
|
2576
2653
|
touchAction: "none"
|
|
2577
2654
|
});
|
|
2578
2655
|
Object.assign(canvas.style, {
|
|
@@ -2635,8 +2712,8 @@ function resolvePivotSelectionRect(layout, selection) {
|
|
|
2635
2712
|
const kind = resolvePivotSelectionKind(selection);
|
|
2636
2713
|
const startX = kind === "row" || kind === "row-cell" ? layout.rowHeaderRect.x : layout.bodyRect.x + (layout.columnOffsets[indexes.startColumnIndex] ?? 0) - layout.scroll.left;
|
|
2637
2714
|
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
|
|
2639
|
-
const endY = layout
|
|
2715
|
+
const startY = kind === "column" ? layout.columnHeaderRect.y : resolvePivotRowTop(layout, indexes.startRowIndex);
|
|
2716
|
+
const endY = resolvePivotRowTop(layout, indexes.endRowIndex) + layout.rowHeight;
|
|
2640
2717
|
const clipRect = resolvePivotSelectionClipRect(layout, selection);
|
|
2641
2718
|
const visibleStartX = Math.max(startX, clipRect.x);
|
|
2642
2719
|
const visibleEndX = Math.min(endX, clipRect.x + clipRect.width);
|
|
@@ -2650,6 +2727,13 @@ function resolvePivotSelectionRect(layout, selection) {
|
|
|
2650
2727
|
height: visibleEndY - visibleStartY
|
|
2651
2728
|
};
|
|
2652
2729
|
}
|
|
2730
|
+
function resolvePivotRowTop(layout, rowIndex) {
|
|
2731
|
+
const frozenRange = layout.frozenBottomRowRange;
|
|
2732
|
+
if (frozenRange && rowIndex >= frozenRange.start && rowIndex < frozenRange.end) {
|
|
2733
|
+
return layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - rowIndex) * layout.rowHeight;
|
|
2734
|
+
}
|
|
2735
|
+
return layout.bodyRect.y + rowIndex * layout.rowHeight - layout.scroll.top;
|
|
2736
|
+
}
|
|
2653
2737
|
function resolvePivotSelectionClipRect(layout, selection) {
|
|
2654
2738
|
const kind = resolvePivotSelectionKind(selection);
|
|
2655
2739
|
if (kind === "row" || kind === "row-cell") {
|
|
@@ -2794,26 +2878,127 @@ var defaultPivotTableTheme = {
|
|
|
2794
2878
|
};
|
|
2795
2879
|
|
|
2796
2880
|
// src/rendering/draw-scrollbars.ts
|
|
2797
|
-
function drawPivotScrollbars(context, layout) {
|
|
2881
|
+
function drawPivotScrollbars(context, layout, borderColor, verticalFrame) {
|
|
2798
2882
|
context.save();
|
|
2883
|
+
if (layout.horizontal && verticalFrame) {
|
|
2884
|
+
drawHorizontalScrollbarMask(
|
|
2885
|
+
context,
|
|
2886
|
+
layout.horizontal,
|
|
2887
|
+
verticalFrame.bodyBackgroundColor
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
if (layout.vertical && verticalFrame) {
|
|
2891
|
+
drawVerticalScrollbarFrame(
|
|
2892
|
+
context,
|
|
2893
|
+
layout.vertical,
|
|
2894
|
+
verticalFrame,
|
|
2895
|
+
borderColor
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2799
2898
|
if (layout.horizontal) {
|
|
2800
|
-
drawAxis(context, layout.horizontal, "horizontal");
|
|
2899
|
+
drawAxis(context, layout.horizontal, "horizontal", borderColor);
|
|
2801
2900
|
}
|
|
2802
2901
|
if (layout.vertical) {
|
|
2803
|
-
drawAxis(context, layout.vertical, "vertical");
|
|
2902
|
+
drawAxis(context, layout.vertical, "vertical", borderColor);
|
|
2903
|
+
}
|
|
2904
|
+
if (layout.horizontal) {
|
|
2905
|
+
drawHorizontalScrollbarTopBorder(
|
|
2906
|
+
context,
|
|
2907
|
+
layout.horizontal,
|
|
2908
|
+
layout.vertical,
|
|
2909
|
+
borderColor
|
|
2910
|
+
);
|
|
2804
2911
|
}
|
|
2805
2912
|
context.restore();
|
|
2806
2913
|
}
|
|
2807
|
-
function
|
|
2808
|
-
|
|
2914
|
+
function drawHorizontalScrollbarMask(context, layout, backgroundColor) {
|
|
2915
|
+
const left = layout.decreaseButton.x;
|
|
2916
|
+
const right = layout.increaseButton.x + layout.increaseButton.width;
|
|
2917
|
+
const top = layout.decreaseButton.y;
|
|
2918
|
+
const height = layout.decreaseButton.height;
|
|
2919
|
+
if (right <= left || height <= 0) return;
|
|
2920
|
+
context.save();
|
|
2921
|
+
context.fillStyle = backgroundColor;
|
|
2922
|
+
context.fillRect(left, top, right - left, height);
|
|
2923
|
+
context.restore();
|
|
2924
|
+
}
|
|
2925
|
+
function drawHorizontalScrollbarTopBorder(context, horizontal, vertical, borderColor) {
|
|
2926
|
+
const left = Math.round(horizontal.decreaseButton.x) + 0.5;
|
|
2927
|
+
const horizontalRight = horizontal.increaseButton.x + horizontal.increaseButton.width;
|
|
2928
|
+
const verticalRight = vertical ? vertical.decreaseButton.x + vertical.decreaseButton.width : horizontalRight;
|
|
2929
|
+
const right = Math.round(Math.max(horizontalRight, verticalRight)) - 0.5;
|
|
2930
|
+
const top = Math.round(horizontal.decreaseButton.y) + 0.5;
|
|
2931
|
+
if (right <= left) return;
|
|
2932
|
+
context.save();
|
|
2933
|
+
context.strokeStyle = borderColor;
|
|
2934
|
+
context.lineWidth = 1;
|
|
2935
|
+
context.beginPath();
|
|
2936
|
+
context.moveTo(left, top);
|
|
2937
|
+
context.lineTo(right, top);
|
|
2938
|
+
context.stroke();
|
|
2939
|
+
context.restore();
|
|
2940
|
+
}
|
|
2941
|
+
function drawAxis(context, layout, axis, borderColor) {
|
|
2809
2942
|
drawButton(context, layout.decreaseButton, axis, "decrease");
|
|
2810
2943
|
drawButton(context, layout.increaseButton, axis, "increase");
|
|
2811
2944
|
drawThumb(context, layout.thumb);
|
|
2945
|
+
drawTrackBorder(context, layout.track, axis, borderColor);
|
|
2946
|
+
}
|
|
2947
|
+
function drawTrackBorder(context, rect, axis, borderColor) {
|
|
2948
|
+
const left = Math.round(rect.x) + 0.5;
|
|
2949
|
+
const right = Math.round(rect.x + rect.width) - 0.5;
|
|
2950
|
+
const top = Math.round(rect.y) + 0.5;
|
|
2951
|
+
const bottom = Math.round(rect.y + rect.height) - 0.5;
|
|
2952
|
+
if (right <= left || bottom <= top) return;
|
|
2953
|
+
context.save();
|
|
2954
|
+
context.strokeStyle = borderColor;
|
|
2955
|
+
context.lineWidth = 1;
|
|
2956
|
+
context.beginPath();
|
|
2957
|
+
if (axis === "horizontal") {
|
|
2958
|
+
context.moveTo(left, top);
|
|
2959
|
+
context.lineTo(right, top);
|
|
2960
|
+
} else {
|
|
2961
|
+
context.moveTo(left, top);
|
|
2962
|
+
context.lineTo(left, bottom);
|
|
2963
|
+
}
|
|
2964
|
+
context.stroke();
|
|
2965
|
+
context.restore();
|
|
2812
2966
|
}
|
|
2813
|
-
function
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2967
|
+
function drawVerticalScrollbarFrame(context, layout, frame, borderColor) {
|
|
2968
|
+
const left = Math.round(layout.decreaseButton.x) + 0.5;
|
|
2969
|
+
const top = Math.round(frame.top) + 0.5;
|
|
2970
|
+
const bottom = Math.round(frame.top + frame.height) - 0.5;
|
|
2971
|
+
const headerBottom = Math.round(frame.headerBottom) + 0.5;
|
|
2972
|
+
if (bottom <= top) return;
|
|
2973
|
+
context.save();
|
|
2974
|
+
context.fillStyle = frame.headerBackgroundColor;
|
|
2975
|
+
context.fillRect(
|
|
2976
|
+
layout.decreaseButton.x,
|
|
2977
|
+
frame.top,
|
|
2978
|
+
layout.decreaseButton.width,
|
|
2979
|
+
Math.max(frame.headerBottom - frame.top, 0)
|
|
2980
|
+
);
|
|
2981
|
+
context.fillStyle = frame.bodyBackgroundColor;
|
|
2982
|
+
context.fillRect(
|
|
2983
|
+
layout.decreaseButton.x,
|
|
2984
|
+
frame.headerBottom,
|
|
2985
|
+
layout.decreaseButton.width,
|
|
2986
|
+
Math.max(frame.top + frame.height - frame.headerBottom, 0)
|
|
2987
|
+
);
|
|
2988
|
+
context.strokeStyle = borderColor;
|
|
2989
|
+
context.lineWidth = 1;
|
|
2990
|
+
context.beginPath();
|
|
2991
|
+
context.moveTo(left, top);
|
|
2992
|
+
context.lineTo(left, bottom);
|
|
2993
|
+
context.stroke();
|
|
2994
|
+
if (headerBottom > top && headerBottom < bottom) {
|
|
2995
|
+
const right = Math.round(layout.decreaseButton.x + layout.decreaseButton.width) - 0.5;
|
|
2996
|
+
context.beginPath();
|
|
2997
|
+
context.moveTo(left, headerBottom);
|
|
2998
|
+
context.lineTo(right, headerBottom);
|
|
2999
|
+
context.stroke();
|
|
3000
|
+
}
|
|
3001
|
+
context.restore();
|
|
2817
3002
|
}
|
|
2818
3003
|
function drawThumb(context, rect) {
|
|
2819
3004
|
context.fillStyle = "rgba(100, 116, 139, 0.8)";
|
|
@@ -2821,28 +3006,25 @@ function drawThumb(context, rect) {
|
|
|
2821
3006
|
context.fill();
|
|
2822
3007
|
}
|
|
2823
3008
|
function drawButton(context, rect, axis, direction) {
|
|
2824
|
-
context.fillStyle = "rgba(226, 232, 240, 0.92)";
|
|
2825
|
-
context.strokeStyle = "rgba(148, 163, 184, 0.65)";
|
|
2826
|
-
context.lineWidth = 1;
|
|
2827
|
-
roundRect(context, rect, rect.height / 2);
|
|
2828
|
-
context.fill();
|
|
2829
|
-
context.stroke();
|
|
2830
3009
|
context.fillStyle = "rgba(71, 85, 105, 0.88)";
|
|
2831
3010
|
context.beginPath();
|
|
2832
3011
|
const centerX = rect.x + rect.width / 2;
|
|
2833
3012
|
const centerY = rect.y + rect.height / 2;
|
|
3013
|
+
const iconSize = Math.min(rect.width, rect.height) * 0.64;
|
|
3014
|
+
const directionOffset = iconSize * 0.4;
|
|
3015
|
+
const crossAxisOffset = iconSize / 2;
|
|
2834
3016
|
if (axis === "horizontal") {
|
|
2835
|
-
const tipX = centerX + (direction === "decrease" ? -1 : 1) *
|
|
2836
|
-
const tailX = centerX + (direction === "decrease" ? 1 : -1) *
|
|
3017
|
+
const tipX = centerX + (direction === "decrease" ? -1 : 1) * directionOffset;
|
|
3018
|
+
const tailX = centerX + (direction === "decrease" ? 1 : -1) * directionOffset;
|
|
2837
3019
|
context.moveTo(tipX, centerY);
|
|
2838
|
-
context.lineTo(tailX, centerY -
|
|
2839
|
-
context.lineTo(tailX, centerY +
|
|
3020
|
+
context.lineTo(tailX, centerY - crossAxisOffset);
|
|
3021
|
+
context.lineTo(tailX, centerY + crossAxisOffset);
|
|
2840
3022
|
} else {
|
|
2841
|
-
const tipY = centerY + (direction === "decrease" ? -1 : 1) *
|
|
2842
|
-
const tailY = centerY + (direction === "decrease" ? 1 : -1) *
|
|
3023
|
+
const tipY = centerY + (direction === "decrease" ? -1 : 1) * directionOffset;
|
|
3024
|
+
const tailY = centerY + (direction === "decrease" ? 1 : -1) * directionOffset;
|
|
2843
3025
|
context.moveTo(centerX, tipY);
|
|
2844
|
-
context.lineTo(centerX -
|
|
2845
|
-
context.lineTo(centerX +
|
|
3026
|
+
context.lineTo(centerX - crossAxisOffset, tailY);
|
|
3027
|
+
context.lineTo(centerX + crossAxisOffset, tailY);
|
|
2846
3028
|
}
|
|
2847
3029
|
context.closePath();
|
|
2848
3030
|
context.fill();
|
|
@@ -2895,25 +3077,22 @@ function drawPivotGridRect(context, rect, color, options) {
|
|
|
2895
3077
|
context.stroke();
|
|
2896
3078
|
context.restore();
|
|
2897
3079
|
}
|
|
2898
|
-
function
|
|
2899
|
-
|
|
2900
|
-
context.
|
|
2901
|
-
context.strokeStyle = color;
|
|
2902
|
-
context.lineWidth = 1;
|
|
2903
|
-
context.beginPath();
|
|
2904
|
-
context.moveTo(x, rect.y);
|
|
2905
|
-
context.lineTo(x, rect.y + rect.height);
|
|
2906
|
-
context.stroke();
|
|
2907
|
-
context.restore();
|
|
3080
|
+
function clipPivotViewport(context, rect, radius = 8) {
|
|
3081
|
+
appendRoundedRectPath(context, rect, radius);
|
|
3082
|
+
context.clip();
|
|
2908
3083
|
}
|
|
2909
|
-
function
|
|
2910
|
-
const
|
|
3084
|
+
function drawPivotViewportBorder(context, rect, color, radius = 8) {
|
|
3085
|
+
const borderRect = {
|
|
3086
|
+
x: rect.x + 0.5,
|
|
3087
|
+
y: rect.y + 0.5,
|
|
3088
|
+
width: Math.max(rect.width - 1, 0),
|
|
3089
|
+
height: Math.max(rect.height - 1, 0)
|
|
3090
|
+
};
|
|
3091
|
+
if (borderRect.width <= 0 || borderRect.height <= 0) return;
|
|
2911
3092
|
context.save();
|
|
2912
3093
|
context.strokeStyle = color;
|
|
2913
3094
|
context.lineWidth = 1;
|
|
2914
|
-
context
|
|
2915
|
-
context.moveTo(rect.x, y);
|
|
2916
|
-
context.lineTo(rect.x + rect.width, y);
|
|
3095
|
+
appendRoundedRectPath(context, borderRect, radius);
|
|
2917
3096
|
context.stroke();
|
|
2918
3097
|
context.restore();
|
|
2919
3098
|
}
|
|
@@ -2931,6 +3110,21 @@ function drawPivotHeaderBoundaries(context, options, color) {
|
|
|
2931
3110
|
context.stroke();
|
|
2932
3111
|
context.restore();
|
|
2933
3112
|
}
|
|
3113
|
+
function appendRoundedRectPath(context, rect, radius) {
|
|
3114
|
+
const nextRadius = Math.max(
|
|
3115
|
+
Math.min(radius, rect.width / 2, rect.height / 2),
|
|
3116
|
+
0
|
|
3117
|
+
);
|
|
3118
|
+
const right = rect.x + rect.width;
|
|
3119
|
+
const bottom = rect.y + rect.height;
|
|
3120
|
+
context.beginPath();
|
|
3121
|
+
context.moveTo(rect.x + nextRadius, rect.y);
|
|
3122
|
+
context.arcTo(right, rect.y, right, bottom, nextRadius);
|
|
3123
|
+
context.arcTo(right, bottom, rect.x, bottom, nextRadius);
|
|
3124
|
+
context.arcTo(rect.x, bottom, rect.x, rect.y, nextRadius);
|
|
3125
|
+
context.arcTo(rect.x, rect.y, right, rect.y, nextRadius);
|
|
3126
|
+
context.closePath();
|
|
3127
|
+
}
|
|
2934
3128
|
|
|
2935
3129
|
// src/domain/layout/sticky-row-header.ts
|
|
2936
3130
|
function resolveStickyRowHeaderContentRect(params) {
|
|
@@ -2967,7 +3161,15 @@ function renderPivotTable(params) {
|
|
|
2967
3161
|
activeNodePathKey
|
|
2968
3162
|
);
|
|
2969
3163
|
const areaSelection = isPivotAreaSelection(layout, params.selection);
|
|
3164
|
+
const viewportWidth = resolvePivotViewportWidth(layout);
|
|
2970
3165
|
context.clearRect(0, 0, layout.width, layout.height);
|
|
3166
|
+
context.save();
|
|
3167
|
+
clipPivotViewport(context, {
|
|
3168
|
+
x: 0,
|
|
3169
|
+
y: 0,
|
|
3170
|
+
width: viewportWidth,
|
|
3171
|
+
height: layout.height
|
|
3172
|
+
});
|
|
2971
3173
|
context.font = `${theme.fontSize}px ${theme.fontFamily}`;
|
|
2972
3174
|
context.textBaseline = "middle";
|
|
2973
3175
|
withClip(context, layout.reportFilterRect, () => {
|
|
@@ -3081,17 +3283,13 @@ function renderPivotTable(params) {
|
|
|
3081
3283
|
theme.borderColor
|
|
3082
3284
|
);
|
|
3083
3285
|
if (options.debug) drawDebugOverlay(context, layout, theme);
|
|
3084
|
-
drawPivotScrollbars(context, layout.scrollbar
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
theme.
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
context,
|
|
3092
|
-
{ x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
|
|
3093
|
-
theme.borderColor
|
|
3094
|
-
);
|
|
3286
|
+
drawPivotScrollbars(context, layout.scrollbar, theme.borderColor, {
|
|
3287
|
+
top: 0,
|
|
3288
|
+
height: contentHeight,
|
|
3289
|
+
headerBottom: layout.bodyRect.y,
|
|
3290
|
+
headerBackgroundColor: theme.columnHeaderBackgroundColor,
|
|
3291
|
+
bodyBackgroundColor: theme.backgroundColor
|
|
3292
|
+
});
|
|
3095
3293
|
const selectionRect = resolvePivotSelectionRect(layout, params.selection);
|
|
3096
3294
|
if (selectionRect) {
|
|
3097
3295
|
const selectionClipRect = resolvePivotSelectionClipRect(
|
|
@@ -3109,11 +3307,22 @@ function renderPivotTable(params) {
|
|
|
3109
3307
|
if (params.resizeGuideX != null) {
|
|
3110
3308
|
drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
|
|
3111
3309
|
}
|
|
3310
|
+
context.restore();
|
|
3311
|
+
drawPivotViewportBorder(
|
|
3312
|
+
context,
|
|
3313
|
+
{ x: 0, y: 0, width: viewportWidth, height: contentHeight },
|
|
3314
|
+
theme.borderColor
|
|
3315
|
+
);
|
|
3112
3316
|
return {
|
|
3113
3317
|
headerCellCount: layout.reportFilterCells.length + layout.rowFieldHeaderCells.length + layout.rowHeaderCells.length + layout.columnHeaderCells.length,
|
|
3114
3318
|
bodyCellCount: layout.bodyCells.length
|
|
3115
3319
|
};
|
|
3116
3320
|
}
|
|
3321
|
+
function resolvePivotViewportWidth(layout) {
|
|
3322
|
+
const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
|
|
3323
|
+
const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
|
|
3324
|
+
return Math.max(layout.tableWidth, horizontalRight, verticalRight);
|
|
3325
|
+
}
|
|
3117
3326
|
function drawPivotResizeGuide(context, x, height) {
|
|
3118
3327
|
context.save();
|
|
3119
3328
|
context.strokeStyle = "#1677ff";
|
|
@@ -3599,7 +3808,7 @@ function hitTestPivotLayout(layout, x, y) {
|
|
|
3599
3808
|
onSortIcon: false
|
|
3600
3809
|
};
|
|
3601
3810
|
}
|
|
3602
|
-
const bodyCell = layout.bodyCells
|
|
3811
|
+
const bodyCell = findLastContaining(layout.bodyCells, x, y);
|
|
3603
3812
|
if (bodyCell) {
|
|
3604
3813
|
return {
|
|
3605
3814
|
region: "body",
|
|
@@ -3609,9 +3818,7 @@ function hitTestPivotLayout(layout, x, y) {
|
|
|
3609
3818
|
onSortIcon: false
|
|
3610
3819
|
};
|
|
3611
3820
|
}
|
|
3612
|
-
const rowHeaderCell = layout.rowHeaderCells
|
|
3613
|
-
(cell) => contains(cell.rect, x, y)
|
|
3614
|
-
);
|
|
3821
|
+
const rowHeaderCell = findLastContaining(layout.rowHeaderCells, x, y);
|
|
3615
3822
|
if (rowHeaderCell) {
|
|
3616
3823
|
return {
|
|
3617
3824
|
region: "rowHeader",
|
|
@@ -3664,6 +3871,13 @@ function isSortIconHit(cell, x) {
|
|
|
3664
3871
|
function contains(rect, x, y) {
|
|
3665
3872
|
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
3666
3873
|
}
|
|
3874
|
+
function findLastContaining(cells, x, y) {
|
|
3875
|
+
for (let index = cells.length - 1; index >= 0; index -= 1) {
|
|
3876
|
+
const cell = cells[index];
|
|
3877
|
+
if (cell && contains(cell.rect, x, y)) return cell;
|
|
3878
|
+
}
|
|
3879
|
+
return void 0;
|
|
3880
|
+
}
|
|
3667
3881
|
|
|
3668
3882
|
// src/interaction/text-selection.ts
|
|
3669
3883
|
function resolvePivotTextOffset(params) {
|
|
@@ -3862,6 +4076,16 @@ function hitTestPivotScrollbar(layout, x, y) {
|
|
|
3862
4076
|
}
|
|
3863
4077
|
return null;
|
|
3864
4078
|
}
|
|
4079
|
+
function resolvePivotScrollbarTrackScroll(params) {
|
|
4080
|
+
const movableLength = Math.max(params.trackLength - params.thumbLength, 0);
|
|
4081
|
+
if (movableLength === 0) return 0;
|
|
4082
|
+
const thumbStart = utils.clamp(
|
|
4083
|
+
params.pointerValue - params.thumbLength / 2,
|
|
4084
|
+
params.trackStart,
|
|
4085
|
+
params.trackStart + movableLength
|
|
4086
|
+
);
|
|
4087
|
+
return (thumbStart - params.trackStart) / movableLength * params.maxScroll;
|
|
4088
|
+
}
|
|
3865
4089
|
function resolveRole(layout, x, y) {
|
|
3866
4090
|
if (utils.isPointInRect(x, y, layout.thumb)) return "thumb";
|
|
3867
4091
|
if (utils.isPointInRect(x, y, layout.decreaseButton)) return "decrease-button";
|
|
@@ -3900,6 +4124,16 @@ function createAddress(rowSlot, columnSlot, indicatorKey) {
|
|
|
3900
4124
|
// src/domain/interaction/row-selection.ts
|
|
3901
4125
|
function resolvePivotRowIndexAtY(layout, y) {
|
|
3902
4126
|
if (layout.rowSlots.length === 0 || layout.rowHeight <= 0) return null;
|
|
4127
|
+
const frozenRange = layout.frozenBottomRowRange;
|
|
4128
|
+
if (frozenRange) {
|
|
4129
|
+
const frozenTop = layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - frozenRange.start) * layout.rowHeight;
|
|
4130
|
+
if (y >= frozenTop) {
|
|
4131
|
+
return Math.min(
|
|
4132
|
+
frozenRange.start + Math.floor((y - frozenTop) / layout.rowHeight),
|
|
4133
|
+
frozenRange.end - 1
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
3903
4137
|
const index = Math.floor(
|
|
3904
4138
|
(y - layout.bodyRect.y + layout.scroll.top) / layout.rowHeight
|
|
3905
4139
|
);
|
|
@@ -4018,6 +4252,8 @@ var PivotTable = class {
|
|
|
4018
4252
|
__publicField(this, "wheelScrollTarget", null);
|
|
4019
4253
|
__publicField(this, "wheelScrollFrameId", null);
|
|
4020
4254
|
__publicField(this, "wheelScrollLastTime", 0);
|
|
4255
|
+
__publicField(this, "continuousScrollTimer", null);
|
|
4256
|
+
__publicField(this, "continuousScrollPointerId", null);
|
|
4021
4257
|
__publicField(this, "resizeSession", null);
|
|
4022
4258
|
__publicField(this, "resizeGuideX", null);
|
|
4023
4259
|
__publicField(this, "suppressNextClick", false);
|
|
@@ -4096,6 +4332,7 @@ var PivotTable = class {
|
|
|
4096
4332
|
this.scheduleRender();
|
|
4097
4333
|
});
|
|
4098
4334
|
__publicField(this, "handleClick", (event) => {
|
|
4335
|
+
this.options.ui?.onContextMenuRequest?.(null);
|
|
4099
4336
|
if (this.suppressNextClick) {
|
|
4100
4337
|
this.suppressNextClick = false;
|
|
4101
4338
|
return;
|
|
@@ -4414,6 +4651,8 @@ var PivotTable = class {
|
|
|
4414
4651
|
});
|
|
4415
4652
|
__publicField(this, "handlePointerUp", (event) => {
|
|
4416
4653
|
if (!this.host) return;
|
|
4654
|
+
const endedContinuousScroll = this.continuousScrollPointerId === event.pointerId;
|
|
4655
|
+
if (endedContinuousScroll) this.stopContinuousScrollbarScroll();
|
|
4417
4656
|
const endedCellSelection = this.cellSelectionDrag?.pointerId === event.pointerId;
|
|
4418
4657
|
if (endedCellSelection) this.cellSelectionDrag = null;
|
|
4419
4658
|
if (this.textSelectionDrag?.pointerId === event.pointerId) {
|
|
@@ -4432,7 +4671,7 @@ var PivotTable = class {
|
|
|
4432
4671
|
if (this.scrollbarDrag?.pointerId === event.pointerId) {
|
|
4433
4672
|
this.scrollbarDrag = null;
|
|
4434
4673
|
}
|
|
4435
|
-
if (!this.resizeSession && !this.scrollbarDrag && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
|
|
4674
|
+
if (!this.resizeSession && !this.scrollbarDrag && !endedContinuousScroll && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
|
|
4436
4675
|
return;
|
|
4437
4676
|
if (this.host.root.hasPointerCapture(event.pointerId)) {
|
|
4438
4677
|
this.host.root.releasePointerCapture(event.pointerId);
|
|
@@ -4605,11 +4844,16 @@ var PivotTable = class {
|
|
|
4605
4844
|
(slot) => (slot.nodeId ?? "") === address.columnNodeId && (!slot.indicatorKey || slot.indicatorKey === address.indicatorKey)
|
|
4606
4845
|
);
|
|
4607
4846
|
if (rowIndex < 0 || columnIndex < 0) return false;
|
|
4608
|
-
const
|
|
4847
|
+
const frozenRange = this.layout.frozenBottomRowRange;
|
|
4848
|
+
const frozenBottomHeight = frozenRange ? (frozenRange.end - frozenRange.start) * this.layout.rowHeight : 0;
|
|
4849
|
+
const top = frozenRange && rowIndex >= frozenRange.start ? this.scroll.top : resolveVisibleOffset({
|
|
4609
4850
|
current: this.scroll.top,
|
|
4610
4851
|
itemStart: rowIndex * this.layout.rowHeight,
|
|
4611
4852
|
itemSize: this.layout.rowHeight,
|
|
4612
|
-
viewportSize:
|
|
4853
|
+
viewportSize: Math.max(
|
|
4854
|
+
this.layout.bodyRect.height - frozenBottomHeight,
|
|
4855
|
+
0
|
|
4856
|
+
)
|
|
4613
4857
|
});
|
|
4614
4858
|
const left = resolveVisibleOffset({
|
|
4615
4859
|
current: this.scroll.left,
|
|
@@ -4673,6 +4917,7 @@ var PivotTable = class {
|
|
|
4673
4917
|
if (this.frameId !== null) cancelAnimationFrame(this.frameId);
|
|
4674
4918
|
this.frameId = null;
|
|
4675
4919
|
this.stopWheelScrollSmoothing();
|
|
4920
|
+
this.stopContinuousScrollbarScroll();
|
|
4676
4921
|
this.stopResizeObserver?.();
|
|
4677
4922
|
this.stopResizeObserver = null;
|
|
4678
4923
|
this.host?.root.removeEventListener("pointermove", this.handlePointerMove);
|
|
@@ -5041,17 +5286,27 @@ var PivotTable = class {
|
|
|
5041
5286
|
event.preventDefault();
|
|
5042
5287
|
this.suppressNextClick = true;
|
|
5043
5288
|
if (hit.role === "decrease-button" || hit.role === "increase-button") {
|
|
5044
|
-
this.
|
|
5289
|
+
this.startContinuousScrollbarScroll(
|
|
5290
|
+
hit.axis,
|
|
5291
|
+
hit.role === "decrease-button" ? -1 : 1,
|
|
5292
|
+
event.pointerId
|
|
5293
|
+
);
|
|
5294
|
+
this.host.root.setPointerCapture(event.pointerId);
|
|
5045
5295
|
return;
|
|
5046
5296
|
}
|
|
5047
5297
|
const rootRect = this.host.root.getBoundingClientRect();
|
|
5048
5298
|
const pointer = hit.axis === "horizontal" ? event.clientX - rootRect.left : event.clientY - rootRect.top;
|
|
5049
5299
|
if (hit.role === "track") {
|
|
5050
|
-
const
|
|
5051
|
-
|
|
5300
|
+
const nextScroll = resolvePivotScrollbarTrackScroll({
|
|
5301
|
+
pointerValue: pointer,
|
|
5302
|
+
trackStart: hit.axis === "horizontal" ? axisLayout.track.x : axisLayout.track.y,
|
|
5303
|
+
trackLength: hit.axis === "horizontal" ? axisLayout.track.width : axisLayout.track.height,
|
|
5304
|
+
thumbLength: hit.axis === "horizontal" ? axisLayout.thumb.width : axisLayout.thumb.height,
|
|
5305
|
+
maxScroll: hit.axis === "horizontal" ? this.layout.viewport.maxScrollLeft : this.layout.viewport.maxScrollTop
|
|
5306
|
+
});
|
|
5052
5307
|
this.applyScrollPosition(
|
|
5053
|
-
|
|
5054
|
-
|
|
5308
|
+
hit.axis === "horizontal" ? nextScroll : this.scroll.left,
|
|
5309
|
+
hit.axis === "vertical" ? nextScroll : this.scroll.top
|
|
5055
5310
|
);
|
|
5056
5311
|
return;
|
|
5057
5312
|
}
|
|
@@ -5095,6 +5350,21 @@ var PivotTable = class {
|
|
|
5095
5350
|
this.scroll.top + (axis === "vertical" ? direction * this.layout.rowHeight : 0)
|
|
5096
5351
|
);
|
|
5097
5352
|
}
|
|
5353
|
+
startContinuousScrollbarScroll(axis, direction, pointerId) {
|
|
5354
|
+
this.stopContinuousScrollbarScroll();
|
|
5355
|
+
this.continuousScrollPointerId = pointerId;
|
|
5356
|
+
this.stepScrollbar(axis, direction);
|
|
5357
|
+
this.continuousScrollTimer = setInterval(() => {
|
|
5358
|
+
this.stepScrollbar(axis, direction);
|
|
5359
|
+
}, 80);
|
|
5360
|
+
}
|
|
5361
|
+
stopContinuousScrollbarScroll() {
|
|
5362
|
+
if (this.continuousScrollTimer !== null) {
|
|
5363
|
+
clearInterval(this.continuousScrollTimer);
|
|
5364
|
+
this.continuousScrollTimer = null;
|
|
5365
|
+
}
|
|
5366
|
+
this.continuousScrollPointerId = null;
|
|
5367
|
+
}
|
|
5098
5368
|
applyScrollPosition(left, top) {
|
|
5099
5369
|
if (!this.layout) return;
|
|
5100
5370
|
const next = {
|