bolt-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/dist/index.d.mts CHANGED
@@ -67,6 +67,25 @@ interface ColumnType<T = unknown> {
67
67
  * `true` copies the raw value; pass a function for custom copy text.
68
68
  */
69
69
  copy?: boolean | ((value: unknown, record: T, index: number) => string);
70
+ /** Custom context menu items appended to this column's header right-click menu. */
71
+ columnHeaderContextMenuItems?: ColumnContextMenuItem[];
72
+ /** Custom context menu items appended to every cell's right-click menu in this column. */
73
+ columnCellContextMenuItems?: CellContextMenuItem<T>[];
74
+ }
75
+ /** A single item in the cell right-click context menu (column-level). */
76
+ interface CellContextMenuItem<T = unknown> {
77
+ /** Unique identifier for this menu item, used as the React `key`. */
78
+ key: string;
79
+ /** The label shown in the menu. Can be a string or React node. */
80
+ label: React.ReactNode;
81
+ /** Optional icon shown to the left of the label. */
82
+ icon?: React.ReactNode;
83
+ /** When `true`, the label renders in red to indicate a destructive action. */
84
+ danger?: boolean;
85
+ /** When `true`, the item is grayed out and click handler is not called. */
86
+ disabled?: boolean;
87
+ /** Called when the user clicks this menu item. Receives the column key, row record, and row index. */
88
+ onClick: (columnKey: string, record: T, rowIndex: number) => void;
70
89
  }
71
90
  /** A single item in the column header right-click context menu. */
72
91
  interface ColumnContextMenuItem {
@@ -201,11 +220,6 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
201
220
  readonly onEndReachedThreshold?: number;
202
221
  /** When true and data is empty, shows shimmer skeleton rows. With data, appends shimmer rows at bottom. */
203
222
  readonly isLoading?: boolean;
204
- /** Scroll indicator configuration (reserved for future use). */
205
- readonly scrollIndicators?: {
206
- vertical?: boolean;
207
- horizontal?: boolean;
208
- };
209
223
  /** Called when the user changes sort direction. Provide for server-side sorting. */
210
224
  readonly onSortChange?: (columnKey: string, direction: SortDirection) => void;
211
225
  /** Called when the user applies or clears a column filter. Provide for server-side filtering. */
@@ -218,6 +232,10 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
218
232
  readonly layoutLoading?: boolean;
219
233
  /** Custom React node to render when the table has no data and is not loading. */
220
234
  readonly emptyRenderer?: React$1.ReactNode;
235
+ /** Returns a CSS class name for a given row based on its record and index. Useful for Tailwind or any CSS class-based conditional row styling. */
236
+ readonly rowClassName?: (record: T, index: number) => string;
237
+ /** Returns inline CSS styles for a given row based on its record and index. Useful for dynamic per-row styling. */
238
+ readonly rowStyle?: (record: T, index: number) => React$1.CSSProperties;
221
239
  }
222
240
  interface ClassNamesTypes {
223
241
  /** Applied to all non-pinned column header cells. */
@@ -263,7 +281,7 @@ interface StylesTypes {
263
281
  /** CSS color string for pinned column cells and headers background. */
264
282
  pinnedBg?: string;
265
283
  }
266
- declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
284
+ declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, rowClassName, rowStyle, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
267
285
 
268
286
  interface DraggableHeaderProps {
269
287
  /** Column definition for this header cell. */
@@ -367,7 +385,11 @@ interface TableBodyProps {
367
385
  gridTemplateColumns?: string;
368
386
  /** Height of the column header row in pixels */
369
387
  headerHeight?: number;
388
+ /** Returns a CSS class name for a given row based on its record and index */
389
+ rowClassName?: (record: DataRecord, index: number) => string;
390
+ /** Returns inline CSS styles for a given row based on its record and index */
391
+ rowStyle?: (record: DataRecord, index: number) => React$1.CSSProperties;
370
392
  }
371
393
  declare const TableBody: React$1.FC<TableBodyProps>;
372
394
 
373
- export { BoltTable, type BoltTableIcons, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
395
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,25 @@ interface ColumnType<T = unknown> {
67
67
  * `true` copies the raw value; pass a function for custom copy text.
68
68
  */
69
69
  copy?: boolean | ((value: unknown, record: T, index: number) => string);
70
+ /** Custom context menu items appended to this column's header right-click menu. */
71
+ columnHeaderContextMenuItems?: ColumnContextMenuItem[];
72
+ /** Custom context menu items appended to every cell's right-click menu in this column. */
73
+ columnCellContextMenuItems?: CellContextMenuItem<T>[];
74
+ }
75
+ /** A single item in the cell right-click context menu (column-level). */
76
+ interface CellContextMenuItem<T = unknown> {
77
+ /** Unique identifier for this menu item, used as the React `key`. */
78
+ key: string;
79
+ /** The label shown in the menu. Can be a string or React node. */
80
+ label: React.ReactNode;
81
+ /** Optional icon shown to the left of the label. */
82
+ icon?: React.ReactNode;
83
+ /** When `true`, the label renders in red to indicate a destructive action. */
84
+ danger?: boolean;
85
+ /** When `true`, the item is grayed out and click handler is not called. */
86
+ disabled?: boolean;
87
+ /** Called when the user clicks this menu item. Receives the column key, row record, and row index. */
88
+ onClick: (columnKey: string, record: T, rowIndex: number) => void;
70
89
  }
71
90
  /** A single item in the column header right-click context menu. */
72
91
  interface ColumnContextMenuItem {
@@ -201,11 +220,6 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
201
220
  readonly onEndReachedThreshold?: number;
202
221
  /** When true and data is empty, shows shimmer skeleton rows. With data, appends shimmer rows at bottom. */
203
222
  readonly isLoading?: boolean;
204
- /** Scroll indicator configuration (reserved for future use). */
205
- readonly scrollIndicators?: {
206
- vertical?: boolean;
207
- horizontal?: boolean;
208
- };
209
223
  /** Called when the user changes sort direction. Provide for server-side sorting. */
210
224
  readonly onSortChange?: (columnKey: string, direction: SortDirection) => void;
211
225
  /** Called when the user applies or clears a column filter. Provide for server-side filtering. */
@@ -218,6 +232,10 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
218
232
  readonly layoutLoading?: boolean;
219
233
  /** Custom React node to render when the table has no data and is not loading. */
220
234
  readonly emptyRenderer?: React$1.ReactNode;
235
+ /** Returns a CSS class name for a given row based on its record and index. Useful for Tailwind or any CSS class-based conditional row styling. */
236
+ readonly rowClassName?: (record: T, index: number) => string;
237
+ /** Returns inline CSS styles for a given row based on its record and index. Useful for dynamic per-row styling. */
238
+ readonly rowStyle?: (record: T, index: number) => React$1.CSSProperties;
221
239
  }
222
240
  interface ClassNamesTypes {
223
241
  /** Applied to all non-pinned column header cells. */
@@ -263,7 +281,7 @@ interface StylesTypes {
263
281
  /** CSS color string for pinned column cells and headers background. */
264
282
  pinnedBg?: string;
265
283
  }
266
- declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
284
+ declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, rowClassName, rowStyle, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
267
285
 
268
286
  interface DraggableHeaderProps {
269
287
  /** Column definition for this header cell. */
@@ -367,7 +385,11 @@ interface TableBodyProps {
367
385
  gridTemplateColumns?: string;
368
386
  /** Height of the column header row in pixels */
369
387
  headerHeight?: number;
388
+ /** Returns a CSS class name for a given row based on its record and index */
389
+ rowClassName?: (record: DataRecord, index: number) => string;
390
+ /** Returns inline CSS styles for a given row based on its record and index */
391
+ rowStyle?: (record: DataRecord, index: number) => React$1.CSSProperties;
370
392
  }
371
393
  declare const TableBody: React$1.FC<TableBodyProps>;
372
394
 
373
- export { BoltTable, type BoltTableIcons, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
395
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody };
package/dist/index.js CHANGED
@@ -713,7 +713,7 @@ var DraggableHeader = import_react.default.memo(
713
713
  ] });
714
714
  },
715
715
  (prevProps, nextProps) => {
716
- return prevProps.column.width === nextProps.column.width && prevProps.column.key === nextProps.column.key && prevProps.column.pinned === nextProps.column.pinned && prevProps.column.sortable === nextProps.column.sortable && prevProps.column.filterable === nextProps.column.filterable && prevProps.column.sorter === nextProps.column.sorter && prevProps.column.filterFn === nextProps.column.filterFn && prevProps.visualIndex === nextProps.visualIndex && prevProps.stickyOffset === nextProps.stickyOffset && prevProps.isLastColumn === nextProps.isLastColumn && prevProps.sortDirection === nextProps.sortDirection && prevProps.filterValue === nextProps.filterValue && prevProps.classNames === nextProps.classNames && prevProps.styles === nextProps.styles;
716
+ return prevProps.column.width === nextProps.column.width && prevProps.column.key === nextProps.column.key && prevProps.column.pinned === nextProps.column.pinned && prevProps.column.sortable === nextProps.column.sortable && prevProps.column.filterable === nextProps.column.filterable && prevProps.column.sorter === nextProps.column.sorter && prevProps.column.filterFn === nextProps.column.filterFn && prevProps.visualIndex === nextProps.visualIndex && prevProps.stickyOffset === nextProps.stickyOffset && prevProps.isLastColumn === nextProps.isLastColumn && prevProps.sortDirection === nextProps.sortDirection && prevProps.filterValue === nextProps.filterValue && prevProps.classNames === nextProps.classNames && prevProps.styles === nextProps.styles && prevProps.customContextMenuItems === nextProps.customContextMenuItems;
717
717
  }
718
718
  );
719
719
  DraggableHeader.displayName = "DraggableHeader";
@@ -1085,7 +1085,9 @@ var TableBody = ({
1085
1085
  pinnedTopData = [],
1086
1086
  pinnedBottomData = [],
1087
1087
  gridTemplateColumns,
1088
- headerHeight = 36
1088
+ headerHeight = 36,
1089
+ rowClassName,
1090
+ rowStyle
1089
1091
  }) => {
1090
1092
  const virtualItems = rowVirtualizer.getVirtualItems();
1091
1093
  const totalSize = rowVirtualizer.getTotalSize();
@@ -1137,6 +1139,8 @@ var TableBody = ({
1137
1139
  const cellValue = row[col.dataIndex];
1138
1140
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1139
1141
  const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1142
+ const rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1143
+ const rowSty = rowStyle ? rowStyle(row, virtualRow.index) : void 0;
1140
1144
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1141
1145
  "div",
1142
1146
  {
@@ -1144,12 +1148,14 @@ var TableBody = ({
1144
1148
  "data-column-key": col.key,
1145
1149
  "data-bt-cell": "",
1146
1150
  "data-selected": isSelected || void 0,
1151
+ className: rowCls || void 0,
1147
1152
  style: {
1148
1153
  position: "absolute",
1149
1154
  top: `${virtualRow.start}px`,
1150
1155
  left: 0,
1151
1156
  right: 0,
1152
- height: `${virtualRow.size}px`
1157
+ height: `${virtualRow.size}px`,
1158
+ ...rowSty
1153
1159
  },
1154
1160
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1155
1161
  "div",
@@ -1271,15 +1277,18 @@ var TableBody = ({
1271
1277
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1272
1278
  const isSelected = selectedKeySet.has(rk);
1273
1279
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1280
+ const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1281
+ const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1274
1282
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1275
1283
  "div",
1276
1284
  {
1277
- className: classNames?.pinnedRow ?? "",
1285
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1278
1286
  style: {
1279
1287
  display: "grid",
1280
1288
  gridTemplateColumns: gridTemplateColumns ?? "",
1281
1289
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1282
- ...styles?.pinnedRow
1290
+ ...styles?.pinnedRow,
1291
+ ...rowSty
1283
1292
  },
1284
1293
  children: orderedColumns.map((col) => {
1285
1294
  const cellValue = row[col.dataIndex];
@@ -1376,15 +1385,18 @@ var TableBody = ({
1376
1385
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1377
1386
  const isSelected = selectedKeySet.has(rk);
1378
1387
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1388
+ const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1389
+ const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1379
1390
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1380
1391
  "div",
1381
1392
  {
1382
- className: classNames?.pinnedRow ?? "",
1393
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1383
1394
  style: {
1384
1395
  display: "grid",
1385
1396
  gridTemplateColumns: gridTemplateColumns ?? "",
1386
1397
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1387
- ...styles?.pinnedRow
1398
+ ...styles?.pinnedRow,
1399
+ ...rowSty
1388
1400
  },
1389
1401
  children: orderedColumns.map((col) => {
1390
1402
  const cellValue = row[col.dataIndex];
@@ -1502,7 +1514,9 @@ function BoltTable({
1502
1514
  columnContextMenuItems,
1503
1515
  autoHeight = true,
1504
1516
  layoutLoading,
1505
- emptyRenderer
1517
+ emptyRenderer,
1518
+ rowClassName,
1519
+ rowStyle
1506
1520
  }) {
1507
1521
  const [columns, setColumns] = (0, import_react4.useState)(initialColumns);
1508
1522
  const [columnOrder, setColumnOrder] = (0, import_react4.useState)(
@@ -2489,7 +2503,8 @@ function BoltTable({
2489
2503
  );
2490
2504
  const hasCopy = col?.copy;
2491
2505
  const hasRowPin = !!onRowPin;
2492
- if (!hasCopy && !hasRowPin) return;
2506
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2507
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2493
2508
  e.preventDefault();
2494
2509
  setCellContextMenu({
2495
2510
  x: Math.min(e.clientX, window.innerWidth - 200),
@@ -2517,7 +2532,8 @@ function BoltTable({
2517
2532
  );
2518
2533
  const hasCopy = col?.copy;
2519
2534
  const hasRowPin = !!onRowPin;
2520
- if (!hasCopy && !hasRowPin) return;
2535
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2536
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2521
2537
  setCellContextMenu({
2522
2538
  x: Math.min(touch.clientX, window.innerWidth - 200),
2523
2539
  y: Math.min(touch.clientY, window.innerHeight - 200),
@@ -2649,7 +2665,7 @@ function BoltTable({
2649
2665
  filterValue: columnFilters[column.key] ?? "",
2650
2666
  onFilter: handleColumnFilter,
2651
2667
  onClearFilter: handleClearFilter,
2652
- customContextMenuItems: columnContextMenuItems
2668
+ customContextMenuItems: column.columnHeaderContextMenuItems ? [...columnContextMenuItems ?? [], ...column.columnHeaderContextMenuItems] : columnContextMenuItems
2653
2669
  },
2654
2670
  column.key
2655
2671
  );
@@ -2717,7 +2733,9 @@ function BoltTable({
2717
2733
  pinnedTopData: pinnedTopRows,
2718
2734
  pinnedBottomData: pinnedBottomRows,
2719
2735
  gridTemplateColumns,
2720
- headerHeight: HEADER_HEIGHT
2736
+ headerHeight: HEADER_HEIGHT,
2737
+ rowClassName,
2738
+ rowStyle
2721
2739
  }
2722
2740
  )
2723
2741
  ]
@@ -3174,7 +3192,42 @@ function BoltTable({
3174
3192
  "Copy"
3175
3193
  ]
3176
3194
  }
3177
- )
3195
+ ),
3196
+ menuCol?.columnCellContextMenuItems && menuCol.columnCellContextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3197
+ (hasCopy || hasRowPin) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3198
+ "div",
3199
+ {
3200
+ style: {
3201
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3202
+ margin: "4px 0"
3203
+ }
3204
+ }
3205
+ ),
3206
+ menuCol.columnCellContextMenuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3207
+ "button",
3208
+ {
3209
+ "data-bt-ctx-item": "",
3210
+ disabled: item.disabled,
3211
+ style: {
3212
+ ...btnStyle,
3213
+ cursor: item.disabled ? "not-allowed" : "pointer",
3214
+ opacity: item.disabled ? 0.5 : 1,
3215
+ color: item.danger ? "#ef4444" : "inherit"
3216
+ },
3217
+ onClick: () => {
3218
+ if (menuRecord) {
3219
+ item.onClick(menuCol.key, menuRecord, menuRowIndex);
3220
+ }
3221
+ setCellContextMenu(null);
3222
+ },
3223
+ children: [
3224
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { display: "flex", width: 14, height: 14, alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: item.icon }),
3225
+ item.label
3226
+ ]
3227
+ },
3228
+ item.key
3229
+ ))
3230
+ ] })
3178
3231
  ]
3179
3232
  }
3180
3233
  ),
package/dist/index.mjs CHANGED
@@ -679,7 +679,7 @@ var DraggableHeader = React.memo(
679
679
  ] });
680
680
  },
681
681
  (prevProps, nextProps) => {
682
- return prevProps.column.width === nextProps.column.width && prevProps.column.key === nextProps.column.key && prevProps.column.pinned === nextProps.column.pinned && prevProps.column.sortable === nextProps.column.sortable && prevProps.column.filterable === nextProps.column.filterable && prevProps.column.sorter === nextProps.column.sorter && prevProps.column.filterFn === nextProps.column.filterFn && prevProps.visualIndex === nextProps.visualIndex && prevProps.stickyOffset === nextProps.stickyOffset && prevProps.isLastColumn === nextProps.isLastColumn && prevProps.sortDirection === nextProps.sortDirection && prevProps.filterValue === nextProps.filterValue && prevProps.classNames === nextProps.classNames && prevProps.styles === nextProps.styles;
682
+ return prevProps.column.width === nextProps.column.width && prevProps.column.key === nextProps.column.key && prevProps.column.pinned === nextProps.column.pinned && prevProps.column.sortable === nextProps.column.sortable && prevProps.column.filterable === nextProps.column.filterable && prevProps.column.sorter === nextProps.column.sorter && prevProps.column.filterFn === nextProps.column.filterFn && prevProps.visualIndex === nextProps.visualIndex && prevProps.stickyOffset === nextProps.stickyOffset && prevProps.isLastColumn === nextProps.isLastColumn && prevProps.sortDirection === nextProps.sortDirection && prevProps.filterValue === nextProps.filterValue && prevProps.classNames === nextProps.classNames && prevProps.styles === nextProps.styles && prevProps.customContextMenuItems === nextProps.customContextMenuItems;
683
683
  }
684
684
  );
685
685
  DraggableHeader.displayName = "DraggableHeader";
@@ -1051,7 +1051,9 @@ var TableBody = ({
1051
1051
  pinnedTopData = [],
1052
1052
  pinnedBottomData = [],
1053
1053
  gridTemplateColumns,
1054
- headerHeight = 36
1054
+ headerHeight = 36,
1055
+ rowClassName,
1056
+ rowStyle
1055
1057
  }) => {
1056
1058
  const virtualItems = rowVirtualizer.getVirtualItems();
1057
1059
  const totalSize = rowVirtualizer.getTotalSize();
@@ -1103,6 +1105,8 @@ var TableBody = ({
1103
1105
  const cellValue = row[col.dataIndex];
1104
1106
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1105
1107
  const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1108
+ const rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1109
+ const rowSty = rowStyle ? rowStyle(row, virtualRow.index) : void 0;
1106
1110
  return /* @__PURE__ */ jsx4(
1107
1111
  "div",
1108
1112
  {
@@ -1110,12 +1114,14 @@ var TableBody = ({
1110
1114
  "data-column-key": col.key,
1111
1115
  "data-bt-cell": "",
1112
1116
  "data-selected": isSelected || void 0,
1117
+ className: rowCls || void 0,
1113
1118
  style: {
1114
1119
  position: "absolute",
1115
1120
  top: `${virtualRow.start}px`,
1116
1121
  left: 0,
1117
1122
  right: 0,
1118
- height: `${virtualRow.size}px`
1123
+ height: `${virtualRow.size}px`,
1124
+ ...rowSty
1119
1125
  },
1120
1126
  children: /* @__PURE__ */ jsx4(
1121
1127
  "div",
@@ -1237,15 +1243,18 @@ var TableBody = ({
1237
1243
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1238
1244
  const isSelected = selectedKeySet.has(rk);
1239
1245
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1246
+ const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1247
+ const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1240
1248
  return /* @__PURE__ */ jsx4(
1241
1249
  "div",
1242
1250
  {
1243
- className: classNames?.pinnedRow ?? "",
1251
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1244
1252
  style: {
1245
1253
  display: "grid",
1246
1254
  gridTemplateColumns: gridTemplateColumns ?? "",
1247
1255
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1248
- ...styles?.pinnedRow
1256
+ ...styles?.pinnedRow,
1257
+ ...rowSty
1249
1258
  },
1250
1259
  children: orderedColumns.map((col) => {
1251
1260
  const cellValue = row[col.dataIndex];
@@ -1342,15 +1351,18 @@ var TableBody = ({
1342
1351
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1343
1352
  const isSelected = selectedKeySet.has(rk);
1344
1353
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1354
+ const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1355
+ const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1345
1356
  return /* @__PURE__ */ jsx4(
1346
1357
  "div",
1347
1358
  {
1348
- className: classNames?.pinnedRow ?? "",
1359
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1349
1360
  style: {
1350
1361
  display: "grid",
1351
1362
  gridTemplateColumns: gridTemplateColumns ?? "",
1352
1363
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1353
- ...styles?.pinnedRow
1364
+ ...styles?.pinnedRow,
1365
+ ...rowSty
1354
1366
  },
1355
1367
  children: orderedColumns.map((col) => {
1356
1368
  const cellValue = row[col.dataIndex];
@@ -1468,7 +1480,9 @@ function BoltTable({
1468
1480
  columnContextMenuItems,
1469
1481
  autoHeight = true,
1470
1482
  layoutLoading,
1471
- emptyRenderer
1483
+ emptyRenderer,
1484
+ rowClassName,
1485
+ rowStyle
1472
1486
  }) {
1473
1487
  const [columns, setColumns] = useState2(initialColumns);
1474
1488
  const [columnOrder, setColumnOrder] = useState2(
@@ -2455,7 +2469,8 @@ function BoltTable({
2455
2469
  );
2456
2470
  const hasCopy = col?.copy;
2457
2471
  const hasRowPin = !!onRowPin;
2458
- if (!hasCopy && !hasRowPin) return;
2472
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2473
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2459
2474
  e.preventDefault();
2460
2475
  setCellContextMenu({
2461
2476
  x: Math.min(e.clientX, window.innerWidth - 200),
@@ -2483,7 +2498,8 @@ function BoltTable({
2483
2498
  );
2484
2499
  const hasCopy = col?.copy;
2485
2500
  const hasRowPin = !!onRowPin;
2486
- if (!hasCopy && !hasRowPin) return;
2501
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2502
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2487
2503
  setCellContextMenu({
2488
2504
  x: Math.min(touch.clientX, window.innerWidth - 200),
2489
2505
  y: Math.min(touch.clientY, window.innerHeight - 200),
@@ -2615,7 +2631,7 @@ function BoltTable({
2615
2631
  filterValue: columnFilters[column.key] ?? "",
2616
2632
  onFilter: handleColumnFilter,
2617
2633
  onClearFilter: handleClearFilter,
2618
- customContextMenuItems: columnContextMenuItems
2634
+ customContextMenuItems: column.columnHeaderContextMenuItems ? [...columnContextMenuItems ?? [], ...column.columnHeaderContextMenuItems] : columnContextMenuItems
2619
2635
  },
2620
2636
  column.key
2621
2637
  );
@@ -2683,7 +2699,9 @@ function BoltTable({
2683
2699
  pinnedTopData: pinnedTopRows,
2684
2700
  pinnedBottomData: pinnedBottomRows,
2685
2701
  gridTemplateColumns,
2686
- headerHeight: HEADER_HEIGHT
2702
+ headerHeight: HEADER_HEIGHT,
2703
+ rowClassName,
2704
+ rowStyle
2687
2705
  }
2688
2706
  )
2689
2707
  ]
@@ -3140,7 +3158,42 @@ function BoltTable({
3140
3158
  "Copy"
3141
3159
  ]
3142
3160
  }
3143
- )
3161
+ ),
3162
+ menuCol?.columnCellContextMenuItems && menuCol.columnCellContextMenuItems.length > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
3163
+ (hasCopy || hasRowPin) && /* @__PURE__ */ jsx5(
3164
+ "div",
3165
+ {
3166
+ style: {
3167
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3168
+ margin: "4px 0"
3169
+ }
3170
+ }
3171
+ ),
3172
+ menuCol.columnCellContextMenuItems.map((item) => /* @__PURE__ */ jsxs5(
3173
+ "button",
3174
+ {
3175
+ "data-bt-ctx-item": "",
3176
+ disabled: item.disabled,
3177
+ style: {
3178
+ ...btnStyle,
3179
+ cursor: item.disabled ? "not-allowed" : "pointer",
3180
+ opacity: item.disabled ? 0.5 : 1,
3181
+ color: item.danger ? "#ef4444" : "inherit"
3182
+ },
3183
+ onClick: () => {
3184
+ if (menuRecord) {
3185
+ item.onClick(menuCol.key, menuRecord, menuRowIndex);
3186
+ }
3187
+ setCellContextMenu(null);
3188
+ },
3189
+ children: [
3190
+ item.icon && /* @__PURE__ */ jsx5("span", { style: { display: "flex", width: 14, height: 14, alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: item.icon }),
3191
+ item.label
3192
+ ]
3193
+ },
3194
+ item.key
3195
+ ))
3196
+ ] })
3144
3197
  ]
3145
3198
  }
3146
3199
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bolt-table",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Virtualized React table with column drag & drop, pinning, resizing, sorting, filtering, and pagination.",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",