@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.js
CHANGED
|
@@ -728,14 +728,11 @@ function matchesSearchKeyword(value, filter) {
|
|
|
728
728
|
return filter.searchExact ? text === keyword : text.includes(keyword);
|
|
729
729
|
}
|
|
730
730
|
function matchesFilterConditions(value, filter) {
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (!firstActive) return evaluateCondition(value, second);
|
|
737
|
-
if (!secondActive) return evaluateCondition(value, first);
|
|
738
|
-
return filter.conditionRelation === "or" ? evaluateCondition(value, first) || evaluateCondition(value, second) : evaluateCondition(value, first) && evaluateCondition(value, second);
|
|
731
|
+
const conditions = (filter.conditions?.length ? filter.conditions : [filter.condition, filter.secondCondition]).filter(
|
|
732
|
+
(condition) => condition != null && condition.operator !== "none"
|
|
733
|
+
);
|
|
734
|
+
if (conditions.length === 0) return true;
|
|
735
|
+
return filter.conditionRelation === "or" ? conditions.some((condition) => evaluateCondition(value, condition)) : conditions.every((condition) => evaluateCondition(value, condition));
|
|
739
736
|
}
|
|
740
737
|
function evaluateCondition(value, condition) {
|
|
741
738
|
const text = value == null ? "" : String(value);
|
|
@@ -974,7 +971,7 @@ function computePivotScrollbarLayout(params) {
|
|
|
974
971
|
}
|
|
975
972
|
if (hasVertical) {
|
|
976
973
|
const fullHeight = Math.max(
|
|
977
|
-
params.bodyHeight - VERTICAL_TOP_INSET
|
|
974
|
+
params.bodyHeight - VERTICAL_TOP_INSET,
|
|
978
975
|
0
|
|
979
976
|
);
|
|
980
977
|
const buttonSize = Math.min(SCROLLBAR_THICKNESS, fullHeight / 3);
|
|
@@ -1030,6 +1027,7 @@ var DEFAULT_ROW_HEIGHT = 40;
|
|
|
1030
1027
|
var DEFAULT_COLUMN_WIDTH = 120;
|
|
1031
1028
|
var DEFAULT_HEADER_SIZE = 40;
|
|
1032
1029
|
var DEFAULT_ROW_DIMENSION_WIDTH2 = 140;
|
|
1030
|
+
var SCROLLBAR_THICKNESS2 = 10;
|
|
1033
1031
|
function buildPivotLayoutSnapshot(params) {
|
|
1034
1032
|
const { model, options, visibleRowNodes, visibleColumnNodes, width, height } = params;
|
|
1035
1033
|
const indicatorLayout = options.indicatorLayout ?? "columns";
|
|
@@ -1062,8 +1060,8 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1062
1060
|
const columnHeaderHeight = columnHeaderLevels * DEFAULT_HEADER_SIZE;
|
|
1063
1061
|
const reportFilterHeight = (options.filterDimensions?.length ?? 0) * rowHeight;
|
|
1064
1062
|
const tableHeaderHeight = reportFilterHeight + columnHeaderHeight;
|
|
1065
|
-
const
|
|
1066
|
-
const
|
|
1063
|
+
const rawBodyWidth = Math.max(width - rowHeaderWidth, 0);
|
|
1064
|
+
const rawAvailableBodyHeight = Math.max(height - tableHeaderHeight, 0);
|
|
1067
1065
|
const rowSlots = createPivotAxisSlots({
|
|
1068
1066
|
nodes: visibleRowNodes,
|
|
1069
1067
|
indicators: options.indicators.map((indicator) => indicator.key),
|
|
@@ -1086,19 +1084,52 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1086
1084
|
const columnOffsets = createOffsets(columnWidths);
|
|
1087
1085
|
const totalBodyWidth = columnOffsets[columnOffsets.length - 1] ?? 0;
|
|
1088
1086
|
const totalBodyHeight = rowSlots.length * rowHeight;
|
|
1087
|
+
let hasHorizontalScrollbar = totalBodyWidth > rawBodyWidth;
|
|
1088
|
+
let hasVerticalScrollbar = totalBodyHeight > rawAvailableBodyHeight;
|
|
1089
|
+
for (let index = 0; index < 2; index += 1) {
|
|
1090
|
+
hasHorizontalScrollbar = totalBodyWidth > Math.max(
|
|
1091
|
+
rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1092
|
+
0
|
|
1093
|
+
);
|
|
1094
|
+
hasVerticalScrollbar = totalBodyHeight > Math.max(
|
|
1095
|
+
rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1096
|
+
0
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const bodyWidth = Math.max(
|
|
1100
|
+
rawBodyWidth - (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1101
|
+
0
|
|
1102
|
+
);
|
|
1103
|
+
const availableBodyHeight = Math.max(
|
|
1104
|
+
rawAvailableBodyHeight - (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1105
|
+
0
|
|
1106
|
+
);
|
|
1107
|
+
const frozenBottomRowRange = resolveFrozenBottomRowRange(rowSlots);
|
|
1108
|
+
const frozenBottomRowCount = frozenBottomRowRange ? frozenBottomRowRange.end - frozenBottomRowRange.start : 0;
|
|
1109
|
+
const frozenBottomHeight = frozenBottomRowCount * rowHeight;
|
|
1110
|
+
const scrollableRowCount = frozenBottomRowRange?.start ?? rowSlots.length;
|
|
1111
|
+
const totalScrollableBodyHeight = scrollableRowCount * rowHeight;
|
|
1089
1112
|
const tableWidth = rowHeaderWidth + Math.min(bodyWidth, totalBodyWidth);
|
|
1090
1113
|
const bodyHeight = Math.min(availableBodyHeight, totalBodyHeight);
|
|
1091
|
-
const
|
|
1114
|
+
const visibleFrozenBottomHeight = Math.min(frozenBottomHeight, bodyHeight);
|
|
1115
|
+
const scrollableBodyHeight = Math.max(
|
|
1116
|
+
bodyHeight - visibleFrozenBottomHeight,
|
|
1117
|
+
0
|
|
1118
|
+
);
|
|
1119
|
+
const fittedHeight = tableHeaderHeight + bodyHeight + (hasHorizontalScrollbar ? SCROLLBAR_THICKNESS2 : 0);
|
|
1092
1120
|
const maxScrollLeft = Math.max(totalBodyWidth - bodyWidth, 0);
|
|
1093
|
-
const maxScrollTop = Math.max(
|
|
1121
|
+
const maxScrollTop = Math.max(
|
|
1122
|
+
totalScrollableBodyHeight - scrollableBodyHeight,
|
|
1123
|
+
0
|
|
1124
|
+
);
|
|
1094
1125
|
const scroll = {
|
|
1095
1126
|
left: clamp(params.scroll.left, 0, maxScrollLeft),
|
|
1096
1127
|
top: clamp(params.scroll.top, 0, maxScrollTop)
|
|
1097
1128
|
};
|
|
1098
1129
|
const visibleRowRange = resolveVisibleRange({
|
|
1099
|
-
count:
|
|
1130
|
+
count: scrollableRowCount,
|
|
1100
1131
|
itemSize: rowHeight,
|
|
1101
|
-
viewportSize:
|
|
1132
|
+
viewportSize: scrollableBodyHeight,
|
|
1102
1133
|
offset: scroll.top,
|
|
1103
1134
|
overscan: options.overscanRowCount ?? 1
|
|
1104
1135
|
});
|
|
@@ -1133,6 +1164,23 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1133
1164
|
dimensionCount: options.rows.length,
|
|
1134
1165
|
indicators: options.indicators
|
|
1135
1166
|
});
|
|
1167
|
+
if (frozenBottomRowRange) {
|
|
1168
|
+
rowHeaderCells.push(
|
|
1169
|
+
...buildRowHeaderCells({
|
|
1170
|
+
model,
|
|
1171
|
+
options,
|
|
1172
|
+
slots: rowSlots,
|
|
1173
|
+
range: frozenBottomRowRange,
|
|
1174
|
+
rowHeight,
|
|
1175
|
+
rowDimensionWidths,
|
|
1176
|
+
columnHeaderHeight: tableHeaderHeight,
|
|
1177
|
+
scrollTop: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight,
|
|
1178
|
+
expandedNodeIds: params.expandedRowNodeIds,
|
|
1179
|
+
dimensionCount: options.rows.length,
|
|
1180
|
+
indicators: options.indicators
|
|
1181
|
+
})
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1136
1184
|
const columnHeaderCells = buildColumnHeaderCells({
|
|
1137
1185
|
model,
|
|
1138
1186
|
options,
|
|
@@ -1159,14 +1207,34 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1159
1207
|
columnHeaderHeight: tableHeaderHeight,
|
|
1160
1208
|
scroll
|
|
1161
1209
|
});
|
|
1210
|
+
if (frozenBottomRowRange) {
|
|
1211
|
+
bodyCells.push(
|
|
1212
|
+
...buildBodyCells({
|
|
1213
|
+
model,
|
|
1214
|
+
rowSlots,
|
|
1215
|
+
columnSlots,
|
|
1216
|
+
rowRange: frozenBottomRowRange,
|
|
1217
|
+
columnRange: visibleColumnRange,
|
|
1218
|
+
rowHeight,
|
|
1219
|
+
columnWidths,
|
|
1220
|
+
columnOffsets,
|
|
1221
|
+
rowHeaderWidth,
|
|
1222
|
+
columnHeaderHeight: tableHeaderHeight,
|
|
1223
|
+
scroll: {
|
|
1224
|
+
left: scroll.left,
|
|
1225
|
+
top: frozenBottomRowRange.start * rowHeight - scrollableBodyHeight
|
|
1226
|
+
}
|
|
1227
|
+
})
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1162
1230
|
const scrollbar = computePivotScrollbarLayout({
|
|
1163
|
-
width,
|
|
1231
|
+
width: tableWidth + (hasVerticalScrollbar ? SCROLLBAR_THICKNESS2 : 0),
|
|
1164
1232
|
height: fittedHeight,
|
|
1165
1233
|
bodyTop: tableHeaderHeight,
|
|
1166
|
-
bodyHeight,
|
|
1234
|
+
bodyHeight: scrollableBodyHeight,
|
|
1167
1235
|
bodyWidth,
|
|
1168
1236
|
totalBodyWidth,
|
|
1169
|
-
totalBodyHeight,
|
|
1237
|
+
totalBodyHeight: totalScrollableBodyHeight,
|
|
1170
1238
|
scrollLeft: scroll.left,
|
|
1171
1239
|
scrollTop: scroll.top,
|
|
1172
1240
|
maxScrollLeft,
|
|
@@ -1218,6 +1286,7 @@ function buildPivotLayoutSnapshot(params) {
|
|
|
1218
1286
|
rowSlots,
|
|
1219
1287
|
columnSlots,
|
|
1220
1288
|
visibleRowRange,
|
|
1289
|
+
frozenBottomRowRange,
|
|
1221
1290
|
visibleColumnRange,
|
|
1222
1291
|
reportFilterCells,
|
|
1223
1292
|
rowFieldHeaderCells,
|
|
@@ -1352,6 +1421,13 @@ function createAxisSlot(node, indicatorKey) {
|
|
|
1352
1421
|
kind: node?.kind ?? "empty"
|
|
1353
1422
|
};
|
|
1354
1423
|
}
|
|
1424
|
+
function resolveFrozenBottomRowRange(slots) {
|
|
1425
|
+
let start = slots.length;
|
|
1426
|
+
while (start > 0 && slots[start - 1]?.kind === "grandTotal") {
|
|
1427
|
+
start -= 1;
|
|
1428
|
+
}
|
|
1429
|
+
return start < slots.length ? { start, end: slots.length } : null;
|
|
1430
|
+
}
|
|
1355
1431
|
function buildRowHeaderCells(params) {
|
|
1356
1432
|
const cells = [];
|
|
1357
1433
|
for (let rowIndex = params.range.start; rowIndex < params.range.end; rowIndex += 1) {
|
|
@@ -2552,6 +2628,7 @@ function createPivotDomHost(container) {
|
|
|
2552
2628
|
right: "0",
|
|
2553
2629
|
height: "100%",
|
|
2554
2630
|
overflow: "hidden",
|
|
2631
|
+
borderRadius: "8px",
|
|
2555
2632
|
touchAction: "none"
|
|
2556
2633
|
});
|
|
2557
2634
|
Object.assign(canvas.style, {
|
|
@@ -2614,8 +2691,8 @@ function resolvePivotSelectionRect(layout, selection) {
|
|
|
2614
2691
|
const kind = resolvePivotSelectionKind(selection);
|
|
2615
2692
|
const startX = kind === "row" || kind === "row-cell" ? layout.rowHeaderRect.x : layout.bodyRect.x + (layout.columnOffsets[indexes.startColumnIndex] ?? 0) - layout.scroll.left;
|
|
2616
2693
|
const endX = layout.bodyRect.x + (layout.columnOffsets[indexes.endColumnIndex] ?? 0) + (layout.columnWidths[indexes.endColumnIndex] ?? layout.columnWidth) - layout.scroll.left;
|
|
2617
|
-
const startY = kind === "column" ? layout.columnHeaderRect.y : layout
|
|
2618
|
-
const endY = layout
|
|
2694
|
+
const startY = kind === "column" ? layout.columnHeaderRect.y : resolvePivotRowTop(layout, indexes.startRowIndex);
|
|
2695
|
+
const endY = resolvePivotRowTop(layout, indexes.endRowIndex) + layout.rowHeight;
|
|
2619
2696
|
const clipRect = resolvePivotSelectionClipRect(layout, selection);
|
|
2620
2697
|
const visibleStartX = Math.max(startX, clipRect.x);
|
|
2621
2698
|
const visibleEndX = Math.min(endX, clipRect.x + clipRect.width);
|
|
@@ -2629,6 +2706,13 @@ function resolvePivotSelectionRect(layout, selection) {
|
|
|
2629
2706
|
height: visibleEndY - visibleStartY
|
|
2630
2707
|
};
|
|
2631
2708
|
}
|
|
2709
|
+
function resolvePivotRowTop(layout, rowIndex) {
|
|
2710
|
+
const frozenRange = layout.frozenBottomRowRange;
|
|
2711
|
+
if (frozenRange && rowIndex >= frozenRange.start && rowIndex < frozenRange.end) {
|
|
2712
|
+
return layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - rowIndex) * layout.rowHeight;
|
|
2713
|
+
}
|
|
2714
|
+
return layout.bodyRect.y + rowIndex * layout.rowHeight - layout.scroll.top;
|
|
2715
|
+
}
|
|
2632
2716
|
function resolvePivotSelectionClipRect(layout, selection) {
|
|
2633
2717
|
const kind = resolvePivotSelectionKind(selection);
|
|
2634
2718
|
if (kind === "row" || kind === "row-cell") {
|
|
@@ -2773,26 +2857,127 @@ var defaultPivotTableTheme = {
|
|
|
2773
2857
|
};
|
|
2774
2858
|
|
|
2775
2859
|
// src/rendering/draw-scrollbars.ts
|
|
2776
|
-
function drawPivotScrollbars(context, layout) {
|
|
2860
|
+
function drawPivotScrollbars(context, layout, borderColor, verticalFrame) {
|
|
2777
2861
|
context.save();
|
|
2862
|
+
if (layout.horizontal && verticalFrame) {
|
|
2863
|
+
drawHorizontalScrollbarMask(
|
|
2864
|
+
context,
|
|
2865
|
+
layout.horizontal,
|
|
2866
|
+
verticalFrame.bodyBackgroundColor
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
if (layout.vertical && verticalFrame) {
|
|
2870
|
+
drawVerticalScrollbarFrame(
|
|
2871
|
+
context,
|
|
2872
|
+
layout.vertical,
|
|
2873
|
+
verticalFrame,
|
|
2874
|
+
borderColor
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2778
2877
|
if (layout.horizontal) {
|
|
2779
|
-
drawAxis(context, layout.horizontal, "horizontal");
|
|
2878
|
+
drawAxis(context, layout.horizontal, "horizontal", borderColor);
|
|
2780
2879
|
}
|
|
2781
2880
|
if (layout.vertical) {
|
|
2782
|
-
drawAxis(context, layout.vertical, "vertical");
|
|
2881
|
+
drawAxis(context, layout.vertical, "vertical", borderColor);
|
|
2882
|
+
}
|
|
2883
|
+
if (layout.horizontal) {
|
|
2884
|
+
drawHorizontalScrollbarTopBorder(
|
|
2885
|
+
context,
|
|
2886
|
+
layout.horizontal,
|
|
2887
|
+
layout.vertical,
|
|
2888
|
+
borderColor
|
|
2889
|
+
);
|
|
2783
2890
|
}
|
|
2784
2891
|
context.restore();
|
|
2785
2892
|
}
|
|
2786
|
-
function
|
|
2787
|
-
|
|
2893
|
+
function drawHorizontalScrollbarMask(context, layout, backgroundColor) {
|
|
2894
|
+
const left = layout.decreaseButton.x;
|
|
2895
|
+
const right = layout.increaseButton.x + layout.increaseButton.width;
|
|
2896
|
+
const top = layout.decreaseButton.y;
|
|
2897
|
+
const height = layout.decreaseButton.height;
|
|
2898
|
+
if (right <= left || height <= 0) return;
|
|
2899
|
+
context.save();
|
|
2900
|
+
context.fillStyle = backgroundColor;
|
|
2901
|
+
context.fillRect(left, top, right - left, height);
|
|
2902
|
+
context.restore();
|
|
2903
|
+
}
|
|
2904
|
+
function drawHorizontalScrollbarTopBorder(context, horizontal, vertical, borderColor) {
|
|
2905
|
+
const left = Math.round(horizontal.decreaseButton.x) + 0.5;
|
|
2906
|
+
const horizontalRight = horizontal.increaseButton.x + horizontal.increaseButton.width;
|
|
2907
|
+
const verticalRight = vertical ? vertical.decreaseButton.x + vertical.decreaseButton.width : horizontalRight;
|
|
2908
|
+
const right = Math.round(Math.max(horizontalRight, verticalRight)) - 0.5;
|
|
2909
|
+
const top = Math.round(horizontal.decreaseButton.y) + 0.5;
|
|
2910
|
+
if (right <= left) return;
|
|
2911
|
+
context.save();
|
|
2912
|
+
context.strokeStyle = borderColor;
|
|
2913
|
+
context.lineWidth = 1;
|
|
2914
|
+
context.beginPath();
|
|
2915
|
+
context.moveTo(left, top);
|
|
2916
|
+
context.lineTo(right, top);
|
|
2917
|
+
context.stroke();
|
|
2918
|
+
context.restore();
|
|
2919
|
+
}
|
|
2920
|
+
function drawAxis(context, layout, axis, borderColor) {
|
|
2788
2921
|
drawButton(context, layout.decreaseButton, axis, "decrease");
|
|
2789
2922
|
drawButton(context, layout.increaseButton, axis, "increase");
|
|
2790
2923
|
drawThumb(context, layout.thumb);
|
|
2924
|
+
drawTrackBorder(context, layout.track, axis, borderColor);
|
|
2925
|
+
}
|
|
2926
|
+
function drawTrackBorder(context, rect, axis, borderColor) {
|
|
2927
|
+
const left = Math.round(rect.x) + 0.5;
|
|
2928
|
+
const right = Math.round(rect.x + rect.width) - 0.5;
|
|
2929
|
+
const top = Math.round(rect.y) + 0.5;
|
|
2930
|
+
const bottom = Math.round(rect.y + rect.height) - 0.5;
|
|
2931
|
+
if (right <= left || bottom <= top) return;
|
|
2932
|
+
context.save();
|
|
2933
|
+
context.strokeStyle = borderColor;
|
|
2934
|
+
context.lineWidth = 1;
|
|
2935
|
+
context.beginPath();
|
|
2936
|
+
if (axis === "horizontal") {
|
|
2937
|
+
context.moveTo(left, top);
|
|
2938
|
+
context.lineTo(right, top);
|
|
2939
|
+
} else {
|
|
2940
|
+
context.moveTo(left, top);
|
|
2941
|
+
context.lineTo(left, bottom);
|
|
2942
|
+
}
|
|
2943
|
+
context.stroke();
|
|
2944
|
+
context.restore();
|
|
2791
2945
|
}
|
|
2792
|
-
function
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2946
|
+
function drawVerticalScrollbarFrame(context, layout, frame, borderColor) {
|
|
2947
|
+
const left = Math.round(layout.decreaseButton.x) + 0.5;
|
|
2948
|
+
const top = Math.round(frame.top) + 0.5;
|
|
2949
|
+
const bottom = Math.round(frame.top + frame.height) - 0.5;
|
|
2950
|
+
const headerBottom = Math.round(frame.headerBottom) + 0.5;
|
|
2951
|
+
if (bottom <= top) return;
|
|
2952
|
+
context.save();
|
|
2953
|
+
context.fillStyle = frame.headerBackgroundColor;
|
|
2954
|
+
context.fillRect(
|
|
2955
|
+
layout.decreaseButton.x,
|
|
2956
|
+
frame.top,
|
|
2957
|
+
layout.decreaseButton.width,
|
|
2958
|
+
Math.max(frame.headerBottom - frame.top, 0)
|
|
2959
|
+
);
|
|
2960
|
+
context.fillStyle = frame.bodyBackgroundColor;
|
|
2961
|
+
context.fillRect(
|
|
2962
|
+
layout.decreaseButton.x,
|
|
2963
|
+
frame.headerBottom,
|
|
2964
|
+
layout.decreaseButton.width,
|
|
2965
|
+
Math.max(frame.top + frame.height - frame.headerBottom, 0)
|
|
2966
|
+
);
|
|
2967
|
+
context.strokeStyle = borderColor;
|
|
2968
|
+
context.lineWidth = 1;
|
|
2969
|
+
context.beginPath();
|
|
2970
|
+
context.moveTo(left, top);
|
|
2971
|
+
context.lineTo(left, bottom);
|
|
2972
|
+
context.stroke();
|
|
2973
|
+
if (headerBottom > top && headerBottom < bottom) {
|
|
2974
|
+
const right = Math.round(layout.decreaseButton.x + layout.decreaseButton.width) - 0.5;
|
|
2975
|
+
context.beginPath();
|
|
2976
|
+
context.moveTo(left, headerBottom);
|
|
2977
|
+
context.lineTo(right, headerBottom);
|
|
2978
|
+
context.stroke();
|
|
2979
|
+
}
|
|
2980
|
+
context.restore();
|
|
2796
2981
|
}
|
|
2797
2982
|
function drawThumb(context, rect) {
|
|
2798
2983
|
context.fillStyle = "rgba(100, 116, 139, 0.8)";
|
|
@@ -2800,28 +2985,25 @@ function drawThumb(context, rect) {
|
|
|
2800
2985
|
context.fill();
|
|
2801
2986
|
}
|
|
2802
2987
|
function drawButton(context, rect, axis, direction) {
|
|
2803
|
-
context.fillStyle = "rgba(226, 232, 240, 0.92)";
|
|
2804
|
-
context.strokeStyle = "rgba(148, 163, 184, 0.65)";
|
|
2805
|
-
context.lineWidth = 1;
|
|
2806
|
-
roundRect(context, rect, rect.height / 2);
|
|
2807
|
-
context.fill();
|
|
2808
|
-
context.stroke();
|
|
2809
2988
|
context.fillStyle = "rgba(71, 85, 105, 0.88)";
|
|
2810
2989
|
context.beginPath();
|
|
2811
2990
|
const centerX = rect.x + rect.width / 2;
|
|
2812
2991
|
const centerY = rect.y + rect.height / 2;
|
|
2992
|
+
const iconSize = Math.min(rect.width, rect.height) * 0.64;
|
|
2993
|
+
const directionOffset = iconSize * 0.4;
|
|
2994
|
+
const crossAxisOffset = iconSize / 2;
|
|
2813
2995
|
if (axis === "horizontal") {
|
|
2814
|
-
const tipX = centerX + (direction === "decrease" ? -1 : 1) *
|
|
2815
|
-
const tailX = centerX + (direction === "decrease" ? 1 : -1) *
|
|
2996
|
+
const tipX = centerX + (direction === "decrease" ? -1 : 1) * directionOffset;
|
|
2997
|
+
const tailX = centerX + (direction === "decrease" ? 1 : -1) * directionOffset;
|
|
2816
2998
|
context.moveTo(tipX, centerY);
|
|
2817
|
-
context.lineTo(tailX, centerY -
|
|
2818
|
-
context.lineTo(tailX, centerY +
|
|
2999
|
+
context.lineTo(tailX, centerY - crossAxisOffset);
|
|
3000
|
+
context.lineTo(tailX, centerY + crossAxisOffset);
|
|
2819
3001
|
} else {
|
|
2820
|
-
const tipY = centerY + (direction === "decrease" ? -1 : 1) *
|
|
2821
|
-
const tailY = centerY + (direction === "decrease" ? 1 : -1) *
|
|
3002
|
+
const tipY = centerY + (direction === "decrease" ? -1 : 1) * directionOffset;
|
|
3003
|
+
const tailY = centerY + (direction === "decrease" ? 1 : -1) * directionOffset;
|
|
2822
3004
|
context.moveTo(centerX, tipY);
|
|
2823
|
-
context.lineTo(centerX -
|
|
2824
|
-
context.lineTo(centerX +
|
|
3005
|
+
context.lineTo(centerX - crossAxisOffset, tailY);
|
|
3006
|
+
context.lineTo(centerX + crossAxisOffset, tailY);
|
|
2825
3007
|
}
|
|
2826
3008
|
context.closePath();
|
|
2827
3009
|
context.fill();
|
|
@@ -2874,25 +3056,22 @@ function drawPivotGridRect(context, rect, color, options) {
|
|
|
2874
3056
|
context.stroke();
|
|
2875
3057
|
context.restore();
|
|
2876
3058
|
}
|
|
2877
|
-
function
|
|
2878
|
-
|
|
2879
|
-
context.
|
|
2880
|
-
context.strokeStyle = color;
|
|
2881
|
-
context.lineWidth = 1;
|
|
2882
|
-
context.beginPath();
|
|
2883
|
-
context.moveTo(x, rect.y);
|
|
2884
|
-
context.lineTo(x, rect.y + rect.height);
|
|
2885
|
-
context.stroke();
|
|
2886
|
-
context.restore();
|
|
3059
|
+
function clipPivotViewport(context, rect, radius = 8) {
|
|
3060
|
+
appendRoundedRectPath(context, rect, radius);
|
|
3061
|
+
context.clip();
|
|
2887
3062
|
}
|
|
2888
|
-
function
|
|
2889
|
-
const
|
|
3063
|
+
function drawPivotViewportBorder(context, rect, color, radius = 8) {
|
|
3064
|
+
const borderRect = {
|
|
3065
|
+
x: rect.x + 0.5,
|
|
3066
|
+
y: rect.y + 0.5,
|
|
3067
|
+
width: Math.max(rect.width - 1, 0),
|
|
3068
|
+
height: Math.max(rect.height - 1, 0)
|
|
3069
|
+
};
|
|
3070
|
+
if (borderRect.width <= 0 || borderRect.height <= 0) return;
|
|
2890
3071
|
context.save();
|
|
2891
3072
|
context.strokeStyle = color;
|
|
2892
3073
|
context.lineWidth = 1;
|
|
2893
|
-
context
|
|
2894
|
-
context.moveTo(rect.x, y);
|
|
2895
|
-
context.lineTo(rect.x + rect.width, y);
|
|
3074
|
+
appendRoundedRectPath(context, borderRect, radius);
|
|
2896
3075
|
context.stroke();
|
|
2897
3076
|
context.restore();
|
|
2898
3077
|
}
|
|
@@ -2910,6 +3089,21 @@ function drawPivotHeaderBoundaries(context, options, color) {
|
|
|
2910
3089
|
context.stroke();
|
|
2911
3090
|
context.restore();
|
|
2912
3091
|
}
|
|
3092
|
+
function appendRoundedRectPath(context, rect, radius) {
|
|
3093
|
+
const nextRadius = Math.max(
|
|
3094
|
+
Math.min(radius, rect.width / 2, rect.height / 2),
|
|
3095
|
+
0
|
|
3096
|
+
);
|
|
3097
|
+
const right = rect.x + rect.width;
|
|
3098
|
+
const bottom = rect.y + rect.height;
|
|
3099
|
+
context.beginPath();
|
|
3100
|
+
context.moveTo(rect.x + nextRadius, rect.y);
|
|
3101
|
+
context.arcTo(right, rect.y, right, bottom, nextRadius);
|
|
3102
|
+
context.arcTo(right, bottom, rect.x, bottom, nextRadius);
|
|
3103
|
+
context.arcTo(rect.x, bottom, rect.x, rect.y, nextRadius);
|
|
3104
|
+
context.arcTo(rect.x, rect.y, right, rect.y, nextRadius);
|
|
3105
|
+
context.closePath();
|
|
3106
|
+
}
|
|
2913
3107
|
|
|
2914
3108
|
// src/domain/layout/sticky-row-header.ts
|
|
2915
3109
|
function resolveStickyRowHeaderContentRect(params) {
|
|
@@ -2946,7 +3140,15 @@ function renderPivotTable(params) {
|
|
|
2946
3140
|
activeNodePathKey
|
|
2947
3141
|
);
|
|
2948
3142
|
const areaSelection = isPivotAreaSelection(layout, params.selection);
|
|
3143
|
+
const viewportWidth = resolvePivotViewportWidth(layout);
|
|
2949
3144
|
context.clearRect(0, 0, layout.width, layout.height);
|
|
3145
|
+
context.save();
|
|
3146
|
+
clipPivotViewport(context, {
|
|
3147
|
+
x: 0,
|
|
3148
|
+
y: 0,
|
|
3149
|
+
width: viewportWidth,
|
|
3150
|
+
height: layout.height
|
|
3151
|
+
});
|
|
2950
3152
|
context.font = `${theme.fontSize}px ${theme.fontFamily}`;
|
|
2951
3153
|
context.textBaseline = "middle";
|
|
2952
3154
|
withClip(context, layout.reportFilterRect, () => {
|
|
@@ -3060,17 +3262,13 @@ function renderPivotTable(params) {
|
|
|
3060
3262
|
theme.borderColor
|
|
3061
3263
|
);
|
|
3062
3264
|
if (options.debug) drawDebugOverlay(context, layout, theme);
|
|
3063
|
-
drawPivotScrollbars(context, layout.scrollbar
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
theme.
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
context,
|
|
3071
|
-
{ x: 0, y: 0, width: layout.tableWidth, height: contentHeight },
|
|
3072
|
-
theme.borderColor
|
|
3073
|
-
);
|
|
3265
|
+
drawPivotScrollbars(context, layout.scrollbar, theme.borderColor, {
|
|
3266
|
+
top: 0,
|
|
3267
|
+
height: contentHeight,
|
|
3268
|
+
headerBottom: layout.bodyRect.y,
|
|
3269
|
+
headerBackgroundColor: theme.columnHeaderBackgroundColor,
|
|
3270
|
+
bodyBackgroundColor: theme.backgroundColor
|
|
3271
|
+
});
|
|
3074
3272
|
const selectionRect = resolvePivotSelectionRect(layout, params.selection);
|
|
3075
3273
|
if (selectionRect) {
|
|
3076
3274
|
const selectionClipRect = resolvePivotSelectionClipRect(
|
|
@@ -3088,11 +3286,22 @@ function renderPivotTable(params) {
|
|
|
3088
3286
|
if (params.resizeGuideX != null) {
|
|
3089
3287
|
drawPivotResizeGuide(context, params.resizeGuideX, contentHeight);
|
|
3090
3288
|
}
|
|
3289
|
+
context.restore();
|
|
3290
|
+
drawPivotViewportBorder(
|
|
3291
|
+
context,
|
|
3292
|
+
{ x: 0, y: 0, width: viewportWidth, height: contentHeight },
|
|
3293
|
+
theme.borderColor
|
|
3294
|
+
);
|
|
3091
3295
|
return {
|
|
3092
3296
|
headerCellCount: layout.reportFilterCells.length + layout.rowFieldHeaderCells.length + layout.rowHeaderCells.length + layout.columnHeaderCells.length,
|
|
3093
3297
|
bodyCellCount: layout.bodyCells.length
|
|
3094
3298
|
};
|
|
3095
3299
|
}
|
|
3300
|
+
function resolvePivotViewportWidth(layout) {
|
|
3301
|
+
const horizontalRight = layout.scrollbar.horizontal ? layout.scrollbar.horizontal.increaseButton.x + layout.scrollbar.horizontal.increaseButton.width : 0;
|
|
3302
|
+
const verticalRight = layout.scrollbar.vertical ? layout.scrollbar.vertical.decreaseButton.x + layout.scrollbar.vertical.decreaseButton.width : 0;
|
|
3303
|
+
return Math.max(layout.tableWidth, horizontalRight, verticalRight);
|
|
3304
|
+
}
|
|
3096
3305
|
function drawPivotResizeGuide(context, x, height) {
|
|
3097
3306
|
context.save();
|
|
3098
3307
|
context.strokeStyle = "#1677ff";
|
|
@@ -3578,7 +3787,7 @@ function hitTestPivotLayout(layout, x, y) {
|
|
|
3578
3787
|
onSortIcon: false
|
|
3579
3788
|
};
|
|
3580
3789
|
}
|
|
3581
|
-
const bodyCell = layout.bodyCells
|
|
3790
|
+
const bodyCell = findLastContaining(layout.bodyCells, x, y);
|
|
3582
3791
|
if (bodyCell) {
|
|
3583
3792
|
return {
|
|
3584
3793
|
region: "body",
|
|
@@ -3588,9 +3797,7 @@ function hitTestPivotLayout(layout, x, y) {
|
|
|
3588
3797
|
onSortIcon: false
|
|
3589
3798
|
};
|
|
3590
3799
|
}
|
|
3591
|
-
const rowHeaderCell = layout.rowHeaderCells
|
|
3592
|
-
(cell) => contains(cell.rect, x, y)
|
|
3593
|
-
);
|
|
3800
|
+
const rowHeaderCell = findLastContaining(layout.rowHeaderCells, x, y);
|
|
3594
3801
|
if (rowHeaderCell) {
|
|
3595
3802
|
return {
|
|
3596
3803
|
region: "rowHeader",
|
|
@@ -3643,6 +3850,13 @@ function isSortIconHit(cell, x) {
|
|
|
3643
3850
|
function contains(rect, x, y) {
|
|
3644
3851
|
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
3645
3852
|
}
|
|
3853
|
+
function findLastContaining(cells, x, y) {
|
|
3854
|
+
for (let index = cells.length - 1; index >= 0; index -= 1) {
|
|
3855
|
+
const cell = cells[index];
|
|
3856
|
+
if (cell && contains(cell.rect, x, y)) return cell;
|
|
3857
|
+
}
|
|
3858
|
+
return void 0;
|
|
3859
|
+
}
|
|
3646
3860
|
|
|
3647
3861
|
// src/interaction/text-selection.ts
|
|
3648
3862
|
function resolvePivotTextOffset(params) {
|
|
@@ -3841,6 +4055,16 @@ function hitTestPivotScrollbar(layout, x, y) {
|
|
|
3841
4055
|
}
|
|
3842
4056
|
return null;
|
|
3843
4057
|
}
|
|
4058
|
+
function resolvePivotScrollbarTrackScroll(params) {
|
|
4059
|
+
const movableLength = Math.max(params.trackLength - params.thumbLength, 0);
|
|
4060
|
+
if (movableLength === 0) return 0;
|
|
4061
|
+
const thumbStart = clamp(
|
|
4062
|
+
params.pointerValue - params.thumbLength / 2,
|
|
4063
|
+
params.trackStart,
|
|
4064
|
+
params.trackStart + movableLength
|
|
4065
|
+
);
|
|
4066
|
+
return (thumbStart - params.trackStart) / movableLength * params.maxScroll;
|
|
4067
|
+
}
|
|
3844
4068
|
function resolveRole(layout, x, y) {
|
|
3845
4069
|
if (isPointInRect(x, y, layout.thumb)) return "thumb";
|
|
3846
4070
|
if (isPointInRect(x, y, layout.decreaseButton)) return "decrease-button";
|
|
@@ -3879,6 +4103,16 @@ function createAddress(rowSlot, columnSlot, indicatorKey) {
|
|
|
3879
4103
|
// src/domain/interaction/row-selection.ts
|
|
3880
4104
|
function resolvePivotRowIndexAtY(layout, y) {
|
|
3881
4105
|
if (layout.rowSlots.length === 0 || layout.rowHeight <= 0) return null;
|
|
4106
|
+
const frozenRange = layout.frozenBottomRowRange;
|
|
4107
|
+
if (frozenRange) {
|
|
4108
|
+
const frozenTop = layout.bodyRect.y + layout.bodyRect.height - (frozenRange.end - frozenRange.start) * layout.rowHeight;
|
|
4109
|
+
if (y >= frozenTop) {
|
|
4110
|
+
return Math.min(
|
|
4111
|
+
frozenRange.start + Math.floor((y - frozenTop) / layout.rowHeight),
|
|
4112
|
+
frozenRange.end - 1
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
3882
4116
|
const index = Math.floor(
|
|
3883
4117
|
(y - layout.bodyRect.y + layout.scroll.top) / layout.rowHeight
|
|
3884
4118
|
);
|
|
@@ -3997,6 +4231,8 @@ var PivotTable = class {
|
|
|
3997
4231
|
__publicField(this, "wheelScrollTarget", null);
|
|
3998
4232
|
__publicField(this, "wheelScrollFrameId", null);
|
|
3999
4233
|
__publicField(this, "wheelScrollLastTime", 0);
|
|
4234
|
+
__publicField(this, "continuousScrollTimer", null);
|
|
4235
|
+
__publicField(this, "continuousScrollPointerId", null);
|
|
4000
4236
|
__publicField(this, "resizeSession", null);
|
|
4001
4237
|
__publicField(this, "resizeGuideX", null);
|
|
4002
4238
|
__publicField(this, "suppressNextClick", false);
|
|
@@ -4075,6 +4311,7 @@ var PivotTable = class {
|
|
|
4075
4311
|
this.scheduleRender();
|
|
4076
4312
|
});
|
|
4077
4313
|
__publicField(this, "handleClick", (event) => {
|
|
4314
|
+
this.options.ui?.onContextMenuRequest?.(null);
|
|
4078
4315
|
if (this.suppressNextClick) {
|
|
4079
4316
|
this.suppressNextClick = false;
|
|
4080
4317
|
return;
|
|
@@ -4393,6 +4630,8 @@ var PivotTable = class {
|
|
|
4393
4630
|
});
|
|
4394
4631
|
__publicField(this, "handlePointerUp", (event) => {
|
|
4395
4632
|
if (!this.host) return;
|
|
4633
|
+
const endedContinuousScroll = this.continuousScrollPointerId === event.pointerId;
|
|
4634
|
+
if (endedContinuousScroll) this.stopContinuousScrollbarScroll();
|
|
4396
4635
|
const endedCellSelection = this.cellSelectionDrag?.pointerId === event.pointerId;
|
|
4397
4636
|
if (endedCellSelection) this.cellSelectionDrag = null;
|
|
4398
4637
|
if (this.textSelectionDrag?.pointerId === event.pointerId) {
|
|
@@ -4411,7 +4650,7 @@ var PivotTable = class {
|
|
|
4411
4650
|
if (this.scrollbarDrag?.pointerId === event.pointerId) {
|
|
4412
4651
|
this.scrollbarDrag = null;
|
|
4413
4652
|
}
|
|
4414
|
-
if (!this.resizeSession && !this.scrollbarDrag && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
|
|
4653
|
+
if (!this.resizeSession && !this.scrollbarDrag && !endedContinuousScroll && !endedCellSelection && !endedRowHeaderSelection && !this.host.root.hasPointerCapture(event.pointerId))
|
|
4415
4654
|
return;
|
|
4416
4655
|
if (this.host.root.hasPointerCapture(event.pointerId)) {
|
|
4417
4656
|
this.host.root.releasePointerCapture(event.pointerId);
|
|
@@ -4584,11 +4823,16 @@ var PivotTable = class {
|
|
|
4584
4823
|
(slot) => (slot.nodeId ?? "") === address.columnNodeId && (!slot.indicatorKey || slot.indicatorKey === address.indicatorKey)
|
|
4585
4824
|
);
|
|
4586
4825
|
if (rowIndex < 0 || columnIndex < 0) return false;
|
|
4587
|
-
const
|
|
4826
|
+
const frozenRange = this.layout.frozenBottomRowRange;
|
|
4827
|
+
const frozenBottomHeight = frozenRange ? (frozenRange.end - frozenRange.start) * this.layout.rowHeight : 0;
|
|
4828
|
+
const top = frozenRange && rowIndex >= frozenRange.start ? this.scroll.top : resolveVisibleOffset({
|
|
4588
4829
|
current: this.scroll.top,
|
|
4589
4830
|
itemStart: rowIndex * this.layout.rowHeight,
|
|
4590
4831
|
itemSize: this.layout.rowHeight,
|
|
4591
|
-
viewportSize:
|
|
4832
|
+
viewportSize: Math.max(
|
|
4833
|
+
this.layout.bodyRect.height - frozenBottomHeight,
|
|
4834
|
+
0
|
|
4835
|
+
)
|
|
4592
4836
|
});
|
|
4593
4837
|
const left = resolveVisibleOffset({
|
|
4594
4838
|
current: this.scroll.left,
|
|
@@ -4652,6 +4896,7 @@ var PivotTable = class {
|
|
|
4652
4896
|
if (this.frameId !== null) cancelAnimationFrame(this.frameId);
|
|
4653
4897
|
this.frameId = null;
|
|
4654
4898
|
this.stopWheelScrollSmoothing();
|
|
4899
|
+
this.stopContinuousScrollbarScroll();
|
|
4655
4900
|
this.stopResizeObserver?.();
|
|
4656
4901
|
this.stopResizeObserver = null;
|
|
4657
4902
|
this.host?.root.removeEventListener("pointermove", this.handlePointerMove);
|
|
@@ -5020,17 +5265,27 @@ var PivotTable = class {
|
|
|
5020
5265
|
event.preventDefault();
|
|
5021
5266
|
this.suppressNextClick = true;
|
|
5022
5267
|
if (hit.role === "decrease-button" || hit.role === "increase-button") {
|
|
5023
|
-
this.
|
|
5268
|
+
this.startContinuousScrollbarScroll(
|
|
5269
|
+
hit.axis,
|
|
5270
|
+
hit.role === "decrease-button" ? -1 : 1,
|
|
5271
|
+
event.pointerId
|
|
5272
|
+
);
|
|
5273
|
+
this.host.root.setPointerCapture(event.pointerId);
|
|
5024
5274
|
return;
|
|
5025
5275
|
}
|
|
5026
5276
|
const rootRect = this.host.root.getBoundingClientRect();
|
|
5027
5277
|
const pointer = hit.axis === "horizontal" ? event.clientX - rootRect.left : event.clientY - rootRect.top;
|
|
5028
5278
|
if (hit.role === "track") {
|
|
5029
|
-
const
|
|
5030
|
-
|
|
5279
|
+
const nextScroll = resolvePivotScrollbarTrackScroll({
|
|
5280
|
+
pointerValue: pointer,
|
|
5281
|
+
trackStart: hit.axis === "horizontal" ? axisLayout.track.x : axisLayout.track.y,
|
|
5282
|
+
trackLength: hit.axis === "horizontal" ? axisLayout.track.width : axisLayout.track.height,
|
|
5283
|
+
thumbLength: hit.axis === "horizontal" ? axisLayout.thumb.width : axisLayout.thumb.height,
|
|
5284
|
+
maxScroll: hit.axis === "horizontal" ? this.layout.viewport.maxScrollLeft : this.layout.viewport.maxScrollTop
|
|
5285
|
+
});
|
|
5031
5286
|
this.applyScrollPosition(
|
|
5032
|
-
|
|
5033
|
-
|
|
5287
|
+
hit.axis === "horizontal" ? nextScroll : this.scroll.left,
|
|
5288
|
+
hit.axis === "vertical" ? nextScroll : this.scroll.top
|
|
5034
5289
|
);
|
|
5035
5290
|
return;
|
|
5036
5291
|
}
|
|
@@ -5074,6 +5329,21 @@ var PivotTable = class {
|
|
|
5074
5329
|
this.scroll.top + (axis === "vertical" ? direction * this.layout.rowHeight : 0)
|
|
5075
5330
|
);
|
|
5076
5331
|
}
|
|
5332
|
+
startContinuousScrollbarScroll(axis, direction, pointerId) {
|
|
5333
|
+
this.stopContinuousScrollbarScroll();
|
|
5334
|
+
this.continuousScrollPointerId = pointerId;
|
|
5335
|
+
this.stepScrollbar(axis, direction);
|
|
5336
|
+
this.continuousScrollTimer = setInterval(() => {
|
|
5337
|
+
this.stepScrollbar(axis, direction);
|
|
5338
|
+
}, 80);
|
|
5339
|
+
}
|
|
5340
|
+
stopContinuousScrollbarScroll() {
|
|
5341
|
+
if (this.continuousScrollTimer !== null) {
|
|
5342
|
+
clearInterval(this.continuousScrollTimer);
|
|
5343
|
+
this.continuousScrollTimer = null;
|
|
5344
|
+
}
|
|
5345
|
+
this.continuousScrollPointerId = null;
|
|
5346
|
+
}
|
|
5077
5347
|
applyScrollPosition(left, top) {
|
|
5078
5348
|
if (!this.layout) return;
|
|
5079
5349
|
const next = {
|