@lensui/lens-table 0.1.17 → 0.1.19

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 CHANGED
@@ -98,6 +98,7 @@ export function Example() {
98
98
  | `verticalBorderless` | `boolean` | `false` | 已兼容保留,建议改用 `borderless`。 |
99
99
  | `horizontalBorderless` | `boolean` | `false` | 是否隐藏横向行分隔线及上下边框,同时保留纵向列分隔线。可与 `striped` 配合使用。 |
100
100
  | `frameBorderless` | `boolean` | `false` | 是否隐藏表格四周外框,同时保留内部行列分隔线。 |
101
+ | `rowStyle` | `(row, rowIndex) => { color?: string; backgroundColor?: string }` | - | 自定义整行绘制样式,覆盖数据列及自动生成的选择列、序号列和拖拽列。 |
101
102
  | `striped` | `boolean \| string` | `false` | 是否显示斑马纹行背景。传`true` 使用主题变量,传颜色字符串可自定义斑马纹颜色,例如 `'#f6faff'`。 |
102
103
  | `highlight` | `{ editedCells?: boolean \| string; insertedRows?: boolean \| string }` | `{}` | 高亮反馈配置。`editedCells` 控制编辑过的单元格底色,`insertedRows` 控制乐观插入行底色;传 `true` 使用默认色,传颜色字符串可自定义。 |
103
104
  | `cellSpans` | `TableCellSpan[] \| ((ctx) => TableCellSpan \| { rowSpan?: number; colSpan?: number } \| null)` | `[]` | 合并主体单元格配置。可传数组,也可传函数按单元格生成规则。推荐使用 `rowKey` 定位;横向合并会限制在同一个固定列分区内,工具列不会参与合并。 |
package/dist/index.cjs CHANGED
@@ -1007,6 +1007,7 @@ function paintGrid(options) {
1007
1007
  const y = bodyTop + rowIndex * rowHeight - scrollTop + shift;
1008
1008
  const row = rows[rowIndex];
1009
1009
  const rowKey = getRowKey(row, rowIndex);
1010
+ const rowStyle = options.rowStyle?.(row, rowIndex);
1010
1011
  for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
1011
1012
  if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
1012
1013
  if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
@@ -1029,6 +1030,10 @@ function paintGrid(options) {
1029
1030
  ctx.fillStyle = striped && rowIndex % 2 === 1 ? colors.stripe : colors.background;
1030
1031
  ctx.fillRect(x, y, cellWidth, cellHeight);
1031
1032
  }
1033
+ if (rowStyle?.backgroundColor) {
1034
+ ctx.fillStyle = rowStyle.backgroundColor;
1035
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1036
+ }
1032
1037
  if (isAxisSelected) {
1033
1038
  ctx.fillStyle = colors.axisSelectionFill;
1034
1039
  ctx.fillRect(x, y, cellWidth, cellHeight);
@@ -1074,14 +1079,14 @@ function paintGrid(options) {
1074
1079
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1075
1080
  }
1076
1081
  if (rowSpan > 1) {
1077
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1082
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1078
1083
  ctx.fillStyle = mergedFill;
1079
1084
  for (let offset = 1; offset < rowSpan; offset += 1) {
1080
1085
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1081
1086
  }
1082
1087
  }
1083
1088
  if (colSpan > 1 && !verticalBorderless) {
1084
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1089
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1085
1090
  ctx.fillStyle = mergedFill;
1086
1091
  let offsetLeft = 0;
1087
1092
  for (let offset = 1; offset < colSpan; offset += 1) {
@@ -1178,7 +1183,7 @@ function paintGrid(options) {
1178
1183
  ctx.beginPath();
1179
1184
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1180
1185
  ctx.clip();
1181
- ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? colors.text;
1186
+ ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1182
1187
  ctx.font = createCellFont(bodyFontSize);
1183
1188
  ctx.textBaseline = "alphabetic";
1184
1189
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
@@ -1626,6 +1631,7 @@ function TableInner({
1626
1631
  horizontalBorderless = false,
1627
1632
  frameBorderless = false,
1628
1633
  striped = false,
1634
+ rowStyle,
1629
1635
  highlight,
1630
1636
  cellSpans = [],
1631
1637
  loading = false,
@@ -1657,6 +1663,7 @@ function TableInner({
1657
1663
  onSortChange,
1658
1664
  onFilterChange,
1659
1665
  onCellChange,
1666
+ editTrigger = "double-click",
1660
1667
  onCellClick,
1661
1668
  onCellDoubleClick,
1662
1669
  onCellContextMenu,
@@ -1753,6 +1760,7 @@ function TableInner({
1753
1760
  }, [columns, headerCells, headerDepth]);
1754
1761
  const canvasRef = (0, import_react5.useRef)(null);
1755
1762
  const scrollerRef = (0, import_react5.useRef)(null);
1763
+ const rootRef = (0, import_react5.useRef)(null);
1756
1764
  const frameRef = (0, import_react5.useRef)(null);
1757
1765
  const textFrameRef = (0, import_react5.useRef)(null);
1758
1766
  const toastTimerRef = (0, import_react5.useRef)(null);
@@ -2036,6 +2044,18 @@ function TableInner({
2036
2044
  const [selection, setSelection] = useControllableValue(resolvedSelectedCell, resolvedDefaultSelectedCell, handleSelectedCellChange);
2037
2045
  const [selectionRange, setSelectionRange] = (0, import_react5.useState)(null);
2038
2046
  const rangeDragRef = (0, import_react5.useRef)(null);
2047
+ (0, import_react5.useEffect)(() => {
2048
+ if (!selection) return void 0;
2049
+ const handleOutsidePointerDown = (event) => {
2050
+ const target = event.target;
2051
+ if (!(target instanceof Node) || rootRef.current?.contains(target)) return;
2052
+ clickedCellRef.current = null;
2053
+ setSelectionRange(null);
2054
+ setSelection(null);
2055
+ };
2056
+ document.addEventListener("pointerdown", handleOutsidePointerDown);
2057
+ return () => document.removeEventListener("pointerdown", handleOutsidePointerDown);
2058
+ }, [selection, setSelection]);
2039
2059
  const applyRowOrder = (0, import_react5.useCallback)((sourceIndex, targetIndex) => {
2040
2060
  if (sourceIndex === targetIndex || sourceIndex < 0 || targetIndex < 0 || sourceIndex >= rows.length || targetIndex >= rows.length) return;
2041
2061
  const nextEntries = rows.map((row, index) => ({ row, key: getDataRowKey(row, index) }));
@@ -2311,8 +2331,8 @@ function TableInner({
2311
2331
  const scroll = scrollRef.current;
2312
2332
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2313
2333
  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 };
2314
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2315
- }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2334
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2335
+ }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rowStyle, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2316
2336
  const scheduleDraw = (0, import_react5.useCallback)(() => {
2317
2337
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2318
2338
  }, [draw]);
@@ -4595,7 +4615,7 @@ function TableInner({
4595
4615
  };
4596
4616
  const currentScroll = scrollRef.current;
4597
4617
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4598
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4618
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ref: rootRef, className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4599
4619
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { ref: scrollerRef, className: `rvg-scroller${contextMenu ? " is-context-menu-open" : ""}`, style: { bottom: scrollerBottom }, onScroll: handleScroll, onCopy: handleCopy, onContextMenu: handleContextMenu, tabIndex: 0, role: "grid", "aria-label": ariaLabel, "aria-rowcount": rows.length, "aria-colcount": columns.length, onKeyDown: handleKeyDown, children: [
4600
4620
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4601
4621
  "canvas",
@@ -4777,7 +4797,12 @@ function TableInner({
4777
4797
  return;
4778
4798
  }
4779
4799
  const cell = locateCell(event.clientX, event.clientY);
4780
- if (!cell) return;
4800
+ if (!cell) {
4801
+ clickedCellRef.current = null;
4802
+ setSelectionRange(null);
4803
+ setSelection(null);
4804
+ return;
4805
+ }
4781
4806
  if (columns[cell.columnIndex].rowDragHandle || columns[cell.columnIndex].rowNumber) {
4782
4807
  clickedCellRef.current = null;
4783
4808
  return;
@@ -4801,6 +4826,7 @@ function TableInner({
4801
4826
  selectRow(cell.rowIndex, event);
4802
4827
  }
4803
4828
  revealCell(cell);
4829
+ if (editTrigger === "single-click") beginEdit(cell);
4804
4830
  },
4805
4831
  onDoubleClick: (event) => {
4806
4832
  const cell = clickedCellRef.current ?? locateCell(event.clientX, event.clientY);
@@ -4809,7 +4835,7 @@ function TableInner({
4809
4835
  if (column.rowNumber || column.rowSelection || column.rowDragHandle) return;
4810
4836
  const row = rows[cell.rowIndex];
4811
4837
  onCellDoubleClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
4812
- if (!event.defaultPrevented) beginEdit(cell);
4838
+ if (!event.defaultPrevented && editTrigger === "double-click") beginEdit(cell);
4813
4839
  },
4814
4840
  onContextMenu: handleContextMenu
4815
4841
  }
package/dist/index.d.cts CHANGED
@@ -334,6 +334,11 @@ type TableContextMenu<Row> = boolean | TableContextMenuConfig<Row>;
334
334
  interface TableProps<Row extends object> {
335
335
  columns: GridColumn<Row>[];
336
336
  rows: Row[];
337
+ /** Custom row paint style. Applies to data and generated utility columns. */
338
+ rowStyle?: (row: Row, rowIndex: number) => {
339
+ color?: string;
340
+ backgroundColor?: string;
341
+ };
337
342
  /** Stable row id. Defaults to the row's `id` field when omitted. */
338
343
  rowKey?: keyof Row | ((row: Row, index: number) => GridKey);
339
344
  width?: number | string;
@@ -413,6 +418,8 @@ interface TableProps<Row extends object> {
413
418
  /** Overrides the built-in text filtering. Return the rows that match the internally managed filter values. */
414
419
  onFilterChange?: (rows: Row[], values: Readonly<Record<string, string>>) => Row[];
415
420
  onCellChange?: (change: CellChange<Row>) => void | Promise<void>;
421
+ /** Starts editing from a single or double click. Defaults to double-click. */
422
+ editTrigger?: 'single-click' | 'double-click';
416
423
  /** Fires on a data cell click, before the default selection behavior. */
417
424
  onCellClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
418
425
  /** Fires before double-click editing. preventDefault() cancels editing. */
package/dist/index.d.ts CHANGED
@@ -334,6 +334,11 @@ type TableContextMenu<Row> = boolean | TableContextMenuConfig<Row>;
334
334
  interface TableProps<Row extends object> {
335
335
  columns: GridColumn<Row>[];
336
336
  rows: Row[];
337
+ /** Custom row paint style. Applies to data and generated utility columns. */
338
+ rowStyle?: (row: Row, rowIndex: number) => {
339
+ color?: string;
340
+ backgroundColor?: string;
341
+ };
337
342
  /** Stable row id. Defaults to the row's `id` field when omitted. */
338
343
  rowKey?: keyof Row | ((row: Row, index: number) => GridKey);
339
344
  width?: number | string;
@@ -413,6 +418,8 @@ interface TableProps<Row extends object> {
413
418
  /** Overrides the built-in text filtering. Return the rows that match the internally managed filter values. */
414
419
  onFilterChange?: (rows: Row[], values: Readonly<Record<string, string>>) => Row[];
415
420
  onCellChange?: (change: CellChange<Row>) => void | Promise<void>;
421
+ /** Starts editing from a single or double click. Defaults to double-click. */
422
+ editTrigger?: 'single-click' | 'double-click';
416
423
  /** Fires on a data cell click, before the default selection behavior. */
417
424
  onCellClick?: (cell: TableCellContext<Row>, event: MouseEvent<HTMLElement>) => void;
418
425
  /** Fires before double-click editing. preventDefault() cancels editing. */
package/dist/index.js CHANGED
@@ -977,6 +977,7 @@ function paintGrid(options) {
977
977
  const y = bodyTop + rowIndex * rowHeight - scrollTop + shift;
978
978
  const row = rows[rowIndex];
979
979
  const rowKey = getRowKey(row, rowIndex);
980
+ const rowStyle = options.rowStyle?.(row, rowIndex);
980
981
  for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
981
982
  if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
982
983
  if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
@@ -999,6 +1000,10 @@ function paintGrid(options) {
999
1000
  ctx.fillStyle = striped && rowIndex % 2 === 1 ? colors.stripe : colors.background;
1000
1001
  ctx.fillRect(x, y, cellWidth, cellHeight);
1001
1002
  }
1003
+ if (rowStyle?.backgroundColor) {
1004
+ ctx.fillStyle = rowStyle.backgroundColor;
1005
+ ctx.fillRect(x, y, cellWidth, cellHeight);
1006
+ }
1002
1007
  if (isAxisSelected) {
1003
1008
  ctx.fillStyle = colors.axisSelectionFill;
1004
1009
  ctx.fillRect(x, y, cellWidth, cellHeight);
@@ -1044,14 +1049,14 @@ function paintGrid(options) {
1044
1049
  ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
1045
1050
  }
1046
1051
  if (rowSpan > 1) {
1047
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1052
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1048
1053
  ctx.fillStyle = mergedFill;
1049
1054
  for (let offset = 1; offset < rowSpan; offset += 1) {
1050
1055
  ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
1051
1056
  }
1052
1057
  }
1053
1058
  if (colSpan > 1 && !verticalBorderless) {
1054
- const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1059
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : isAxisSelected ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? rowStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
1055
1060
  ctx.fillStyle = mergedFill;
1056
1061
  let offsetLeft = 0;
1057
1062
  for (let offset = 1; offset < colSpan; offset += 1) {
@@ -1148,7 +1153,7 @@ function paintGrid(options) {
1148
1153
  ctx.beginPath();
1149
1154
  ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
1150
1155
  ctx.clip();
1151
- ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? colors.text;
1156
+ ctx.fillStyle = isAxisSelected && !isSelectedCell && !isRangeCell ? colors.axisSelectionText : cellStyle?.color ?? rowStyle?.color ?? colors.text;
1152
1157
  ctx.font = createCellFont(bodyFontSize);
1153
1158
  ctx.textBaseline = "alphabetic";
1154
1159
  ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
@@ -1596,6 +1601,7 @@ function TableInner({
1596
1601
  horizontalBorderless = false,
1597
1602
  frameBorderless = false,
1598
1603
  striped = false,
1604
+ rowStyle,
1599
1605
  highlight,
1600
1606
  cellSpans = [],
1601
1607
  loading = false,
@@ -1627,6 +1633,7 @@ function TableInner({
1627
1633
  onSortChange,
1628
1634
  onFilterChange,
1629
1635
  onCellChange,
1636
+ editTrigger = "double-click",
1630
1637
  onCellClick,
1631
1638
  onCellDoubleClick,
1632
1639
  onCellContextMenu,
@@ -1723,6 +1730,7 @@ function TableInner({
1723
1730
  }, [columns, headerCells, headerDepth]);
1724
1731
  const canvasRef = useRef2(null);
1725
1732
  const scrollerRef = useRef2(null);
1733
+ const rootRef = useRef2(null);
1726
1734
  const frameRef = useRef2(null);
1727
1735
  const textFrameRef = useRef2(null);
1728
1736
  const toastTimerRef = useRef2(null);
@@ -2006,6 +2014,18 @@ function TableInner({
2006
2014
  const [selection, setSelection] = useControllableValue(resolvedSelectedCell, resolvedDefaultSelectedCell, handleSelectedCellChange);
2007
2015
  const [selectionRange, setSelectionRange] = useState4(null);
2008
2016
  const rangeDragRef = useRef2(null);
2017
+ useEffect2(() => {
2018
+ if (!selection) return void 0;
2019
+ const handleOutsidePointerDown = (event) => {
2020
+ const target = event.target;
2021
+ if (!(target instanceof Node) || rootRef.current?.contains(target)) return;
2022
+ clickedCellRef.current = null;
2023
+ setSelectionRange(null);
2024
+ setSelection(null);
2025
+ };
2026
+ document.addEventListener("pointerdown", handleOutsidePointerDown);
2027
+ return () => document.removeEventListener("pointerdown", handleOutsidePointerDown);
2028
+ }, [selection, setSelection]);
2009
2029
  const applyRowOrder = useCallback2((sourceIndex, targetIndex) => {
2010
2030
  if (sourceIndex === targetIndex || sourceIndex < 0 || targetIndex < 0 || sourceIndex >= rows.length || targetIndex >= rows.length) return;
2011
2031
  const nextEntries = rows.map((row, index) => ({ row, key: getDataRowKey(row, index) }));
@@ -2281,8 +2301,8 @@ function TableInner({
2281
2301
  const scroll = scrollRef.current;
2282
2302
  const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
2283
2303
  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 };
2284
- paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2285
- }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2304
+ paintGrid({ context, width: viewport.width, height: renderHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, bodyFontSize, headerActionSlotWidth, headerHeight, headerLeafTop, headerLeafHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, horizontalBorderless, frameBorderless, extendVerticalGridLines: !autoHeight, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, rowStyle, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, rowSelectionMode, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, columnDropTarget, colors: themeColors });
2305
+ }, [autoHeight, bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columnDropTarget, columns, editedCellHighlightColor, editedCellKeys, editing, renderHeight, filterValues, fixedHeader, frameBorderless, getRowKey, hasStripedRows, hasVerticalBorders, headerActionSlotWidth, headerDepth, headerHeight, headerLeafHeight, headerLeafTop, horizontalBorderless, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, rowDragPreview, rowHeight, rowSelectionMode, rowStyle, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
2286
2306
  const scheduleDraw = useCallback2(() => {
2287
2307
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
2288
2308
  }, [draw]);
@@ -4565,7 +4585,7 @@ function TableInner({
4565
4585
  };
4566
4586
  const currentScroll = scrollRef.current;
4567
4587
  const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
4568
- return /* @__PURE__ */ jsxs7("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4588
+ return /* @__PURE__ */ jsxs7("div", { ref: rootRef, className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${horizontalBorderless ? " is-horizontal-borderless" : ""}${frameBorderless ? " is-frame-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
4569
4589
  /* @__PURE__ */ jsxs7("div", { ref: scrollerRef, className: `rvg-scroller${contextMenu ? " is-context-menu-open" : ""}`, style: { bottom: scrollerBottom }, onScroll: handleScroll, onCopy: handleCopy, onContextMenu: handleContextMenu, tabIndex: 0, role: "grid", "aria-label": ariaLabel, "aria-rowcount": rows.length, "aria-colcount": columns.length, onKeyDown: handleKeyDown, children: [
4570
4590
  /* @__PURE__ */ jsx7(
4571
4591
  "canvas",
@@ -4747,7 +4767,12 @@ function TableInner({
4747
4767
  return;
4748
4768
  }
4749
4769
  const cell = locateCell(event.clientX, event.clientY);
4750
- if (!cell) return;
4770
+ if (!cell) {
4771
+ clickedCellRef.current = null;
4772
+ setSelectionRange(null);
4773
+ setSelection(null);
4774
+ return;
4775
+ }
4751
4776
  if (columns[cell.columnIndex].rowDragHandle || columns[cell.columnIndex].rowNumber) {
4752
4777
  clickedCellRef.current = null;
4753
4778
  return;
@@ -4771,6 +4796,7 @@ function TableInner({
4771
4796
  selectRow(cell.rowIndex, event);
4772
4797
  }
4773
4798
  revealCell(cell);
4799
+ if (editTrigger === "single-click") beginEdit(cell);
4774
4800
  },
4775
4801
  onDoubleClick: (event) => {
4776
4802
  const cell = clickedCellRef.current ?? locateCell(event.clientX, event.clientY);
@@ -4779,7 +4805,7 @@ function TableInner({
4779
4805
  if (column.rowNumber || column.rowSelection || column.rowDragHandle) return;
4780
4806
  const row = rows[cell.rowIndex];
4781
4807
  onCellDoubleClick?.({ ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] }, event);
4782
- if (!event.defaultPrevented) beginEdit(cell);
4808
+ if (!event.defaultPrevented && editTrigger === "double-click") beginEdit(cell);
4783
4809
  },
4784
4810
  onContextMenu: handleContextMenu
4785
4811
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensui/lens-table",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "A lightweight, canvas-rendered React table component for LensUI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",