@lensui/lens-table 0.1.4 → 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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ var ZH_CN_GRID_LABELS = {
27
27
  sortDesc: "\u5F53\u524D\uFF1A\u964D\u5E8F",
28
28
  sortBoth: "\u5347\u5E8F / \u964D\u5E8F",
29
29
  dragColumn: "\u62D6\u52A8\u5217\u6392\u5E8F",
30
+ revealSelectedCell: "\u5B9A\u4F4D\u5230\u9009\u4E2D\u5355\u5143\u683C",
30
31
  previousYear: "\u4E0A\u4E00\u5E74",
31
32
  nextYear: "\u4E0B\u4E00\u5E74",
32
33
  previousMonth: "\u4E0A\u4E2A\u6708",
@@ -95,6 +96,7 @@ var EN_US_GRID_LABELS = {
95
96
  sortDesc: "Current: descending",
96
97
  sortBoth: "Ascending / descending",
97
98
  dragColumn: "Drag column to reorder",
99
+ revealSelectedCell: "Reveal selected cell",
98
100
  previousYear: "Previous year",
99
101
  nextYear: "Next year",
100
102
  previousMonth: "Previous month",
@@ -303,7 +305,6 @@ function LoadingSpinnerIcon() {
303
305
  // src/components/editors/ChoiceEditor.tsx
304
306
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
307
  function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
306
- const editorStyle = typeof style.width === "number" ? { ...style, width: Math.max(0, style.width - 1) } : style;
307
308
  const collection = createListCollection({
308
309
  items: options,
309
310
  itemToValue: (item) => item.value,
@@ -313,7 +314,7 @@ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCan
313
314
  Select.Root,
314
315
  {
315
316
  className: "rvg-choice-editor",
316
- style: editorStyle,
317
+ style,
317
318
  collection,
318
319
  value: value ? [value] : [],
319
320
  defaultOpen: true,
@@ -884,6 +885,7 @@ function paintGrid(options) {
884
885
  const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? value + metrics[index].width : value, 0);
885
886
  const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
886
887
  const rightFixedLeft = width - rightFixedWidth;
888
+ const rightFixedBoundaryInset = rightFixedWidth > 0 && !verticalBorderless ? 1 : 0;
887
889
  const leftOffsets = /* @__PURE__ */ new Map();
888
890
  let leftOffset = 0;
889
891
  for (let index = 0; index < columns.length; index += 1) {
@@ -1107,22 +1109,22 @@ function paintGrid(options) {
1107
1109
  const markerBottom = Math.min(bottom, height);
1108
1110
  const startX = getColumnX(columnStart);
1109
1111
  const endX = getColumnX(columnEnd) + metrics[columnEnd].width;
1110
- const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth - 1, startX);
1112
+ const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth, startX);
1111
1113
  const visibleRight = columns[columnEnd].fixed === "right" ? Math.min(width, endX) : Math.min(rightFixedLeft, endX);
1112
1114
  ctx.fillStyle = colors.selection;
1113
1115
  if (markerBottom > markerTop) {
1114
1116
  if (columns[columnStart].fixed === void 0 && startX < leftFixedWidth) {
1115
- ctx.fillRect(Math.max(0, leftFixedWidth - 1), markerTop, 2, markerBottom - markerTop);
1117
+ ctx.fillRect(Math.max(0, leftFixedWidth), markerTop, 2, markerBottom - markerTop);
1116
1118
  }
1117
1119
  if (columns[columnEnd].fixed === void 0 && endX > rightFixedLeft) {
1118
- ctx.fillRect(Math.max(0, rightFixedLeft - 1), markerTop, 2, markerBottom - markerTop);
1120
+ ctx.fillRect(Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset), markerTop, 2, markerBottom - markerTop);
1119
1121
  }
1120
1122
  }
1121
1123
  if (visibleRight > visibleLeft) {
1122
1124
  if (top < clipTop) ctx.fillRect(visibleLeft, clipTop, visibleRight - visibleLeft, 2);
1123
1125
  if (bottom > height) ctx.fillRect(visibleLeft, height - 2, visibleRight - visibleLeft, 2);
1124
1126
  } else if (top < clipTop || bottom > height) {
1125
- const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth - 1) : Math.max(0, rightFixedLeft - 1);
1127
+ const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth) : Math.max(0, rightFixedLeft - 2 - rightFixedBoundaryInset);
1126
1128
  ctx.fillRect(markerX, top < clipTop ? clipTop : height - 2, 2, 2);
1127
1129
  }
1128
1130
  }
@@ -1144,15 +1146,15 @@ function paintGrid(options) {
1144
1146
  const visibleRight = x + selectionWidth;
1145
1147
  const visibleBottom = y + selectionHeight;
1146
1148
  const selectionTop = fixedHeader ? bodyTop : 0;
1147
- const selectionClipTop = fixedHeader ? Math.max(0, selectionTop - 1) : selectionTop;
1149
+ const selectionClipTop = selectionTop;
1148
1150
  const hiddenAboveHeader = visibleBottom <= selectionTop;
1149
1151
  const hiddenBelowViewport = y >= height;
1150
1152
  const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1151
1153
  const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1152
- const leftMarkerX = Math.max(0, leftFixedWidth - 1);
1154
+ const leftMarkerX = Math.max(0, leftFixedWidth);
1153
1155
  if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1154
1156
  const markerSize = 2;
1155
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1157
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1156
1158
  const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1157
1159
  ctx.fillStyle = colors.selection;
1158
1160
  ctx.fillRect(markerX, markerY, markerSize, markerSize);
@@ -1160,7 +1162,7 @@ function paintGrid(options) {
1160
1162
  }
1161
1163
  ctx.save();
1162
1164
  ctx.beginPath();
1163
- const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth - 1);
1165
+ const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
1164
1166
  const clipRight = Math.min(width, selectionAreaRight + 1);
1165
1167
  ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1166
1168
  ctx.clip();
@@ -1178,14 +1180,14 @@ function paintGrid(options) {
1178
1180
  if (hiddenBehindLeft || hiddenBehindRight) {
1179
1181
  const markerTop = Math.max(y - borderWidth, selectionTop);
1180
1182
  const markerBottom = Math.min(y + selectionHeight + 1, height);
1181
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1183
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1182
1184
  ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1183
1185
  }
1184
1186
  if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1185
1187
  ctx.restore();
1186
1188
  return;
1187
1189
  }
1188
- const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft - 1);
1190
+ const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
1189
1191
  const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1190
1192
  const borderTop = Math.max(y - borderWidth, selectionClipTop);
1191
1193
  const borderBottom = Math.min(y + selectionHeight - 1, height - borderWidth);
@@ -1294,6 +1296,25 @@ var createEmptyCellSpanLookup = () => ({
1294
1296
  covered: /* @__PURE__ */ new Map(),
1295
1297
  maxRowSpan: 1
1296
1298
  });
1299
+ var summaryFunctionIds = /* @__PURE__ */ new WeakMap();
1300
+ var nextSummaryFunctionId = 1;
1301
+ function getSummaryFunctionId(fn) {
1302
+ if (!fn) return "";
1303
+ const existing = summaryFunctionIds.get(fn);
1304
+ if (existing !== void 0) return existing;
1305
+ const next = nextSummaryFunctionId;
1306
+ nextSummaryFunctionId += 1;
1307
+ summaryFunctionIds.set(fn, next);
1308
+ return next;
1309
+ }
1310
+ function getSummarySignature(columns) {
1311
+ return columns.filter((column) => Boolean(column.summary)).map((column) => [
1312
+ column.key,
1313
+ String(column.dataIndex ?? ""),
1314
+ typeof column.summary === "function" ? getSummaryFunctionId(column.summary) : "sum",
1315
+ getSummaryFunctionId(column.formatter)
1316
+ ].join("")).join("");
1317
+ }
1297
1318
  async function calculateSummaryValues(columns, rows) {
1298
1319
  const values = /* @__PURE__ */ new Map();
1299
1320
  for (const column of columns) {
@@ -1478,6 +1499,7 @@ function Table({
1478
1499
  const dragGuideRef = useRef2(null);
1479
1500
  const rowDragGuideRef = useRef2(null);
1480
1501
  const selectionFocusRef = useRef2(null);
1502
+ const selectionEdgeTargetRef = useRef2(null);
1481
1503
  const verticalScrollbarRef = useRef2(null);
1482
1504
  const verticalScrollbarThumbRef = useRef2(null);
1483
1505
  const verticalScrollbarDragRef = useRef2(null);
@@ -1725,7 +1747,8 @@ function Table({
1725
1747
  const getScrollableColumnLeft = useCallback2((columnIndex) => scrollableColumnLefts[columnIndex] ?? fixedWidth, [fixedWidth, scrollableColumnLefts]);
1726
1748
  const hasRowSelectionColumn = useMemo(() => columns.some((column) => column.rowSelection), [columns]);
1727
1749
  const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1728
- const hasSummaryRow = useMemo(() => rows.length > 0 && summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, rows.length, summaryEnabled]);
1750
+ const summarySignature = useMemo(() => getSummarySignature(columns), [columns]);
1751
+ const hasSummaryRow = rows.length > 0 && summaryEnabled && summarySignature.length > 0;
1729
1752
  const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1730
1753
  const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1731
1754
  const bodyTop = headerHeight + topSummaryHeight;
@@ -1734,21 +1757,21 @@ function Table({
1734
1757
  const effectiveHeight = autoHeight ? Math.max(bodyTop + bottomSummaryHeight, Math.min(resolvedHeight, realContentHeight)) : resolvedHeight;
1735
1758
  const renderHeight = Math.max(0, effectiveHeight - bottomSummaryHeight);
1736
1759
  const bodyViewportHeight = Math.max(0, renderHeight - (fixedHeader ? headerHeight + topSummaryHeight : 0));
1737
- const [summaryState, setSummaryState] = useState4(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1738
- const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1739
- const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1760
+ const [summaryState, setSummaryState] = useState4(() => ({ signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1761
+ const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.signature !== summarySignature || summaryState.rows !== rows);
1762
+ const summaryValues = hasSummaryRow && summaryState.signature === summarySignature && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1740
1763
  useEffect2(() => {
1741
1764
  let cancelled = false;
1742
1765
  if (!hasSummaryRow) {
1743
- setSummaryState((current) => current.columns === null && current.rows === null && current.values.size === 0 && !current.pending ? current : { columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1766
+ setSummaryState((current) => current.signature === "" && current.rows === null && current.values.size === 0 && !current.pending ? current : { signature: "", rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1744
1767
  return () => {
1745
1768
  cancelled = true;
1746
1769
  };
1747
1770
  }
1748
1771
  setSummaryState((current) => ({
1749
- columns,
1772
+ signature: summarySignature,
1750
1773
  rows,
1751
- values: current.columns === columns && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1774
+ values: current.signature === summarySignature && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1752
1775
  pending: true
1753
1776
  }));
1754
1777
  if (loading) {
@@ -1761,13 +1784,13 @@ function Table({
1761
1784
  if (cancelled) return;
1762
1785
  const values = await calculateSummaryValues(columns, rows);
1763
1786
  if (cancelled) return;
1764
- setSummaryState({ columns, rows, values, pending: false });
1787
+ setSummaryState({ signature: summarySignature, rows, values, pending: false });
1765
1788
  };
1766
1789
  void run();
1767
1790
  return () => {
1768
1791
  cancelled = true;
1769
1792
  };
1770
- }, [columns, hasSummaryRow, loading, rows]);
1793
+ }, [hasSummaryRow, loading, rows, summarySignature]);
1771
1794
  const domRange = useMemo(() => {
1772
1795
  if (!isVirtualized) return { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1773
1796
  const currentScroll2 = scrollRef.current;
@@ -1914,6 +1937,158 @@ function Table({
1914
1937
  const scheduleDraw = useCallback2(() => {
1915
1938
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1916
1939
  }, [draw]);
1940
+ const drawImmediately = useCallback2(() => {
1941
+ if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
1942
+ frameRef.current = null;
1943
+ draw();
1944
+ }, [draw]);
1945
+ const getSelectionHorizontalVisibility = useCallback2((scrollLeft) => {
1946
+ if (!selection || selectionRange) return "visible";
1947
+ const column = columns[selection.columnIndex];
1948
+ if (!column || column.fixed !== void 0) return "visible";
1949
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1950
+ const left = getScrollableColumnLeft(selection.columnIndex) - scrollLeft;
1951
+ const right = left + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1952
+ if (right <= fixedWidth) return "hidden-left";
1953
+ if (left >= viewport.width - rightFixedWidth) return "hidden-right";
1954
+ return "visible";
1955
+ }, [columns, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, rightFixedWidth, selection, selectionRange, viewport.width]);
1956
+ const getSelectionEdgeIndicator = useCallback2((scrollLeft, scrollTop) => {
1957
+ if (!selection || selectionRange) return null;
1958
+ const row = rows[selection.rowIndex];
1959
+ const metric = metrics[selection.columnIndex];
1960
+ const column = columns[selection.columnIndex];
1961
+ if (!row || !metric || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey) return null;
1962
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1963
+ 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;
1964
+ const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1965
+ const rawTop = bodyTop + selection.rowIndex * rowHeight - scrollTop;
1966
+ const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
1967
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1968
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1969
+ const verticalStart = fixedHeader ? bodyTop : 0;
1970
+ const hiddenLeft = column.fixed === void 0 && rawRight <= horizontalStart;
1971
+ const hiddenRight = column.fixed === void 0 && rawLeft >= horizontalEnd;
1972
+ const hiddenTop = rawBottom <= verticalStart;
1973
+ const hiddenBottom = rawTop >= renderHeight;
1974
+ if (!hiddenLeft && !hiddenRight && !hiddenTop && !hiddenBottom) return null;
1975
+ const borderWidth = 2;
1976
+ const cornerLength = 10;
1977
+ const hitSlop = 6;
1978
+ const targetSize = borderWidth + hitSlop * 2;
1979
+ const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
1980
+ const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
1981
+ const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
1982
+ if (hiddenLeft || hiddenRight) {
1983
+ let markerTop = Math.max(rawTop - borderWidth, verticalStart);
1984
+ let markerBottom = Math.min(rawBottom + 1, renderHeight);
1985
+ const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
1986
+ const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
1987
+ if (topClip > 0 || bottomClip > 0) {
1988
+ const foldAtTop = topClip > 0;
1989
+ const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
1990
+ if (fold > borderWidth) {
1991
+ const cornerWidth = fold;
1992
+ const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
1993
+ const cornerOnLeft = hiddenLeft;
1994
+ const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
1995
+ const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
1996
+ return {
1997
+ side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
1998
+ orientation: "corner",
1999
+ style: {
2000
+ left: shapeLeft - hitSlop,
2001
+ top: shapeTop - hitSlop,
2002
+ width: cornerWidth + hitSlop * 2,
2003
+ height: cornerHeight + hitSlop * 2
2004
+ },
2005
+ corner: { width: cornerWidth, height: cornerHeight }
2006
+ };
2007
+ }
2008
+ }
2009
+ if (markerBottom <= markerTop) return null;
2010
+ return {
2011
+ side: hiddenLeft ? "left" : "right",
2012
+ orientation: "vertical",
2013
+ viewportEdge: hiddenLeft && horizontalStart === 0 ? "left" : hiddenRight && horizontalEnd === viewport.width ? "right" : void 0,
2014
+ style: {
2015
+ left: markerX - hitSlop,
2016
+ top: markerTop,
2017
+ width: targetSize,
2018
+ height: markerBottom - markerTop
2019
+ }
2020
+ };
2021
+ }
2022
+ const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
2023
+ let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
2024
+ let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2025
+ const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
2026
+ const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
2027
+ if (leftClip > 0 || rightClip > 0) {
2028
+ const foldAtLeft = leftClip > 0;
2029
+ const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
2030
+ if (fold > borderWidth) {
2031
+ const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
2032
+ const cornerHeight = fold;
2033
+ const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
2034
+ const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
2035
+ return {
2036
+ side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
2037
+ orientation: "corner",
2038
+ style: {
2039
+ left: shapeLeft - hitSlop,
2040
+ top: shapeTop - hitSlop,
2041
+ width: cornerWidth + hitSlop * 2,
2042
+ height: cornerHeight + hitSlop * 2
2043
+ },
2044
+ corner: { width: cornerWidth, height: cornerHeight }
2045
+ };
2046
+ }
2047
+ }
2048
+ if (markerRight <= markerLeft) return null;
2049
+ return {
2050
+ side: hiddenTop ? "top" : "bottom",
2051
+ orientation: "horizontal",
2052
+ viewportEdge: hiddenTop && verticalStart === 0 ? "top" : hiddenBottom ? "bottom" : void 0,
2053
+ style: {
2054
+ left: markerLeft,
2055
+ top: markerY - hitSlop,
2056
+ width: markerRight - markerLeft,
2057
+ height: targetSize
2058
+ }
2059
+ };
2060
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2061
+ const syncSelectionEdgeTarget = useCallback2((scrollLeft, scrollTop) => {
2062
+ const target = selectionEdgeTargetRef.current;
2063
+ if (!target) return;
2064
+ const indicator = getSelectionEdgeIndicator(scrollLeft, scrollTop);
2065
+ if (!indicator) {
2066
+ target.style.visibility = "hidden";
2067
+ return;
2068
+ }
2069
+ const orientationChanged = !target.classList.contains(`is-${indicator.orientation}`);
2070
+ target.className = `rvg-selection-edge-target is-${indicator.orientation} is-${indicator.side}${indicator.viewportEdge ? ` is-viewport-${indicator.viewportEdge}` : ""}${orientationChanged ? " is-shape-syncing" : ""}`;
2071
+ target.style.left = `${Number(indicator.style.left)}px`;
2072
+ target.style.top = `${Number(indicator.style.top)}px`;
2073
+ target.style.width = `${Number(indicator.style.width)}px`;
2074
+ target.style.height = `${Number(indicator.style.height)}px`;
2075
+ if (indicator.corner) {
2076
+ target.style.setProperty("--rvg-corner-width", `${indicator.corner.width}px`);
2077
+ target.style.setProperty("--rvg-corner-height", `${indicator.corner.height}px`);
2078
+ target.style.setProperty("--rvg-corner-hover-width-extension", "4px");
2079
+ target.style.setProperty("--rvg-corner-hover-height-extension", "4px");
2080
+ } else {
2081
+ target.style.removeProperty("--rvg-corner-width");
2082
+ target.style.removeProperty("--rvg-corner-height");
2083
+ target.style.removeProperty("--rvg-corner-hover-width-extension");
2084
+ target.style.removeProperty("--rvg-corner-hover-height-extension");
2085
+ }
2086
+ target.style.visibility = "visible";
2087
+ if (orientationChanged) {
2088
+ void target.offsetWidth;
2089
+ target.classList.remove("is-shape-syncing");
2090
+ }
2091
+ }, [getSelectionEdgeIndicator]);
1917
2092
  useLayoutEffect2(() => {
1918
2093
  const element = scrollerRef.current;
1919
2094
  if (!element) return;
@@ -2164,7 +2339,13 @@ function Table({
2164
2339
  }
2165
2340
  const nextThumbLeft = Math.max(0, Math.min(trackWidth - thumbWidth, event.clientX - trackRect.left - thumbWidth / 2));
2166
2341
  const maxThumbLeft = Math.max(1, trackWidth - thumbWidth);
2167
- scroller.scrollLeft = nextThumbLeft / maxThumbLeft * maxScrollLeft;
2342
+ const previousScrollLeft = scroller.scrollLeft;
2343
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * maxScrollLeft;
2344
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2345
+ scroller.scrollLeft = nextScrollLeft;
2346
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2347
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2348
+ if (visibilityChanged) drawImmediately();
2168
2349
  updateCustomScrollbars();
2169
2350
  horizontalScrollbarDragRef.current = {
2170
2351
  pointerId: event.pointerId,
@@ -2174,7 +2355,7 @@ function Table({
2174
2355
  thumbWidth,
2175
2356
  maxScrollLeft
2176
2357
  };
2177
- }, [showCustomScrollbar, updateCustomScrollbars]);
2358
+ }, [drawImmediately, getSelectionHorizontalVisibility, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars]);
2178
2359
  const handleHorizontalScrollbarPointerMove = useCallback2((event) => {
2179
2360
  const drag = horizontalScrollbarDragRef.current;
2180
2361
  const scroller = scrollerRef.current;
@@ -2182,9 +2363,15 @@ function Table({
2182
2363
  event.preventDefault();
2183
2364
  const maxThumbLeft = Math.max(1, drag.trackWidth - drag.thumbWidth);
2184
2365
  const nextThumbLeft = Math.max(0, Math.min(maxThumbLeft, drag.startLeft + event.clientX - drag.startX));
2185
- scroller.scrollLeft = nextThumbLeft / maxThumbLeft * drag.maxScrollLeft;
2366
+ const previousScrollLeft = scroller.scrollLeft;
2367
+ const nextScrollLeft = nextThumbLeft / maxThumbLeft * drag.maxScrollLeft;
2368
+ const visibilityChanged = getSelectionHorizontalVisibility(previousScrollLeft) !== getSelectionHorizontalVisibility(nextScrollLeft);
2369
+ scroller.scrollLeft = nextScrollLeft;
2370
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2371
+ syncSelectionEdgeTarget(scroller.scrollLeft, scroller.scrollTop);
2372
+ if (visibilityChanged) drawImmediately();
2186
2373
  updateCustomScrollbars();
2187
- }, [updateCustomScrollbars]);
2374
+ }, [drawImmediately, getSelectionHorizontalVisibility, syncSelectionEdgeTarget, updateCustomScrollbars]);
2188
2375
  const handleHorizontalScrollbarPointerUp = useCallback2((event) => {
2189
2376
  const drag = horizontalScrollbarDragRef.current;
2190
2377
  if (!drag || drag.pointerId !== event.pointerId) return;
@@ -2199,6 +2386,7 @@ function Table({
2199
2386
  const scrollDeltaX = element.scrollLeft - previousScroll.left;
2200
2387
  const scrollDeltaY = element.scrollTop - previousScroll.top;
2201
2388
  scrollRef.current = { left: element.scrollLeft, top: element.scrollTop };
2389
+ syncSelectionEdgeTarget(element.scrollLeft, element.scrollTop);
2202
2390
  const focus = selectionFocusRef.current;
2203
2391
  if (focus && selection && !selectionRange) {
2204
2392
  const row = rows[selection.rowIndex];
@@ -2220,9 +2408,9 @@ function Table({
2220
2408
  focus.style.display = "none";
2221
2409
  } else {
2222
2410
  const borderWidth = 2;
2223
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2411
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
2224
2412
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
2225
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2413
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
2226
2414
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
2227
2415
  focus.style.display = "block";
2228
2416
  focus.style.left = `${left}px`;
@@ -2252,7 +2440,7 @@ function Table({
2252
2440
  setHoveredCellTooltip(null);
2253
2441
  scheduleDraw();
2254
2442
  setEditing(null);
2255
- }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, showCustomScrollbar, updateCustomScrollbars, viewport.width]);
2443
+ }, [bodyTop, columns, renderHeight, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, metrics, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, showCustomScrollbar, syncSelectionEdgeTarget, updateCustomScrollbars, viewport.width]);
2256
2444
  const locateColumn = useCallback2((clientX) => {
2257
2445
  const canvas = canvasRef.current;
2258
2446
  if (!canvas) return -1;
@@ -2340,6 +2528,30 @@ function Table({
2340
2528
  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;
2341
2529
  }, [columns, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, viewport.width]);
2342
2530
  const getHeaderCellWidth = useCallback2((cell) => cell.leaf ? metrics[cell.startIndex]?.width ?? 0 : getCellDisplayWidth(cell.startIndex, cell.endIndex - cell.startIndex + 1), [getCellDisplayWidth, metrics]);
2531
+ const getHeaderCellEdgeFromMetrics = useCallback2((startIndex, endIndex, edge, nextMetrics) => {
2532
+ const fixedSide = columns[startIndex]?.fixed;
2533
+ const width2 = nextMetrics.slice(startIndex, endIndex + 1).reduce((total, metric) => total + metric.width, 0);
2534
+ let left;
2535
+ if (fixedSide === "left") {
2536
+ left = 0;
2537
+ for (let index = 0; index < startIndex; index += 1) {
2538
+ if (columns[index].fixed === "left") left += nextMetrics[index]?.width ?? 0;
2539
+ }
2540
+ } else if (fixedSide === "right") {
2541
+ let rightOffset = 0;
2542
+ for (let index = columns.length - 1; index > startIndex; index -= 1) {
2543
+ if (columns[index].fixed === "right") rightOffset += nextMetrics[index]?.width ?? 0;
2544
+ }
2545
+ left = viewport.width - rightOffset - width2;
2546
+ } else {
2547
+ left = 0;
2548
+ for (let index = 0; index < startIndex; index += 1) {
2549
+ if (columns[index].fixed === void 0) left += nextMetrics[index]?.width ?? 0;
2550
+ }
2551
+ left += fixedWidth - scrollRef.current.left;
2552
+ }
2553
+ return left + (edge === "right" ? width2 : 0);
2554
+ }, [columns, fixedWidth, viewport.width]);
2343
2555
  const locateHeaderResizeHit = useCallback2((clientX, clientY) => {
2344
2556
  if (!columnResizable || !canvasRef.current) return null;
2345
2557
  const rect = canvasRef.current.getBoundingClientRect();
@@ -2350,6 +2562,7 @@ function Table({
2350
2562
  const level = getHeaderLevelAtY(localY, headerY);
2351
2563
  let closest = null;
2352
2564
  let distance = 6;
2565
+ let priority = -1;
2353
2566
  for (const cell of headerCells) {
2354
2567
  if (cell.level !== level) continue;
2355
2568
  if (columns[cell.startIndex]?.rowSelection || columns[cell.startIndex]?.rowDragHandle || columns[cell.startIndex]?.rowNumber) continue;
@@ -2357,8 +2570,10 @@ function Table({
2357
2570
  const edge = fixedSide === "right" && cell.leaf ? "left" : "right";
2358
2571
  const edgeX = getHeaderCellLeft(cell) + (edge === "right" ? getHeaderCellWidth(cell) : 0);
2359
2572
  const nextDistance = Math.abs(localX - edgeX);
2360
- if (nextDistance < distance) {
2573
+ const nextPriority = fixedSide === "right" ? 2 : fixedSide === "left" ? 1 : 0;
2574
+ if (nextDistance < distance || nextDistance === distance && nextPriority > priority) {
2361
2575
  distance = nextDistance;
2576
+ priority = nextPriority;
2362
2577
  closest = { cell, edge };
2363
2578
  }
2364
2579
  }
@@ -2570,6 +2785,10 @@ function Table({
2570
2785
  columnDragPreviewRef.current = { sourceIndex, targetIndex: destinationIndex };
2571
2786
  const targetLeft = Number(target?.targetLeft);
2572
2787
  const targetWidth = Number(target?.targetWidth);
2788
+ if (!Number.isFinite(targetLeft) || !Number.isFinite(targetWidth)) {
2789
+ setDragGuide(null);
2790
+ return;
2791
+ }
2573
2792
  const guideTop = headerRowOffsets[Number(source.data.headerLevel)] ?? 0;
2574
2793
  setDragGuide(edge === "right" ? targetLeft + targetWidth : targetLeft, guideTop);
2575
2794
  },
@@ -2967,18 +3186,28 @@ function Table({
2967
3186
  const editorStyle = useMemo(() => {
2968
3187
  if (!editing) return void 0;
2969
3188
  const metric = metrics[editing.columnIndex];
2970
- const fixedSide = columns[editing.columnIndex].fixed;
3189
+ const column = columns[editing.columnIndex];
3190
+ if (!metric || !column) return void 0;
3191
+ const fixedSide = column.fixed;
2971
3192
  const span = getCellSpan(editing.rowIndex, editing.columnIndex);
2972
3193
  const cellWidth = getCellDisplayWidth(editing.columnIndex, span?.colSpan ?? 1);
2973
3194
  const cellHeight = rowHeight * (span?.rowSpan ?? 1);
2974
3195
  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;
3196
+ const cellTop = bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top;
3197
+ const horizontalStart = fixedSide === void 0 ? fixedWidth : 0;
3198
+ const horizontalEnd = fixedSide === void 0 ? viewport.width - rightFixedWidth : viewport.width;
3199
+ const verticalStart = fixedHeader ? bodyTop : 0;
3200
+ const visibleLeft = Math.max(cellLeft, horizontalStart);
3201
+ const visibleRight = Math.min(cellLeft + cellWidth, horizontalEnd, viewport.width);
3202
+ const visibleTop = Math.max(cellTop, verticalStart);
3203
+ const visibleBottom = Math.min(cellTop + cellHeight, renderHeight);
2975
3204
  return {
2976
- left: (fixedSide ? cellLeft : Math.max(cellLeft, fixedWidth)) + 1,
2977
- top: Math.max(fixedHeader ? bodyTop : 0, bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top) + 1,
2978
- width: cellWidth - 2,
2979
- height: cellHeight - 2
3205
+ left: visibleLeft,
3206
+ top: visibleTop,
3207
+ width: Math.max(0, visibleRight - visibleLeft),
3208
+ height: Math.max(0, visibleBottom - visibleTop)
2980
3209
  };
2981
- }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, rightFixedOffsets, rowHeight, viewport.width]);
3210
+ }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, viewport.width]);
2982
3211
  const handleColumnPointerDown = useCallback2((event) => {
2983
3212
  const rect = event.currentTarget.getBoundingClientRect();
2984
3213
  const localY = event.clientY - rect.top;
@@ -2998,9 +3227,10 @@ function Table({
2998
3227
  columnIndex: resizeIndex,
2999
3228
  startX: event.clientX,
3000
3229
  startWidth: startWidths.reduce((total, width2) => total + width2, 0),
3001
- startGuideX,
3002
3230
  guideTop: headerRowOffsets[resizeCell.level] ?? 0,
3003
3231
  resizeEdge,
3232
+ resizeStartIndex: resizeCell.startIndex,
3233
+ resizeEndIndex: resizeCell.endIndex,
3004
3234
  resizeIndices,
3005
3235
  startWidths
3006
3236
  };
@@ -3017,6 +3247,12 @@ function Table({
3017
3247
  const minWidth = getMinimumColumnWidth(column, columnDraggable);
3018
3248
  const pointerDelta = Math.round(event.clientX - drag.startX);
3019
3249
  const delta = drag.resizeEdge === "left" ? -pointerDelta : pointerDelta;
3250
+ const setGuideFromWidths = (nextWidths) => {
3251
+ const widthByIndex = new Map(drag.resizeIndices.map((columnIndex, index) => [columnIndex, nextWidths[index]]));
3252
+ const nextColumns = columns.map((column2, index) => widthByIndex.has(index) ? { ...column2, width: widthByIndex.get(index) } : column2);
3253
+ const nextMetrics = buildColumnMetrics(nextColumns, { columnDraggable, viewportWidth: viewport.width });
3254
+ setResizeGuideX(getHeaderCellEdgeFromMetrics(drag.resizeStartIndex, drag.resizeEndIndex, drag.resizeEdge, nextMetrics));
3255
+ };
3020
3256
  if (drag.resizeIndices.length > 1) {
3021
3257
  const minWidths = drag.resizeIndices.map((index) => getMinimumColumnWidth(columns[index], columnDraggable));
3022
3258
  const minTotalWidth = minWidths.reduce((total, width3) => total + width3, 0);
@@ -3033,13 +3269,13 @@ function Table({
3033
3269
  drag.resizeIndices.forEach((columnIndex, index) => {
3034
3270
  resizeColumn(columns[columnIndex].key, nextWidths[index]);
3035
3271
  });
3036
- setResizeGuideX(drag.startGuideX + (drag.resizeEdge === "left" ? drag.startWidth - targetTotalWidth : targetTotalWidth - drag.startWidth));
3272
+ setGuideFromWidths(nextWidths);
3037
3273
  return;
3038
3274
  }
3039
3275
  const width2 = Math.max(minWidth, Math.round(drag.startWidth + delta));
3040
3276
  resizeColumn(column.key, width2);
3041
- setResizeGuideX(drag.startGuideX + (drag.resizeEdge === "left" ? drag.startWidth - width2 : width2 - drag.startWidth));
3042
- }, [columnDraggable, columns, resizeColumn]);
3277
+ setGuideFromWidths([width2]);
3278
+ }, [columnDraggable, columns, getHeaderCellEdgeFromMetrics, resizeColumn, viewport.width]);
3043
3279
  const handleColumnPointerUp = useCallback2((event) => {
3044
3280
  const drag = columnDragRef.current;
3045
3281
  if (!drag) return;
@@ -3620,9 +3856,9 @@ function Table({
3620
3856
  const verticalStart = fixedHeader ? bodyTop : 0;
3621
3857
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) return null;
3622
3858
  const borderWidth = 2;
3623
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
3859
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
3624
3860
  const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
3625
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
3861
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
3626
3862
  const bottom = Math.round(Math.min(rawBottom + 1, renderHeight));
3627
3863
  return {
3628
3864
  left,
@@ -3631,6 +3867,19 @@ function Table({
3631
3867
  height: Math.max(0, bottom - top)
3632
3868
  };
3633
3869
  })();
3870
+ const selectionEdgeIndicator = getSelectionEdgeIndicator(scrollPosition.left, scrollPosition.top);
3871
+ const selectionEdgeStyle = selectionEdgeIndicator ? {
3872
+ ...selectionEdgeIndicator.style,
3873
+ ...selectionEdgeIndicator.corner ? {
3874
+ "--rvg-corner-width": `${selectionEdgeIndicator.corner.width}px`,
3875
+ "--rvg-corner-height": `${selectionEdgeIndicator.corner.height}px`,
3876
+ "--rvg-corner-hover-width-extension": "4px",
3877
+ "--rvg-corner-hover-height-extension": "4px"
3878
+ } : null
3879
+ } : void 0;
3880
+ useLayoutEffect2(() => {
3881
+ syncSelectionEdgeTarget(scrollPosition.left, scrollPosition.top);
3882
+ }, [scrollPosition.left, scrollPosition.top, syncSelectionEdgeTarget]);
3634
3883
  const handleContextMenu = (event) => {
3635
3884
  event.preventDefault();
3636
3885
  event.stopPropagation();
@@ -4078,6 +4327,27 @@ function Table({
4078
4327
  }
4079
4328
  ),
4080
4329
  selectionOutlineStyle && /* @__PURE__ */ jsx7("div", { ref: selectionFocusRef, className: "rvg-selection-focus", style: selectionOutlineStyle, "aria-hidden": "true" }),
4330
+ selectionEdgeIndicator && selection && /* @__PURE__ */ jsx7(
4331
+ "button",
4332
+ {
4333
+ ref: selectionEdgeTargetRef,
4334
+ type: "button",
4335
+ className: `rvg-selection-edge-target is-${selectionEdgeIndicator.orientation} is-${selectionEdgeIndicator.side}${selectionEdgeIndicator.viewportEdge ? ` is-viewport-${selectionEdgeIndicator.viewportEdge}` : ""}`,
4336
+ style: selectionEdgeStyle,
4337
+ "aria-label": labels.revealSelectedCell,
4338
+ title: labels.revealSelectedCell,
4339
+ onPointerDown: (event) => {
4340
+ event.preventDefault();
4341
+ event.stopPropagation();
4342
+ },
4343
+ onClick: (event) => {
4344
+ event.stopPropagation();
4345
+ scrollerRef.current?.focus({ preventScroll: true });
4346
+ revealCell(selection);
4347
+ },
4348
+ children: /* @__PURE__ */ jsx7("span", { className: "rvg-selection-edge-halo", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", children: /* @__PURE__ */ jsx7("path", { d: "M 0 100 L 0 0 L 100 0" }) }) })
4349
+ }
4350
+ ),
4081
4351
  contextMenu && /* @__PURE__ */ jsx7("div", { ref: contextMenuRef, className: "rvg-context-menu", role: "menu", style: { left: contextMenu.left, top: contextMenu.top }, children: contextMenu.type === "header" ? (() => {
4082
4352
  const column = columns[contextMenu.columnIndex];
4083
4353
  const headerContext = { column, columnIndex: contextMenu.columnIndex };
@@ -4402,8 +4672,8 @@ function Table({
4402
4672
  column.key
4403
4673
  );
4404
4674
  }) }, rowIndex)),
4405
- fixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
4406
- rightFixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
4675
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
4676
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
4407
4677
  ] }),
4408
4678
  loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ jsx7("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ jsx7(LoadingSpinnerIcon, {}) })
4409
4679
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",