@lensui/lens-table 0.1.3 → 0.1.5
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 +28 -2
- package/dist/index.cjs +1087 -209
- package/dist/index.css +414 -4
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1087 -209
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -57,6 +57,7 @@ var ZH_CN_GRID_LABELS = {
|
|
|
57
57
|
sortDesc: "\u5F53\u524D\uFF1A\u964D\u5E8F",
|
|
58
58
|
sortBoth: "\u5347\u5E8F / \u964D\u5E8F",
|
|
59
59
|
dragColumn: "\u62D6\u52A8\u5217\u6392\u5E8F",
|
|
60
|
+
revealSelectedCell: "\u5B9A\u4F4D\u5230\u9009\u4E2D\u5355\u5143\u683C",
|
|
60
61
|
previousYear: "\u4E0A\u4E00\u5E74",
|
|
61
62
|
nextYear: "\u4E0B\u4E00\u5E74",
|
|
62
63
|
previousMonth: "\u4E0A\u4E2A\u6708",
|
|
@@ -125,6 +126,7 @@ var EN_US_GRID_LABELS = {
|
|
|
125
126
|
sortDesc: "Current: descending",
|
|
126
127
|
sortBoth: "Ascending / descending",
|
|
127
128
|
dragColumn: "Drag column to reorder",
|
|
129
|
+
revealSelectedCell: "Reveal selected cell",
|
|
128
130
|
previousYear: "Previous year",
|
|
129
131
|
nextYear: "Next year",
|
|
130
132
|
previousMonth: "Previous month",
|
|
@@ -333,7 +335,6 @@ function LoadingSpinnerIcon() {
|
|
|
333
335
|
// src/components/editors/ChoiceEditor.tsx
|
|
334
336
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
335
337
|
function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
|
|
336
|
-
const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
|
|
337
338
|
const collection = (0, import_select.createListCollection)({
|
|
338
339
|
items: options,
|
|
339
340
|
itemToValue: (item) => item.value,
|
|
@@ -343,7 +344,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
|
|
|
343
344
|
import_select.Select.Root,
|
|
344
345
|
{
|
|
345
346
|
className: "rvg-choice-editor",
|
|
346
|
-
style
|
|
347
|
+
style,
|
|
347
348
|
collection,
|
|
348
349
|
value: value ? [value] : [],
|
|
349
350
|
defaultOpen: true,
|
|
@@ -893,6 +894,8 @@ function ellipsizeText(context, text, maxWidth) {
|
|
|
893
894
|
}
|
|
894
895
|
function paintGrid(options) {
|
|
895
896
|
const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, headerHeight, fixedHeader, verticalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview } = options;
|
|
897
|
+
const headerLeafTop = options.headerLeafTop ?? 0;
|
|
898
|
+
const headerLeafHeight = options.headerLeafHeight ?? headerHeight;
|
|
896
899
|
const colors = { ...COLORS, ...options.colors };
|
|
897
900
|
const bodyTop = options.bodyTop ?? headerHeight;
|
|
898
901
|
const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
|
|
@@ -909,9 +912,17 @@ function paintGrid(options) {
|
|
|
909
912
|
ctx.fillRect(0, 0, width, height);
|
|
910
913
|
ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
911
914
|
ctx.textBaseline = "middle";
|
|
912
|
-
const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ?
|
|
915
|
+
const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
|
|
913
916
|
const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
|
|
914
917
|
const rightFixedLeft = width - rightFixedWidth;
|
|
918
|
+
const rightFixedBoundaryInset = rightFixedWidth > 0 && !verticalBorderless ? 1 : 0;
|
|
919
|
+
const leftOffsets = /* @__PURE__ */ new Map();
|
|
920
|
+
let leftOffset = 0;
|
|
921
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
922
|
+
if (columns[index].fixed !== "left") continue;
|
|
923
|
+
leftOffsets.set(index, leftOffset);
|
|
924
|
+
leftOffset += metrics[index].width;
|
|
925
|
+
}
|
|
915
926
|
const rightOffsets = /* @__PURE__ */ new Map();
|
|
916
927
|
let rightOffset = 0;
|
|
917
928
|
for (let index = columns.length - 1; index >= 0; index -= 1) {
|
|
@@ -919,8 +930,14 @@ function paintGrid(options) {
|
|
|
919
930
|
rightOffsets.set(index, rightOffset);
|
|
920
931
|
rightOffset += metrics[index].width;
|
|
921
932
|
}
|
|
933
|
+
const scrollableLefts = /* @__PURE__ */ new Map();
|
|
934
|
+
let scrollableLeft = leftFixedWidth;
|
|
935
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
936
|
+
scrollableLefts.set(index, scrollableLeft);
|
|
937
|
+
if (columns[index].fixed === void 0) scrollableLeft += metrics[index].width;
|
|
938
|
+
}
|
|
922
939
|
const getColumnX = (index) => {
|
|
923
|
-
return columns[index].fixed === "left" ?
|
|
940
|
+
return columns[index].fixed === "left" ? leftOffsets.get(index) ?? 0 : columns[index].fixed === "right" ? width - (rightOffsets.get(index) ?? 0) - metrics[index].width : (scrollableLefts.get(index) ?? leftFixedWidth) - scrollLeft;
|
|
924
941
|
};
|
|
925
942
|
const paintBodyColumns = (layer) => {
|
|
926
943
|
const rowStart = Math.max(0, range.rowStart - Math.max(0, options.maxRowSpan - 1));
|
|
@@ -1070,7 +1087,7 @@ function paintGrid(options) {
|
|
|
1070
1087
|
if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
|
|
1071
1088
|
ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
|
|
1072
1089
|
const column = columns[columnIndex];
|
|
1073
|
-
if (!column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
|
|
1090
|
+
if (headerLeafTop === 0 && !column.rowSelection && !column.rowDragHandle && !column.rowNumber && !column.renderHeader && column.title) {
|
|
1074
1091
|
ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
1075
1092
|
const titleWidth = ctx.measureText(column.title).width;
|
|
1076
1093
|
const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
|
|
@@ -1087,11 +1104,11 @@ function paintGrid(options) {
|
|
|
1087
1104
|
const textX = titleAlign === "right" ? x + contentWidth - padding : titleAlign === "center" ? x + centeredTextX : x + padding;
|
|
1088
1105
|
ctx.save();
|
|
1089
1106
|
ctx.beginPath();
|
|
1090
|
-
ctx.rect(x + 2, headerY + 1, Math.max(0, clipWidth - 4),
|
|
1107
|
+
ctx.rect(x + 2, headerY + headerLeafTop + 1, Math.max(0, clipWidth - 4), headerLeafHeight - 2);
|
|
1091
1108
|
ctx.clip();
|
|
1092
1109
|
ctx.fillStyle = colors.muted;
|
|
1093
1110
|
ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
|
|
1094
|
-
ctx.fillText(title, textX, headerY +
|
|
1111
|
+
ctx.fillText(title, textX, headerY + headerLeafTop + headerLeafHeight / 2);
|
|
1095
1112
|
ctx.restore();
|
|
1096
1113
|
}
|
|
1097
1114
|
}
|
|
@@ -1099,22 +1116,6 @@ function paintGrid(options) {
|
|
|
1099
1116
|
paintHeaderColumns("scroll");
|
|
1100
1117
|
paintHeaderColumns("left");
|
|
1101
1118
|
paintHeaderColumns("right");
|
|
1102
|
-
if (leftFixedWidth > 0 && scrollLeft > 0) {
|
|
1103
|
-
const shadowWidth = 10;
|
|
1104
|
-
const shadow = ctx.createLinearGradient(leftFixedWidth, 0, leftFixedWidth + shadowWidth, 0);
|
|
1105
|
-
shadow.addColorStop(0, "rgba(5, 5, 5, 0.06)");
|
|
1106
|
-
shadow.addColorStop(1, "rgba(5, 5, 5, 0)");
|
|
1107
|
-
ctx.fillStyle = shadow;
|
|
1108
|
-
ctx.fillRect(leftFixedWidth, 0, shadowWidth, height);
|
|
1109
|
-
}
|
|
1110
|
-
if (rightFixedWidth > 0 && scrollLeft < (metrics[metrics.length - 1]?.right ?? 0) - width) {
|
|
1111
|
-
const shadowWidth = 10;
|
|
1112
|
-
const shadow = ctx.createLinearGradient(rightFixedLeft - shadowWidth, 0, rightFixedLeft, 0);
|
|
1113
|
-
shadow.addColorStop(0, "rgba(5, 5, 5, 0)");
|
|
1114
|
-
shadow.addColorStop(1, "rgba(5, 5, 5, 0.06)");
|
|
1115
|
-
ctx.fillStyle = shadow;
|
|
1116
|
-
ctx.fillRect(rightFixedLeft - shadowWidth, 0, shadowWidth, height);
|
|
1117
|
-
}
|
|
1118
1119
|
if (rightFixedWidth > 0 && !verticalBorderless) {
|
|
1119
1120
|
ctx.fillStyle = colors.grid;
|
|
1120
1121
|
ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
|
|
@@ -1138,22 +1139,22 @@ function paintGrid(options) {
|
|
|
1138
1139
|
const markerBottom = Math.min(bottom, height);
|
|
1139
1140
|
const startX = getColumnX(columnStart);
|
|
1140
1141
|
const endX = getColumnX(columnEnd) + metrics[columnEnd].width;
|
|
1141
|
-
const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth
|
|
1142
|
+
const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth, startX);
|
|
1142
1143
|
const visibleRight = columns[columnEnd].fixed === "right" ? Math.min(width, endX) : Math.min(rightFixedLeft, endX);
|
|
1143
1144
|
ctx.fillStyle = colors.selection;
|
|
1144
1145
|
if (markerBottom > markerTop) {
|
|
1145
1146
|
if (columns[columnStart].fixed === void 0 && startX < leftFixedWidth) {
|
|
1146
|
-
ctx.fillRect(Math.max(0, leftFixedWidth
|
|
1147
|
+
ctx.fillRect(Math.max(0, leftFixedWidth), markerTop, 2, markerBottom - markerTop);
|
|
1147
1148
|
}
|
|
1148
1149
|
if (columns[columnEnd].fixed === void 0 && endX > rightFixedLeft) {
|
|
1149
|
-
ctx.fillRect(Math.max(0, rightFixedLeft -
|
|
1150
|
+
ctx.fillRect(Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset), markerTop, 2, markerBottom - markerTop);
|
|
1150
1151
|
}
|
|
1151
1152
|
}
|
|
1152
1153
|
if (visibleRight > visibleLeft) {
|
|
1153
1154
|
if (top < clipTop) ctx.fillRect(visibleLeft, clipTop, visibleRight - visibleLeft, 2);
|
|
1154
1155
|
if (bottom > height) ctx.fillRect(visibleLeft, height - 2, visibleRight - visibleLeft, 2);
|
|
1155
1156
|
} else if (top < clipTop || bottom > height) {
|
|
1156
|
-
const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth
|
|
1157
|
+
const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth) : Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset);
|
|
1157
1158
|
ctx.fillRect(markerX, top < clipTop ? clipTop : height - 2, 2, 2);
|
|
1158
1159
|
}
|
|
1159
1160
|
}
|
|
@@ -1175,15 +1176,15 @@ function paintGrid(options) {
|
|
|
1175
1176
|
const visibleRight = x + selectionWidth;
|
|
1176
1177
|
const visibleBottom = y + selectionHeight;
|
|
1177
1178
|
const selectionTop = fixedHeader ? bodyTop : 0;
|
|
1178
|
-
const selectionClipTop =
|
|
1179
|
+
const selectionClipTop = selectionTop;
|
|
1179
1180
|
const hiddenAboveHeader = visibleBottom <= selectionTop;
|
|
1180
1181
|
const hiddenBelowViewport = y >= height;
|
|
1181
1182
|
const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
|
|
1182
1183
|
const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
|
|
1183
|
-
const leftMarkerX = Math.max(0, leftFixedWidth
|
|
1184
|
+
const leftMarkerX = Math.max(0, leftFixedWidth);
|
|
1184
1185
|
if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
|
|
1185
1186
|
const markerSize = 2;
|
|
1186
|
-
const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
|
|
1187
|
+
const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
|
|
1187
1188
|
const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
|
|
1188
1189
|
ctx.fillStyle = colors.selection;
|
|
1189
1190
|
ctx.fillRect(markerX, markerY, markerSize, markerSize);
|
|
@@ -1191,7 +1192,7 @@ function paintGrid(options) {
|
|
|
1191
1192
|
}
|
|
1192
1193
|
ctx.save();
|
|
1193
1194
|
ctx.beginPath();
|
|
1194
|
-
const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth
|
|
1195
|
+
const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
|
|
1195
1196
|
const clipRight = Math.min(width, selectionAreaRight + 1);
|
|
1196
1197
|
ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
|
|
1197
1198
|
ctx.clip();
|
|
@@ -1209,14 +1210,14 @@ function paintGrid(options) {
|
|
|
1209
1210
|
if (hiddenBehindLeft || hiddenBehindRight) {
|
|
1210
1211
|
const markerTop = Math.max(y - borderWidth, selectionTop);
|
|
1211
1212
|
const markerBottom = Math.min(y + selectionHeight + 1, height);
|
|
1212
|
-
const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
|
|
1213
|
+
const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
|
|
1213
1214
|
ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
|
|
1214
1215
|
}
|
|
1215
1216
|
if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
|
|
1216
1217
|
ctx.restore();
|
|
1217
1218
|
return;
|
|
1218
1219
|
}
|
|
1219
|
-
const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft
|
|
1220
|
+
const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
|
|
1220
1221
|
const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
|
|
1221
1222
|
const borderTop = Math.max(y - borderWidth, selectionClipTop);
|
|
1222
1223
|
const borderBottom = Math.min(y + selectionHeight - 1, height - borderWidth);
|
|
@@ -1266,6 +1267,45 @@ var getRowNumberColumnWidth = (rowCount) => {
|
|
|
1266
1267
|
const digits = String(Math.max(1, rowCount)).length;
|
|
1267
1268
|
return digits <= 4 ? 44 : 44 + (digits - 4) * 8;
|
|
1268
1269
|
};
|
|
1270
|
+
function getColumnDepth(column) {
|
|
1271
|
+
if (!column.children?.length) return 1;
|
|
1272
|
+
return 1 + Math.max(...column.children.map(getColumnDepth));
|
|
1273
|
+
}
|
|
1274
|
+
function flattenDataColumns(columns, inheritedFixed, insideGroup = false) {
|
|
1275
|
+
return columns.flatMap((column) => {
|
|
1276
|
+
if (!column.children?.length) {
|
|
1277
|
+
return insideGroup ? [{ ...column, fixed: inheritedFixed }] : [column];
|
|
1278
|
+
}
|
|
1279
|
+
return flattenDataColumns(column.children, column.fixed ?? inheritedFixed, true);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
function buildHeaderCells(sourceColumns, utilityColumnCount, depth) {
|
|
1283
|
+
const cells = [];
|
|
1284
|
+
let leafIndex = utilityColumnCount;
|
|
1285
|
+
const walk = (column, level, inheritedFixed, insideGroup, rootIndex, parentKey) => {
|
|
1286
|
+
const fixedColumn = insideGroup ? { ...column, fixed: inheritedFixed } : column;
|
|
1287
|
+
if (!fixedColumn.children?.length) {
|
|
1288
|
+
const startIndex2 = leafIndex;
|
|
1289
|
+
leafIndex += 1;
|
|
1290
|
+
cells.push({ key: fixedColumn.key, column: fixedColumn, level: depth - 1, startIndex: startIndex2, endIndex: startIndex2, leaf: true, parentKey, rootIndex });
|
|
1291
|
+
return { startIndex: startIndex2, endIndex: startIndex2 };
|
|
1292
|
+
}
|
|
1293
|
+
const startIndex = leafIndex;
|
|
1294
|
+
fixedColumn.children.forEach((child) => walk(child, level + 1, fixedColumn.fixed, true, rootIndex, fixedColumn.key));
|
|
1295
|
+
const endIndex = leafIndex - 1;
|
|
1296
|
+
cells.push({ key: fixedColumn.key, column: fixedColumn, level, startIndex, endIndex, leaf: false, parentKey, rootIndex });
|
|
1297
|
+
return { startIndex, endIndex };
|
|
1298
|
+
};
|
|
1299
|
+
sourceColumns.forEach((column, index) => walk(column, 0, void 0, false, index));
|
|
1300
|
+
return cells;
|
|
1301
|
+
}
|
|
1302
|
+
function hasNestedFixedColumns(columns, insideGroup = false) {
|
|
1303
|
+
return columns.some((column) => insideGroup && column.fixed !== void 0 || (column.children?.length ? hasNestedFixedColumns(column.children, true) : false));
|
|
1304
|
+
}
|
|
1305
|
+
function isProductionRuntime() {
|
|
1306
|
+
const env = globalThis.process?.env?.NODE_ENV;
|
|
1307
|
+
return env === "production";
|
|
1308
|
+
}
|
|
1269
1309
|
var createPendingInsertId = () => `table-inserted-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
|
|
1270
1310
|
var waitForPaint = () => new Promise((resolve) => {
|
|
1271
1311
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
@@ -1286,6 +1326,25 @@ var createEmptyCellSpanLookup = () => ({
|
|
|
1286
1326
|
covered: /* @__PURE__ */ new Map(),
|
|
1287
1327
|
maxRowSpan: 1
|
|
1288
1328
|
});
|
|
1329
|
+
var summaryFunctionIds = /* @__PURE__ */ new WeakMap();
|
|
1330
|
+
var nextSummaryFunctionId = 1;
|
|
1331
|
+
function getSummaryFunctionId(fn) {
|
|
1332
|
+
if (!fn) return "";
|
|
1333
|
+
const existing = summaryFunctionIds.get(fn);
|
|
1334
|
+
if (existing !== void 0) return existing;
|
|
1335
|
+
const next = nextSummaryFunctionId;
|
|
1336
|
+
nextSummaryFunctionId += 1;
|
|
1337
|
+
summaryFunctionIds.set(fn, next);
|
|
1338
|
+
return next;
|
|
1339
|
+
}
|
|
1340
|
+
function getSummarySignature(columns) {
|
|
1341
|
+
return columns.filter((column) => Boolean(column.summary)).map((column) => [
|
|
1342
|
+
column.key,
|
|
1343
|
+
String(column.dataIndex ?? ""),
|
|
1344
|
+
typeof column.summary === "function" ? getSummaryFunctionId(column.summary) : "sum",
|
|
1345
|
+
getSummaryFunctionId(column.formatter)
|
|
1346
|
+
].join("")).join("");
|
|
1347
|
+
}
|
|
1289
1348
|
async function calculateSummaryValues(columns, rows) {
|
|
1290
1349
|
const values = /* @__PURE__ */ new Map();
|
|
1291
1350
|
for (const column of columns) {
|
|
@@ -1380,10 +1439,24 @@ function Table({
|
|
|
1380
1439
|
}) {
|
|
1381
1440
|
const rowHeight = layout?.rowHeight ?? rowHeightProp ?? 36;
|
|
1382
1441
|
const baseHeaderHeight = layout?.headerHeight ?? headerHeightProp ?? 40;
|
|
1442
|
+
const headerDepth = (0, import_react5.useMemo)(() => Math.max(1, ...sourceColumns.map(getColumnDepth)), [sourceColumns]);
|
|
1383
1443
|
const { language, labels } = (0, import_react5.useMemo)(() => resolveGridLocale(locale), [locale]);
|
|
1384
1444
|
const customHeaderContentRefs = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1445
|
+
const customHeaderMeasurementsRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
1446
|
+
const [measuredHeaderRowHeights, setMeasuredHeaderRowHeights] = (0, import_react5.useState)([]);
|
|
1447
|
+
const headerRowHeights = (0, import_react5.useMemo)(() => Array.from({ length: headerDepth }, (_, level) => Math.max(baseHeaderHeight, measuredHeaderRowHeights[level] ?? 0)), [baseHeaderHeight, headerDepth, measuredHeaderRowHeights]);
|
|
1448
|
+
const headerRowOffsets = (0, import_react5.useMemo)(() => {
|
|
1449
|
+
const offsets = [];
|
|
1450
|
+
let top = 0;
|
|
1451
|
+
headerRowHeights.forEach((height2) => {
|
|
1452
|
+
offsets.push(top);
|
|
1453
|
+
top += height2;
|
|
1454
|
+
});
|
|
1455
|
+
return offsets;
|
|
1456
|
+
}, [headerRowHeights]);
|
|
1457
|
+
const headerHeight = headerRowHeights.reduce((total, height2) => total + height2, 0);
|
|
1458
|
+
const headerLeafTop = headerRowOffsets[headerDepth - 1] ?? 0;
|
|
1459
|
+
const headerLeafHeight = headerRowHeights[headerDepth - 1] ?? baseHeaderHeight;
|
|
1387
1460
|
const hasVerticalBorders = !(borderless ?? verticalBorderless ?? false);
|
|
1388
1461
|
const hasStripedRows = Boolean(striped);
|
|
1389
1462
|
const stripedColor = typeof striped === "string" ? striped : void 0;
|
|
@@ -1403,6 +1476,13 @@ function Table({
|
|
|
1403
1476
|
const summaryEmptyValue = typeof summary === "object" ? summary.emptyValue ?? "" : "";
|
|
1404
1477
|
const contextMenuEnabled = contextMenuConfig !== false;
|
|
1405
1478
|
const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
|
|
1479
|
+
const [resizedColumnWidths, setResizedColumnWidths] = (0, import_react5.useState)({});
|
|
1480
|
+
const leafSourceColumns = (0, import_react5.useMemo)(() => flattenDataColumns(sourceColumns), [sourceColumns]);
|
|
1481
|
+
(0, import_react5.useEffect)(() => {
|
|
1482
|
+
if (!isProductionRuntime() && hasNestedFixedColumns(sourceColumns)) {
|
|
1483
|
+
console.warn("[Table] Multi-level headers only support fixed columns on top-level columns. Nested fixed values are ignored.");
|
|
1484
|
+
}
|
|
1485
|
+
}, [sourceColumns]);
|
|
1406
1486
|
const columns = (0, import_react5.useMemo)(() => {
|
|
1407
1487
|
const utilityColumns = [];
|
|
1408
1488
|
if (rowDraggable) {
|
|
@@ -1414,9 +1494,24 @@ function Table({
|
|
|
1414
1494
|
if (rowNumber) {
|
|
1415
1495
|
utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
|
|
1416
1496
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1497
|
+
const dataColumns = leafSourceColumns.map((column) => {
|
|
1498
|
+
const resizedWidth = resizedColumnWidths[column.key];
|
|
1499
|
+
return resizedWidth === void 0 ? column : { ...column, width: resizedWidth };
|
|
1500
|
+
});
|
|
1501
|
+
return [...utilityColumns, ...dataColumns];
|
|
1502
|
+
}, [leafSourceColumns, resizedColumnWidths, rowDraggable, rowNumber, rowSelection, sourceRows.length]);
|
|
1419
1503
|
const utilityColumnCount = (rowDraggable ? 1 : 0) + (rowSelection ? 1 : 0) + (rowNumber ? 1 : 0);
|
|
1504
|
+
const headerCells = (0, import_react5.useMemo)(() => buildHeaderCells(sourceColumns, utilityColumnCount, headerDepth), [headerDepth, sourceColumns, utilityColumnCount]);
|
|
1505
|
+
const activeCustomHeaderLevels = (0, import_react5.useMemo)(() => {
|
|
1506
|
+
const levels = /* @__PURE__ */ new Map();
|
|
1507
|
+
columns.forEach((column) => {
|
|
1508
|
+
if (column.renderHeader) levels.set(column.key, headerDepth - 1);
|
|
1509
|
+
});
|
|
1510
|
+
headerCells.forEach((cell) => {
|
|
1511
|
+
if (cell.column.renderHeader) levels.set(`group:${cell.key}:${cell.level}`, cell.level);
|
|
1512
|
+
});
|
|
1513
|
+
return levels;
|
|
1514
|
+
}, [columns, headerCells, headerDepth]);
|
|
1420
1515
|
const canvasRef = (0, import_react5.useRef)(null);
|
|
1421
1516
|
const scrollerRef = (0, import_react5.useRef)(null);
|
|
1422
1517
|
const frameRef = (0, import_react5.useRef)(null);
|
|
@@ -1434,6 +1529,15 @@ function Table({
|
|
|
1434
1529
|
const dragGuideRef = (0, import_react5.useRef)(null);
|
|
1435
1530
|
const rowDragGuideRef = (0, import_react5.useRef)(null);
|
|
1436
1531
|
const selectionFocusRef = (0, import_react5.useRef)(null);
|
|
1532
|
+
const selectionEdgeTargetRef = (0, import_react5.useRef)(null);
|
|
1533
|
+
const verticalScrollbarRef = (0, import_react5.useRef)(null);
|
|
1534
|
+
const verticalScrollbarThumbRef = (0, import_react5.useRef)(null);
|
|
1535
|
+
const verticalScrollbarDragRef = (0, import_react5.useRef)(null);
|
|
1536
|
+
const horizontalScrollbarRef = (0, import_react5.useRef)(null);
|
|
1537
|
+
const horizontalScrollbarThumbRef = (0, import_react5.useRef)(null);
|
|
1538
|
+
const horizontalScrollbarDragRef = (0, import_react5.useRef)(null);
|
|
1539
|
+
const verticalScrollbarVisibilityTimerRef = (0, import_react5.useRef)(null);
|
|
1540
|
+
const horizontalScrollbarVisibilityTimerRef = (0, import_react5.useRef)(null);
|
|
1437
1541
|
const rowByKeyRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
1438
1542
|
const rowIndexByKeyRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
1439
1543
|
const headerIconsRef = (0, import_react5.useRef)(null);
|
|
@@ -1486,24 +1590,57 @@ function Table({
|
|
|
1486
1590
|
rangeDragRef.current = null;
|
|
1487
1591
|
setSelectionRange(null);
|
|
1488
1592
|
}, [rangeSelection]);
|
|
1593
|
+
(0, import_react5.useEffect)(() => {
|
|
1594
|
+
setResizedColumnWidths((current) => {
|
|
1595
|
+
const sourceColumnByKey = new Map(leafSourceColumns.map((column) => [column.key, column]));
|
|
1596
|
+
let changed = false;
|
|
1597
|
+
const next = {};
|
|
1598
|
+
Object.entries(current).forEach(([key, value]) => {
|
|
1599
|
+
const sourceColumn = sourceColumnByKey.get(key);
|
|
1600
|
+
if (!sourceColumn) {
|
|
1601
|
+
changed = true;
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
if (sourceColumn.width !== void 0 && sourceColumn.width !== value) {
|
|
1605
|
+
changed = true;
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
next[key] = value;
|
|
1609
|
+
});
|
|
1610
|
+
return changed ? next : current;
|
|
1611
|
+
});
|
|
1612
|
+
}, [leafSourceColumns]);
|
|
1489
1613
|
(0, import_react5.useEffect)(() => () => {
|
|
1490
1614
|
if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
|
|
1491
1615
|
if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
|
|
1492
1616
|
}, []);
|
|
1493
1617
|
const measureCustomHeaderHeight = (0, import_react5.useCallback)(() => {
|
|
1494
|
-
|
|
1618
|
+
const nextHeights = Array.from({ length: headerDepth }, () => baseHeaderHeight);
|
|
1495
1619
|
customHeaderContentRefs.current.forEach((node) => {
|
|
1496
|
-
|
|
1620
|
+
const headerCell = node.closest(".rvg-header-title");
|
|
1621
|
+
const key = headerCell?.dataset.measureKey;
|
|
1622
|
+
const level = key ? activeCustomHeaderLevels.get(key) : void 0;
|
|
1623
|
+
if (!key || level === void 0 || level < 0 || level >= nextHeights.length) return;
|
|
1624
|
+
customHeaderMeasurementsRef.current.set(key, { level, height: Math.ceil(node.scrollHeight) + 16 });
|
|
1497
1625
|
});
|
|
1498
|
-
|
|
1499
|
-
|
|
1626
|
+
customHeaderMeasurementsRef.current.forEach((measurement, key) => {
|
|
1627
|
+
if (!activeCustomHeaderLevels.has(key)) return;
|
|
1628
|
+
nextHeights[measurement.level] = Math.max(nextHeights[measurement.level], measurement.height);
|
|
1629
|
+
});
|
|
1630
|
+
setMeasuredHeaderRowHeights((current) => {
|
|
1631
|
+
const changed = nextHeights.length !== current.length || nextHeights.some((height2, index) => height2 !== current[index]);
|
|
1632
|
+
return changed ? nextHeights : current;
|
|
1633
|
+
});
|
|
1634
|
+
}, [activeCustomHeaderLevels, baseHeaderHeight, headerDepth]);
|
|
1500
1635
|
(0, import_react5.useLayoutEffect)(() => {
|
|
1501
|
-
const activeCustomHeaderKeys = new Set(columns.filter((column) => column.renderHeader).map((column) => column.key));
|
|
1502
1636
|
customHeaderContentRefs.current.forEach((_, key) => {
|
|
1503
|
-
if (!
|
|
1637
|
+
if (!activeCustomHeaderLevels.has(key)) customHeaderContentRefs.current.delete(key);
|
|
1638
|
+
});
|
|
1639
|
+
customHeaderMeasurementsRef.current.forEach((_, key) => {
|
|
1640
|
+
if (!activeCustomHeaderLevels.has(key)) customHeaderMeasurementsRef.current.delete(key);
|
|
1504
1641
|
});
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1642
|
+
if (activeCustomHeaderLevels.size === 0) {
|
|
1643
|
+
setMeasuredHeaderRowHeights([]);
|
|
1507
1644
|
return;
|
|
1508
1645
|
}
|
|
1509
1646
|
measureCustomHeaderHeight();
|
|
@@ -1511,7 +1648,7 @@ function Table({
|
|
|
1511
1648
|
const observer = new ResizeObserver(measureCustomHeaderHeight);
|
|
1512
1649
|
customHeaderContentRefs.current.forEach((node) => observer.observe(node));
|
|
1513
1650
|
return () => observer.disconnect();
|
|
1514
|
-
}, [
|
|
1651
|
+
}, [activeCustomHeaderLevels, measureCustomHeaderHeight, viewport.width]);
|
|
1515
1652
|
const [filterEditor, setFilterEditor] = (0, import_react5.useState)(null);
|
|
1516
1653
|
const [contextMenu, setContextMenu] = (0, import_react5.useState)(null);
|
|
1517
1654
|
const [insertCounts, setInsertCounts] = (0, import_react5.useState)({ before: 1, after: 1 });
|
|
@@ -1521,6 +1658,7 @@ function Table({
|
|
|
1521
1658
|
const [deleteBusy, setDeleteBusy] = (0, import_react5.useState)(false);
|
|
1522
1659
|
const [toast, setToast] = (0, import_react5.useState)(null);
|
|
1523
1660
|
const [resizeGuideX, setResizeGuideX] = (0, import_react5.useState)(null);
|
|
1661
|
+
const [resizeGuideTop, setResizeGuideTop] = (0, import_react5.useState)(0);
|
|
1524
1662
|
const [hoveredHeaderAction, setHoveredHeaderAction] = (0, import_react5.useState)(null);
|
|
1525
1663
|
const [visibleHeaderTooltip, setVisibleHeaderTooltip] = (0, import_react5.useState)(null);
|
|
1526
1664
|
const [hoveredCellTooltip, setHoveredCellTooltip] = (0, import_react5.useState)(null);
|
|
@@ -1605,7 +1743,17 @@ function Table({
|
|
|
1605
1743
|
const metrics = (0, import_react5.useMemo)(() => buildColumnMetrics(columns, { columnDraggable, viewportWidth: viewport.width }), [columnDraggable, columns, viewport.width]);
|
|
1606
1744
|
const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
|
|
1607
1745
|
const contentHeight = rows.length * rowHeight;
|
|
1608
|
-
const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ?
|
|
1746
|
+
const fixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "left" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
|
|
1747
|
+
const leftFixedOffsets = (0, import_react5.useMemo)(() => {
|
|
1748
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
1749
|
+
let offset = 0;
|
|
1750
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
1751
|
+
if (columns[index].fixed !== "left") continue;
|
|
1752
|
+
offsets.set(index, offset);
|
|
1753
|
+
offset += metrics[index].width;
|
|
1754
|
+
}
|
|
1755
|
+
return offsets;
|
|
1756
|
+
}, [columns, metrics]);
|
|
1609
1757
|
const rightFixedWidth = (0, import_react5.useMemo)(() => columns.reduce((width2, column, index) => column.fixed === "right" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
|
|
1610
1758
|
const rightFixedOffsets = (0, import_react5.useMemo)(() => {
|
|
1611
1759
|
const offsets = /* @__PURE__ */ new Map();
|
|
@@ -1617,9 +1765,20 @@ function Table({
|
|
|
1617
1765
|
}
|
|
1618
1766
|
return offsets;
|
|
1619
1767
|
}, [columns, metrics]);
|
|
1768
|
+
const scrollableColumnLefts = (0, import_react5.useMemo)(() => {
|
|
1769
|
+
const lefts = [];
|
|
1770
|
+
let width2 = fixedWidth;
|
|
1771
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
1772
|
+
lefts[index] = width2;
|
|
1773
|
+
if (columns[index].fixed === void 0) width2 += metrics[index].width;
|
|
1774
|
+
}
|
|
1775
|
+
return lefts;
|
|
1776
|
+
}, [columns, fixedWidth, metrics]);
|
|
1777
|
+
const getScrollableColumnLeft = (0, import_react5.useCallback)((columnIndex) => scrollableColumnLefts[columnIndex] ?? fixedWidth, [fixedWidth, scrollableColumnLefts]);
|
|
1620
1778
|
const hasRowSelectionColumn = (0, import_react5.useMemo)(() => columns.some((column) => column.rowSelection), [columns]);
|
|
1621
1779
|
const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
|
|
1622
|
-
const
|
|
1780
|
+
const summarySignature = (0, import_react5.useMemo)(() => getSummarySignature(columns), [columns]);
|
|
1781
|
+
const hasSummaryRow = rows.length > 0 && summaryEnabled && summarySignature.length > 0;
|
|
1623
1782
|
const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
|
|
1624
1783
|
const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
|
|
1625
1784
|
const bodyTop = headerHeight + topSummaryHeight;
|
|
@@ -1627,22 +1786,22 @@ function Table({
|
|
|
1627
1786
|
const realContentHeight = bodyTop + bodyContentHeight + bottomSummaryHeight + (contentWidth > viewport.width ? horizontalScrollbarHeight : 0);
|
|
1628
1787
|
const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
|
|
1629
1788
|
const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
|
|
1630
|
-
const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight : 0)
|
|
1631
|
-
const [summaryState, setSummaryState] = (0, import_react5.useState)(() => ({
|
|
1632
|
-
const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.
|
|
1633
|
-
const summaryValues = hasSummaryRow && summaryState.
|
|
1789
|
+
const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight + topSummaryHeight : 0));
|
|
1790
|
+
const [summaryState, setSummaryState] = (0, import_react5.useState)(() => ({ signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
|
|
1791
|
+
const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.signature !== summarySignature || summaryState.rows !== rows);
|
|
1792
|
+
const summaryValues = hasSummaryRow && summaryState.signature === summarySignature && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
|
|
1634
1793
|
(0, import_react5.useEffect)(() => {
|
|
1635
1794
|
let cancelled = false;
|
|
1636
1795
|
if (!hasSummaryRow) {
|
|
1637
|
-
setSummaryState((current) => current.
|
|
1796
|
+
setSummaryState((current) => current.signature === "" && current.rows === null && current.values.size === 0 && !current.pending ? current : { signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false });
|
|
1638
1797
|
return () => {
|
|
1639
1798
|
cancelled = true;
|
|
1640
1799
|
};
|
|
1641
1800
|
}
|
|
1642
1801
|
setSummaryState((current) => ({
|
|
1643
|
-
|
|
1802
|
+
signature: summarySignature,
|
|
1644
1803
|
rows,
|
|
1645
|
-
values: current.
|
|
1804
|
+
values: current.signature === summarySignature && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
|
|
1646
1805
|
pending: true
|
|
1647
1806
|
}));
|
|
1648
1807
|
if (loading) {
|
|
@@ -1655,13 +1814,13 @@ function Table({
|
|
|
1655
1814
|
if (cancelled) return;
|
|
1656
1815
|
const values = await calculateSummaryValues(columns, rows);
|
|
1657
1816
|
if (cancelled) return;
|
|
1658
|
-
setSummaryState({
|
|
1817
|
+
setSummaryState({ signature: summarySignature, rows, values, pending: false });
|
|
1659
1818
|
};
|
|
1660
1819
|
void run();
|
|
1661
1820
|
return () => {
|
|
1662
1821
|
cancelled = true;
|
|
1663
1822
|
};
|
|
1664
|
-
}, [
|
|
1823
|
+
}, [hasSummaryRow, loading, rows, summarySignature]);
|
|
1665
1824
|
const domRange = (0, import_react5.useMemo)(() => {
|
|
1666
1825
|
if (!isVirtualized) return { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
|
|
1667
1826
|
const currentScroll2 = scrollRef.current;
|
|
@@ -1803,11 +1962,163 @@ function Table({
|
|
|
1803
1962
|
const scroll = scrollRef.current;
|
|
1804
1963
|
const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
|
|
1805
1964
|
const range = isVirtualized ? getViewportRange(scroll.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan) : { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
|
|
1806
|
-
paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
|
|
1807
|
-
}, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
|
|
1965
|
+
paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
|
|
1966
|
+
}, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
|
|
1808
1967
|
const scheduleDraw = (0, import_react5.useCallback)(() => {
|
|
1809
1968
|
if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
|
|
1810
1969
|
}, [draw]);
|
|
1970
|
+
const drawImmediately = (0, import_react5.useCallback)(() => {
|
|
1971
|
+
if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
|
|
1972
|
+
frameRef.current = null;
|
|
1973
|
+
draw();
|
|
1974
|
+
}, [draw]);
|
|
1975
|
+
const getSelectionHorizontalVisibility = (0, import_react5.useCallback)((scrollLeft) => {
|
|
1976
|
+
if (!selection || selectionRange) return "visible";
|
|
1977
|
+
const column = columns[selection.columnIndex];
|
|
1978
|
+
if (!column || column.fixed !== void 0) return "visible";
|
|
1979
|
+
const span = getCellSpan(selection.rowIndex, selection.columnIndex);
|
|
1980
|
+
const left = getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
|
|
1981
|
+
const right = left + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
|
|
1982
|
+
if (right <= fixedWidth) return "hidden-left";
|
|
1983
|
+
if (left >= viewport.width - rightFixedWidth) return "hidden-right";
|
|
1984
|
+
return "visible";
|
|
1985
|
+
}, [columns, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, rightFixedWidth, selection, selectionRange, viewport.width]);
|
|
1986
|
+
const getSelectionEdgeIndicator = (0, import_react5.useCallback)((scrollLeft, scrollTop) => {
|
|
1987
|
+
if (!selection || selectionRange) return null;
|
|
1988
|
+
const row = rows[selection.rowIndex];
|
|
1989
|
+
const metric = metrics[selection.columnIndex];
|
|
1990
|
+
const column = columns[selection.columnIndex];
|
|
1991
|
+
if (!row || !metric || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey) return null;
|
|
1992
|
+
const span = getCellSpan(selection.rowIndex, selection.columnIndex);
|
|
1993
|
+
const rawLeft = column.fixed === "left" ? leftFixedOffsets.get(selection.columnIndex) ?? 0 : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
|
|
1994
|
+
const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
|
|
1995
|
+
const rawTop = bodyTop + selection.rowIndex * rowHeight - scrollTop;
|
|
1996
|
+
const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
|
|
1997
|
+
const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
|
|
1998
|
+
const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
|
|
1999
|
+
const verticalStart = fixedHeader ? bodyTop : 0;
|
|
2000
|
+
const hiddenLeft = column.fixed === void 0 && rawRight <= horizontalStart;
|
|
2001
|
+
const hiddenRight = column.fixed === void 0 && rawLeft >= horizontalEnd;
|
|
2002
|
+
const hiddenTop = rawBottom <= verticalStart;
|
|
2003
|
+
const hiddenBottom = rawTop >= renderHeight;
|
|
2004
|
+
if (!hiddenLeft && !hiddenRight && !hiddenTop && !hiddenBottom) return null;
|
|
2005
|
+
const borderWidth = 2;
|
|
2006
|
+
const cornerLength = 10;
|
|
2007
|
+
const hitSlop = 6;
|
|
2008
|
+
const targetSize = borderWidth + hitSlop * 2;
|
|
2009
|
+
const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
|
|
2010
|
+
const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
|
|
2011
|
+
const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
|
|
2012
|
+
if (hiddenLeft || hiddenRight) {
|
|
2013
|
+
let markerTop = Math.max(rawTop - borderWidth, verticalStart);
|
|
2014
|
+
let markerBottom = Math.min(rawBottom + 1, renderHeight);
|
|
2015
|
+
const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
|
|
2016
|
+
const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
|
|
2017
|
+
if (topClip > 0 || bottomClip > 0) {
|
|
2018
|
+
const foldAtTop = topClip > 0;
|
|
2019
|
+
const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
|
|
2020
|
+
if (fold > borderWidth) {
|
|
2021
|
+
const cornerWidth = fold;
|
|
2022
|
+
const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
|
|
2023
|
+
const cornerOnLeft = hiddenLeft;
|
|
2024
|
+
const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
|
|
2025
|
+
const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
|
|
2026
|
+
return {
|
|
2027
|
+
side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
|
|
2028
|
+
orientation: "corner",
|
|
2029
|
+
style: {
|
|
2030
|
+
left: shapeLeft - hitSlop,
|
|
2031
|
+
top: shapeTop - hitSlop,
|
|
2032
|
+
width: cornerWidth + hitSlop * 2,
|
|
2033
|
+
height: cornerHeight + hitSlop * 2
|
|
2034
|
+
},
|
|
2035
|
+
corner: { width: cornerWidth, height: cornerHeight }
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
if (markerBottom <= markerTop) return null;
|
|
2040
|
+
return {
|
|
2041
|
+
side: hiddenLeft ? "left" : "right",
|
|
2042
|
+
orientation: "vertical",
|
|
2043
|
+
viewportEdge: hiddenLeft && horizontalStart === 0 ? "left" : hiddenRight && horizontalEnd === viewport.width ? "right" : void 0,
|
|
2044
|
+
style: {
|
|
2045
|
+
left: markerX - hitSlop,
|
|
2046
|
+
top: markerTop,
|
|
2047
|
+
width: targetSize,
|
|
2048
|
+
height: markerBottom - markerTop
|
|
2049
|
+
}
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
|
|
2053
|
+
let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
|
|
2054
|
+
let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
|
|
2055
|
+
const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
|
|
2056
|
+
const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
|
|
2057
|
+
if (leftClip > 0 || rightClip > 0) {
|
|
2058
|
+
const foldAtLeft = leftClip > 0;
|
|
2059
|
+
const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
|
|
2060
|
+
if (fold > borderWidth) {
|
|
2061
|
+
const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
|
|
2062
|
+
const cornerHeight = fold;
|
|
2063
|
+
const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
|
|
2064
|
+
const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
|
|
2065
|
+
return {
|
|
2066
|
+
side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
|
|
2067
|
+
orientation: "corner",
|
|
2068
|
+
style: {
|
|
2069
|
+
left: shapeLeft - hitSlop,
|
|
2070
|
+
top: shapeTop - hitSlop,
|
|
2071
|
+
width: cornerWidth + hitSlop * 2,
|
|
2072
|
+
height: cornerHeight + hitSlop * 2
|
|
2073
|
+
},
|
|
2074
|
+
corner: { width: cornerWidth, height: cornerHeight }
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (markerRight <= markerLeft) return null;
|
|
2079
|
+
return {
|
|
2080
|
+
side: hiddenTop ? "top" : "bottom",
|
|
2081
|
+
orientation: "horizontal",
|
|
2082
|
+
viewportEdge: hiddenTop && verticalStart === 0 ? "top" : hiddenBottom ? "bottom" : void 0,
|
|
2083
|
+
style: {
|
|
2084
|
+
left: markerLeft,
|
|
2085
|
+
top: markerY - hitSlop,
|
|
2086
|
+
width: markerRight - markerLeft,
|
|
2087
|
+
height: targetSize
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
}, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
|
|
2091
|
+
const syncSelectionEdgeTarget = (0, import_react5.useCallback)((scrollLeft, scrollTop) => {
|
|
2092
|
+
const target = selectionEdgeTargetRef.current;
|
|
2093
|
+
if (!target) return;
|
|
2094
|
+
const indicator = getSelectionEdgeIndicator(scrollLeft, scrollTop);
|
|
2095
|
+
if (!indicator) {
|
|
2096
|
+
target.style.visibility = "hidden";
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
const orientationChanged = !target.classList.contains(`is-${indicator.orientation}`);
|
|
2100
|
+
target.className = `rvg-selection-edge-target is-${indicator.orientation} is-${indicator.side}${indicator.viewportEdge ? ` is-viewport-${indicator.viewportEdge}` : ""}${orientationChanged ? " is-shape-syncing" : ""}`;
|
|
2101
|
+
target.style.left = `${Number(indicator.style.left)}px`;
|
|
2102
|
+
target.style.top = `${Number(indicator.style.top)}px`;
|
|
2103
|
+
target.style.width = `${Number(indicator.style.width)}px`;
|
|
2104
|
+
target.style.height = `${Number(indicator.style.height)}px`;
|
|
2105
|
+
if (indicator.corner) {
|
|
2106
|
+
target.style.setProperty("--rvg-corner-width", `${indicator.corner.width}px`);
|
|
2107
|
+
target.style.setProperty("--rvg-corner-height", `${indicator.corner.height}px`);
|
|
2108
|
+
target.style.setProperty("--rvg-corner-hover-width-extension", "4px");
|
|
2109
|
+
target.style.setProperty("--rvg-corner-hover-height-extension", "4px");
|
|
2110
|
+
} else {
|
|
2111
|
+
target.style.removeProperty("--rvg-corner-width");
|
|
2112
|
+
target.style.removeProperty("--rvg-corner-height");
|
|
2113
|
+
target.style.removeProperty("--rvg-corner-hover-width-extension");
|
|
2114
|
+
target.style.removeProperty("--rvg-corner-hover-height-extension");
|
|
2115
|
+
}
|
|
2116
|
+
target.style.visibility = "visible";
|
|
2117
|
+
if (orientationChanged) {
|
|
2118
|
+
void target.offsetWidth;
|
|
2119
|
+
target.classList.remove("is-shape-syncing");
|
|
2120
|
+
}
|
|
2121
|
+
}, [getSelectionEdgeIndicator]);
|
|
1811
2122
|
(0, import_react5.useLayoutEffect)(() => {
|
|
1812
2123
|
const element = scrollerRef.current;
|
|
1813
2124
|
if (!element) return;
|
|
@@ -1841,6 +2152,8 @@ function Table({
|
|
|
1841
2152
|
if (sortDebounceRef.current !== null) clearTimeout(sortDebounceRef.current);
|
|
1842
2153
|
if (textFrameRef.current !== null) cancelAnimationFrame(textFrameRef.current);
|
|
1843
2154
|
if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
|
|
2155
|
+
if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
|
|
2156
|
+
if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
|
|
1844
2157
|
}, []);
|
|
1845
2158
|
(0, import_react5.useEffect)(() => {
|
|
1846
2159
|
const scroller = scrollerRef.current;
|
|
@@ -1892,10 +2205,218 @@ function Table({
|
|
|
1892
2205
|
scroller.addEventListener("scroll", lockScroll);
|
|
1893
2206
|
return () => scroller.removeEventListener("scroll", lockScroll);
|
|
1894
2207
|
}, [insertBusy]);
|
|
2208
|
+
const showCustomScrollbar = (0, import_react5.useCallback)((axis, persist = false) => {
|
|
2209
|
+
const showVertical = axis === "vertical" || axis === "both";
|
|
2210
|
+
const showHorizontal = axis === "horizontal" || axis === "both";
|
|
2211
|
+
if (showVertical) {
|
|
2212
|
+
verticalScrollbarRef.current?.classList.add("is-active");
|
|
2213
|
+
if (verticalScrollbarVisibilityTimerRef.current !== null) clearTimeout(verticalScrollbarVisibilityTimerRef.current);
|
|
2214
|
+
if (!persist) {
|
|
2215
|
+
verticalScrollbarVisibilityTimerRef.current = setTimeout(() => {
|
|
2216
|
+
verticalScrollbarRef.current?.classList.remove("is-active");
|
|
2217
|
+
verticalScrollbarVisibilityTimerRef.current = null;
|
|
2218
|
+
}, 900);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (showHorizontal) {
|
|
2222
|
+
horizontalScrollbarRef.current?.classList.add("is-active");
|
|
2223
|
+
if (horizontalScrollbarVisibilityTimerRef.current !== null) clearTimeout(horizontalScrollbarVisibilityTimerRef.current);
|
|
2224
|
+
if (!persist) {
|
|
2225
|
+
horizontalScrollbarVisibilityTimerRef.current = setTimeout(() => {
|
|
2226
|
+
horizontalScrollbarRef.current?.classList.remove("is-active");
|
|
2227
|
+
horizontalScrollbarVisibilityTimerRef.current = null;
|
|
2228
|
+
}, 900);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}, []);
|
|
2232
|
+
const keepCustomScrollbarVisible = (0, import_react5.useCallback)((axis) => {
|
|
2233
|
+
showCustomScrollbar(axis, true);
|
|
2234
|
+
}, [showCustomScrollbar]);
|
|
2235
|
+
const releaseCustomScrollbarVisible = (0, import_react5.useCallback)((axis) => {
|
|
2236
|
+
if (axis === "vertical" && verticalScrollbarDragRef.current) return;
|
|
2237
|
+
if (axis === "horizontal" && horizontalScrollbarDragRef.current) return;
|
|
2238
|
+
showCustomScrollbar(axis);
|
|
2239
|
+
}, [showCustomScrollbar]);
|
|
2240
|
+
const updateCustomScrollbars = (0, import_react5.useCallback)(() => {
|
|
2241
|
+
const scroller = scrollerRef.current;
|
|
2242
|
+
const verticalTrack = verticalScrollbarRef.current;
|
|
2243
|
+
const verticalThumb = verticalScrollbarThumbRef.current;
|
|
2244
|
+
const horizontalTrack = horizontalScrollbarRef.current;
|
|
2245
|
+
const horizontalThumb = horizontalScrollbarThumbRef.current;
|
|
2246
|
+
if (!scroller || !verticalTrack || !verticalThumb || !horizontalTrack || !horizontalThumb) return;
|
|
2247
|
+
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
2248
|
+
if (maxScrollTop <= 1 || renderHeight <= 0) {
|
|
2249
|
+
verticalTrack.style.display = "none";
|
|
2250
|
+
} else {
|
|
2251
|
+
const top = fixedHeader ? bodyTop : 0;
|
|
2252
|
+
const bottom = bottomSummaryHeight;
|
|
2253
|
+
const trackHeight = Math.max(0, effectiveHeight - top - bottom);
|
|
2254
|
+
if (trackHeight <= 0) {
|
|
2255
|
+
verticalTrack.style.display = "none";
|
|
2256
|
+
} else {
|
|
2257
|
+
const thumbHeight = Math.max(28, Math.round(trackHeight * (scroller.clientHeight / scroller.scrollHeight)));
|
|
2258
|
+
const maxThumbTop = Math.max(0, trackHeight - thumbHeight);
|
|
2259
|
+
const thumbTop = maxScrollTop > 0 ? Math.round(scroller.scrollTop / maxScrollTop * maxThumbTop) : 0;
|
|
2260
|
+
verticalTrack.style.display = "block";
|
|
2261
|
+
verticalTrack.style.top = `${top}px`;
|
|
2262
|
+
verticalTrack.style.bottom = `${bottom}px`;
|
|
2263
|
+
verticalThumb.style.height = `${Math.min(trackHeight, thumbHeight)}px`;
|
|
2264
|
+
verticalThumb.style.transform = `translateY(${thumbTop}px)`;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
|
|
2268
|
+
if (maxScrollLeft <= 1 || viewport.width <= 0) {
|
|
2269
|
+
horizontalTrack.style.display = "none";
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
const trackLeft = fixedWidth;
|
|
2273
|
+
const trackRight = rightFixedWidth;
|
|
2274
|
+
const trackWidth = Math.max(0, viewport.width - trackLeft - trackRight);
|
|
2275
|
+
if (trackWidth <= 0) {
|
|
2276
|
+
horizontalTrack.style.display = "none";
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const thumbWidth = Math.max(36, Math.round(trackWidth * (scroller.clientWidth / scroller.scrollWidth)));
|
|
2280
|
+
const maxThumbLeft = Math.max(0, trackWidth - thumbWidth);
|
|
2281
|
+
const thumbLeft = maxScrollLeft > 0 ? Math.round(scroller.scrollLeft / maxScrollLeft * maxThumbLeft) : 0;
|
|
2282
|
+
horizontalTrack.style.display = "block";
|
|
2283
|
+
horizontalTrack.style.left = `${trackLeft}px`;
|
|
2284
|
+
horizontalTrack.style.right = `${trackRight}px`;
|
|
2285
|
+
horizontalTrack.style.bottom = `${bottomSummaryHeight + 2}px`;
|
|
2286
|
+
horizontalThumb.style.width = `${Math.min(trackWidth, thumbWidth)}px`;
|
|
2287
|
+
horizontalThumb.style.transform = `translateX(${thumbLeft}px)`;
|
|
2288
|
+
}, [bodyTop, bottomSummaryHeight, effectiveHeight, fixedHeader, fixedWidth, renderHeight, rightFixedWidth, viewport.width]);
|
|
2289
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
2290
|
+
updateCustomScrollbars();
|
|
2291
|
+
}, [contentHeight, contentWidth, updateCustomScrollbars, viewport.height, viewport.width]);
|
|
2292
|
+
const handleVerticalScrollbarPointerDown = (0, import_react5.useCallback)((event) => {
|
|
2293
|
+
const scroller = scrollerRef.current;
|
|
2294
|
+
const track = verticalScrollbarRef.current;
|
|
2295
|
+
const thumb = verticalScrollbarThumbRef.current;
|
|
2296
|
+
if (!scroller || !track || !thumb || event.button !== 0) return;
|
|
2297
|
+
event.preventDefault();
|
|
2298
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2299
|
+
const trackRect = track.getBoundingClientRect();
|
|
2300
|
+
const thumbRect = thumb.getBoundingClientRect();
|
|
2301
|
+
const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
|
|
2302
|
+
const trackHeight = trackRect.height;
|
|
2303
|
+
const thumbHeight = thumbRect.height;
|
|
2304
|
+
showCustomScrollbar("vertical", true);
|
|
2305
|
+
if (event.target === thumb) {
|
|
2306
|
+
verticalScrollbarDragRef.current = {
|
|
2307
|
+
pointerId: event.pointerId,
|
|
2308
|
+
startY: event.clientY,
|
|
2309
|
+
startTop: thumbRect.top - trackRect.top,
|
|
2310
|
+
trackHeight,
|
|
2311
|
+
thumbHeight,
|
|
2312
|
+
maxScrollTop
|
|
2313
|
+
};
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
const nextThumbTop = Math.max(0, Math.min(trackHeight - thumbHeight, event.clientY - trackRect.top - thumbHeight / 2));
|
|
2317
|
+
const maxThumbTop = Math.max(1, trackHeight - thumbHeight);
|
|
2318
|
+
scroller.scrollTop = nextThumbTop / maxThumbTop * maxScrollTop;
|
|
2319
|
+
updateCustomScrollbars();
|
|
2320
|
+
verticalScrollbarDragRef.current = {
|
|
2321
|
+
pointerId: event.pointerId,
|
|
2322
|
+
startY: event.clientY,
|
|
2323
|
+
startTop: nextThumbTop,
|
|
2324
|
+
trackHeight,
|
|
2325
|
+
thumbHeight,
|
|
2326
|
+
maxScrollTop
|
|
2327
|
+
};
|
|
2328
|
+
}, [showCustomScrollbar, updateCustomScrollbars]);
|
|
2329
|
+
const handleVerticalScrollbarPointerMove = (0, import_react5.useCallback)((event) => {
|
|
2330
|
+
const drag = verticalScrollbarDragRef.current;
|
|
2331
|
+
const scroller = scrollerRef.current;
|
|
2332
|
+
if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
|
|
2333
|
+
event.preventDefault();
|
|
2334
|
+
const maxThumbTop = Math.max(1, drag.trackHeight - drag.thumbHeight);
|
|
2335
|
+
const nextThumbTop = Math.max(0, Math.min(maxThumbTop, drag.startTop + event.clientY - drag.startY));
|
|
2336
|
+
scroller.scrollTop = nextThumbTop / maxThumbTop * drag.maxScrollTop;
|
|
2337
|
+
updateCustomScrollbars();
|
|
2338
|
+
}, [updateCustomScrollbars]);
|
|
2339
|
+
const handleVerticalScrollbarPointerUp = (0, import_react5.useCallback)((event) => {
|
|
2340
|
+
const drag = verticalScrollbarDragRef.current;
|
|
2341
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
2342
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
2343
|
+
verticalScrollbarDragRef.current = null;
|
|
2344
|
+
showCustomScrollbar("vertical");
|
|
2345
|
+
}, [showCustomScrollbar]);
|
|
2346
|
+
const handleHorizontalScrollbarPointerDown = (0, import_react5.useCallback)((event) => {
|
|
2347
|
+
const scroller = scrollerRef.current;
|
|
2348
|
+
const track = horizontalScrollbarRef.current;
|
|
2349
|
+
const thumb = horizontalScrollbarThumbRef.current;
|
|
2350
|
+
if (!scroller || !track || !thumb || event.button !== 0) return;
|
|
2351
|
+
event.preventDefault();
|
|
2352
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2353
|
+
const trackRect = track.getBoundingClientRect();
|
|
2354
|
+
const thumbRect = thumb.getBoundingClientRect();
|
|
2355
|
+
const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;
|
|
2356
|
+
const trackWidth = trackRect.width;
|
|
2357
|
+
const thumbWidth = thumbRect.width;
|
|
2358
|
+
showCustomScrollbar("horizontal", true);
|
|
2359
|
+
if (event.target === thumb) {
|
|
2360
|
+
horizontalScrollbarDragRef.current = {
|
|
2361
|
+
pointerId: event.pointerId,
|
|
2362
|
+
startX: event.clientX,
|
|
2363
|
+
startLeft: thumbRect.left - trackRect.left,
|
|
2364
|
+
trackWidth,
|
|
2365
|
+
thumbWidth,
|
|
2366
|
+
maxScrollLeft
|
|
2367
|
+
};
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const nextThumbLeft = Math.max(0, Math.min(trackWidth - thumbWidth, event.clientX - trackRect.left - thumbWidth / 2));
|
|
2371
|
+
const maxThumbLeft = Math.max(1, trackWidth - thumbWidth);
|
|
2372
|
+
const previousScrollLeft = scroller.scrollLeft;
|
|
2373
|
+
const nextScrollLeft = nextThumbLeft / maxThumbLeft * maxScrollLeft;
|
|
2374
|
+
const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
|
|
2375
|
+
scroller.scrollLeft = nextScrollLeft;
|
|
2376
|
+
scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
2377
|
+
syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
|
|
2378
|
+
if (visibilityChanged) drawImmediately();
|
|
2379
|
+
updateCustomScrollbars();
|
|
2380
|
+
horizontalScrollbarDragRef.current = {
|
|
2381
|
+
pointerId: event.pointerId,
|
|
2382
|
+
startX: event.clientX,
|
|
2383
|
+
startLeft: nextThumbLeft,
|
|
2384
|
+
trackWidth,
|
|
2385
|
+
thumbWidth,
|
|
2386
|
+
maxScrollLeft
|
|
2387
|
+
};
|
|
2388
|
+
}, [drawImmediately, getSelectionHorizontalVisibility, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars]);
|
|
2389
|
+
const handleHorizontalScrollbarPointerMove = (0, import_react5.useCallback)((event) => {
|
|
2390
|
+
const drag = horizontalScrollbarDragRef.current;
|
|
2391
|
+
const scroller = scrollerRef.current;
|
|
2392
|
+
if (!drag || !scroller || drag.pointerId !== event.pointerId) return;
|
|
2393
|
+
event.preventDefault();
|
|
2394
|
+
const maxThumbLeft = Math.max(1, drag.trackWidth - drag.thumbWidth);
|
|
2395
|
+
const nextThumbLeft = Math.max(0, Math.min(maxThumbLeft, drag.startLeft + event.clientX - drag.startX));
|
|
2396
|
+
const previousScrollLeft = scroller.scrollLeft;
|
|
2397
|
+
const nextScrollLeft = nextThumbLeft / maxThumbLeft * drag.maxScrollLeft;
|
|
2398
|
+
const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
|
|
2399
|
+
scroller.scrollLeft = nextScrollLeft;
|
|
2400
|
+
scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
2401
|
+
syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
|
|
2402
|
+
if (visibilityChanged) drawImmediately();
|
|
2403
|
+
updateCustomScrollbars();
|
|
2404
|
+
}, [drawImmediately, getSelectionHorizontalVisibility, syncSelectionEdgeTarget, updateCustomScrollbars]);
|
|
2405
|
+
const handleHorizontalScrollbarPointerUp = (0, import_react5.useCallback)((event) => {
|
|
2406
|
+
const drag = horizontalScrollbarDragRef.current;
|
|
2407
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
2408
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
2409
|
+
horizontalScrollbarDragRef.current = null;
|
|
2410
|
+
showCustomScrollbar("horizontal");
|
|
2411
|
+
}, [showCustomScrollbar]);
|
|
1895
2412
|
const handleScroll = (0, import_react5.useCallback)(() => {
|
|
1896
2413
|
const element = scrollerRef.current;
|
|
1897
2414
|
if (!element) return;
|
|
2415
|
+
const previousScroll = scrollRef.current;
|
|
2416
|
+
const scrollDeltaX = element.scrollLeft - previousScroll.left;
|
|
2417
|
+
const scrollDeltaY = element.scrollTop - previousScroll.top;
|
|
1898
2418
|
scrollRef.current = { left: element.scrollLeft, top: element.scrollTop };
|
|
2419
|
+
syncSelectionEdgeTarget(element.scrollLeft, element.scrollTop);
|
|
1899
2420
|
const focus = selectionFocusRef.current;
|
|
1900
2421
|
if (focus && selection && !selectionRange) {
|
|
1901
2422
|
const row = rows[selection.rowIndex];
|
|
@@ -1905,7 +2426,7 @@ function Table({
|
|
|
1905
2426
|
if (!validSelection) {
|
|
1906
2427
|
focus.style.display = "none";
|
|
1907
2428
|
} else {
|
|
1908
|
-
const rawLeft = column.fixed === "left" ?
|
|
2429
|
+
const rawLeft = column.fixed === "left" ? leftFixedOffsets.get(selection.columnIndex) ?? 0 : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(selection.columnIndex) - element.scrollLeft;
|
|
1909
2430
|
const rawTop = bodyTop + selection.rowIndex * rowHeight - element.scrollTop;
|
|
1910
2431
|
const span = getCellSpan(selection.rowIndex, selection.columnIndex);
|
|
1911
2432
|
const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
|
|
@@ -1917,9 +2438,9 @@ function Table({
|
|
|
1917
2438
|
focus.style.display = "none";
|
|
1918
2439
|
} else {
|
|
1919
2440
|
const borderWidth = 2;
|
|
1920
|
-
const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart
|
|
2441
|
+
const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
|
|
1921
2442
|
const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
|
|
1922
|
-
const top = Math.round(Math.max(rawTop - borderWidth, verticalStart
|
|
2443
|
+
const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
|
|
1923
2444
|
const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
|
|
1924
2445
|
focus.style.display = "block";
|
|
1925
2446
|
focus.style.left = `${left}px`;
|
|
@@ -1938,6 +2459,10 @@ function Table({
|
|
|
1938
2459
|
if (scrollingTextRef.current) scrollingTextRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
|
|
1939
2460
|
if (scrollingHeaderIconsRef.current) scrollingHeaderIconsRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
|
|
1940
2461
|
if (headerIconsRef.current) headerIconsRef.current.style.transform = `translateY(${fixedHeader ? 0 : -element.scrollTop}px)`;
|
|
2462
|
+
updateCustomScrollbars();
|
|
2463
|
+
if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {
|
|
2464
|
+
showCustomScrollbar(scrollDeltaX !== 0 && scrollDeltaY !== 0 ? "both" : scrollDeltaX !== 0 ? "horizontal" : "vertical");
|
|
2465
|
+
}
|
|
1941
2466
|
if (hoveredHeaderActionRef.current) {
|
|
1942
2467
|
hoveredHeaderActionRef.current = null;
|
|
1943
2468
|
setHoveredHeaderAction(null);
|
|
@@ -1945,7 +2470,7 @@ function Table({
|
|
|
1945
2470
|
setHoveredCellTooltip(null);
|
|
1946
2471
|
scheduleDraw();
|
|
1947
2472
|
setEditing(null);
|
|
1948
|
-
}, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
|
|
2473
|
+
}, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars, viewport.width]);
|
|
1949
2474
|
const locateColumn = (0, import_react5.useCallback)((clientX) => {
|
|
1950
2475
|
const canvas = canvasRef.current;
|
|
1951
2476
|
if (!canvas) return -1;
|
|
@@ -1953,7 +2478,11 @@ function Table({
|
|
|
1953
2478
|
const localX = clientX - rect.left;
|
|
1954
2479
|
let columnIndex;
|
|
1955
2480
|
if (localX < fixedWidth) {
|
|
1956
|
-
columnIndex =
|
|
2481
|
+
columnIndex = columns.findIndex((column, index) => {
|
|
2482
|
+
if (column.fixed !== "left") return false;
|
|
2483
|
+
const left = leftFixedOffsets.get(index) ?? 0;
|
|
2484
|
+
return localX >= left && localX < left + metrics[index].width;
|
|
2485
|
+
});
|
|
1957
2486
|
} else if (rightFixedWidth > 0 && localX >= viewport.width - rightFixedWidth) {
|
|
1958
2487
|
columnIndex = columns.findIndex((column, index) => {
|
|
1959
2488
|
if (column.fixed !== "right") return false;
|
|
@@ -1961,41 +2490,33 @@ function Table({
|
|
|
1961
2490
|
return localX >= left && localX < left + metrics[index].width;
|
|
1962
2491
|
});
|
|
1963
2492
|
} else {
|
|
1964
|
-
|
|
2493
|
+
const scrollX = localX + scrollRef.current.left;
|
|
2494
|
+
columnIndex = columns.findIndex((column, index) => {
|
|
2495
|
+
if (column.fixed !== void 0) return false;
|
|
2496
|
+
const left = getScrollableColumnLeft(index);
|
|
2497
|
+
return scrollX >= left && scrollX < left + metrics[index].width;
|
|
2498
|
+
});
|
|
1965
2499
|
}
|
|
1966
2500
|
return columnIndex;
|
|
1967
|
-
}, [columns, fixedWidth, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
|
|
2501
|
+
}, [columns, fixedWidth, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
|
|
1968
2502
|
const getDisplayedColumnLeft = (0, import_react5.useCallback)((columnIndex) => {
|
|
1969
2503
|
const metric = metrics[columnIndex];
|
|
1970
|
-
if (columns[columnIndex].fixed === "left") return
|
|
2504
|
+
if (columns[columnIndex].fixed === "left") return leftFixedOffsets.get(columnIndex) ?? 0;
|
|
1971
2505
|
if (columns[columnIndex].fixed === "right") return viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metric.width;
|
|
1972
|
-
return
|
|
1973
|
-
}, [columns, metrics, rightFixedOffsets, viewport.width]);
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
for (let index = 0; index < columns.length; index += 1) {
|
|
1980
|
-
if (columns[index].rowSelection || columns[index].rowDragHandle || columns[index].rowNumber) continue;
|
|
1981
|
-
const nextDistance = Math.abs(localX - (getDisplayedColumnLeft(index) + metrics[index].width));
|
|
1982
|
-
if (nextDistance < distance) {
|
|
1983
|
-
distance = nextDistance;
|
|
1984
|
-
closest = index;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return closest;
|
|
1988
|
-
}, [columnResizable, columns, getDisplayedColumnLeft, metrics]);
|
|
1989
|
-
const findResizeNeighbor = (0, import_react5.useCallback)((columnIndex) => {
|
|
1990
|
-
const resizedColumn = columns[columnIndex];
|
|
1991
|
-
for (let index = columnIndex + 1; index < columns.length; index += 1) {
|
|
1992
|
-
const column = columns[index];
|
|
1993
|
-
if (column.rowSelection || column.rowDragHandle || column.rowNumber) continue;
|
|
1994
|
-
if (resizedColumn?.fixed && column.fixed !== resizedColumn.fixed) return -1;
|
|
1995
|
-
return index;
|
|
2506
|
+
return getScrollableColumnLeft(columnIndex) - scrollRef.current.left;
|
|
2507
|
+
}, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
|
|
2508
|
+
const getHeaderLevelAtY = (0, import_react5.useCallback)((localY, headerY) => {
|
|
2509
|
+
const y = localY - headerY;
|
|
2510
|
+
for (let level = 0; level < headerRowHeights.length; level += 1) {
|
|
2511
|
+
const top = headerRowOffsets[level] ?? 0;
|
|
2512
|
+
if (y >= top && y < top + headerRowHeights[level]) return level;
|
|
1996
2513
|
}
|
|
1997
|
-
return -1;
|
|
1998
|
-
}, [
|
|
2514
|
+
return headerDepth - 1;
|
|
2515
|
+
}, [headerDepth, headerRowHeights, headerRowOffsets]);
|
|
2516
|
+
const resizeColumn = (0, import_react5.useCallback)((columnKey, width2) => {
|
|
2517
|
+
setResizedColumnWidths((current) => current[columnKey] === width2 ? current : { ...current, [columnKey]: width2 });
|
|
2518
|
+
onColumnResize?.(columnKey, width2);
|
|
2519
|
+
}, [onColumnResize]);
|
|
1999
2520
|
const locateHeaderAction = (0, import_react5.useCallback)((clientX, columnIndex) => {
|
|
2000
2521
|
if (columnIndex < 0 || !canvasRef.current) return null;
|
|
2001
2522
|
const column = columns[columnIndex];
|
|
@@ -2030,11 +2551,97 @@ function Table({
|
|
|
2030
2551
|
const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
|
|
2031
2552
|
return localX >= columnRight - 16 && localX < columnRight - 5;
|
|
2032
2553
|
}, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
|
|
2033
|
-
const
|
|
2554
|
+
const getHeaderCellLeft = (0, import_react5.useCallback)((cell) => {
|
|
2555
|
+
const startMetric = metrics[cell.startIndex];
|
|
2556
|
+
const fixedSide = columns[cell.startIndex]?.fixed;
|
|
2557
|
+
if (!startMetric) return 0;
|
|
2558
|
+
return fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex) - scrollRef.current.left;
|
|
2559
|
+
}, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
|
|
2560
|
+
const getHeaderCellWidth = (0, import_react5.useCallback)((cell) => cell.leaf ? metrics[cell.startIndex]?.width ?? 0 : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1), [getCellDisplayWidth, metrics]);
|
|
2561
|
+
const getHeaderCellEdgeFromMetrics = (0, import_react5.useCallback)((startIndex, endIndex, edge, nextMetrics) => {
|
|
2562
|
+
const fixedSide = columns[startIndex]?.fixed;
|
|
2563
|
+
const width2 = nextMetrics.slice(startIndex, endIndex + 1).reduce((total, metric) => total + metric.width, 0);
|
|
2564
|
+
let left;
|
|
2565
|
+
if (fixedSide === "left") {
|
|
2566
|
+
left = 0;
|
|
2567
|
+
for (let index = 0; index < startIndex; index += 1) {
|
|
2568
|
+
if (columns[index].fixed === "left") left += nextMetrics[index]?.width ?? 0;
|
|
2569
|
+
}
|
|
2570
|
+
} else if (fixedSide === "right") {
|
|
2571
|
+
let rightOffset = 0;
|
|
2572
|
+
for (let index = columns.length - 1; index > startIndex; index -= 1) {
|
|
2573
|
+
if (columns[index].fixed === "right") rightOffset += nextMetrics[index]?.width ?? 0;
|
|
2574
|
+
}
|
|
2575
|
+
left = viewport.width - rightOffset - width2;
|
|
2576
|
+
} else {
|
|
2577
|
+
left = 0;
|
|
2578
|
+
for (let index = 0; index < startIndex; index += 1) {
|
|
2579
|
+
if (columns[index].fixed === void 0) left += nextMetrics[index]?.width ?? 0;
|
|
2580
|
+
}
|
|
2581
|
+
left += fixedWidth - scrollRef.current.left;
|
|
2582
|
+
}
|
|
2583
|
+
return left + (edge === "right" ? width2 : 0);
|
|
2584
|
+
}, [columns, fixedWidth, viewport.width]);
|
|
2585
|
+
const locateHeaderResizeHit = (0, import_react5.useCallback)((clientX, clientY) => {
|
|
2586
|
+
if (!columnResizable || !canvasRef.current) return null;
|
|
2587
|
+
const rect = canvasRef.current.getBoundingClientRect();
|
|
2588
|
+
const localX = clientX - rect.left;
|
|
2589
|
+
const localY = clientY - rect.top;
|
|
2590
|
+
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
2591
|
+
if (localY < headerY || localY >= headerY + headerHeight) return null;
|
|
2592
|
+
const level = getHeaderLevelAtY(localY, headerY);
|
|
2593
|
+
let closest = null;
|
|
2594
|
+
let distance = 6;
|
|
2595
|
+
let priority = -1;
|
|
2596
|
+
for (const cell of headerCells) {
|
|
2597
|
+
if (cell.level !== level) continue;
|
|
2598
|
+
if (columns[cell.startIndex]?.rowSelection || columns[cell.startIndex]?.rowDragHandle || columns[cell.startIndex]?.rowNumber) continue;
|
|
2599
|
+
const fixedSide = columns[cell.startIndex]?.fixed;
|
|
2600
|
+
const edge = fixedSide === "right" && cell.leaf ? "left" : "right";
|
|
2601
|
+
const edgeX = getHeaderCellLeft(cell) + (edge === "right" ? getHeaderCellWidth(cell) : 0);
|
|
2602
|
+
const nextDistance = Math.abs(localX - edgeX);
|
|
2603
|
+
const nextPriority = fixedSide === "right" ? 2 : fixedSide === "left" ? 1 : 0;
|
|
2604
|
+
if (nextDistance < distance || nextDistance === distance && nextPriority > priority) {
|
|
2605
|
+
distance = nextDistance;
|
|
2606
|
+
priority = nextPriority;
|
|
2607
|
+
closest = { cell, edge };
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
return closest;
|
|
2611
|
+
}, [columnResizable, columns, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
|
|
2612
|
+
const locateHeaderResizeCell = (0, import_react5.useCallback)((clientX, clientY) => locateHeaderResizeHit(clientX, clientY)?.cell ?? null, [locateHeaderResizeHit]);
|
|
2613
|
+
const locateHeaderCellDragHandle = (0, import_react5.useCallback)((clientX, cell) => {
|
|
2614
|
+
if (!columnDraggable || !canvasRef.current) return false;
|
|
2615
|
+
if (cell.leaf) return locateHeaderDragHandle(clientX, cell.startIndex);
|
|
2616
|
+
const localX = clientX - canvasRef.current.getBoundingClientRect().left;
|
|
2617
|
+
const right = getHeaderCellLeft(cell) + getHeaderCellWidth(cell);
|
|
2618
|
+
return localX >= right - 16 && localX < right - 5;
|
|
2619
|
+
}, [columnDraggable, getHeaderCellLeft, getHeaderCellWidth, locateHeaderDragHandle]);
|
|
2620
|
+
const locateHeaderCell = (0, import_react5.useCallback)((clientX, clientY) => {
|
|
2621
|
+
const canvas = canvasRef.current;
|
|
2622
|
+
if (!canvas) return null;
|
|
2623
|
+
const rect = canvas.getBoundingClientRect();
|
|
2624
|
+
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
2625
|
+
const localX = clientX - rect.left;
|
|
2626
|
+
const localY = clientY - rect.top;
|
|
2627
|
+
if (localY < headerY || localY >= headerY + headerHeight) return null;
|
|
2628
|
+
const level = getHeaderLevelAtY(localY, headerY);
|
|
2629
|
+
for (const cell of headerCells) {
|
|
2630
|
+
if (cell.level !== level) continue;
|
|
2631
|
+
const left = getHeaderCellLeft(cell);
|
|
2632
|
+
const width2 = getHeaderCellWidth(cell);
|
|
2633
|
+
if (localX >= left && localX < left + width2) return cell;
|
|
2634
|
+
}
|
|
2635
|
+
return null;
|
|
2636
|
+
}, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, getHeaderLevelAtY, headerCells, headerHeight]);
|
|
2637
|
+
const setDragGuide = (0, import_react5.useCallback)((x, top = 0) => {
|
|
2034
2638
|
const guide = dragGuideRef.current;
|
|
2035
2639
|
if (!guide) return;
|
|
2036
2640
|
guide.style.display = x === null ? "none" : "block";
|
|
2037
|
-
if (x !== null)
|
|
2641
|
+
if (x !== null) {
|
|
2642
|
+
guide.style.top = `${top}px`;
|
|
2643
|
+
guide.style.transform = `translateX(${Math.round(x)}px)`;
|
|
2644
|
+
}
|
|
2038
2645
|
}, []);
|
|
2039
2646
|
const setRowDragGuide = (0, import_react5.useCallback)((y) => {
|
|
2040
2647
|
const guide = rowDragGuideRef.current;
|
|
@@ -2052,6 +2659,15 @@ function Table({
|
|
|
2052
2659
|
(0, import_react5.useEffect)(() => {
|
|
2053
2660
|
const canvas = canvasRef.current;
|
|
2054
2661
|
if (!canvas || !columnDraggable && !rowDraggable) return;
|
|
2662
|
+
const getColumnDropIndex = (sourceIndex, targetIndex, edge) => {
|
|
2663
|
+
if (edge === "right") return targetIndex;
|
|
2664
|
+
const destinationIndex = targetIndex - (sourceIndex < targetIndex ? 1 : 0);
|
|
2665
|
+
return Math.max(0, Math.min(columns.length - 1, destinationIndex));
|
|
2666
|
+
};
|
|
2667
|
+
const getColumnDropPlacement = (sourceIndex, targetIndex, edge) => {
|
|
2668
|
+
if (edge !== "right") return "before";
|
|
2669
|
+
return targetIndex < sourceIndex ? "before" : "after";
|
|
2670
|
+
};
|
|
2055
2671
|
const getHeaderColumn = (clientX, clientY) => {
|
|
2056
2672
|
const rect = canvas.getBoundingClientRect();
|
|
2057
2673
|
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
@@ -2059,22 +2675,35 @@ function Table({
|
|
|
2059
2675
|
if (localY < headerY || localY >= headerY + headerHeight) return -1;
|
|
2060
2676
|
return locateColumn(clientX);
|
|
2061
2677
|
};
|
|
2678
|
+
const getHeaderDragCell = (clientX, clientY) => {
|
|
2679
|
+
const cell = locateHeaderCell(clientX, clientY);
|
|
2680
|
+
if (!cell) return null;
|
|
2681
|
+
return locateHeaderCellDragHandle(clientX, cell) && !locateHeaderResizeCell(clientX, clientY) ? cell : null;
|
|
2682
|
+
};
|
|
2062
2683
|
const cleanupDrag = (0, import_adapter.draggable)({
|
|
2063
2684
|
element: canvas,
|
|
2064
2685
|
canDrag: ({ input }) => {
|
|
2065
|
-
const
|
|
2066
|
-
if (
|
|
2686
|
+
const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
|
|
2687
|
+
if (headerDragCell) return true;
|
|
2067
2688
|
const columnIndex = locateColumn(input.clientX);
|
|
2068
2689
|
return rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(input.clientY) >= 0;
|
|
2069
2690
|
},
|
|
2070
2691
|
getInitialData: ({ input }) => {
|
|
2071
|
-
const
|
|
2072
|
-
if (
|
|
2692
|
+
const headerDragCell = columnDraggable ? getHeaderDragCell(input.clientX, input.clientY) : null;
|
|
2693
|
+
if (!headerDragCell) {
|
|
2073
2694
|
return { type: "table-row", sourceIndex: locateRow(input.clientY) };
|
|
2074
2695
|
}
|
|
2075
|
-
const sourceIndex =
|
|
2696
|
+
const sourceIndex = headerDragCell.startIndex;
|
|
2076
2697
|
columnDragPreviewRef.current = { sourceIndex, targetIndex: sourceIndex };
|
|
2077
|
-
return {
|
|
2698
|
+
return {
|
|
2699
|
+
type: "table-column",
|
|
2700
|
+
sourceIndex,
|
|
2701
|
+
sourceKey: headerDragCell.key,
|
|
2702
|
+
parentKey: headerDragCell.parentKey,
|
|
2703
|
+
headerLevel: headerDragCell.level,
|
|
2704
|
+
headerKind: headerDragCell.leaf ? "column" : "group",
|
|
2705
|
+
fixed: columns[headerDragCell.startIndex]?.fixed
|
|
2706
|
+
};
|
|
2078
2707
|
},
|
|
2079
2708
|
onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
|
|
2080
2709
|
const sourceIndex = Number(source.data.sourceIndex);
|
|
@@ -2177,31 +2806,35 @@ function Table({
|
|
|
2177
2806
|
return;
|
|
2178
2807
|
}
|
|
2179
2808
|
const targetIndex = Number(target?.targetIndex);
|
|
2180
|
-
if (!Number.isInteger(targetIndex) ||
|
|
2809
|
+
if (!Number.isInteger(targetIndex) || target?.headerKind !== source.data.headerKind || target?.parentKey !== source.data.parentKey || target?.fixed !== source.data.fixed) {
|
|
2181
2810
|
setDragGuide(null);
|
|
2182
2811
|
return;
|
|
2183
2812
|
}
|
|
2184
2813
|
const edge = target?.columnEdge;
|
|
2185
|
-
|
|
2186
|
-
if (sourceIndex < destinationIndex) destinationIndex -= 1;
|
|
2187
|
-
destinationIndex = Math.max(0, Math.min(columns.length - 1, destinationIndex));
|
|
2814
|
+
const destinationIndex = getColumnDropIndex(sourceIndex, targetIndex, edge);
|
|
2188
2815
|
columnDragPreviewRef.current = { sourceIndex, targetIndex: destinationIndex };
|
|
2189
|
-
const targetLeft =
|
|
2190
|
-
|
|
2816
|
+
const targetLeft = Number(target?.targetLeft);
|
|
2817
|
+
const targetWidth = Number(target?.targetWidth);
|
|
2818
|
+
if (!Number.isFinite(targetLeft) || !Number.isFinite(targetWidth)) {
|
|
2819
|
+
setDragGuide(null);
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
const guideTop = headerRowOffsets[Number(source.data.headerLevel)] ?? 0;
|
|
2823
|
+
setDragGuide(edge === "right" ? targetLeft + targetWidth : targetLeft, guideTop);
|
|
2191
2824
|
},
|
|
2192
2825
|
onDrop: ({ source, location }) => {
|
|
2193
2826
|
const target = location.current.dropTargets[0]?.data;
|
|
2194
2827
|
const sourceIndex = Number(source.data.sourceIndex);
|
|
2195
2828
|
if (source.data.type === "table-row") {
|
|
2196
|
-
const
|
|
2197
|
-
let targetIndex2 =
|
|
2829
|
+
const rawTargetIndex2 = Number(target?.targetIndex);
|
|
2830
|
+
let targetIndex2 = rawTargetIndex2 + (target?.rowEdge === "bottom" ? 1 : 0);
|
|
2198
2831
|
if (sourceIndex < targetIndex2) targetIndex2 -= 1;
|
|
2199
2832
|
targetIndex2 = Math.max(0, Math.min(rows.length - 1, targetIndex2));
|
|
2200
2833
|
scrollerRef.current?.classList.add("rvg-row-drop-settling");
|
|
2201
2834
|
setRowDragPreview(null);
|
|
2202
2835
|
setRowDragGuide(null);
|
|
2203
2836
|
canvas.style.cursor = "default";
|
|
2204
|
-
if (Number.isInteger(
|
|
2837
|
+
if (Number.isInteger(rawTargetIndex2) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
|
|
2205
2838
|
requestAnimationFrame(() => {
|
|
2206
2839
|
requestAnimationFrame(() => scrollerRef.current?.classList.remove("rvg-row-drop-settling"));
|
|
2207
2840
|
});
|
|
@@ -2210,12 +2843,23 @@ function Table({
|
|
|
2210
2843
|
}, 0);
|
|
2211
2844
|
return;
|
|
2212
2845
|
}
|
|
2213
|
-
const
|
|
2846
|
+
const rawTargetIndex = Number(target?.targetIndex);
|
|
2847
|
+
const targetIndex = columnDragPreviewRef.current?.targetIndex ?? (Number.isInteger(rawTargetIndex) ? getColumnDropIndex(sourceIndex, rawTargetIndex, target?.columnEdge) : rawTargetIndex);
|
|
2214
2848
|
canvas.style.cursor = "default";
|
|
2215
2849
|
setDragGuide(null);
|
|
2216
2850
|
columnDragPreviewRef.current = null;
|
|
2217
|
-
if (Number.isInteger(targetIndex) && sourceIndex !== targetIndex &&
|
|
2218
|
-
onColumnOrderChange?.(
|
|
2851
|
+
if (Number.isInteger(rawTargetIndex) && Number.isInteger(targetIndex) && sourceIndex !== targetIndex && target?.headerKind === source.data.headerKind && target?.parentKey === source.data.parentKey && target?.fixed === source.data.fixed) {
|
|
2852
|
+
onColumnOrderChange?.(
|
|
2853
|
+
sourceIndex - utilityColumnCount,
|
|
2854
|
+
targetIndex - utilityColumnCount,
|
|
2855
|
+
{
|
|
2856
|
+
type: source.data.headerKind === "group" ? "group" : "column",
|
|
2857
|
+
sourceKey: String(source.data.sourceKey),
|
|
2858
|
+
targetKey: String(target.targetKey),
|
|
2859
|
+
parentKey: typeof source.data.parentKey === "string" ? source.data.parentKey : void 0,
|
|
2860
|
+
placement: getColumnDropPlacement(sourceIndex, rawTargetIndex, target.columnEdge)
|
|
2861
|
+
}
|
|
2862
|
+
);
|
|
2219
2863
|
}
|
|
2220
2864
|
window.setTimeout(() => {
|
|
2221
2865
|
suppressClickRef.current = false;
|
|
@@ -2233,24 +2877,38 @@ function Table({
|
|
|
2233
2877
|
const rowEdge = input.clientY - canvas.getBoundingClientRect().top < rowTop + rowHeight / 2 ? "top" : "bottom";
|
|
2234
2878
|
return (0, import_closest_edge.attachClosestEdge)({ targetIndex: targetIndex2, rowEdge }, { element, input, allowedEdges: ["top", "bottom"] });
|
|
2235
2879
|
}
|
|
2236
|
-
|
|
2237
|
-
if (
|
|
2880
|
+
const targetCell = locateHeaderCell(input.clientX, input.clientY);
|
|
2881
|
+
if (!targetCell) return { targetIndex: -1 };
|
|
2882
|
+
let targetIndex = targetCell.startIndex;
|
|
2883
|
+
const targetFixed = columns[targetCell.startIndex]?.fixed;
|
|
2884
|
+
const headerKind = targetCell.leaf ? "column" : "group";
|
|
2885
|
+
if (source.data.headerKind !== headerKind || source.data.parentKey !== targetCell.parentKey || source.data.fixed !== targetFixed) return { targetIndex: -1 };
|
|
2238
2886
|
if (columns[targetIndex].rowSelection || columns[targetIndex].rowDragHandle || columns[targetIndex].rowNumber) {
|
|
2239
2887
|
const fixedSide = columns[Number(source.data.sourceIndex)]?.fixed;
|
|
2240
2888
|
targetIndex = columns.findIndex((column, index) => index > targetIndex && column.fixed === fixedSide && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
|
|
2241
2889
|
if (targetIndex < 0) return { targetIndex: -1 };
|
|
2242
2890
|
return { targetIndex, columnEdge: "left" };
|
|
2243
2891
|
}
|
|
2244
|
-
const left =
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2892
|
+
const left = getHeaderCellLeft(targetCell);
|
|
2893
|
+
const targetWidth = targetCell.leaf ? metrics[targetIndex].width : getHeaderCellWidth(targetCell);
|
|
2894
|
+
const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + targetWidth / 2 ? "left" : "right";
|
|
2895
|
+
return (0, import_closest_edge.attachClosestEdge)({
|
|
2896
|
+
targetIndex,
|
|
2897
|
+
targetKey: targetCell.key,
|
|
2898
|
+
parentKey: targetCell.parentKey,
|
|
2899
|
+
headerKind,
|
|
2900
|
+
fixed: targetFixed,
|
|
2901
|
+
targetLeft: left,
|
|
2902
|
+
targetWidth,
|
|
2903
|
+
columnEdge
|
|
2904
|
+
}, { element, input, allowedEdges: ["left", "right"] });
|
|
2247
2905
|
}
|
|
2248
2906
|
});
|
|
2249
2907
|
return () => {
|
|
2250
2908
|
cleanupDrag();
|
|
2251
2909
|
cleanupDrop();
|
|
2252
2910
|
};
|
|
2253
|
-
}, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getRowKey, headerHeight, locateColumn,
|
|
2911
|
+
}, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getHeaderCellWidth, getRowKey, headerHeight, locateColumn, locateHeaderCell, locateHeaderCellDragHandle, locateHeaderResizeCell, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
|
|
2254
2912
|
const locateCell = (0, import_react5.useCallback)((clientX, clientY) => {
|
|
2255
2913
|
const canvas = canvasRef.current;
|
|
2256
2914
|
if (!canvas) return null;
|
|
@@ -2357,8 +3015,9 @@ function Table({
|
|
|
2357
3015
|
const horizontalFocusGap = 0;
|
|
2358
3016
|
const verticalFocusGap = 0;
|
|
2359
3017
|
if (column.fixed === void 0) {
|
|
2360
|
-
|
|
2361
|
-
if (
|
|
3018
|
+
const cellLeft = getScrollableColumnLeft(cell.columnIndex);
|
|
3019
|
+
if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
|
|
3020
|
+
if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
|
|
2362
3021
|
}
|
|
2363
3022
|
const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
|
|
2364
3023
|
const rowBottom = rowTop + rowHeight * rowSpan;
|
|
@@ -2371,7 +3030,7 @@ function Table({
|
|
|
2371
3030
|
scroller.scrollTop = nextTop;
|
|
2372
3031
|
scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
|
|
2373
3032
|
scheduleDraw();
|
|
2374
|
-
}, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
|
|
3033
|
+
}, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
|
|
2375
3034
|
const beginEdit = (0, import_react5.useCallback)((cell) => {
|
|
2376
3035
|
const column = columns[cell.columnIndex];
|
|
2377
3036
|
if (!column?.editable || column.dataIndex === void 0) return;
|
|
@@ -2392,8 +3051,9 @@ function Table({
|
|
|
2392
3051
|
const horizontalFocusGap = 0;
|
|
2393
3052
|
const verticalFocusGap = 0;
|
|
2394
3053
|
if (column.fixed === void 0) {
|
|
2395
|
-
|
|
2396
|
-
if (
|
|
3054
|
+
const cellLeft = getScrollableColumnLeft(cell.columnIndex);
|
|
3055
|
+
if (cellLeft - nextLeft < fixedWidth) nextLeft = Math.max(0, cellLeft - fixedWidth - horizontalFocusGap);
|
|
3056
|
+
if (cellLeft + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
|
|
2397
3057
|
}
|
|
2398
3058
|
const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
|
|
2399
3059
|
const rowBottom = rowTop + rowHeight * rowSpan;
|
|
@@ -2415,7 +3075,7 @@ function Table({
|
|
|
2415
3075
|
return;
|
|
2416
3076
|
}
|
|
2417
3077
|
setEditing(cell);
|
|
2418
|
-
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
3078
|
+
}, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
|
|
2419
3079
|
const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
|
|
2420
3080
|
const markCellEdited = (cell, previousValue, nextValue) => {
|
|
2421
3081
|
const key = getCellEditKey(cell);
|
|
@@ -2546,75 +3206,113 @@ function Table({
|
|
|
2546
3206
|
const visibleRowHeight = fixedHeader ? renderHeight - bodyTop : renderHeight;
|
|
2547
3207
|
if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
|
|
2548
3208
|
if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
|
|
2549
|
-
if (columns[columnIndex].fixed === void 0
|
|
2550
|
-
|
|
3209
|
+
if (columns[columnIndex].fixed === void 0) {
|
|
3210
|
+
const cellLeft = getScrollableColumnLeft(columnIndex);
|
|
3211
|
+
if (cellLeft < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, cellLeft - fixedWidth);
|
|
3212
|
+
if (cellLeft + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = cellLeft + cellWidth - viewport.width + rightFixedWidth;
|
|
3213
|
+
}
|
|
2551
3214
|
}
|
|
2552
|
-
}, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
|
|
3215
|
+
}, [beginEdit, bodyTop, columns, editing, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
|
|
2553
3216
|
const editorStyle = (0, import_react5.useMemo)(() => {
|
|
2554
3217
|
if (!editing) return void 0;
|
|
2555
3218
|
const metric = metrics[editing.columnIndex];
|
|
2556
|
-
const
|
|
3219
|
+
const column = columns[editing.columnIndex];
|
|
3220
|
+
if (!metric || !column) return void 0;
|
|
3221
|
+
const fixedSide = column.fixed;
|
|
2557
3222
|
const span = getCellSpan(editing.rowIndex, editing.columnIndex);
|
|
2558
3223
|
const cellWidth = getCellDisplayWidth(editing.columnIndex, span?.colSpan ?? 1);
|
|
2559
3224
|
const cellHeight = rowHeight * (span?.rowSpan ?? 1);
|
|
2560
|
-
const cellLeft = fixedSide === "left" ?
|
|
3225
|
+
const cellLeft = fixedSide === "left" ? leftFixedOffsets.get(editing.columnIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(editing.columnIndex) ?? 0) - metric.width : getScrollableColumnLeft(editing.columnIndex) - scrollRef.current.left;
|
|
3226
|
+
const cellTop = bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top;
|
|
3227
|
+
const horizontalStart = fixedSide === void 0 ? fixedWidth : 0;
|
|
3228
|
+
const horizontalEnd = fixedSide === void 0 ? viewport.width - rightFixedWidth : viewport.width;
|
|
3229
|
+
const verticalStart = fixedHeader ? bodyTop : 0;
|
|
3230
|
+
const visibleLeft = Math.max(cellLeft, horizontalStart);
|
|
3231
|
+
const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
|
|
3232
|
+
const visibleTop = Math.max(cellTop, verticalStart);
|
|
3233
|
+
const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
|
|
2561
3234
|
return {
|
|
2562
|
-
left:
|
|
2563
|
-
top:
|
|
2564
|
-
width:
|
|
2565
|
-
height:
|
|
3235
|
+
left: visibleLeft,
|
|
3236
|
+
top: visibleTop,
|
|
3237
|
+
width: Math.max(0, visibleRight - visibleLeft),
|
|
3238
|
+
height: Math.max(0, visibleBottom - visibleTop)
|
|
2566
3239
|
};
|
|
2567
|
-
}, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedOffsets, rowHeight, viewport.width]);
|
|
3240
|
+
}, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
|
|
2568
3241
|
const handleColumnPointerDown = (0, import_react5.useCallback)((event) => {
|
|
2569
3242
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
2570
3243
|
const localY = event.clientY - rect.top;
|
|
2571
3244
|
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
2572
3245
|
if (localY < headerY || localY >= headerY + headerHeight) return;
|
|
2573
|
-
const
|
|
2574
|
-
if (
|
|
3246
|
+
const resizeHit = locateHeaderResizeHit(event.clientX, event.clientY);
|
|
3247
|
+
if (resizeHit) {
|
|
3248
|
+
const { cell: resizeCell, edge: resizeEdge } = resizeHit;
|
|
2575
3249
|
event.preventDefault();
|
|
2576
3250
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2577
|
-
const
|
|
2578
|
-
const
|
|
3251
|
+
const resizeIndex = resizeCell.endIndex;
|
|
3252
|
+
const resizeIndices = resizeCell.leaf ? [resizeIndex] : Array.from({ length: resizeCell.endIndex - resizeCell.startIndex + 1 }, (_, index) => resizeCell.startIndex + index);
|
|
3253
|
+
const startWidths = resizeIndices.map((index) => metrics[index].width);
|
|
3254
|
+
const startGuideX = getHeaderCellLeft(resizeCell) + (resizeEdge === "right" ? getHeaderCellWidth(resizeCell) : 0);
|
|
2579
3255
|
columnDragRef.current = {
|
|
2580
3256
|
type: "resize",
|
|
2581
3257
|
columnIndex: resizeIndex,
|
|
2582
3258
|
startX: event.clientX,
|
|
2583
|
-
startWidth:
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
3259
|
+
startWidth: startWidths.reduce((total, width2) => total + width2, 0),
|
|
3260
|
+
guideTop: headerRowOffsets[resizeCell.level] ?? 0,
|
|
3261
|
+
resizeEdge,
|
|
3262
|
+
resizeStartIndex: resizeCell.startIndex,
|
|
3263
|
+
resizeEndIndex: resizeCell.endIndex,
|
|
3264
|
+
resizeIndices,
|
|
3265
|
+
startWidths
|
|
2587
3266
|
};
|
|
2588
3267
|
suppressClickRef.current = true;
|
|
3268
|
+
setResizeGuideTop(headerRowOffsets[resizeCell.level] ?? 0);
|
|
2589
3269
|
setResizeGuideX(startGuideX);
|
|
2590
3270
|
return;
|
|
2591
3271
|
}
|
|
2592
|
-
}, [
|
|
3272
|
+
}, [fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerHeight, headerRowOffsets, locateHeaderResizeHit, metrics]);
|
|
2593
3273
|
const handleColumnPointerMove = (0, import_react5.useCallback)((event) => {
|
|
2594
3274
|
const drag = columnDragRef.current;
|
|
2595
3275
|
if (!drag) return;
|
|
2596
3276
|
const column = columns[drag.columnIndex];
|
|
2597
3277
|
const minWidth = getMinimumColumnWidth(column, columnDraggable);
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
const
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
3278
|
+
const pointerDelta = Math.round(event.clientX - drag.startX);
|
|
3279
|
+
const delta = drag.resizeEdge === "left" ? -pointerDelta : pointerDelta;
|
|
3280
|
+
const setGuideFromWidths = (nextWidths) => {
|
|
3281
|
+
const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
|
|
3282
|
+
const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
|
|
3283
|
+
const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: viewport.width });
|
|
3284
|
+
setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
|
|
3285
|
+
};
|
|
3286
|
+
if (drag.resizeIndices.length > 1) {
|
|
3287
|
+
const minWidths = drag.resizeIndices.map((index) => getMinimumColumnWidth(columns[index], columnDraggable));
|
|
3288
|
+
const minTotalWidth = minWidths.reduce((total, width3) => total + width3, 0);
|
|
3289
|
+
const targetTotalWidth = Math.max(minTotalWidth, drag.startWidth + delta);
|
|
3290
|
+
const ratio = drag.startWidth > 0 ? targetTotalWidth / drag.startWidth : 1;
|
|
3291
|
+
const nextWidths = drag.startWidths.map((width3, index) => Math.max(minWidths[index], Math.round(width3 * ratio)));
|
|
3292
|
+
let remainder = targetTotalWidth - nextWidths.reduce((total, width3) => total + width3, 0);
|
|
3293
|
+
for (let index = nextWidths.length - 1; index >= 0 && remainder !== 0; index -= 1) {
|
|
3294
|
+
const next = nextWidths[index] + (remainder > 0 ? 1 : -1);
|
|
3295
|
+
if (next < minWidths[index]) continue;
|
|
3296
|
+
nextWidths[index] = next;
|
|
3297
|
+
remainder += remainder > 0 ? -1 : 1;
|
|
3298
|
+
}
|
|
3299
|
+
drag.resizeIndices.forEach((columnIndex, index) => {
|
|
3300
|
+
resizeColumn(columns[columnIndex].key, nextWidths[index]);
|
|
3301
|
+
});
|
|
3302
|
+
setGuideFromWidths(nextWidths);
|
|
2606
3303
|
return;
|
|
2607
3304
|
}
|
|
2608
3305
|
const width2 = Math.max(minWidth, Math.round(drag.startWidth + delta));
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
}, [columnDraggable, columns,
|
|
3306
|
+
resizeColumn(column.key, width2);
|
|
3307
|
+
setGuideFromWidths([width2]);
|
|
3308
|
+
}, [columnDraggable, columns, getHeaderCellEdgeFromMetrics, resizeColumn, viewport.width]);
|
|
2612
3309
|
const handleColumnPointerUp = (0, import_react5.useCallback)((event) => {
|
|
2613
3310
|
const drag = columnDragRef.current;
|
|
2614
3311
|
if (!drag) return;
|
|
2615
3312
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
2616
3313
|
columnDragRef.current = null;
|
|
2617
3314
|
setResizeGuideX(null);
|
|
3315
|
+
setResizeGuideTop(0);
|
|
2618
3316
|
}, []);
|
|
2619
3317
|
const queueSortStateChange = (0, import_react5.useCallback)((next) => {
|
|
2620
3318
|
setDisplaySortState(next);
|
|
@@ -2627,7 +3325,7 @@ function Table({
|
|
|
2627
3325
|
const renderHeaderIcons = (columnIndex, left) => {
|
|
2628
3326
|
const column = columns[columnIndex];
|
|
2629
3327
|
if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
|
|
2630
|
-
const top = (
|
|
3328
|
+
const top = headerLeafTop + (headerLeafHeight - 14) / 2;
|
|
2631
3329
|
const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
|
|
2632
3330
|
let right = left + metrics[columnIndex].width - (visibleActions.drag ? 18 : 4);
|
|
2633
3331
|
const icons = [];
|
|
@@ -2653,44 +3351,87 @@ function Table({
|
|
|
2653
3351
|
}
|
|
2654
3352
|
return icons;
|
|
2655
3353
|
};
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
const { columnIndex, action } = visibleHeaderTooltip;
|
|
2659
|
-
const column = columns[columnIndex];
|
|
3354
|
+
const createHeaderTooltip = (0, import_react5.useCallback)((action, columnIndex, cell) => {
|
|
3355
|
+
const column = cell?.column ?? columns[columnIndex];
|
|
2660
3356
|
if (!column) return null;
|
|
2661
|
-
const
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
3357
|
+
const cellLeft = cell ? getHeaderCellLeft(cell) : getDisplayedColumnLeft(columnIndex);
|
|
3358
|
+
const cellWidth = cell ? getHeaderCellWidth(cell) : metrics[columnIndex]?.width ?? 0;
|
|
3359
|
+
const level = cell?.level ?? headerDepth - 1;
|
|
3360
|
+
const rowTop = (fixedHeader ? 0 : -scrollPosition.top) + (headerRowOffsets[level] ?? 0);
|
|
3361
|
+
const rowHeight2 = headerRowHeights[level] ?? baseHeaderHeight;
|
|
3362
|
+
let anchorX = cellLeft + cellWidth / 2;
|
|
3363
|
+
let anchorLeft = cellLeft;
|
|
3364
|
+
let label = column.title;
|
|
2666
3365
|
if (action === "title") {
|
|
2667
|
-
const
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
const
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
3366
|
+
const canvas = canvasRef.current;
|
|
3367
|
+
const context = canvas?.getContext("2d");
|
|
3368
|
+
if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
3369
|
+
const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
|
|
3370
|
+
const visibleActions = cell?.leaf || !cell ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
|
|
3371
|
+
const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
|
|
3372
|
+
const contentWidth2 = Math.max(0, cellWidth - actionWidth);
|
|
3373
|
+
const maxTextWidth = Math.max(0, contentWidth2 - 20);
|
|
3374
|
+
const textWidth = Math.min(titleWidth, maxTextWidth);
|
|
3375
|
+
if (column.align === "right") {
|
|
3376
|
+
anchorX = cellLeft + contentWidth2 - 10;
|
|
3377
|
+
anchorLeft = anchorX - textWidth;
|
|
3378
|
+
} else if (column.align === "center") {
|
|
3379
|
+
anchorX = cellLeft + contentWidth2 / 2 + textWidth / 2;
|
|
3380
|
+
anchorLeft = cellLeft + contentWidth2 / 2 - textWidth / 2;
|
|
3381
|
+
} else {
|
|
3382
|
+
anchorLeft = cellLeft + 10;
|
|
3383
|
+
anchorX = anchorLeft + textWidth;
|
|
3384
|
+
}
|
|
2675
3385
|
} else if (action === "drag") {
|
|
2676
|
-
anchorX =
|
|
3386
|
+
anchorX = cellLeft + cellWidth;
|
|
2677
3387
|
anchorLeft = anchorX;
|
|
2678
3388
|
label = labels.dragColumn;
|
|
2679
3389
|
} else if (action === "sort") {
|
|
2680
3390
|
const direction = displaySortState?.columnKey === column.key ? displaySortState.direction : null;
|
|
2681
3391
|
label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
|
|
2682
|
-
|
|
2683
|
-
|
|
3392
|
+
anchorX = cellLeft + cellWidth - 22;
|
|
3393
|
+
anchorLeft = anchorX;
|
|
3394
|
+
} else if (action === "filter") {
|
|
2684
3395
|
const value = filterValues[column.key];
|
|
2685
3396
|
label = value ? labels.filterWithValue(value) : labels.filter;
|
|
3397
|
+
anchorX = cellLeft + cellWidth - 40;
|
|
3398
|
+
anchorLeft = anchorX;
|
|
2686
3399
|
}
|
|
2687
3400
|
const placement = anchorX + 328 > viewport.width ? "left" : "right";
|
|
2688
3401
|
return {
|
|
3402
|
+
key: `${action}:${cell ? `cell:${cell.key}:${cell.level}` : `column:${column.key}`}:${Math.round(anchorX)}:${Math.round(rowTop)}`,
|
|
3403
|
+
columnIndex,
|
|
3404
|
+
action,
|
|
2689
3405
|
left: placement === "right" ? anchorX + 8 : Math.max(8, anchorLeft - 8),
|
|
2690
|
-
top:
|
|
3406
|
+
top: rowTop + rowHeight2 / 2,
|
|
2691
3407
|
label,
|
|
2692
3408
|
placement
|
|
2693
3409
|
};
|
|
3410
|
+
}, [baseHeaderHeight, columnDraggable, columns, displaySortState, filterValues, fixedHeader, getDisplayedColumnLeft, getHeaderCellLeft, getHeaderCellWidth, headerDepth, headerRowHeights, headerRowOffsets, labels, metrics, scrollPosition.top, viewport.width]);
|
|
3411
|
+
const isPointerOnHeaderTitle = (0, import_react5.useCallback)((clientX, clientY, cell) => {
|
|
3412
|
+
const canvas = canvasRef.current;
|
|
3413
|
+
if (!canvas) return false;
|
|
3414
|
+
const rect = canvas.getBoundingClientRect();
|
|
3415
|
+
const localX = clientX - rect.left;
|
|
3416
|
+
const localY = clientY - rect.top;
|
|
3417
|
+
const column = cell.column;
|
|
3418
|
+
const cellLeft = getHeaderCellLeft(cell);
|
|
3419
|
+
const cellWidth = getHeaderCellWidth(cell);
|
|
3420
|
+
const rowTop = (fixedHeader ? 0 : -scrollRef.current.top) + (headerRowOffsets[cell.level] ?? 0);
|
|
3421
|
+
const context = canvas.getContext("2d");
|
|
3422
|
+
if (context) context.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
|
|
3423
|
+
const titleWidth = context?.measureText(column.title).width ?? column.title.length * 13;
|
|
3424
|
+
const visibleActions = cell.leaf ? getVisibleHeaderActions(column, cellWidth, columnDraggable, titleWidth) : null;
|
|
3425
|
+
const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable ? 18 : 0;
|
|
3426
|
+
const contentWidth2 = Math.max(0, cellWidth - actionWidth);
|
|
3427
|
+
const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
|
|
3428
|
+
const textLeft = column.align === "right" ? cellLeft + contentWidth2 - 10 - textWidth : column.align === "center" ? cellLeft + contentWidth2 / 2 - textWidth / 2 : cellLeft + 10;
|
|
3429
|
+
const textTop = rowTop + 8;
|
|
3430
|
+
return localX >= textLeft - 4 && localX <= textLeft + textWidth + 4 && localY >= textTop - 2 && localY <= textTop + 18;
|
|
3431
|
+
}, [columnDraggable, fixedHeader, getHeaderCellLeft, getHeaderCellWidth, headerRowOffsets]);
|
|
3432
|
+
const headerTooltip = (() => {
|
|
3433
|
+
if (!visibleHeaderTooltip) return null;
|
|
3434
|
+
return visibleHeaderTooltip;
|
|
2694
3435
|
})();
|
|
2695
3436
|
const getRangeBounds = () => selectionRange ? {
|
|
2696
3437
|
rowStart: Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex),
|
|
@@ -2749,7 +3490,7 @@ function Table({
|
|
|
2749
3490
|
}, 1800);
|
|
2750
3491
|
};
|
|
2751
3492
|
const showHeaderTooltipAfterDelay = (next) => {
|
|
2752
|
-
const nextKey = next ?
|
|
3493
|
+
const nextKey = next ? next.key : "";
|
|
2753
3494
|
if (nextKey && pendingHeaderTooltipKeyRef.current === nextKey) return;
|
|
2754
3495
|
pendingHeaderTooltipKeyRef.current = nextKey;
|
|
2755
3496
|
if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
|
|
@@ -2997,12 +3738,14 @@ function Table({
|
|
|
2997
3738
|
const renderHeaderTitle = (columnIndex, left) => {
|
|
2998
3739
|
const column = columns[columnIndex];
|
|
2999
3740
|
const currentScrollTop = scrollRef.current.top;
|
|
3741
|
+
const headerTop = fixedHeader ? 0 : -currentScrollTop;
|
|
3742
|
+
const leafTop = headerTop + headerLeafTop;
|
|
3000
3743
|
if (column.rowNumber) {
|
|
3001
3744
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3002
3745
|
"div",
|
|
3003
3746
|
{
|
|
3004
3747
|
className: "rvg-header-title",
|
|
3005
|
-
style: { left, top:
|
|
3748
|
+
style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
|
|
3006
3749
|
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: column.title })
|
|
3007
3750
|
},
|
|
3008
3751
|
column.key
|
|
@@ -3016,7 +3759,7 @@ function Table({
|
|
|
3016
3759
|
"div",
|
|
3017
3760
|
{
|
|
3018
3761
|
className: "rvg-header-selection-icon",
|
|
3019
|
-
style: { left, top:
|
|
3762
|
+
style: { left, top: headerTop, width: metrics[columnIndex].width, height: headerHeight },
|
|
3020
3763
|
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectionIcon, { checked: allSelected, indeterminate })
|
|
3021
3764
|
},
|
|
3022
3765
|
column.key
|
|
@@ -3029,11 +3772,13 @@ function Table({
|
|
|
3029
3772
|
"div",
|
|
3030
3773
|
{
|
|
3031
3774
|
className: "rvg-header-title is-custom",
|
|
3775
|
+
"data-level": headerDepth - 1,
|
|
3776
|
+
"data-measure-key": column.key,
|
|
3032
3777
|
style: {
|
|
3033
3778
|
left,
|
|
3034
|
-
top:
|
|
3779
|
+
top: leafTop,
|
|
3035
3780
|
width: Math.max(0, metrics[columnIndex].width - actionWidth),
|
|
3036
|
-
height:
|
|
3781
|
+
height: headerLeafHeight,
|
|
3037
3782
|
justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
|
|
3038
3783
|
textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
|
|
3039
3784
|
},
|
|
@@ -3041,8 +3786,10 @@ function Table({
|
|
|
3041
3786
|
"span",
|
|
3042
3787
|
{
|
|
3043
3788
|
ref: (node) => {
|
|
3044
|
-
if (node)
|
|
3045
|
-
|
|
3789
|
+
if (node) {
|
|
3790
|
+
customHeaderContentRefs.current.set(column.key, node);
|
|
3791
|
+
requestAnimationFrame(measureCustomHeaderHeight);
|
|
3792
|
+
} else customHeaderContentRefs.current.delete(column.key);
|
|
3046
3793
|
},
|
|
3047
3794
|
children: column.renderHeader(column)
|
|
3048
3795
|
}
|
|
@@ -3053,6 +3800,61 @@ function Table({
|
|
|
3053
3800
|
}
|
|
3054
3801
|
return null;
|
|
3055
3802
|
};
|
|
3803
|
+
const renderGroupedHeaderCell = (cell) => {
|
|
3804
|
+
const startMetric = metrics[cell.startIndex];
|
|
3805
|
+
const endMetric = metrics[cell.endIndex];
|
|
3806
|
+
if (!startMetric || !endMetric) return null;
|
|
3807
|
+
const column = cell.column;
|
|
3808
|
+
const fixedSide = columns[cell.startIndex]?.fixed;
|
|
3809
|
+
const currentScrollTop = scrollRef.current.top;
|
|
3810
|
+
const rowHeightForLevel = headerRowHeights[cell.level] ?? baseHeaderHeight;
|
|
3811
|
+
const headerTop = (fixedHeader ? 0 : -currentScrollTop) + (headerRowOffsets[cell.level] ?? 0);
|
|
3812
|
+
const left = fixedSide === "left" ? leftFixedOffsets.get(cell.startIndex) ?? 0 : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(cell.startIndex) ?? 0) - startMetric.width : getScrollableColumnLeft(cell.startIndex);
|
|
3813
|
+
const width2 = cell.leaf ? startMetric.width : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1);
|
|
3814
|
+
const visibleActions = cell.leaf ? getVisibleHeaderActions(column, startMetric.width, columnDraggable, measureHeaderTitleWidth(cell.startIndex)) : null;
|
|
3815
|
+
const actionWidth = visibleActions ? (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18 : columnDraggable && !cell.leaf ? 18 : 0;
|
|
3816
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3817
|
+
"div",
|
|
3818
|
+
{
|
|
3819
|
+
className: `rvg-header-title rvg-header-group${column.renderHeader ? " is-custom" : ""}${cell.leaf ? " is-leaf" : ""}`,
|
|
3820
|
+
"data-level": cell.level,
|
|
3821
|
+
"data-measure-key": `group:${cell.key}:${cell.level}`,
|
|
3822
|
+
style: {
|
|
3823
|
+
left,
|
|
3824
|
+
top: headerTop,
|
|
3825
|
+
width: width2,
|
|
3826
|
+
height: rowHeightForLevel,
|
|
3827
|
+
paddingRight: cell.leaf ? actionWidth + 10 : 10,
|
|
3828
|
+
justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
|
|
3829
|
+
textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left"
|
|
3830
|
+
},
|
|
3831
|
+
children: [
|
|
3832
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3833
|
+
"span",
|
|
3834
|
+
{
|
|
3835
|
+
ref: (node) => {
|
|
3836
|
+
if (!column.renderHeader) return;
|
|
3837
|
+
const key = `group:${cell.key}:${cell.level}`;
|
|
3838
|
+
if (node) {
|
|
3839
|
+
customHeaderContentRefs.current.set(key, node);
|
|
3840
|
+
requestAnimationFrame(measureCustomHeaderHeight);
|
|
3841
|
+
} else customHeaderContentRefs.current.delete(key);
|
|
3842
|
+
},
|
|
3843
|
+
children: column.renderHeader ? column.renderHeader(column) : column.title
|
|
3844
|
+
}
|
|
3845
|
+
),
|
|
3846
|
+
!cell.leaf && columnDraggable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3847
|
+
HeaderDragIcon,
|
|
3848
|
+
{
|
|
3849
|
+
className: `rvg-header-icon rvg-header-drag-icon${hoveredHeaderAction?.columnIndex === cell.startIndex && hoveredHeaderAction.action === "drag" ? " is-hovered" : ""}`,
|
|
3850
|
+
style: { left: width2 - 16, top: (rowHeightForLevel - 14) / 2 }
|
|
3851
|
+
}
|
|
3852
|
+
)
|
|
3853
|
+
]
|
|
3854
|
+
},
|
|
3855
|
+
`group:${cell.key}:${cell.level}`
|
|
3856
|
+
);
|
|
3857
|
+
};
|
|
3056
3858
|
const rangeHandlePosition = (() => {
|
|
3057
3859
|
if (!rangeSelection) return null;
|
|
3058
3860
|
const bounds = getRangeBounds();
|
|
@@ -3084,9 +3886,9 @@ function Table({
|
|
|
3084
3886
|
const verticalStart = fixedHeader ? bodyTop : 0;
|
|
3085
3887
|
if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
|
|
3086
3888
|
const borderWidth = 2;
|
|
3087
|
-
const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart
|
|
3889
|
+
const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
|
|
3088
3890
|
const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
|
|
3089
|
-
const top = Math.round(Math.max(rawTop - borderWidth, verticalStart
|
|
3891
|
+
const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
|
|
3090
3892
|
const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
|
|
3091
3893
|
return {
|
|
3092
3894
|
left,
|
|
@@ -3095,6 +3897,19 @@ function Table({
|
|
|
3095
3897
|
height: Math.max(0, bottom - top)
|
|
3096
3898
|
};
|
|
3097
3899
|
})();
|
|
3900
|
+
const selectionEdgeIndicator = getSelectionEdgeIndicator(scrollPosition.left, scrollPosition.top);
|
|
3901
|
+
const selectionEdgeStyle = selectionEdgeIndicator ? {
|
|
3902
|
+
...selectionEdgeIndicator.style,
|
|
3903
|
+
...selectionEdgeIndicator.corner ? {
|
|
3904
|
+
"--rvg-corner-width": `${selectionEdgeIndicator.corner.width}px`,
|
|
3905
|
+
"--rvg-corner-height": `${selectionEdgeIndicator.corner.height}px`,
|
|
3906
|
+
"--rvg-corner-hover-width-extension": "4px",
|
|
3907
|
+
"--rvg-corner-hover-height-extension": "4px"
|
|
3908
|
+
} : null
|
|
3909
|
+
} : void 0;
|
|
3910
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
3911
|
+
syncSelectionEdgeTarget(scrollPosition.left, scrollPosition.top);
|
|
3912
|
+
}, [scrollPosition.left, scrollPosition.top, syncSelectionEdgeTarget]);
|
|
3098
3913
|
const handleContextMenu = (event) => {
|
|
3099
3914
|
event.preventDefault();
|
|
3100
3915
|
event.stopPropagation();
|
|
@@ -3244,15 +4059,19 @@ function Table({
|
|
|
3244
4059
|
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
3245
4060
|
const columnIndex = locateColumn(event.clientX);
|
|
3246
4061
|
const inHeader = localY >= headerY && localY < headerY + headerHeight;
|
|
3247
|
-
const
|
|
3248
|
-
const
|
|
3249
|
-
const
|
|
3250
|
-
const
|
|
4062
|
+
const inLeafHeader = inHeader && localY >= headerY + headerLeafTop;
|
|
4063
|
+
const hoveredHeaderCell = inHeader ? locateHeaderCell(event.clientX, event.clientY) : null;
|
|
4064
|
+
const hoveredAction = inLeafHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
|
|
4065
|
+
const hoveredDragHandle = Boolean(hoveredHeaderCell && locateHeaderCellDragHandle(event.clientX, hoveredHeaderCell));
|
|
4066
|
+
const hoveredDragColumnIndex = hoveredHeaderCell?.startIndex ?? columnIndex;
|
|
4067
|
+
const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && hoveredHeaderCell && Boolean(hoveredHeaderCell.column.title) && isPointerOnHeaderTitle(event.clientX, event.clientY, hoveredHeaderCell) && (tooltipConfig.header || hoveredHeaderCell.leaf && isHeaderTitleTruncated(hoveredHeaderCell.startIndex));
|
|
4068
|
+
const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex: hoveredDragColumnIndex, action: "drag" } : hoveredTitle ? { columnIndex: hoveredHeaderCell.startIndex, action: "title" } : null;
|
|
4069
|
+
const nextTooltip = hoveredAction && hoveredHeaderCell ? createHeaderTooltip(hoveredAction, columnIndex, hoveredHeaderCell) : hoveredDragHandle && hoveredHeaderCell ? createHeaderTooltip("drag", hoveredDragColumnIndex, hoveredHeaderCell) : hoveredTitle && hoveredHeaderCell ? createHeaderTooltip("title", hoveredHeaderCell.startIndex, hoveredHeaderCell) : null;
|
|
3251
4070
|
const previousHover = hoveredHeaderActionRef.current;
|
|
3252
4071
|
if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
|
|
3253
4072
|
hoveredHeaderActionRef.current = nextHover;
|
|
3254
4073
|
setHoveredHeaderAction(nextHover);
|
|
3255
|
-
showHeaderTooltipAfterDelay(
|
|
4074
|
+
showHeaderTooltipAfterDelay(nextTooltip);
|
|
3256
4075
|
scheduleDraw();
|
|
3257
4076
|
}
|
|
3258
4077
|
if (inHeader) {
|
|
@@ -3276,8 +4095,8 @@ function Table({
|
|
|
3276
4095
|
if (annotation?.content.trim()) {
|
|
3277
4096
|
const cellLeft = getDisplayedColumnLeft(cell.columnIndex);
|
|
3278
4097
|
const cellTop = bodyTop + cell.rowIndex * rowHeight - scrollRef.current.top;
|
|
3279
|
-
const
|
|
3280
|
-
showCellTooltip(
|
|
4098
|
+
const nextTooltip2 = { rowIndex: cell.rowIndex, columnIndex: cell.columnIndex, left: cellLeft + metrics[cell.columnIndex].width + 6, top: cellTop + rowHeight / 2, label: annotation.content, color: annotation.color, annotation: true };
|
|
4099
|
+
showCellTooltip(nextTooltip2);
|
|
3281
4100
|
} else {
|
|
3282
4101
|
const label = getCellLabel(cell.rowIndex, cell.columnIndex);
|
|
3283
4102
|
const context = event.currentTarget.getContext("2d");
|
|
@@ -3304,7 +4123,7 @@ function Table({
|
|
|
3304
4123
|
showCellTooltipAfterDelay(null);
|
|
3305
4124
|
} else {
|
|
3306
4125
|
const placement = textRight + 330 > viewport.width ? "left" : "right";
|
|
3307
|
-
const
|
|
4126
|
+
const nextTooltip2 = {
|
|
3308
4127
|
rowIndex: cell.rowIndex,
|
|
3309
4128
|
columnIndex: cell.columnIndex,
|
|
3310
4129
|
left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
|
|
@@ -3312,13 +4131,13 @@ function Table({
|
|
|
3312
4131
|
label,
|
|
3313
4132
|
placement
|
|
3314
4133
|
};
|
|
3315
|
-
showCellTooltipAfterDelay(
|
|
4134
|
+
showCellTooltipAfterDelay(nextTooltip2);
|
|
3316
4135
|
}
|
|
3317
4136
|
}
|
|
3318
4137
|
}
|
|
3319
4138
|
}
|
|
3320
4139
|
}
|
|
3321
|
-
const cursor = inHeader &&
|
|
4140
|
+
const cursor = inHeader && locateHeaderResizeCell(event.clientX, event.clientY) ? "col-resize" : hoveredAction ? "pointer" : hoveredDragHandle ? "move" : columnIndex >= 0 && columns[columnIndex].rowSelection ? "pointer" : !inHeader && rowDraggable && columnIndex >= 0 && columns[columnIndex].rowDragHandle ? "move" : "default";
|
|
3322
4141
|
if (event.currentTarget.style.cursor !== cursor) event.currentTarget.style.cursor = cursor;
|
|
3323
4142
|
},
|
|
3324
4143
|
onMouseLeave: (event) => {
|
|
@@ -3361,6 +4180,7 @@ function Table({
|
|
|
3361
4180
|
const headerY = fixedHeader ? 0 : -scrollRef.current.top;
|
|
3362
4181
|
if (localY >= headerY && localY < headerY + headerHeight) {
|
|
3363
4182
|
clickedCellRef.current = null;
|
|
4183
|
+
if (localY < headerY + headerLeafTop) return;
|
|
3364
4184
|
const columnIndex = locateColumn(event.clientX);
|
|
3365
4185
|
const action = locateHeaderAction(event.clientX, columnIndex);
|
|
3366
4186
|
if (action === "sort") {
|
|
@@ -3422,21 +4242,30 @@ function Table({
|
|
|
3422
4242
|
className: "rvg-text-scroll-content",
|
|
3423
4243
|
style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
|
|
3424
4244
|
children: [
|
|
3425
|
-
columns.map((column, columnIndex) => column.fixed ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderTitle(columnIndex,
|
|
4245
|
+
columns.map((column, columnIndex) => column.fixed || columnIndex < utilityColumnCount || headerDepth > 1 ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderTitle(columnIndex, getScrollableColumnLeft(columnIndex)) }, column.key) : null),
|
|
4246
|
+
headerCells.map((cell) => {
|
|
4247
|
+
if (columns[cell.startIndex]?.fixed) return null;
|
|
4248
|
+
if (cell.endIndex < domRange.columnStart || cell.startIndex >= domRange.columnEnd) return null;
|
|
4249
|
+
return renderGroupedHeaderCell(cell);
|
|
4250
|
+
}),
|
|
3426
4251
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
|
|
3427
4252
|
if (column.fixed || columnIndex < domRange.columnStart || columnIndex >= domRange.columnEnd) return null;
|
|
3428
|
-
return renderCellText(rowIndex, columnIndex,
|
|
4253
|
+
return renderCellText(rowIndex, columnIndex, getScrollableColumnLeft(columnIndex));
|
|
3429
4254
|
})) })
|
|
3430
4255
|
]
|
|
3431
4256
|
}
|
|
3432
4257
|
) }),
|
|
3433
4258
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
|
|
3434
4259
|
if (!column.fixed) return null;
|
|
3435
|
-
const left = column.fixed === "left" ?
|
|
4260
|
+
const left = column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
|
|
3436
4261
|
return renderCellText(rowIndex, columnIndex, left);
|
|
3437
4262
|
})) }),
|
|
3438
|
-
columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
|
|
3439
|
-
|
|
4263
|
+
columns.map((column, columnIndex) => columnIndex < utilityColumnCount || headerDepth === 1 && (column.fixed === "left" || column.fixed === "right") ? renderHeaderTitle(columnIndex, column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width : column.fixed === "left" ? leftFixedOffsets.get(columnIndex) ?? 0 : getScrollableColumnLeft(columnIndex)) : null),
|
|
4264
|
+
headerCells.map((cell) => columns[cell.startIndex]?.fixed ? renderGroupedHeaderCell(cell) : null),
|
|
4265
|
+
fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
4266
|
+
rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-shadow is-right", style: { right: rightFixedWidth } }),
|
|
4267
|
+
hasVerticalBorders && fixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-border is-left", style: { left: fixedWidth - 1 } }),
|
|
4268
|
+
hasVerticalBorders && rightFixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-text-fixed-border is-right", style: { right: rightFixedWidth } })
|
|
3440
4269
|
] }),
|
|
3441
4270
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -renderHeight } })
|
|
3442
4271
|
] }),
|
|
@@ -3444,18 +4273,18 @@ function Table({
|
|
|
3444
4273
|
"div",
|
|
3445
4274
|
{
|
|
3446
4275
|
className: `rvg-summary-row is-${summaryPosition}${hasVerticalBorders ? "" : " is-vertical-borderless"}`,
|
|
3447
|
-
style: { height: rowHeight, top: summaryPosition === "top" ? headerHeight : void 0 },
|
|
4276
|
+
style: { height: rowHeight, top: summaryPosition === "top" ? fixedHeader ? headerHeight : headerHeight - currentScroll.top : void 0 },
|
|
3448
4277
|
children: [
|
|
3449
4278
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-summary-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3450
4279
|
"div",
|
|
3451
4280
|
{
|
|
3452
4281
|
className: "rvg-summary-scroll-content",
|
|
3453
4282
|
style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
|
|
3454
|
-
children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex,
|
|
4283
|
+
children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, getScrollableColumnLeft(columnIndex)))
|
|
3455
4284
|
}
|
|
3456
4285
|
) }),
|
|
3457
4286
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-summary-fixed-layer", children: [
|
|
3458
|
-
columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex,
|
|
4287
|
+
columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, leftFixedOffsets.get(columnIndex) ?? 0) : null),
|
|
3459
4288
|
columns.map((column, columnIndex) => column.fixed === "right" ? renderSummaryCell(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
|
|
3460
4289
|
] }),
|
|
3461
4290
|
fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-summary-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
@@ -3472,11 +4301,11 @@ function Table({
|
|
|
3472
4301
|
ref: scrollingHeaderIconsRef,
|
|
3473
4302
|
className: "rvg-header-icon-layer rvg-header-icon-layer-scroll",
|
|
3474
4303
|
style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-scrollRef.current.left}px)` },
|
|
3475
|
-
children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index,
|
|
4304
|
+
children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, getScrollableColumnLeft(index)) }, column.key))
|
|
3476
4305
|
}
|
|
3477
4306
|
) }),
|
|
3478
4307
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rvg-header-icon-layer", children: [
|
|
3479
|
-
columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index,
|
|
4308
|
+
columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, leftFixedOffsets.get(index) ?? 0) }, column.key) : null),
|
|
3480
4309
|
columns.map((column, index) => column.fixed === "right" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: renderHeaderIcons(index, viewport.width - (rightFixedOffsets.get(index) ?? 0) - metrics[index].width) }, column.key) : null)
|
|
3481
4310
|
] })
|
|
3482
4311
|
] }),
|
|
@@ -3528,6 +4357,27 @@ function Table({
|
|
|
3528
4357
|
}
|
|
3529
4358
|
),
|
|
3530
4359
|
selectionOutlineStyle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: selectionFocusRef, className: "rvg-selection-focus", style: selectionOutlineStyle, "aria-hidden": "true" }),
|
|
4360
|
+
selectionEdgeIndicator && selection && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
4361
|
+
"button",
|
|
4362
|
+
{
|
|
4363
|
+
ref: selectionEdgeTargetRef,
|
|
4364
|
+
type: "button",
|
|
4365
|
+
className: `rvg-selection-edge-target is-${selectionEdgeIndicator.orientation} is-${selectionEdgeIndicator.side}${selectionEdgeIndicator.viewportEdge ? ` is-viewport-${selectionEdgeIndicator.viewportEdge}` : ""}`,
|
|
4366
|
+
style: selectionEdgeStyle,
|
|
4367
|
+
"aria-label": labels.revealSelectedCell,
|
|
4368
|
+
title: labels.revealSelectedCell,
|
|
4369
|
+
onPointerDown: (event) => {
|
|
4370
|
+
event.preventDefault();
|
|
4371
|
+
event.stopPropagation();
|
|
4372
|
+
},
|
|
4373
|
+
onClick: (event) => {
|
|
4374
|
+
event.stopPropagation();
|
|
4375
|
+
scrollerRef.current?.focus({ preventScroll: true });
|
|
4376
|
+
revealCell(selection);
|
|
4377
|
+
},
|
|
4378
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rvg-selection-edge-halo", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M 0 100 L 0 0 L 100 0" }) }) })
|
|
4379
|
+
}
|
|
4380
|
+
),
|
|
3531
4381
|
contextMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: contextMenuRef, className: "rvg-context-menu", role: "menu", style: { left: contextMenu.left, top: contextMenu.top }, children: contextMenu.type === "header" ? (() => {
|
|
3532
4382
|
const column = columns[contextMenu.columnIndex];
|
|
3533
4383
|
const headerContext = { column, columnIndex: contextMenu.columnIndex };
|
|
@@ -3753,12 +4603,40 @@ function Table({
|
|
|
3753
4603
|
] })
|
|
3754
4604
|
] }) }),
|
|
3755
4605
|
toast && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `rvg-toast is-${toast.type}`, role: "status", children: toast.message }),
|
|
4606
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
4607
|
+
"div",
|
|
4608
|
+
{
|
|
4609
|
+
ref: verticalScrollbarRef,
|
|
4610
|
+
className: "rvg-vertical-scrollbar",
|
|
4611
|
+
onPointerEnter: () => keepCustomScrollbarVisible("vertical"),
|
|
4612
|
+
onPointerLeave: () => releaseCustomScrollbarVisible("vertical"),
|
|
4613
|
+
onPointerDown: handleVerticalScrollbarPointerDown,
|
|
4614
|
+
onPointerMove: handleVerticalScrollbarPointerMove,
|
|
4615
|
+
onPointerUp: handleVerticalScrollbarPointerUp,
|
|
4616
|
+
onPointerCancel: handleVerticalScrollbarPointerUp,
|
|
4617
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: verticalScrollbarThumbRef, className: "rvg-vertical-scrollbar-thumb" })
|
|
4618
|
+
}
|
|
4619
|
+
),
|
|
4620
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
4621
|
+
"div",
|
|
4622
|
+
{
|
|
4623
|
+
ref: horizontalScrollbarRef,
|
|
4624
|
+
className: "rvg-horizontal-scrollbar",
|
|
4625
|
+
onPointerEnter: () => keepCustomScrollbarVisible("horizontal"),
|
|
4626
|
+
onPointerLeave: () => releaseCustomScrollbarVisible("horizontal"),
|
|
4627
|
+
onPointerDown: handleHorizontalScrollbarPointerDown,
|
|
4628
|
+
onPointerMove: handleHorizontalScrollbarPointerMove,
|
|
4629
|
+
onPointerUp: handleHorizontalScrollbarPointerUp,
|
|
4630
|
+
onPointerCancel: handleHorizontalScrollbarPointerUp,
|
|
4631
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: horizontalScrollbarThumbRef, className: "rvg-horizontal-scrollbar-thumb" })
|
|
4632
|
+
}
|
|
4633
|
+
),
|
|
3756
4634
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: dragGuideRef, className: "rvg-column-guide" }),
|
|
3757
4635
|
resizeGuideX !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3758
4636
|
"div",
|
|
3759
4637
|
{
|
|
3760
4638
|
className: "rvg-column-guide is-resizing",
|
|
3761
|
-
style: { display: "block", transform: `translateX(${Math.round(resizeGuideX)}px)` }
|
|
4639
|
+
style: { display: "block", top: resizeGuideTop, transform: `translateX(${Math.round(resizeGuideX)}px)` }
|
|
3762
4640
|
}
|
|
3763
4641
|
),
|
|
3764
4642
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: rowDragGuideRef, className: "rvg-row-guide" }),
|
|
@@ -3824,8 +4702,8 @@ function Table({
|
|
|
3824
4702
|
column.key
|
|
3825
4703
|
);
|
|
3826
4704
|
}) }, rowIndex)),
|
|
3827
|
-
fixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
3828
|
-
rightFixedWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
|
|
4705
|
+
fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
|
|
4706
|
+
rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
|
|
3829
4707
|
] }),
|
|
3830
4708
|
loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoadingSpinnerIcon, {}) })
|
|
3831
4709
|
] });
|