bolt-table 0.1.16 → 0.1.18

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 {
@@ -133,6 +152,10 @@ interface PaginationType {
133
152
  total?: number;
134
153
  /** Custom renderer for the "showing X–Y of Z" text in the pagination footer. */
135
154
  showTotal?: (total: number, range: [number, number]) => ReactNode;
155
+ /** When `true`, hides the page-size dropdown selector in the pagination footer. */
156
+ hidePageSelector?: boolean;
157
+ /** Custom page-size options shown in the dropdown. Defaults to `[10, 15, 20, 25, 50, 100]`. */
158
+ pageSizeOptions?: number[];
136
159
  }
137
160
  /** Configuration for row pinning. Pinned rows remain visible during vertical scroll. */
138
161
  interface RowPinningConfig {
@@ -183,9 +206,9 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
183
206
  readonly onColumnHide?: (columnKey: string, hidden: boolean) => void;
184
207
  /** Determines the unique key for each row. Can be a string property name, function, number, or symbol. */
185
208
  readonly rowKey?: string | ((record: T) => string) | number | symbol;
186
- /** Row selection configuration. Prepends a checkbox/radio column when provided. */
187
- expandable?: ExpandableConfig<T>;
188
209
  /** Expandable row configuration. Prepends an expand toggle column when provided. */
210
+ readonly expandable?: ExpandableConfig<T>;
211
+ /** Row selection configuration. Prepends a checkbox/radio column when provided. */
189
212
  readonly rowSelection?: RowSelectionConfig<T>;
190
213
  /** Row pinning configuration. Specified rows render as sticky at top and/or bottom. */
191
214
  readonly rowPinning?: RowPinningConfig;
@@ -197,11 +220,6 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
197
220
  readonly onEndReachedThreshold?: number;
198
221
  /** When true and data is empty, shows shimmer skeleton rows. With data, appends shimmer rows at bottom. */
199
222
  readonly isLoading?: boolean;
200
- /** Scroll indicator configuration (reserved for future use). */
201
- readonly scrollIndicators?: {
202
- vertical?: boolean;
203
- horizontal?: boolean;
204
- };
205
223
  /** Called when the user changes sort direction. Provide for server-side sorting. */
206
224
  readonly onSortChange?: (columnKey: string, direction: SortDirection) => void;
207
225
  /** Called when the user applies or clears a column filter. Provide for server-side filtering. */
@@ -366,4 +384,4 @@ interface TableBodyProps {
366
384
  }
367
385
  declare const TableBody: React$1.FC<TableBodyProps>;
368
386
 
369
- export { BoltTable, type BoltTableIcons, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
387
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, 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 {
@@ -133,6 +152,10 @@ interface PaginationType {
133
152
  total?: number;
134
153
  /** Custom renderer for the "showing X–Y of Z" text in the pagination footer. */
135
154
  showTotal?: (total: number, range: [number, number]) => ReactNode;
155
+ /** When `true`, hides the page-size dropdown selector in the pagination footer. */
156
+ hidePageSelector?: boolean;
157
+ /** Custom page-size options shown in the dropdown. Defaults to `[10, 15, 20, 25, 50, 100]`. */
158
+ pageSizeOptions?: number[];
136
159
  }
137
160
  /** Configuration for row pinning. Pinned rows remain visible during vertical scroll. */
138
161
  interface RowPinningConfig {
@@ -183,9 +206,9 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
183
206
  readonly onColumnHide?: (columnKey: string, hidden: boolean) => void;
184
207
  /** Determines the unique key for each row. Can be a string property name, function, number, or symbol. */
185
208
  readonly rowKey?: string | ((record: T) => string) | number | symbol;
186
- /** Row selection configuration. Prepends a checkbox/radio column when provided. */
187
- expandable?: ExpandableConfig<T>;
188
209
  /** Expandable row configuration. Prepends an expand toggle column when provided. */
210
+ readonly expandable?: ExpandableConfig<T>;
211
+ /** Row selection configuration. Prepends a checkbox/radio column when provided. */
189
212
  readonly rowSelection?: RowSelectionConfig<T>;
190
213
  /** Row pinning configuration. Specified rows render as sticky at top and/or bottom. */
191
214
  readonly rowPinning?: RowPinningConfig;
@@ -197,11 +220,6 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
197
220
  readonly onEndReachedThreshold?: number;
198
221
  /** When true and data is empty, shows shimmer skeleton rows. With data, appends shimmer rows at bottom. */
199
222
  readonly isLoading?: boolean;
200
- /** Scroll indicator configuration (reserved for future use). */
201
- readonly scrollIndicators?: {
202
- vertical?: boolean;
203
- horizontal?: boolean;
204
- };
205
223
  /** Called when the user changes sort direction. Provide for server-side sorting. */
206
224
  readonly onSortChange?: (columnKey: string, direction: SortDirection) => void;
207
225
  /** Called when the user applies or clears a column filter. Provide for server-side filtering. */
@@ -366,4 +384,4 @@ interface TableBodyProps {
366
384
  }
367
385
  declare const TableBody: React$1.FC<TableBodyProps>;
368
386
 
369
- export { BoltTable, type BoltTableIcons, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
387
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
package/dist/index.js CHANGED
@@ -231,26 +231,21 @@ var DraggableHeader = import_react.default.memo(
231
231
  ...isLastColumn ? {} : { maxWidth: widthPx },
232
232
  gridColumn: visualIndex + 1,
233
233
  gridRow: 1,
234
+ display: "flex",
235
+ height: 36,
236
+ alignItems: "center",
237
+ overflow: "hidden",
238
+ textOverflow: "ellipsis",
239
+ whiteSpace: "nowrap",
234
240
  borderTop: "none",
235
241
  borderRight: "none",
236
242
  borderBottom: "1px solid rgba(128,128,128,0.2)",
237
243
  borderLeft: "none",
238
244
  ...column.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
239
245
  ...column.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
240
- ...isPinned ? {
241
- backgroundColor: styles?.pinnedBg,
242
- ...styles?.pinnedHeader
243
- } : {},
244
246
  ...column.style,
245
- ...styles?.header,
246
- backgroundColor: styles?.pinnedBg && isPinned ? styles.pinnedBg : isPinned ? "" : "rgba(128,128,128,0.06)",
247
- display: "flex",
248
- height: 36,
249
- alignItems: "center",
250
- overflow: "hidden",
251
- textOverflow: "ellipsis",
252
- whiteSpace: "nowrap",
253
- ...isPinned ? {} : { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" }
247
+ ...isPinned ? styles?.pinnedHeader : {},
248
+ ...styles?.header
254
249
  };
255
250
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
256
251
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -258,6 +253,7 @@ var DraggableHeader = import_react.default.memo(
258
253
  {
259
254
  "data-column-key": column.key,
260
255
  "data-bt-header": "",
256
+ ...isPinned ? { "data-bt-pinned": "" } : {},
261
257
  style: headerStyle,
262
258
  className: `${column.className ?? ""} ${classNames?.header ?? ""} ${isPinned ? classNames?.pinnedHeader ?? "" : ""}`,
263
259
  onContextMenu: handleContextMenu,
@@ -717,7 +713,7 @@ var DraggableHeader = import_react.default.memo(
717
713
  ] });
718
714
  },
719
715
  (prevProps, nextProps) => {
720
- 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;
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;
721
717
  }
722
718
  );
723
719
  DraggableHeader.displayName = "DraggableHeader";
@@ -907,7 +903,8 @@ var Cell = import_react3.default.memo(
907
903
  height: "100%",
908
904
  boxSizing: "border-box",
909
905
  ...column.style,
910
- ...isPinned ? styles?.pinnedCell : void 0
906
+ ...isPinned ? styles?.pinnedCell : void 0,
907
+ ...styles?.cell
911
908
  },
912
909
  children: shimmerContent
913
910
  }
@@ -963,7 +960,7 @@ var Cell = import_react3.default.memo(
963
960
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
964
961
  "div",
965
962
  {
966
- className: `${column.className ?? ""} ${classNames?.cell ?? ""}`,
963
+ className: `${column.className ?? ""} ${classNames?.cell ?? ""} ${isPinned ? classNames?.pinnedCell ?? "" : ""}`,
967
964
  style: {
968
965
  display: "flex",
969
966
  alignItems: "center",
@@ -975,38 +972,55 @@ var Cell = import_react3.default.memo(
975
972
  height: "100%",
976
973
  boxSizing: "border-box",
977
974
  ...column.style,
978
- ...isPinned ? styles?.pinnedCell : void 0
975
+ ...isPinned ? styles?.pinnedCell : void 0,
976
+ ...styles?.cell
979
977
  },
980
978
  children: content2
981
979
  }
982
980
  );
983
981
  }
984
982
  const content = column.render ? column.render(value, record, rowIndex) : value ?? "";
983
+ const isSystem = column.key === "__select__" || column.key === "__expand__";
985
984
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
986
985
  "div",
987
986
  {
988
- className: `${column.className ?? ""} ${classNames?.cell ?? ""}`,
987
+ className: `${column.className ?? ""} ${classNames?.cell ?? ""} ${isPinned ? classNames?.pinnedCell ?? "" : ""}`,
989
988
  style: {
990
989
  display: "flex",
991
990
  alignItems: "center",
992
991
  overflow: "hidden",
993
- textOverflow: "ellipsis",
994
- whiteSpace: "nowrap",
995
992
  borderBottom: "1px solid rgba(128,128,128,0.2)",
996
993
  paddingLeft: 8,
997
994
  paddingRight: 8,
998
- justifyContent: column.key === "__select__" || column.key === "__expand__" ? "center" : void 0,
995
+ justifyContent: isSystem ? "center" : void 0,
999
996
  height: "100%",
1000
997
  boxSizing: "border-box",
998
+ minWidth: 0,
1001
999
  ...column.style,
1002
- ...isPinned ? styles?.pinnedCell : void 0
1000
+ ...isPinned ? styles?.pinnedCell : void 0,
1001
+ ...styles?.cell
1003
1002
  },
1004
- children: content
1003
+ children: isSystem ? content : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1004
+ "div",
1005
+ {
1006
+ style: {
1007
+ overflow: "hidden",
1008
+ textOverflow: "ellipsis",
1009
+ whiteSpace: "nowrap",
1010
+ minWidth: 0,
1011
+ maxWidth: "100%"
1012
+ },
1013
+ children: content
1014
+ }
1015
+ )
1005
1016
  }
1006
1017
  );
1007
1018
  },
1008
1019
  (prev, next) => {
1009
1020
  if (prev.isLoading !== next.isLoading) return false;
1021
+ if (prev.column.pinned !== next.column.pinned) return false;
1022
+ if (prev.classNames !== next.classNames) return false;
1023
+ if (prev.styles !== next.styles) return false;
1010
1024
  if (prev.column.key === "__select__") {
1011
1025
  return prev.isSelected === next.isSelected && prev.normalizedSelectedKeys === next.normalizedSelectedKeys;
1012
1026
  }
@@ -1101,7 +1115,6 @@ var TableBody = ({
1101
1115
  else if (col.pinned === "right" && stickyOffset !== void 0)
1102
1116
  style.right = `${stickyOffset}px`;
1103
1117
  if (isPinned) {
1104
- style.backgroundColor = styles?.pinnedBg;
1105
1118
  if (styles?.pinnedCell) Object.assign(style, styles.pinnedCell);
1106
1119
  }
1107
1120
  return { key: col.key, style, isPinned };
@@ -1114,6 +1127,7 @@ var TableBody = ({
1114
1127
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1115
1128
  "div",
1116
1129
  {
1130
+ ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1117
1131
  style: colStyle.style,
1118
1132
  children: virtualItems.map((virtualRow) => {
1119
1133
  const row = data[virtualRow.index];
@@ -1454,6 +1468,8 @@ function arrayMove(arr, from, to) {
1454
1468
  return result;
1455
1469
  }
1456
1470
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1471
+ var EMPTY_CLASSNAMES = {};
1472
+ var EMPTY_STYLES = {};
1457
1473
  function BoltTable({
1458
1474
  columns: initialColumns,
1459
1475
  data,
@@ -1462,8 +1478,8 @@ function BoltTable({
1462
1478
  maxExpandedRowHeight,
1463
1479
  accentColor = "#1890ff",
1464
1480
  className = "",
1465
- classNames = {},
1466
- styles = {},
1481
+ classNames = EMPTY_CLASSNAMES,
1482
+ styles = EMPTY_STYLES,
1467
1483
  gripIcon,
1468
1484
  hideGripIcon,
1469
1485
  icons,
@@ -1493,6 +1509,10 @@ function BoltTable({
1493
1509
  () => initialColumns.map((c) => c.key)
1494
1510
  );
1495
1511
  const [activeId, setActiveId] = (0, import_react4.useState)(null);
1512
+ const [mounted, setMounted] = (0, import_react4.useState)(false);
1513
+ import_react4.default.useEffect(() => {
1514
+ setMounted(true);
1515
+ }, []);
1496
1516
  const columnsFingerprintRef = (0, import_react4.useRef)("");
1497
1517
  const newFingerprint = initialColumns.map((c) => {
1498
1518
  const w = typeof c.width === "number" ? Math.round(c.width) : c.width ?? "";
@@ -1728,6 +1748,9 @@ function BoltTable({
1728
1748
  ghost.style.left = `${e.clientX - offsetX}px`;
1729
1749
  ghost.style.top = `${rect.top}px`;
1730
1750
  }
1751
+ const grabStyle = document.createElement("style");
1752
+ grabStyle.textContent = "* { cursor: grabbing !important; }";
1753
+ document.head.appendChild(grabStyle);
1731
1754
  const onMove = (ev) => {
1732
1755
  if (ghost) {
1733
1756
  ghost.style.left = `${ev.clientX - offsetX}px`;
@@ -1756,6 +1779,7 @@ function BoltTable({
1756
1779
  const onUp = () => {
1757
1780
  document.removeEventListener("pointermove", onMove);
1758
1781
  document.removeEventListener("pointerup", onUp);
1782
+ grabStyle.remove();
1759
1783
  const scrollEl = tableAreaRef.current;
1760
1784
  if (scrollEl) {
1761
1785
  scrollEl.querySelectorAll("[data-dragging]").forEach((h) => h.removeAttribute("data-dragging"));
@@ -2084,11 +2108,17 @@ function BoltTable({
2084
2108
  }, [cellContextMenu]);
2085
2109
  const columnFiltersKey = Object.keys(columnFilters).sort().map((k) => `${k}:${columnFilters[k]}`).join("|");
2086
2110
  import_react4.default.useEffect(() => {
2111
+ setInternalPage(1);
2087
2112
  tableAreaRef.current?.scrollTo({ top: 0 });
2088
2113
  }, [columnFiltersKey]);
2089
- const pgEnabled = pagination !== false && !!pagination;
2090
- const pgSize = pgEnabled ? pagination.pageSize ?? 10 : 10;
2091
- const pgCurrent = pgEnabled ? Number(pagination.current ?? 1) : 1;
2114
+ const DEFAULT_PAGE_SIZE = 15;
2115
+ const [internalPage, setInternalPage] = (0, import_react4.useState)(1);
2116
+ const [internalPageSize, setInternalPageSize] = (0, import_react4.useState)(DEFAULT_PAGE_SIZE);
2117
+ const autoPagination = pagination === void 0 && data.length > DEFAULT_PAGE_SIZE;
2118
+ const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2119
+ const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2120
+ const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
2121
+ const pgCurrent = pgEnabled ? isControlledPagination ? Number(pagination.current) : internalPage : 1;
2092
2122
  const needsClientPagination = pgEnabled && unpinnedProcessedData.length > pgSize;
2093
2123
  const paginatedData = (0, import_react4.useMemo)(() => {
2094
2124
  if (!needsClientPagination) return unpinnedProcessedData;
@@ -2194,17 +2224,29 @@ function BoltTable({
2194
2224
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2195
2225
  const currentPage = pgCurrent;
2196
2226
  const pageSize = pgSize;
2197
- const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2227
+ const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2198
2228
  const lastKnownTotalRef = (0, import_react4.useRef)(0);
2199
2229
  if (!isLoading || rawTotal > 0) {
2200
2230
  lastKnownTotalRef.current = rawTotal;
2201
2231
  }
2202
2232
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2203
2233
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2234
+ import_react4.default.useEffect(() => {
2235
+ if (internalPage > totalPages) {
2236
+ setInternalPage(Math.max(1, totalPages));
2237
+ }
2238
+ }, [totalPages, internalPage]);
2204
2239
  const handlePageChange = (p) => {
2205
- if (p >= 1 && p <= totalPages) onPaginationChange?.(p, pageSize);
2240
+ if (p >= 1 && p <= totalPages) {
2241
+ setInternalPage(p);
2242
+ onPaginationChange?.(p, pageSize);
2243
+ }
2244
+ };
2245
+ const handlePageSizeChange = (s) => {
2246
+ setInternalPage(1);
2247
+ setInternalPageSize(s);
2248
+ onPaginationChange?.(1, s);
2206
2249
  };
2207
- const handlePageSizeChange = (s) => onPaginationChange?.(1, s);
2208
2250
  import_react4.default.useEffect(() => {
2209
2251
  if (needsClientPagination) {
2210
2252
  tableAreaRef.current?.scrollTo({ top: 0 });
@@ -2260,6 +2302,14 @@ function BoltTable({
2260
2302
  0%, 100% { opacity: 1; }
2261
2303
  50% { opacity: 0.5; }
2262
2304
  }
2305
+ :where([data-bt-header]) {
2306
+ background-color: rgba(128,128,128,0.06);
2307
+ backdrop-filter: blur(8px);
2308
+ -webkit-backdrop-filter: blur(8px);
2309
+ }
2310
+ :where([data-bt-pinned]) {
2311
+ background-color: ${styles.pinnedBg ?? "Canvas"};
2312
+ }
2263
2313
  [data-row-key][data-hover] > div {
2264
2314
  background-color: ${styles.rowHover?.backgroundColor ?? "rgba(128, 128, 128, 0.1)"};
2265
2315
  }
@@ -2439,7 +2489,8 @@ function BoltTable({
2439
2489
  );
2440
2490
  const hasCopy = col?.copy;
2441
2491
  const hasRowPin = !!onRowPin;
2442
- if (!hasCopy && !hasRowPin) return;
2492
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2493
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2443
2494
  e.preventDefault();
2444
2495
  setCellContextMenu({
2445
2496
  x: Math.min(e.clientX, window.innerWidth - 200),
@@ -2467,7 +2518,8 @@ function BoltTable({
2467
2518
  );
2468
2519
  const hasCopy = col?.copy;
2469
2520
  const hasRowPin = !!onRowPin;
2470
- if (!hasCopy && !hasRowPin) return;
2521
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2522
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2471
2523
  setCellContextMenu({
2472
2524
  x: Math.min(touch.clientX, window.innerWidth - 200),
2473
2525
  y: Math.min(touch.clientY, window.innerHeight - 200),
@@ -2492,6 +2544,8 @@ function BoltTable({
2492
2544
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2493
2545
  "div",
2494
2546
  {
2547
+ "data-bt-header": "",
2548
+ "data-bt-pinned": "",
2495
2549
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2496
2550
  style: {
2497
2551
  display: "flex",
@@ -2502,7 +2556,6 @@ function BoltTable({
2502
2556
  textOverflow: "ellipsis",
2503
2557
  whiteSpace: "nowrap",
2504
2558
  borderBottom: "1px solid rgba(128,128,128,0.2)",
2505
- backgroundColor: styles?.pinnedBg,
2506
2559
  position: "sticky",
2507
2560
  left: columnOffsets.get("__select__") ?? 0,
2508
2561
  top: 0,
@@ -2552,6 +2605,8 @@ function BoltTable({
2552
2605
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2553
2606
  "div",
2554
2607
  {
2608
+ "data-bt-header": "",
2609
+ "data-bt-pinned": "",
2555
2610
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2556
2611
  style: {
2557
2612
  display: "flex",
@@ -2562,7 +2617,6 @@ function BoltTable({
2562
2617
  textOverflow: "ellipsis",
2563
2618
  whiteSpace: "nowrap",
2564
2619
  borderBottom: "1px solid rgba(128,128,128,0.2)",
2565
- backgroundColor: styles?.pinnedBg,
2566
2620
  position: "sticky",
2567
2621
  left: columnOffsets.get("__expand__") ?? 0,
2568
2622
  top: 0,
@@ -2597,7 +2651,7 @@ function BoltTable({
2597
2651
  filterValue: columnFilters[column.key] ?? "",
2598
2652
  onFilter: handleColumnFilter,
2599
2653
  onClearFilter: handleClearFilter,
2600
- customContextMenuItems: columnContextMenuItems
2654
+ customContextMenuItems: column.columnHeaderContextMenuItems ? [...columnContextMenuItems ?? [], ...column.columnHeaderContextMenuItems] : columnContextMenuItems
2601
2655
  },
2602
2656
  column.key
2603
2657
  );
@@ -2676,7 +2730,7 @@ function BoltTable({
2676
2730
  )
2677
2731
  }
2678
2732
  ),
2679
- pagination !== false && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2733
+ pgEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2680
2734
  "div",
2681
2735
  {
2682
2736
  style: {
@@ -2696,7 +2750,7 @@ function BoltTable({
2696
2750
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flex: "1 1 0%", alignItems: "center" }, children: (() => {
2697
2751
  const rangeStart = total > 0 ? (currentPage - 1) * pageSize + 1 : 0;
2698
2752
  const rangeEnd = Math.min(currentPage * pageSize, total);
2699
- return pagination?.showTotal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2753
+ return typeof pagination === "object" && pagination?.showTotal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2700
2754
  "Showing",
2701
2755
  " ",
2702
2756
  pagination.showTotal(total, [rangeStart, rangeEnd]),
@@ -2869,7 +2923,7 @@ function BoltTable({
2869
2923
  justifyContent: "flex-end",
2870
2924
  gap: 8
2871
2925
  },
2872
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2926
+ children: typeof pagination === "object" && pagination?.hidePageSelector ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2873
2927
  "select",
2874
2928
  {
2875
2929
  value: pageSize,
@@ -2887,7 +2941,7 @@ function BoltTable({
2887
2941
  background: "inherit",
2888
2942
  color: "inherit"
2889
2943
  },
2890
- children: [10, 15, 20, 25, 50, 100].map((size) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("option", { value: size, children: [
2944
+ children: (typeof pagination === "object" && pagination?.pageSizeOptions ? pagination.pageSizeOptions : [10, 15, 20, 25, 50, 100]).map((size) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("option", { value: size, children: [
2891
2945
  size,
2892
2946
  " / page"
2893
2947
  ] }, size))
@@ -2901,7 +2955,7 @@ function BoltTable({
2901
2955
  ]
2902
2956
  }
2903
2957
  ),
2904
- typeof document !== "undefined" && (0, import_react_dom2.createPortal)(
2958
+ mounted && (0, import_react_dom2.createPortal)(
2905
2959
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2906
2960
  "div",
2907
2961
  {
@@ -2970,7 +3024,7 @@ function BoltTable({
2970
3024
  ),
2971
3025
  document.body
2972
3026
  ),
2973
- cellContextMenu && typeof document !== "undefined" && (() => {
3027
+ cellContextMenu && mounted && (() => {
2974
3028
  const menuCol = freshOrderedColumns.find(
2975
3029
  (c) => c.key === cellContextMenu.columnKey
2976
3030
  );
@@ -3122,7 +3176,42 @@ function BoltTable({
3122
3176
  "Copy"
3123
3177
  ]
3124
3178
  }
3125
- )
3179
+ ),
3180
+ menuCol?.columnCellContextMenuItems && menuCol.columnCellContextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3181
+ (hasCopy || hasRowPin) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3182
+ "div",
3183
+ {
3184
+ style: {
3185
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3186
+ margin: "4px 0"
3187
+ }
3188
+ }
3189
+ ),
3190
+ menuCol.columnCellContextMenuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3191
+ "button",
3192
+ {
3193
+ "data-bt-ctx-item": "",
3194
+ disabled: item.disabled,
3195
+ style: {
3196
+ ...btnStyle,
3197
+ cursor: item.disabled ? "not-allowed" : "pointer",
3198
+ opacity: item.disabled ? 0.5 : 1,
3199
+ color: item.danger ? "#ef4444" : "inherit"
3200
+ },
3201
+ onClick: () => {
3202
+ if (menuRecord) {
3203
+ item.onClick(menuCol.key, menuRecord, menuRowIndex);
3204
+ }
3205
+ setCellContextMenu(null);
3206
+ },
3207
+ children: [
3208
+ 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 }),
3209
+ item.label
3210
+ ]
3211
+ },
3212
+ item.key
3213
+ ))
3214
+ ] })
3126
3215
  ]
3127
3216
  }
3128
3217
  ),
package/dist/index.mjs CHANGED
@@ -197,26 +197,21 @@ var DraggableHeader = React.memo(
197
197
  ...isLastColumn ? {} : { maxWidth: widthPx },
198
198
  gridColumn: visualIndex + 1,
199
199
  gridRow: 1,
200
+ display: "flex",
201
+ height: 36,
202
+ alignItems: "center",
203
+ overflow: "hidden",
204
+ textOverflow: "ellipsis",
205
+ whiteSpace: "nowrap",
200
206
  borderTop: "none",
201
207
  borderRight: "none",
202
208
  borderBottom: "1px solid rgba(128,128,128,0.2)",
203
209
  borderLeft: "none",
204
210
  ...column.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
205
211
  ...column.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
206
- ...isPinned ? {
207
- backgroundColor: styles?.pinnedBg,
208
- ...styles?.pinnedHeader
209
- } : {},
210
212
  ...column.style,
211
- ...styles?.header,
212
- backgroundColor: styles?.pinnedBg && isPinned ? styles.pinnedBg : isPinned ? "" : "rgba(128,128,128,0.06)",
213
- display: "flex",
214
- height: 36,
215
- alignItems: "center",
216
- overflow: "hidden",
217
- textOverflow: "ellipsis",
218
- whiteSpace: "nowrap",
219
- ...isPinned ? {} : { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" }
213
+ ...isPinned ? styles?.pinnedHeader : {},
214
+ ...styles?.header
220
215
  };
221
216
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
222
217
  /* @__PURE__ */ jsxs2(
@@ -224,6 +219,7 @@ var DraggableHeader = React.memo(
224
219
  {
225
220
  "data-column-key": column.key,
226
221
  "data-bt-header": "",
222
+ ...isPinned ? { "data-bt-pinned": "" } : {},
227
223
  style: headerStyle,
228
224
  className: `${column.className ?? ""} ${classNames?.header ?? ""} ${isPinned ? classNames?.pinnedHeader ?? "" : ""}`,
229
225
  onContextMenu: handleContextMenu,
@@ -683,7 +679,7 @@ var DraggableHeader = React.memo(
683
679
  ] });
684
680
  },
685
681
  (prevProps, nextProps) => {
686
- 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;
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;
687
683
  }
688
684
  );
689
685
  DraggableHeader.displayName = "DraggableHeader";
@@ -873,7 +869,8 @@ var Cell = React3.memo(
873
869
  height: "100%",
874
870
  boxSizing: "border-box",
875
871
  ...column.style,
876
- ...isPinned ? styles?.pinnedCell : void 0
872
+ ...isPinned ? styles?.pinnedCell : void 0,
873
+ ...styles?.cell
877
874
  },
878
875
  children: shimmerContent
879
876
  }
@@ -929,7 +926,7 @@ var Cell = React3.memo(
929
926
  return /* @__PURE__ */ jsx4(
930
927
  "div",
931
928
  {
932
- className: `${column.className ?? ""} ${classNames?.cell ?? ""}`,
929
+ className: `${column.className ?? ""} ${classNames?.cell ?? ""} ${isPinned ? classNames?.pinnedCell ?? "" : ""}`,
933
930
  style: {
934
931
  display: "flex",
935
932
  alignItems: "center",
@@ -941,38 +938,55 @@ var Cell = React3.memo(
941
938
  height: "100%",
942
939
  boxSizing: "border-box",
943
940
  ...column.style,
944
- ...isPinned ? styles?.pinnedCell : void 0
941
+ ...isPinned ? styles?.pinnedCell : void 0,
942
+ ...styles?.cell
945
943
  },
946
944
  children: content2
947
945
  }
948
946
  );
949
947
  }
950
948
  const content = column.render ? column.render(value, record, rowIndex) : value ?? "";
949
+ const isSystem = column.key === "__select__" || column.key === "__expand__";
951
950
  return /* @__PURE__ */ jsx4(
952
951
  "div",
953
952
  {
954
- className: `${column.className ?? ""} ${classNames?.cell ?? ""}`,
953
+ className: `${column.className ?? ""} ${classNames?.cell ?? ""} ${isPinned ? classNames?.pinnedCell ?? "" : ""}`,
955
954
  style: {
956
955
  display: "flex",
957
956
  alignItems: "center",
958
957
  overflow: "hidden",
959
- textOverflow: "ellipsis",
960
- whiteSpace: "nowrap",
961
958
  borderBottom: "1px solid rgba(128,128,128,0.2)",
962
959
  paddingLeft: 8,
963
960
  paddingRight: 8,
964
- justifyContent: column.key === "__select__" || column.key === "__expand__" ? "center" : void 0,
961
+ justifyContent: isSystem ? "center" : void 0,
965
962
  height: "100%",
966
963
  boxSizing: "border-box",
964
+ minWidth: 0,
967
965
  ...column.style,
968
- ...isPinned ? styles?.pinnedCell : void 0
966
+ ...isPinned ? styles?.pinnedCell : void 0,
967
+ ...styles?.cell
969
968
  },
970
- children: content
969
+ children: isSystem ? content : /* @__PURE__ */ jsx4(
970
+ "div",
971
+ {
972
+ style: {
973
+ overflow: "hidden",
974
+ textOverflow: "ellipsis",
975
+ whiteSpace: "nowrap",
976
+ minWidth: 0,
977
+ maxWidth: "100%"
978
+ },
979
+ children: content
980
+ }
981
+ )
971
982
  }
972
983
  );
973
984
  },
974
985
  (prev, next) => {
975
986
  if (prev.isLoading !== next.isLoading) return false;
987
+ if (prev.column.pinned !== next.column.pinned) return false;
988
+ if (prev.classNames !== next.classNames) return false;
989
+ if (prev.styles !== next.styles) return false;
976
990
  if (prev.column.key === "__select__") {
977
991
  return prev.isSelected === next.isSelected && prev.normalizedSelectedKeys === next.normalizedSelectedKeys;
978
992
  }
@@ -1067,7 +1081,6 @@ var TableBody = ({
1067
1081
  else if (col.pinned === "right" && stickyOffset !== void 0)
1068
1082
  style.right = `${stickyOffset}px`;
1069
1083
  if (isPinned) {
1070
- style.backgroundColor = styles?.pinnedBg;
1071
1084
  if (styles?.pinnedCell) Object.assign(style, styles.pinnedCell);
1072
1085
  }
1073
1086
  return { key: col.key, style, isPinned };
@@ -1080,6 +1093,7 @@ var TableBody = ({
1080
1093
  return /* @__PURE__ */ jsx4(
1081
1094
  "div",
1082
1095
  {
1096
+ ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1083
1097
  style: colStyle.style,
1084
1098
  children: virtualItems.map((virtualRow) => {
1085
1099
  const row = data[virtualRow.index];
@@ -1420,6 +1434,8 @@ function arrayMove(arr, from, to) {
1420
1434
  return result;
1421
1435
  }
1422
1436
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1437
+ var EMPTY_CLASSNAMES = {};
1438
+ var EMPTY_STYLES = {};
1423
1439
  function BoltTable({
1424
1440
  columns: initialColumns,
1425
1441
  data,
@@ -1428,8 +1444,8 @@ function BoltTable({
1428
1444
  maxExpandedRowHeight,
1429
1445
  accentColor = "#1890ff",
1430
1446
  className = "",
1431
- classNames = {},
1432
- styles = {},
1447
+ classNames = EMPTY_CLASSNAMES,
1448
+ styles = EMPTY_STYLES,
1433
1449
  gripIcon,
1434
1450
  hideGripIcon,
1435
1451
  icons,
@@ -1459,6 +1475,10 @@ function BoltTable({
1459
1475
  () => initialColumns.map((c) => c.key)
1460
1476
  );
1461
1477
  const [activeId, setActiveId] = useState2(null);
1478
+ const [mounted, setMounted] = useState2(false);
1479
+ React4.useEffect(() => {
1480
+ setMounted(true);
1481
+ }, []);
1462
1482
  const columnsFingerprintRef = useRef4("");
1463
1483
  const newFingerprint = initialColumns.map((c) => {
1464
1484
  const w = typeof c.width === "number" ? Math.round(c.width) : c.width ?? "";
@@ -1694,6 +1714,9 @@ function BoltTable({
1694
1714
  ghost.style.left = `${e.clientX - offsetX}px`;
1695
1715
  ghost.style.top = `${rect.top}px`;
1696
1716
  }
1717
+ const grabStyle = document.createElement("style");
1718
+ grabStyle.textContent = "* { cursor: grabbing !important; }";
1719
+ document.head.appendChild(grabStyle);
1697
1720
  const onMove = (ev) => {
1698
1721
  if (ghost) {
1699
1722
  ghost.style.left = `${ev.clientX - offsetX}px`;
@@ -1722,6 +1745,7 @@ function BoltTable({
1722
1745
  const onUp = () => {
1723
1746
  document.removeEventListener("pointermove", onMove);
1724
1747
  document.removeEventListener("pointerup", onUp);
1748
+ grabStyle.remove();
1725
1749
  const scrollEl = tableAreaRef.current;
1726
1750
  if (scrollEl) {
1727
1751
  scrollEl.querySelectorAll("[data-dragging]").forEach((h) => h.removeAttribute("data-dragging"));
@@ -2050,11 +2074,17 @@ function BoltTable({
2050
2074
  }, [cellContextMenu]);
2051
2075
  const columnFiltersKey = Object.keys(columnFilters).sort().map((k) => `${k}:${columnFilters[k]}`).join("|");
2052
2076
  React4.useEffect(() => {
2077
+ setInternalPage(1);
2053
2078
  tableAreaRef.current?.scrollTo({ top: 0 });
2054
2079
  }, [columnFiltersKey]);
2055
- const pgEnabled = pagination !== false && !!pagination;
2056
- const pgSize = pgEnabled ? pagination.pageSize ?? 10 : 10;
2057
- const pgCurrent = pgEnabled ? Number(pagination.current ?? 1) : 1;
2080
+ const DEFAULT_PAGE_SIZE = 15;
2081
+ const [internalPage, setInternalPage] = useState2(1);
2082
+ const [internalPageSize, setInternalPageSize] = useState2(DEFAULT_PAGE_SIZE);
2083
+ const autoPagination = pagination === void 0 && data.length > DEFAULT_PAGE_SIZE;
2084
+ const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2085
+ const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2086
+ const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
2087
+ const pgCurrent = pgEnabled ? isControlledPagination ? Number(pagination.current) : internalPage : 1;
2058
2088
  const needsClientPagination = pgEnabled && unpinnedProcessedData.length > pgSize;
2059
2089
  const paginatedData = useMemo2(() => {
2060
2090
  if (!needsClientPagination) return unpinnedProcessedData;
@@ -2160,17 +2190,29 @@ function BoltTable({
2160
2190
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2161
2191
  const currentPage = pgCurrent;
2162
2192
  const pageSize = pgSize;
2163
- const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2193
+ const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2164
2194
  const lastKnownTotalRef = useRef4(0);
2165
2195
  if (!isLoading || rawTotal > 0) {
2166
2196
  lastKnownTotalRef.current = rawTotal;
2167
2197
  }
2168
2198
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2169
2199
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2200
+ React4.useEffect(() => {
2201
+ if (internalPage > totalPages) {
2202
+ setInternalPage(Math.max(1, totalPages));
2203
+ }
2204
+ }, [totalPages, internalPage]);
2170
2205
  const handlePageChange = (p) => {
2171
- if (p >= 1 && p <= totalPages) onPaginationChange?.(p, pageSize);
2206
+ if (p >= 1 && p <= totalPages) {
2207
+ setInternalPage(p);
2208
+ onPaginationChange?.(p, pageSize);
2209
+ }
2210
+ };
2211
+ const handlePageSizeChange = (s) => {
2212
+ setInternalPage(1);
2213
+ setInternalPageSize(s);
2214
+ onPaginationChange?.(1, s);
2172
2215
  };
2173
- const handlePageSizeChange = (s) => onPaginationChange?.(1, s);
2174
2216
  React4.useEffect(() => {
2175
2217
  if (needsClientPagination) {
2176
2218
  tableAreaRef.current?.scrollTo({ top: 0 });
@@ -2226,6 +2268,14 @@ function BoltTable({
2226
2268
  0%, 100% { opacity: 1; }
2227
2269
  50% { opacity: 0.5; }
2228
2270
  }
2271
+ :where([data-bt-header]) {
2272
+ background-color: rgba(128,128,128,0.06);
2273
+ backdrop-filter: blur(8px);
2274
+ -webkit-backdrop-filter: blur(8px);
2275
+ }
2276
+ :where([data-bt-pinned]) {
2277
+ background-color: ${styles.pinnedBg ?? "Canvas"};
2278
+ }
2229
2279
  [data-row-key][data-hover] > div {
2230
2280
  background-color: ${styles.rowHover?.backgroundColor ?? "rgba(128, 128, 128, 0.1)"};
2231
2281
  }
@@ -2405,7 +2455,8 @@ function BoltTable({
2405
2455
  );
2406
2456
  const hasCopy = col?.copy;
2407
2457
  const hasRowPin = !!onRowPin;
2408
- if (!hasCopy && !hasRowPin) return;
2458
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2459
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2409
2460
  e.preventDefault();
2410
2461
  setCellContextMenu({
2411
2462
  x: Math.min(e.clientX, window.innerWidth - 200),
@@ -2433,7 +2484,8 @@ function BoltTable({
2433
2484
  );
2434
2485
  const hasCopy = col?.copy;
2435
2486
  const hasRowPin = !!onRowPin;
2436
- if (!hasCopy && !hasRowPin) return;
2487
+ const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2488
+ if (!hasCopy && !hasRowPin && !hasCellItems) return;
2437
2489
  setCellContextMenu({
2438
2490
  x: Math.min(touch.clientX, window.innerWidth - 200),
2439
2491
  y: Math.min(touch.clientY, window.innerHeight - 200),
@@ -2458,6 +2510,8 @@ function BoltTable({
2458
2510
  return /* @__PURE__ */ jsx5(
2459
2511
  "div",
2460
2512
  {
2513
+ "data-bt-header": "",
2514
+ "data-bt-pinned": "",
2461
2515
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2462
2516
  style: {
2463
2517
  display: "flex",
@@ -2468,7 +2522,6 @@ function BoltTable({
2468
2522
  textOverflow: "ellipsis",
2469
2523
  whiteSpace: "nowrap",
2470
2524
  borderBottom: "1px solid rgba(128,128,128,0.2)",
2471
- backgroundColor: styles?.pinnedBg,
2472
2525
  position: "sticky",
2473
2526
  left: columnOffsets.get("__select__") ?? 0,
2474
2527
  top: 0,
@@ -2518,6 +2571,8 @@ function BoltTable({
2518
2571
  return /* @__PURE__ */ jsx5(
2519
2572
  "div",
2520
2573
  {
2574
+ "data-bt-header": "",
2575
+ "data-bt-pinned": "",
2521
2576
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2522
2577
  style: {
2523
2578
  display: "flex",
@@ -2528,7 +2583,6 @@ function BoltTable({
2528
2583
  textOverflow: "ellipsis",
2529
2584
  whiteSpace: "nowrap",
2530
2585
  borderBottom: "1px solid rgba(128,128,128,0.2)",
2531
- backgroundColor: styles?.pinnedBg,
2532
2586
  position: "sticky",
2533
2587
  left: columnOffsets.get("__expand__") ?? 0,
2534
2588
  top: 0,
@@ -2563,7 +2617,7 @@ function BoltTable({
2563
2617
  filterValue: columnFilters[column.key] ?? "",
2564
2618
  onFilter: handleColumnFilter,
2565
2619
  onClearFilter: handleClearFilter,
2566
- customContextMenuItems: columnContextMenuItems
2620
+ customContextMenuItems: column.columnHeaderContextMenuItems ? [...columnContextMenuItems ?? [], ...column.columnHeaderContextMenuItems] : columnContextMenuItems
2567
2621
  },
2568
2622
  column.key
2569
2623
  );
@@ -2642,7 +2696,7 @@ function BoltTable({
2642
2696
  )
2643
2697
  }
2644
2698
  ),
2645
- pagination !== false && /* @__PURE__ */ jsxs5(
2699
+ pgEnabled && /* @__PURE__ */ jsxs5(
2646
2700
  "div",
2647
2701
  {
2648
2702
  style: {
@@ -2662,7 +2716,7 @@ function BoltTable({
2662
2716
  /* @__PURE__ */ jsx5("div", { style: { display: "flex", flex: "1 1 0%", alignItems: "center" }, children: (() => {
2663
2717
  const rangeStart = total > 0 ? (currentPage - 1) * pageSize + 1 : 0;
2664
2718
  const rangeEnd = Math.min(currentPage * pageSize, total);
2665
- return pagination?.showTotal ? /* @__PURE__ */ jsxs5("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2719
+ return typeof pagination === "object" && pagination?.showTotal ? /* @__PURE__ */ jsxs5("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2666
2720
  "Showing",
2667
2721
  " ",
2668
2722
  pagination.showTotal(total, [rangeStart, rangeEnd]),
@@ -2835,7 +2889,7 @@ function BoltTable({
2835
2889
  justifyContent: "flex-end",
2836
2890
  gap: 8
2837
2891
  },
2838
- children: /* @__PURE__ */ jsx5(
2892
+ children: typeof pagination === "object" && pagination?.hidePageSelector ? null : /* @__PURE__ */ jsx5(
2839
2893
  "select",
2840
2894
  {
2841
2895
  value: pageSize,
@@ -2853,7 +2907,7 @@ function BoltTable({
2853
2907
  background: "inherit",
2854
2908
  color: "inherit"
2855
2909
  },
2856
- children: [10, 15, 20, 25, 50, 100].map((size) => /* @__PURE__ */ jsxs5("option", { value: size, children: [
2910
+ children: (typeof pagination === "object" && pagination?.pageSizeOptions ? pagination.pageSizeOptions : [10, 15, 20, 25, 50, 100]).map((size) => /* @__PURE__ */ jsxs5("option", { value: size, children: [
2857
2911
  size,
2858
2912
  " / page"
2859
2913
  ] }, size))
@@ -2867,7 +2921,7 @@ function BoltTable({
2867
2921
  ]
2868
2922
  }
2869
2923
  ),
2870
- typeof document !== "undefined" && createPortal2(
2924
+ mounted && createPortal2(
2871
2925
  /* @__PURE__ */ jsx5(
2872
2926
  "div",
2873
2927
  {
@@ -2936,7 +2990,7 @@ function BoltTable({
2936
2990
  ),
2937
2991
  document.body
2938
2992
  ),
2939
- cellContextMenu && typeof document !== "undefined" && (() => {
2993
+ cellContextMenu && mounted && (() => {
2940
2994
  const menuCol = freshOrderedColumns.find(
2941
2995
  (c) => c.key === cellContextMenu.columnKey
2942
2996
  );
@@ -3088,7 +3142,42 @@ function BoltTable({
3088
3142
  "Copy"
3089
3143
  ]
3090
3144
  }
3091
- )
3145
+ ),
3146
+ menuCol?.columnCellContextMenuItems && menuCol.columnCellContextMenuItems.length > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
3147
+ (hasCopy || hasRowPin) && /* @__PURE__ */ jsx5(
3148
+ "div",
3149
+ {
3150
+ style: {
3151
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3152
+ margin: "4px 0"
3153
+ }
3154
+ }
3155
+ ),
3156
+ menuCol.columnCellContextMenuItems.map((item) => /* @__PURE__ */ jsxs5(
3157
+ "button",
3158
+ {
3159
+ "data-bt-ctx-item": "",
3160
+ disabled: item.disabled,
3161
+ style: {
3162
+ ...btnStyle,
3163
+ cursor: item.disabled ? "not-allowed" : "pointer",
3164
+ opacity: item.disabled ? 0.5 : 1,
3165
+ color: item.danger ? "#ef4444" : "inherit"
3166
+ },
3167
+ onClick: () => {
3168
+ if (menuRecord) {
3169
+ item.onClick(menuCol.key, menuRecord, menuRowIndex);
3170
+ }
3171
+ setCellContextMenu(null);
3172
+ },
3173
+ children: [
3174
+ item.icon && /* @__PURE__ */ jsx5("span", { style: { display: "flex", width: 14, height: 14, alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: item.icon }),
3175
+ item.label
3176
+ ]
3177
+ },
3178
+ item.key
3179
+ ))
3180
+ ] })
3092
3181
  ]
3093
3182
  }
3094
3183
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bolt-table",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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",