@lensui/lens-table 0.1.81 → 0.1.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1144,6 +1144,24 @@ function ellipsizeText(context, text, maxWidth) {
1144
1144
  }
1145
1145
  return `${text.slice(0, start)}${ellipsis}`;
1146
1146
  }
1147
+ function getSelectionOutlineBounds({
1148
+ left,
1149
+ right,
1150
+ top,
1151
+ bottom,
1152
+ horizontalStart,
1153
+ horizontalEnd,
1154
+ verticalStart,
1155
+ verticalEnd,
1156
+ borderWidth = 2
1157
+ }) {
1158
+ return {
1159
+ left: Math.max(left - borderWidth, horizontalStart - borderWidth),
1160
+ right: Math.min(right + 1, horizontalEnd + 1),
1161
+ top: Math.max(top - borderWidth, verticalStart - borderWidth),
1162
+ bottom: Math.min(bottom + borderWidth, verticalEnd + borderWidth)
1163
+ };
1164
+ }
1147
1165
  function paintGrid(options) {
1148
1166
  const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, frameBorderless, extendVerticalGridLines, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
1149
1167
  const headerLeafTop = options.headerLeafTop ?? 0;
@@ -1632,51 +1650,65 @@ function paintGrid(options) {
1632
1650
  const visibleRight = x + selectionWidth;
1633
1651
  const visibleBottom = y + selectionHeight;
1634
1652
  const selectionTop = fixedHeader ? bodyTop : 0;
1635
- const selectionClipTop = selectionTop;
1636
1653
  const hiddenAboveHeader = visibleBottom <= selectionTop;
1637
1654
  const hiddenBelowViewport = y >= height;
1638
1655
  const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1639
1656
  const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1640
- const leftMarkerX = Math.max(0, leftFixedWidth);
1657
+ const leftMarkerX = leftFixedWidth - borderWidth;
1658
+ const rightMarkerX = selectionAreaRight - 1;
1659
+ const topMarkerY = selectionTop - borderWidth;
1660
+ const bottomMarkerY = height;
1641
1661
  if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1642
1662
  const markerSize = 2;
1643
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1644
- const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1663
+ const markerX = hiddenBehindLeft ? leftMarkerX : rightMarkerX;
1664
+ const markerY = hiddenAboveHeader ? topMarkerY : bottomMarkerY;
1645
1665
  ctx.fillStyle = colors.selection;
1646
1666
  ctx.fillRect(markerX, markerY, markerSize, markerSize);
1647
1667
  return;
1648
1668
  }
1669
+ const outline = getSelectionOutlineBounds({
1670
+ left: x,
1671
+ right: visibleRight,
1672
+ top: y,
1673
+ bottom: visibleBottom,
1674
+ horizontalStart: visibleLeft,
1675
+ horizontalEnd: selectionAreaRight,
1676
+ verticalStart: selectionTop,
1677
+ verticalEnd: height,
1678
+ borderWidth
1679
+ });
1649
1680
  ctx.save();
1650
1681
  ctx.beginPath();
1651
- const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
1682
+ const clipLeft = Math.max(0, outline.left);
1652
1683
  const clipRight = Math.min(width, selectionAreaRight + 1);
1653
- ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1684
+ const clipTop = Math.max(0, outline.top);
1685
+ ctx.rect(clipLeft, clipTop, clipRight - clipLeft, height - clipTop);
1654
1686
  ctx.clip();
1655
1687
  ctx.fillStyle = colors.selection;
1656
1688
  if (hiddenAboveHeader) {
1657
- const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1658
- const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1659
- if (markerRight > markerLeft) ctx.fillRect(markerLeft, selectionClipTop, markerRight - markerLeft, borderWidth);
1689
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - borderWidth);
1690
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight + 1);
1691
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, topMarkerY, markerRight - markerLeft, borderWidth);
1660
1692
  }
1661
1693
  if (hiddenBelowViewport) {
1662
- const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1663
- const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1664
- if (markerRight > markerLeft) ctx.fillRect(markerLeft, height - borderWidth, markerRight - markerLeft, borderWidth);
1694
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - borderWidth);
1695
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight + 1);
1696
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, bottomMarkerY, markerRight - markerLeft, borderWidth);
1665
1697
  }
1666
1698
  if (hiddenBehindLeft || hiddenBehindRight) {
1667
- const markerTop = Math.max(y - borderWidth, selectionTop);
1668
- const markerBottom = Math.min(y + selectionHeight + 1, height);
1669
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1699
+ const markerTop = Math.max(y - borderWidth, selectionTop - borderWidth);
1700
+ const markerBottom = Math.min(y + selectionHeight + borderWidth, height + borderWidth);
1701
+ const markerX = hiddenBehindLeft ? leftMarkerX : rightMarkerX;
1670
1702
  ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1671
1703
  }
1672
1704
  if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1673
1705
  ctx.restore();
1674
1706
  return;
1675
1707
  }
1676
- const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
1677
- const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1678
- const borderTop = Math.max(y - borderWidth, selectionClipTop);
1679
- const borderBottom = Math.min(y + selectionHeight, height - borderWidth);
1708
+ const borderLeft = outline.left;
1709
+ const borderRight = outline.right - borderWidth;
1710
+ const borderTop = outline.top;
1711
+ const borderBottom = outline.bottom - borderWidth;
1680
1712
  const borderOuterWidth = borderRight + borderWidth - borderLeft;
1681
1713
  ctx.fillRect(borderLeft, borderTop, borderOuterWidth, borderWidth);
1682
1714
  ctx.fillRect(borderLeft, borderBottom, borderOuterWidth, borderWidth);
@@ -1929,9 +1961,6 @@ function TableInner({
1929
1961
  defaultSelectedCell,
1930
1962
  onSelectedCellChange,
1931
1963
  rangeSelection = false,
1932
- selectedRange,
1933
- defaultSelectedRange,
1934
- onSelectedRangeChange,
1935
1964
  rowSelection,
1936
1965
  selectedRowKeys,
1937
1966
  defaultSelectedRowKeys,
@@ -2341,20 +2370,6 @@ function TableInner({
2341
2370
  }, [columns, getDataRowKey, rows]);
2342
2371
  const resolvedSelectedCell = (0, import_react8.useMemo)(() => resolveSelectionTarget(selectedCell), [resolveSelectionTarget, selectedCell]);
2343
2372
  const resolvedDefaultSelectedCell = (0, import_react8.useMemo)(() => resolveSelectionTarget(defaultSelectedCell), [defaultSelectedCell, resolveSelectionTarget]);
2344
- const resolveSelectionRangeTarget = (0, import_react8.useCallback)((target) => {
2345
- if (target == null) return target;
2346
- const anchor = resolveSelectionTarget(target.anchor);
2347
- const focus = resolveSelectionTarget(target.focus);
2348
- return anchor && focus ? { anchor, focus } : null;
2349
- }, [resolveSelectionTarget]);
2350
- const resolvedSelectedRange = (0, import_react8.useMemo)(
2351
- () => resolveSelectionRangeTarget(selectedRange),
2352
- [resolveSelectionRangeTarget, selectedRange]
2353
- );
2354
- const resolvedDefaultSelectedRange = (0, import_react8.useMemo)(
2355
- () => resolveSelectionRangeTarget(defaultSelectedRange),
2356
- [defaultSelectedRange, resolveSelectionRangeTarget]
2357
- );
2358
2373
  const handleSelectedCellChange = (0, import_react8.useCallback)((next) => {
2359
2374
  if (!onSelectedCellChange) return;
2360
2375
  if (!next) {
@@ -2367,11 +2382,7 @@ function TableInner({
2367
2382
  onSelectedCellChange({ ...next, value });
2368
2383
  }, [columns, onSelectedCellChange, rows]);
2369
2384
  const [selection, setSelection] = useControllableValue(resolvedSelectedCell, resolvedDefaultSelectedCell, handleSelectedCellChange);
2370
- const [selectionRange, setSelectionRange] = useControllableValue(
2371
- resolvedSelectedRange,
2372
- resolvedDefaultSelectedRange,
2373
- onSelectedRangeChange
2374
- );
2385
+ const [selectionRange, setSelectionRange] = (0, import_react8.useState)(null);
2375
2386
  const rangeDragRef = (0, import_react8.useRef)(null);
2376
2387
  (0, import_react8.useEffect)(() => {
2377
2388
  if (!selection) return void 0;
@@ -2717,14 +2728,17 @@ function TableInner({
2717
2728
  const cornerLength = 10;
2718
2729
  const hitSlop = 6;
2719
2730
  const targetSize = borderWidth + hitSlop * 2;
2720
- const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
2721
- const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
2722
- const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
2731
+ const leftBoundary = horizontalStart - borderWidth;
2732
+ const rightBoundary = horizontalEnd - 1;
2733
+ const topBoundary = verticalStart - borderWidth;
2734
+ const bottomBoundary = renderHeight;
2735
+ const markerX = hiddenLeft ? leftBoundary : rightBoundary;
2736
+ const markerY = hiddenTop ? topBoundary : bottomBoundary;
2723
2737
  if (hiddenLeft || hiddenRight) {
2724
- let markerTop = Math.max(rawTop - borderWidth, verticalStart);
2725
- let markerBottom = Math.min(rawBottom + 1, renderHeight);
2726
- const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
2727
- const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
2738
+ const markerTop = Math.max(rawTop - borderWidth, topBoundary);
2739
+ const markerBottom = Math.min(rawBottom + borderWidth, renderHeight + borderWidth);
2740
+ const topClip = Math.max(0, verticalStart - rawTop);
2741
+ const bottomClip = Math.max(0, rawBottom - renderHeight);
2728
2742
  if (topClip > 0 || bottomClip > 0) {
2729
2743
  const foldAtTop = topClip > 0;
2730
2744
  const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
@@ -2733,7 +2747,7 @@ function TableInner({
2733
2747
  const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
2734
2748
  const cornerOnLeft = hiddenLeft;
2735
2749
  const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
2736
- const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
2750
+ const shapeTop = foldAtTop ? topBoundary : renderHeight + borderWidth - cornerHeight;
2737
2751
  return {
2738
2752
  side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
2739
2753
  orientation: "corner",
@@ -2760,19 +2774,19 @@ function TableInner({
2760
2774
  }
2761
2775
  };
2762
2776
  }
2763
- const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
2764
- let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
2765
- let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2766
- const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
2767
- const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
2777
+ const horizontalMarkerEnd = horizontalEnd + 1;
2778
+ const markerLeft = Math.max(rawLeft - borderWidth, leftBoundary);
2779
+ const markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2780
+ const leftClip = Math.max(0, horizontalStart - rawLeft);
2781
+ const rightClip = Math.max(0, rawRight - horizontalEnd);
2768
2782
  if (leftClip > 0 || rightClip > 0) {
2769
2783
  const foldAtLeft = leftClip > 0;
2770
2784
  const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
2771
2785
  if (fold > borderWidth) {
2772
2786
  const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
2773
2787
  const cornerHeight = fold;
2774
- const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
2775
- const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
2788
+ const shapeLeft = foldAtLeft ? leftBoundary : horizontalMarkerEnd - cornerWidth;
2789
+ const shapeTop = hiddenTop ? topBoundary : renderHeight + borderWidth - cornerHeight;
2776
2790
  return {
2777
2791
  side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
2778
2792
  orientation: "corner",
@@ -2798,7 +2812,7 @@ function TableInner({
2798
2812
  height: targetSize
2799
2813
  }
2800
2814
  };
2801
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2815
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2802
2816
  const syncSelectionEdgeTarget = (0, import_react8.useCallback)((scrollLeft, scrollTop) => {
2803
2817
  const target = selectionEdgeTargetRef.current;
2804
2818
  if (!target) return;
@@ -3168,11 +3182,20 @@ function TableInner({
3168
3182
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) {
3169
3183
  focus.style.display = "none";
3170
3184
  } else {
3171
- const borderWidth = 2;
3172
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
3173
- const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
3174
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
3175
- const bottom = Math.round(Math.min(rawBottom + borderWidth, renderHeight));
3185
+ const bounds = getSelectionOutlineBounds({
3186
+ left: rawLeft,
3187
+ right: rawRight,
3188
+ top: rawTop,
3189
+ bottom: rawBottom,
3190
+ horizontalStart,
3191
+ horizontalEnd,
3192
+ verticalStart,
3193
+ verticalEnd: renderHeight
3194
+ });
3195
+ const left = Math.round(bounds.left);
3196
+ const right = Math.round(bounds.right);
3197
+ const top = Math.round(bounds.top);
3198
+ const bottom = Math.round(bounds.bottom);
3176
3199
  focus.style.display = "block";
3177
3200
  focus.style.left = `${left}px`;
3178
3201
  focus.style.top = `${top}px`;
@@ -3497,10 +3520,8 @@ function TableInner({
3497
3520
  if (focus.rowIndex === drag.anchor.rowIndex && focus.columnIndex === drag.anchor.columnIndex) return;
3498
3521
  drag.moved = true;
3499
3522
  event.preventDefault();
3500
- if (selectionRange?.focus.rowIndex === focus.rowIndex && selectionRange.focus.columnIndex === focus.columnIndex)
3501
- return;
3502
- setSelectionRange({ anchor: drag.anchor, focus });
3503
- }, [columns, locateCell, selectionRange, setSelectionRange]);
3523
+ setSelectionRange((current) => current?.focus.rowIndex === focus.rowIndex && current.focus.columnIndex === focus.columnIndex ? current : { anchor: drag.anchor, focus });
3524
+ }, [columns, locateCell]);
3504
3525
  const endRangeDrag = (0, import_react8.useCallback)((event) => {
3505
3526
  const drag = rangeDragRef.current;
3506
3527
  if (!drag) return;
@@ -4710,11 +4731,20 @@ function TableInner({
4710
4731
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
4711
4732
  const verticalStart = fixedHeader ? bodyTop : 0;
4712
4733
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom + dragOffset <= verticalStart || rawTop + dragOffset >= renderHeight) return null;
4713
- const borderWidth = 2;
4714
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
4715
- const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
4716
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
4717
- const bottom = Math.round(Math.min(rawBottom + borderWidth, renderHeight));
4734
+ const bounds = getSelectionOutlineBounds({
4735
+ left: rawLeft,
4736
+ right: rawRight,
4737
+ top: rawTop,
4738
+ bottom: rawBottom,
4739
+ horizontalStart,
4740
+ horizontalEnd,
4741
+ verticalStart,
4742
+ verticalEnd: renderHeight
4743
+ });
4744
+ const left = Math.round(bounds.left);
4745
+ const right = Math.round(bounds.right);
4746
+ const top = Math.round(bounds.top);
4747
+ const bottom = Math.round(bounds.bottom);
4718
4748
  return {
4719
4749
  left,
4720
4750
  top,
package/dist/index.d.cts CHANGED
@@ -415,11 +415,6 @@ interface TableProps<Row extends object> {
415
415
  onSelectedCellChange?: (selection: SelectedCellChange | null) => void;
416
416
  /** Enable spreadsheet-style mouse drag selection across multiple body cells. Defaults to false. */
417
417
  rangeSelection?: boolean;
418
- /** Controlled rectangular cell selection. */
419
- selectedRange?: GridSelectionRangeTarget | null;
420
- /** Initial rectangular cell selection for uncontrolled usage. */
421
- defaultSelectedRange?: GridSelectionRangeTarget | null;
422
- onSelectedRangeChange?: (range: GridSelectionRange | null) => void;
423
418
  rowSelection?: boolean | RowSelectionConfig;
424
419
  selectedRowKeys?: GridKey[];
425
420
  defaultSelectedRowKeys?: GridKey[];
package/dist/index.d.ts CHANGED
@@ -415,11 +415,6 @@ interface TableProps<Row extends object> {
415
415
  onSelectedCellChange?: (selection: SelectedCellChange | null) => void;
416
416
  /** Enable spreadsheet-style mouse drag selection across multiple body cells. Defaults to false. */
417
417
  rangeSelection?: boolean;
418
- /** Controlled rectangular cell selection. */
419
- selectedRange?: GridSelectionRangeTarget | null;
420
- /** Initial rectangular cell selection for uncontrolled usage. */
421
- defaultSelectedRange?: GridSelectionRangeTarget | null;
422
- onSelectedRangeChange?: (range: GridSelectionRange | null) => void;
423
418
  rowSelection?: boolean | RowSelectionConfig;
424
419
  selectedRowKeys?: GridKey[];
425
420
  defaultSelectedRowKeys?: GridKey[];
package/dist/index.js CHANGED
@@ -1114,6 +1114,24 @@ function ellipsizeText(context, text, maxWidth) {
1114
1114
  }
1115
1115
  return `${text.slice(0, start)}${ellipsis}`;
1116
1116
  }
1117
+ function getSelectionOutlineBounds({
1118
+ left,
1119
+ right,
1120
+ top,
1121
+ bottom,
1122
+ horizontalStart,
1123
+ horizontalEnd,
1124
+ verticalStart,
1125
+ verticalEnd,
1126
+ borderWidth = 2
1127
+ }) {
1128
+ return {
1129
+ left: Math.max(left - borderWidth, horizontalStart - borderWidth),
1130
+ right: Math.min(right + 1, horizontalEnd + 1),
1131
+ top: Math.max(top - borderWidth, verticalStart - borderWidth),
1132
+ bottom: Math.min(bottom + borderWidth, verticalEnd + borderWidth)
1133
+ };
1134
+ }
1117
1135
  function paintGrid(options) {
1118
1136
  const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, fixedHeader, verticalBorderless, horizontalBorderless, frameBorderless, extendVerticalGridLines, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, rowSelectionMode, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget } = options;
1119
1137
  const headerLeafTop = options.headerLeafTop ?? 0;
@@ -1602,51 +1620,65 @@ function paintGrid(options) {
1602
1620
  const visibleRight = x + selectionWidth;
1603
1621
  const visibleBottom = y + selectionHeight;
1604
1622
  const selectionTop = fixedHeader ? bodyTop : 0;
1605
- const selectionClipTop = selectionTop;
1606
1623
  const hiddenAboveHeader = visibleBottom <= selectionTop;
1607
1624
  const hiddenBelowViewport = y >= height;
1608
1625
  const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1609
1626
  const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1610
- const leftMarkerX = Math.max(0, leftFixedWidth);
1627
+ const leftMarkerX = leftFixedWidth - borderWidth;
1628
+ const rightMarkerX = selectionAreaRight - 1;
1629
+ const topMarkerY = selectionTop - borderWidth;
1630
+ const bottomMarkerY = height;
1611
1631
  if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1612
1632
  const markerSize = 2;
1613
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1614
- const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1633
+ const markerX = hiddenBehindLeft ? leftMarkerX : rightMarkerX;
1634
+ const markerY = hiddenAboveHeader ? topMarkerY : bottomMarkerY;
1615
1635
  ctx.fillStyle = colors.selection;
1616
1636
  ctx.fillRect(markerX, markerY, markerSize, markerSize);
1617
1637
  return;
1618
1638
  }
1639
+ const outline = getSelectionOutlineBounds({
1640
+ left: x,
1641
+ right: visibleRight,
1642
+ top: y,
1643
+ bottom: visibleBottom,
1644
+ horizontalStart: visibleLeft,
1645
+ horizontalEnd: selectionAreaRight,
1646
+ verticalStart: selectionTop,
1647
+ verticalEnd: height,
1648
+ borderWidth
1649
+ });
1619
1650
  ctx.save();
1620
1651
  ctx.beginPath();
1621
- const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth);
1652
+ const clipLeft = Math.max(0, outline.left);
1622
1653
  const clipRight = Math.min(width, selectionAreaRight + 1);
1623
- ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1654
+ const clipTop = Math.max(0, outline.top);
1655
+ ctx.rect(clipLeft, clipTop, clipRight - clipLeft, height - clipTop);
1624
1656
  ctx.clip();
1625
1657
  ctx.fillStyle = colors.selection;
1626
1658
  if (hiddenAboveHeader) {
1627
- const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1628
- const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1629
- if (markerRight > markerLeft) ctx.fillRect(markerLeft, selectionClipTop, markerRight - markerLeft, borderWidth);
1659
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - borderWidth);
1660
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight + 1);
1661
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, topMarkerY, markerRight - markerLeft, borderWidth);
1630
1662
  }
1631
1663
  if (hiddenBelowViewport) {
1632
- const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1633
- const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1634
- if (markerRight > markerLeft) ctx.fillRect(markerLeft, height - borderWidth, markerRight - markerLeft, borderWidth);
1664
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - borderWidth);
1665
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight + 1);
1666
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, bottomMarkerY, markerRight - markerLeft, borderWidth);
1635
1667
  }
1636
1668
  if (hiddenBehindLeft || hiddenBehindRight) {
1637
- const markerTop = Math.max(y - borderWidth, selectionTop);
1638
- const markerBottom = Math.min(y + selectionHeight + 1, height);
1639
- const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth - rightFixedBoundaryInset;
1669
+ const markerTop = Math.max(y - borderWidth, selectionTop - borderWidth);
1670
+ const markerBottom = Math.min(y + selectionHeight + borderWidth, height + borderWidth);
1671
+ const markerX = hiddenBehindLeft ? leftMarkerX : rightMarkerX;
1640
1672
  ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1641
1673
  }
1642
1674
  if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1643
1675
  ctx.restore();
1644
1676
  return;
1645
1677
  }
1646
- const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft);
1647
- const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1648
- const borderTop = Math.max(y - borderWidth, selectionClipTop);
1649
- const borderBottom = Math.min(y + selectionHeight, height - borderWidth);
1678
+ const borderLeft = outline.left;
1679
+ const borderRight = outline.right - borderWidth;
1680
+ const borderTop = outline.top;
1681
+ const borderBottom = outline.bottom - borderWidth;
1650
1682
  const borderOuterWidth = borderRight + borderWidth - borderLeft;
1651
1683
  ctx.fillRect(borderLeft, borderTop, borderOuterWidth, borderWidth);
1652
1684
  ctx.fillRect(borderLeft, borderBottom, borderOuterWidth, borderWidth);
@@ -1899,9 +1931,6 @@ function TableInner({
1899
1931
  defaultSelectedCell,
1900
1932
  onSelectedCellChange,
1901
1933
  rangeSelection = false,
1902
- selectedRange,
1903
- defaultSelectedRange,
1904
- onSelectedRangeChange,
1905
1934
  rowSelection,
1906
1935
  selectedRowKeys,
1907
1936
  defaultSelectedRowKeys,
@@ -2311,20 +2340,6 @@ function TableInner({
2311
2340
  }, [columns, getDataRowKey, rows]);
2312
2341
  const resolvedSelectedCell = useMemo2(() => resolveSelectionTarget(selectedCell), [resolveSelectionTarget, selectedCell]);
2313
2342
  const resolvedDefaultSelectedCell = useMemo2(() => resolveSelectionTarget(defaultSelectedCell), [defaultSelectedCell, resolveSelectionTarget]);
2314
- const resolveSelectionRangeTarget = useCallback2((target) => {
2315
- if (target == null) return target;
2316
- const anchor = resolveSelectionTarget(target.anchor);
2317
- const focus = resolveSelectionTarget(target.focus);
2318
- return anchor && focus ? { anchor, focus } : null;
2319
- }, [resolveSelectionTarget]);
2320
- const resolvedSelectedRange = useMemo2(
2321
- () => resolveSelectionRangeTarget(selectedRange),
2322
- [resolveSelectionRangeTarget, selectedRange]
2323
- );
2324
- const resolvedDefaultSelectedRange = useMemo2(
2325
- () => resolveSelectionRangeTarget(defaultSelectedRange),
2326
- [defaultSelectedRange, resolveSelectionRangeTarget]
2327
- );
2328
2343
  const handleSelectedCellChange = useCallback2((next) => {
2329
2344
  if (!onSelectedCellChange) return;
2330
2345
  if (!next) {
@@ -2337,11 +2352,7 @@ function TableInner({
2337
2352
  onSelectedCellChange({ ...next, value });
2338
2353
  }, [columns, onSelectedCellChange, rows]);
2339
2354
  const [selection, setSelection] = useControllableValue(resolvedSelectedCell, resolvedDefaultSelectedCell, handleSelectedCellChange);
2340
- const [selectionRange, setSelectionRange] = useControllableValue(
2341
- resolvedSelectedRange,
2342
- resolvedDefaultSelectedRange,
2343
- onSelectedRangeChange
2344
- );
2355
+ const [selectionRange, setSelectionRange] = useState6(null);
2345
2356
  const rangeDragRef = useRef4(null);
2346
2357
  useEffect3(() => {
2347
2358
  if (!selection) return void 0;
@@ -2687,14 +2698,17 @@ function TableInner({
2687
2698
  const cornerLength = 10;
2688
2699
  const hitSlop = 6;
2689
2700
  const targetSize = borderWidth + hitSlop * 2;
2690
- const rightBoundaryInset = rightFixedWidth > 0 && hasVerticalBorders ? 1 : 0;
2691
- const markerX = hiddenLeft ? horizontalStart : horizontalEnd - borderWidth - rightBoundaryInset;
2692
- const markerY = hiddenTop ? verticalStart : renderHeight - borderWidth;
2701
+ const leftBoundary = horizontalStart - borderWidth;
2702
+ const rightBoundary = horizontalEnd - 1;
2703
+ const topBoundary = verticalStart - borderWidth;
2704
+ const bottomBoundary = renderHeight;
2705
+ const markerX = hiddenLeft ? leftBoundary : rightBoundary;
2706
+ const markerY = hiddenTop ? topBoundary : bottomBoundary;
2693
2707
  if (hiddenLeft || hiddenRight) {
2694
- let markerTop = Math.max(rawTop - borderWidth, verticalStart);
2695
- let markerBottom = Math.min(rawBottom + 1, renderHeight);
2696
- const topClip = Math.max(0, verticalStart - (rawTop - borderWidth));
2697
- const bottomClip = Math.max(0, rawBottom + 1 - renderHeight);
2708
+ const markerTop = Math.max(rawTop - borderWidth, topBoundary);
2709
+ const markerBottom = Math.min(rawBottom + borderWidth, renderHeight + borderWidth);
2710
+ const topClip = Math.max(0, verticalStart - rawTop);
2711
+ const bottomClip = Math.max(0, rawBottom - renderHeight);
2698
2712
  if (topClip > 0 || bottomClip > 0) {
2699
2713
  const foldAtTop = topClip > 0;
2700
2714
  const fold = Math.min(cornerLength, foldAtTop ? topClip : bottomClip);
@@ -2703,7 +2717,7 @@ function TableInner({
2703
2717
  const cornerHeight = Math.max(cornerLength, markerBottom - markerTop);
2704
2718
  const cornerOnLeft = hiddenLeft;
2705
2719
  const shapeLeft = cornerOnLeft ? markerX : markerX + borderWidth - cornerWidth;
2706
- const shapeTop = foldAtTop ? verticalStart : renderHeight - cornerHeight;
2720
+ const shapeTop = foldAtTop ? topBoundary : renderHeight + borderWidth - cornerHeight;
2707
2721
  return {
2708
2722
  side: `${foldAtTop ? "top" : "bottom"}-${cornerOnLeft ? "left" : "right"}`,
2709
2723
  orientation: "corner",
@@ -2730,19 +2744,19 @@ function TableInner({
2730
2744
  }
2731
2745
  };
2732
2746
  }
2733
- const horizontalMarkerEnd = horizontalEnd - rightBoundaryInset;
2734
- let markerLeft = Math.max(rawLeft - borderWidth, horizontalStart);
2735
- let markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2736
- const leftClip = Math.max(0, horizontalStart - (rawLeft - borderWidth));
2737
- const rightClip = Math.max(0, rawRight + 1 - horizontalMarkerEnd);
2747
+ const horizontalMarkerEnd = horizontalEnd + 1;
2748
+ const markerLeft = Math.max(rawLeft - borderWidth, leftBoundary);
2749
+ const markerRight = Math.min(rawRight + 1, horizontalMarkerEnd);
2750
+ const leftClip = Math.max(0, horizontalStart - rawLeft);
2751
+ const rightClip = Math.max(0, rawRight - horizontalEnd);
2738
2752
  if (leftClip > 0 || rightClip > 0) {
2739
2753
  const foldAtLeft = leftClip > 0;
2740
2754
  const fold = Math.min(cornerLength, foldAtLeft ? leftClip : rightClip);
2741
2755
  if (fold > borderWidth) {
2742
2756
  const cornerWidth = Math.max(cornerLength, markerRight - markerLeft);
2743
2757
  const cornerHeight = fold;
2744
- const shapeLeft = foldAtLeft ? horizontalStart : horizontalMarkerEnd - cornerWidth;
2745
- const shapeTop = hiddenTop ? verticalStart : renderHeight - cornerHeight;
2758
+ const shapeLeft = foldAtLeft ? leftBoundary : horizontalMarkerEnd - cornerWidth;
2759
+ const shapeTop = hiddenTop ? topBoundary : renderHeight + borderWidth - cornerHeight;
2746
2760
  return {
2747
2761
  side: `${hiddenTop ? "top" : "bottom"}-${foldAtLeft ? "left" : "right"}`,
2748
2762
  orientation: "corner",
@@ -2768,7 +2782,7 @@ function TableInner({
2768
2782
  height: targetSize
2769
2783
  }
2770
2784
  };
2771
- }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, hasVerticalBorders, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2785
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, getScrollableColumnLeft, leftFixedOffsets, metrics, renderHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, selection, selectionRange, viewport.width]);
2772
2786
  const syncSelectionEdgeTarget = useCallback2((scrollLeft, scrollTop) => {
2773
2787
  const target = selectionEdgeTargetRef.current;
2774
2788
  if (!target) return;
@@ -3138,11 +3152,20 @@ function TableInner({
3138
3152
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= renderHeight) {
3139
3153
  focus.style.display = "none";
3140
3154
  } else {
3141
- const borderWidth = 2;
3142
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
3143
- const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
3144
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
3145
- const bottom = Math.round(Math.min(rawBottom + borderWidth, renderHeight));
3155
+ const bounds = getSelectionOutlineBounds({
3156
+ left: rawLeft,
3157
+ right: rawRight,
3158
+ top: rawTop,
3159
+ bottom: rawBottom,
3160
+ horizontalStart,
3161
+ horizontalEnd,
3162
+ verticalStart,
3163
+ verticalEnd: renderHeight
3164
+ });
3165
+ const left = Math.round(bounds.left);
3166
+ const right = Math.round(bounds.right);
3167
+ const top = Math.round(bounds.top);
3168
+ const bottom = Math.round(bounds.bottom);
3146
3169
  focus.style.display = "block";
3147
3170
  focus.style.left = `${left}px`;
3148
3171
  focus.style.top = `${top}px`;
@@ -3467,10 +3490,8 @@ function TableInner({
3467
3490
  if (focus.rowIndex === drag.anchor.rowIndex && focus.columnIndex === drag.anchor.columnIndex) return;
3468
3491
  drag.moved = true;
3469
3492
  event.preventDefault();
3470
- if (selectionRange?.focus.rowIndex === focus.rowIndex && selectionRange.focus.columnIndex === focus.columnIndex)
3471
- return;
3472
- setSelectionRange({ anchor: drag.anchor, focus });
3473
- }, [columns, locateCell, selectionRange, setSelectionRange]);
3493
+ setSelectionRange((current) => current?.focus.rowIndex === focus.rowIndex && current.focus.columnIndex === focus.columnIndex ? current : { anchor: drag.anchor, focus });
3494
+ }, [columns, locateCell]);
3474
3495
  const endRangeDrag = useCallback2((event) => {
3475
3496
  const drag = rangeDragRef.current;
3476
3497
  if (!drag) return;
@@ -4680,11 +4701,20 @@ function TableInner({
4680
4701
  const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
4681
4702
  const verticalStart = fixedHeader ? bodyTop : 0;
4682
4703
  if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom + dragOffset <= verticalStart || rawTop + dragOffset >= renderHeight) return null;
4683
- const borderWidth = 2;
4684
- const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart));
4685
- const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
4686
- const top = Math.round(Math.max(rawTop - borderWidth, verticalStart));
4687
- const bottom = Math.round(Math.min(rawBottom + borderWidth, renderHeight));
4704
+ const bounds = getSelectionOutlineBounds({
4705
+ left: rawLeft,
4706
+ right: rawRight,
4707
+ top: rawTop,
4708
+ bottom: rawBottom,
4709
+ horizontalStart,
4710
+ horizontalEnd,
4711
+ verticalStart,
4712
+ verticalEnd: renderHeight
4713
+ });
4714
+ const left = Math.round(bounds.left);
4715
+ const right = Math.round(bounds.right);
4716
+ const top = Math.round(bounds.top);
4717
+ const bottom = Math.round(bounds.bottom);
4688
4718
  return {
4689
4719
  left,
4690
4720
  top,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",