bolt-table 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -114,6 +114,10 @@ var CopyIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.
114
114
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
115
115
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
116
116
  ] });
117
+ var PencilIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }),
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m15 5 4 4" })
120
+ ] });
117
121
  var EyeOffIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
118
122
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }),
119
123
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }),
@@ -151,7 +155,10 @@ var DraggableHeader = import_react.default.memo(
151
155
  customContextMenuItems,
152
156
  icons,
153
157
  onColumnDragStart,
154
- disabledFilters
158
+ disabledFilters,
159
+ headerGridRow = 1,
160
+ headerHeight = 36,
161
+ stickyTop = 0
155
162
  }) => {
156
163
  const effectivelySortable = isColumnSortable(column);
157
164
  const effectivelyFilterable = !disabledFilters && isColumnFilterable(column);
@@ -225,23 +232,23 @@ var DraggableHeader = import_react.default.memo(
225
232
  const zIndex = isPinned ? 12 : 10;
226
233
  const headerStyle = {
227
234
  position: "sticky",
228
- top: 0,
235
+ top: stickyTop,
229
236
  zIndex,
230
237
  width: isLastColumn ? "100%" : widthPx,
231
238
  minWidth: widthPx,
232
239
  ...isLastColumn ? {} : { maxWidth: widthPx },
233
240
  gridColumn: visualIndex + 1,
234
- gridRow: 1,
241
+ gridRow: headerGridRow,
235
242
  display: "flex",
236
- height: 36,
243
+ height: headerHeight,
237
244
  alignItems: "center",
238
245
  overflow: "hidden",
239
246
  textOverflow: "ellipsis",
240
247
  whiteSpace: "nowrap",
241
248
  borderTop: "none",
242
- borderRight: "none",
249
+ borderRight: "0.5px solid rgba(128,128,128,0.2)",
243
250
  borderBottom: "1px solid rgba(128,128,128,0.2)",
244
- borderLeft: "none",
251
+ borderLeft: "0.5px solid rgba(128,128,128,0.2)",
245
252
  ...column.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
246
253
  ...column.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
247
254
  ...column.style,
@@ -635,7 +642,7 @@ var DraggableHeader = import_react.default.memo(
635
642
  ] });
636
643
  },
637
644
  (prevProps, nextProps) => {
638
- 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 && prevProps.disabledFilters === nextProps.disabledFilters;
645
+ 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 && prevProps.disabledFilters === nextProps.disabledFilters && prevProps.headerGridRow === nextProps.headerGridRow && prevProps.headerHeight === nextProps.headerHeight && prevProps.stickyTop === nextProps.stickyTop;
639
646
  }
640
647
  );
641
648
  DraggableHeader.displayName = "DraggableHeader";
@@ -781,6 +788,61 @@ var ResizeOverlay_default = ResizeOverlay;
781
788
  var import_react3 = __toESM(require("react"));
782
789
  var import_jsx_runtime4 = require("react/jsx-runtime");
783
790
  var SHIMMER_WIDTHS = [55, 70, 45, 80, 60, 50, 75, 65];
791
+ var EditableCell = ({
792
+ value,
793
+ record,
794
+ column,
795
+ rowIndex,
796
+ onEdit,
797
+ onEditComplete
798
+ }) => {
799
+ const [draft, setDraft] = (0, import_react3.useState)(String(value ?? ""));
800
+ const inputRef = (0, import_react3.useRef)(null);
801
+ (0, import_react3.useEffect)(() => {
802
+ setDraft(String(value ?? ""));
803
+ requestAnimationFrame(() => {
804
+ inputRef.current?.focus();
805
+ inputRef.current?.select();
806
+ });
807
+ }, [value]);
808
+ const commit = (0, import_react3.useCallback)(() => {
809
+ const raw = String(value ?? "");
810
+ if (draft !== raw && column.dataIndex) {
811
+ const coerced = typeof value === "number" && !Number.isNaN(Number(draft)) ? Number(draft) : draft;
812
+ onEdit(coerced, record, column.dataIndex, rowIndex);
813
+ }
814
+ onEditComplete();
815
+ }, [draft, value, column.dataIndex, record, rowIndex, onEdit, onEditComplete]);
816
+ const cancel = (0, import_react3.useCallback)(() => {
817
+ onEditComplete();
818
+ }, [onEditComplete]);
819
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
820
+ "input",
821
+ {
822
+ ref: inputRef,
823
+ value: draft,
824
+ onChange: (e) => setDraft(e.target.value),
825
+ onBlur: commit,
826
+ onKeyDown: (e) => {
827
+ if (e.key === "Enter") commit();
828
+ if (e.key === "Escape") cancel();
829
+ },
830
+ style: {
831
+ width: "100%",
832
+ height: "100%",
833
+ border: "none",
834
+ outline: "none",
835
+ background: "transparent",
836
+ font: "inherit",
837
+ color: "inherit",
838
+ padding: 0,
839
+ margin: 0,
840
+ boxSizing: "border-box"
841
+ }
842
+ }
843
+ );
844
+ };
845
+ EditableCell.displayName = "EditableCell";
784
846
  var Cell = import_react3.default.memo(
785
847
  ({
786
848
  value,
@@ -796,7 +858,10 @@ var Cell = import_react3.default.memo(
796
858
  getRowKey,
797
859
  getRawRowKey,
798
860
  accentColor,
799
- isLoading
861
+ isLoading,
862
+ onEdit,
863
+ isEditing,
864
+ onEditComplete
800
865
  }) => {
801
866
  const isPinned = Boolean(column.pinned);
802
867
  if (isLoading && column.key !== "__select__" && column.key !== "__expand__") {
@@ -875,7 +940,7 @@ var Cell = import_react3.default.memo(
875
940
  type: "multiple"
876
941
  });
877
942
  },
878
- style: { cursor: "pointer", accentColor }
943
+ style: { cursor: "pointer", accentColor, colorScheme: "light dark" }
879
944
  }
880
945
  );
881
946
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -900,8 +965,22 @@ var Cell = import_react3.default.memo(
900
965
  }
901
966
  );
902
967
  }
968
+ const isEditable = !!column.editable && !column.render && !!onEdit;
969
+ const showEditor = isEditable && isEditing && onEditComplete;
903
970
  let content;
904
- if (column.render) {
971
+ if (showEditor) {
972
+ content = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
973
+ EditableCell,
974
+ {
975
+ value,
976
+ record,
977
+ column,
978
+ rowIndex,
979
+ onEdit,
980
+ onEditComplete
981
+ }
982
+ );
983
+ } else if (column.render) {
905
984
  try {
906
985
  content = column.render(value, record, rowIndex);
907
986
  } catch {
@@ -930,7 +1009,7 @@ var Cell = import_react3.default.memo(
930
1009
  ...isPinned ? styles?.pinnedCell : void 0,
931
1010
  ...styles?.cell
932
1011
  },
933
- children: isSystem ? content : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1012
+ children: isSystem ? content : showEditor ? content : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
934
1013
  "div",
935
1014
  {
936
1015
  style: {
@@ -951,6 +1030,10 @@ var Cell = import_react3.default.memo(
951
1030
  if (prev.column.pinned !== next.column.pinned) return false;
952
1031
  if (prev.classNames !== next.classNames) return false;
953
1032
  if (prev.styles !== next.styles) return false;
1033
+ if (prev.column.editable !== next.column.editable) return false;
1034
+ if (prev.onEdit !== next.onEdit) return false;
1035
+ if (prev.isEditing !== next.isEditing) return false;
1036
+ if (prev.onEditComplete !== next.onEditComplete) return false;
954
1037
  if (prev.column.key === "__select__") {
955
1038
  return prev.isSelected === next.isSelected && prev.normalizedSelectedKeys === next.normalizedSelectedKeys;
956
1039
  }
@@ -1018,7 +1101,11 @@ var TableBody = ({
1018
1101
  gridTemplateColumns,
1019
1102
  headerHeight = 36,
1020
1103
  rowClassName,
1021
- rowStyle
1104
+ rowStyle,
1105
+ bodyGridRow = 2,
1106
+ onEdit,
1107
+ editingCell,
1108
+ onEditComplete
1022
1109
  }) => {
1023
1110
  const virtualItems = rowVirtualizer.getVirtualItems();
1024
1111
  const totalSize = rowVirtualizer.getTotalSize();
@@ -1040,7 +1127,7 @@ var TableBody = ({
1040
1127
  else if (isPinned) zIndex = 2;
1041
1128
  const style = {
1042
1129
  gridColumn: colIndex + 1,
1043
- gridRow: 2,
1130
+ gridRow: bodyGridRow,
1044
1131
  height: `${totalSize}px`,
1045
1132
  position: isPinned ? "sticky" : "relative",
1046
1133
  zIndex
@@ -1054,7 +1141,7 @@ var TableBody = ({
1054
1141
  }
1055
1142
  return { key: col.key, style, isPinned };
1056
1143
  });
1057
- }, [safeColumns, columnOffsets, totalSize, styles]);
1144
+ }, [safeColumns, columnOffsets, totalSize, styles, bodyGridRow]);
1058
1145
  if (safeData.length === 0 || safeColumns.length === 0) return null;
1059
1146
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1060
1147
  columnStyles.map((colStyle, colIndex) => {
@@ -1072,7 +1159,7 @@ var TableBody = ({
1072
1159
  const rowKey = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1073
1160
  const isSelected = selectedKeySet.has(rowKey);
1074
1161
  const isExpanded = resolvedExpandedKeys?.has(rowKey) ?? false;
1075
- const cellValue = row[col.dataIndex];
1162
+ const cellValue = col.dataIndex != null ? row[col.dataIndex] : void 0;
1076
1163
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1077
1164
  let recordFingerprint;
1078
1165
  if (hasRender && !isRowShimmer) {
@@ -1134,7 +1221,10 @@ var TableBody = ({
1134
1221
  getRawRowKey,
1135
1222
  accentColor,
1136
1223
  isLoading: isRowShimmer,
1137
- recordFingerprint
1224
+ recordFingerprint,
1225
+ onEdit,
1226
+ isEditing: editingCell?.rowKey === rowKey && editingCell?.columnKey === col.key,
1227
+ onEditComplete
1138
1228
  }
1139
1229
  )
1140
1230
  }
@@ -1152,7 +1242,7 @@ var TableBody = ({
1152
1242
  {
1153
1243
  style: {
1154
1244
  gridColumn: "1 / -1",
1155
- gridRow: 2,
1245
+ gridRow: bodyGridRow,
1156
1246
  height: `${totalSize}px`,
1157
1247
  position: "relative",
1158
1248
  zIndex: 15,
@@ -1216,7 +1306,7 @@ var TableBody = ({
1216
1306
  {
1217
1307
  style: {
1218
1308
  gridColumn: "1 / -1",
1219
- gridRow: 2,
1309
+ gridRow: bodyGridRow,
1220
1310
  height: `${totalSize}px`,
1221
1311
  position: "relative",
1222
1312
  zIndex: 20,
@@ -1258,7 +1348,7 @@ var TableBody = ({
1258
1348
  ...rowSty
1259
1349
  },
1260
1350
  children: safeColumns.map((col) => {
1261
- const cellValue = row[col.dataIndex];
1351
+ const cellValue = col.dataIndex != null ? row[col.dataIndex] : void 0;
1262
1352
  const stickyOffset = columnOffsets.get(col.key);
1263
1353
  const isPinned = Boolean(col.pinned);
1264
1354
  let zIndex = 0;
@@ -1316,7 +1406,10 @@ var TableBody = ({
1316
1406
  getRawRowKey,
1317
1407
  accentColor,
1318
1408
  isLoading: false,
1319
- recordFingerprint
1409
+ recordFingerprint,
1410
+ onEdit,
1411
+ isEditing: editingCell?.rowKey === rk && editingCell?.columnKey === col.key,
1412
+ onEditComplete
1320
1413
  }
1321
1414
  )
1322
1415
  }
@@ -1338,7 +1431,7 @@ var TableBody = ({
1338
1431
  {
1339
1432
  style: {
1340
1433
  gridColumn: "1 / -1",
1341
- gridRow: 2,
1434
+ gridRow: bodyGridRow,
1342
1435
  height: `${totalSize}px`,
1343
1436
  position: "relative",
1344
1437
  zIndex: 20,
@@ -1383,7 +1476,7 @@ var TableBody = ({
1383
1476
  ...rowSty
1384
1477
  },
1385
1478
  children: safeColumns.map((col) => {
1386
- const cellValue = row[col.dataIndex];
1479
+ const cellValue = col.dataIndex != null ? row[col.dataIndex] : void 0;
1387
1480
  const stickyOffset = columnOffsets.get(col.key);
1388
1481
  const isPinned = Boolean(col.pinned);
1389
1482
  let zIndex = 0;
@@ -1441,7 +1534,10 @@ var TableBody = ({
1441
1534
  getRawRowKey,
1442
1535
  accentColor,
1443
1536
  isLoading: false,
1444
- recordFingerprint
1537
+ recordFingerprint,
1538
+ onEdit,
1539
+ isEditing: editingCell?.rowKey === rk && editingCell?.columnKey === col.key,
1540
+ onEditComplete
1445
1541
  }
1446
1542
  )
1447
1543
  }
@@ -1471,6 +1567,18 @@ function arrayMove(arr, from, to) {
1471
1567
  result.splice(to, 0, item);
1472
1568
  return result;
1473
1569
  }
1570
+ function flattenColumns(columns) {
1571
+ const result = [];
1572
+ for (const col of columns) {
1573
+ if (col == null) continue;
1574
+ if (col.children && col.children.length > 0) {
1575
+ result.push(...flattenColumns(col.children));
1576
+ } else {
1577
+ result.push(col);
1578
+ }
1579
+ }
1580
+ return result;
1581
+ }
1474
1582
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1475
1583
  var EMPTY_CLASSNAMES = {};
1476
1584
  var EMPTY_STYLES = {};
@@ -1511,7 +1619,10 @@ function BoltTable({
1511
1619
  emptyRenderer,
1512
1620
  rowClassName,
1513
1621
  rowStyle,
1514
- disabledFilters
1622
+ disabledFilters,
1623
+ onCopy,
1624
+ keepPinnedRowsAcrossPages,
1625
+ onEdit
1515
1626
  }) {
1516
1627
  const data = (0, import_react4.useMemo)(() => {
1517
1628
  if (!Array.isArray(rawData)) return STABLE_EMPTY_DATA;
@@ -1520,11 +1631,43 @@ function BoltTable({
1520
1631
  }, [rawData]);
1521
1632
  const initialColumns = (0, import_react4.useMemo)(() => {
1522
1633
  if (!Array.isArray(rawInitialColumns)) return STABLE_EMPTY_COLS;
1523
- const filtered = rawInitialColumns.filter(
1634
+ const safe = rawInitialColumns.filter(
1524
1635
  (col) => col != null && typeof col.key === "string"
1525
1636
  );
1526
- return filtered.length > 0 ? filtered : STABLE_EMPTY_COLS;
1637
+ const flattened = flattenColumns(safe);
1638
+ const validated = flattened.filter(
1639
+ (col) => col != null && typeof col.key === "string"
1640
+ );
1641
+ return validated.length > 0 ? validated : STABLE_EMPTY_COLS;
1642
+ }, [rawInitialColumns]);
1643
+ const headerGroups = (0, import_react4.useMemo)(() => {
1644
+ if (!Array.isArray(rawInitialColumns)) return [];
1645
+ const groups = [];
1646
+ for (const col of rawInitialColumns) {
1647
+ if (col != null && typeof col.key === "string" && col.children && col.children.length > 0) {
1648
+ const leafKeys = flattenColumns([col]).filter((c) => c != null && typeof c.key === "string").map((c) => c.key);
1649
+ if (leafKeys.length > 0) {
1650
+ groups.push({
1651
+ key: col.key,
1652
+ title: col.title,
1653
+ childKeys: leafKeys,
1654
+ style: col.style,
1655
+ className: col.className
1656
+ });
1657
+ }
1658
+ }
1659
+ }
1660
+ return groups;
1527
1661
  }, [rawInitialColumns]);
1662
+ const hasColumnGroups = headerGroups.length > 0;
1663
+ const groupedColumnKeySet = (0, import_react4.useMemo)(() => {
1664
+ if (!hasColumnGroups) return null;
1665
+ const keys = /* @__PURE__ */ new Set();
1666
+ for (const g of headerGroups) {
1667
+ for (const k of g.childKeys) keys.add(k);
1668
+ }
1669
+ return keys;
1670
+ }, [headerGroups, hasColumnGroups]);
1528
1671
  const [columns, setColumns] = (0, import_react4.useState)(initialColumns);
1529
1672
  const [columnOrder, setColumnOrder] = (0, import_react4.useState)(
1530
1673
  () => initialColumns.map((c) => c.key)
@@ -1777,6 +1920,17 @@ function BoltTable({
1777
1920
  };
1778
1921
  }, []);
1779
1922
  const resizeStateRef = (0, import_react4.useRef)(null);
1923
+ const columnGroupMapRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
1924
+ (0, import_react4.useMemo)(() => {
1925
+ const map = /* @__PURE__ */ new Map();
1926
+ for (const g of headerGroups) {
1927
+ for (const k of g.childKeys) map.set(k, g.key);
1928
+ }
1929
+ for (const col of initialColumns) {
1930
+ if (!map.has(col.key)) map.set(col.key, null);
1931
+ }
1932
+ columnGroupMapRef.current = map;
1933
+ }, [headerGroups, initialColumns]);
1780
1934
  const overIdRef = (0, import_react4.useRef)(null);
1781
1935
  const dragActiveIdRef = (0, import_react4.useRef)(null);
1782
1936
  const ghostRef = (0, import_react4.useRef)(null);
@@ -1805,6 +1959,7 @@ function BoltTable({
1805
1959
  const grabStyle = document.createElement("style");
1806
1960
  grabStyle.textContent = "* { cursor: grabbing !important; }";
1807
1961
  document.head.appendChild(grabStyle);
1962
+ const draggedGroup = columnGroupMapRef.current.get(columnKey);
1808
1963
  const onMove = (ev) => {
1809
1964
  if (ghost) {
1810
1965
  ghost.style.left = `${ev.clientX - offsetX}px`;
@@ -1820,6 +1975,11 @@ function BoltTable({
1820
1975
  h.removeAttribute("data-drag-over");
1821
1976
  return;
1822
1977
  }
1978
+ const targetGroup = columnGroupMapRef.current.get(key);
1979
+ if (draggedGroup !== targetGroup) {
1980
+ h.removeAttribute("data-drag-over");
1981
+ return;
1982
+ }
1823
1983
  const r = h.getBoundingClientRect();
1824
1984
  if (ev.clientX >= r.left && ev.clientX <= r.right && ev.clientY >= r.top - 20 && ev.clientY <= r.bottom + 20) {
1825
1985
  newOverId = key;
@@ -1842,7 +2002,7 @@ function BoltTable({
1842
2002
  if (ghost) ghost.style.display = "none";
1843
2003
  const currentOverId = overIdRef.current;
1844
2004
  const currentActiveId = dragActiveIdRef.current;
1845
- if (currentOverId && currentActiveId && currentOverId !== currentActiveId) {
2005
+ if (currentOverId && currentActiveId && currentOverId !== currentActiveId && columnGroupMapRef.current.get(currentActiveId) === columnGroupMapRef.current.get(currentOverId)) {
1846
2006
  import_react4.default.startTransition(() => {
1847
2007
  setColumnOrder((items) => {
1848
2008
  const oldIndex = items.indexOf(currentActiveId);
@@ -2082,7 +2242,7 @@ function BoltTable({
2082
2242
  try {
2083
2243
  const col = columnsLookupRef.current.find((c) => c.key === key);
2084
2244
  if (typeof col?.filterFn === "function") {
2085
- return col.filterFn(columnFilters[key], row, col.dataIndex);
2245
+ return col.filterFn(columnFilters[key], row, col.dataIndex ?? key);
2086
2246
  }
2087
2247
  const cellVal = String(row[key] ?? "").toLowerCase();
2088
2248
  return cellVal.includes(columnFilters[key].toLowerCase());
@@ -2126,8 +2286,10 @@ function BoltTable({
2126
2286
  }
2127
2287
  return result;
2128
2288
  }, [data, sortState, columnFilters]);
2289
+ const pinnedRowCacheRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
2129
2290
  const { pinnedTopRows, pinnedBottomRows, unpinnedProcessedData } = (0, import_react4.useMemo)(() => {
2130
2291
  if (!resolvedRowPinning || !resolvedRowPinning.top?.length && !resolvedRowPinning.bottom?.length) {
2292
+ if (keepPinnedRowsAcrossPages) pinnedRowCacheRef.current.clear();
2131
2293
  return {
2132
2294
  pinnedTopRows: [],
2133
2295
  pinnedBottomRows: [],
@@ -2142,10 +2304,32 @@ function BoltTable({
2142
2304
  processedData.forEach((row, idx) => {
2143
2305
  if (row == null) return;
2144
2306
  const key = getRowKey(row, idx);
2145
- if (topKeySet.has(key)) topMap.set(key, row);
2146
- else if (bottomKeySet.has(key)) bottomMap.set(key, row);
2147
- else rest.push(row);
2307
+ if (topKeySet.has(key)) {
2308
+ topMap.set(key, row);
2309
+ if (keepPinnedRowsAcrossPages) pinnedRowCacheRef.current.set(key, row);
2310
+ } else if (bottomKeySet.has(key)) {
2311
+ bottomMap.set(key, row);
2312
+ if (keepPinnedRowsAcrossPages) pinnedRowCacheRef.current.set(key, row);
2313
+ } else {
2314
+ rest.push(row);
2315
+ }
2148
2316
  });
2317
+ if (keepPinnedRowsAcrossPages) {
2318
+ for (const k of topKeySet) {
2319
+ if (!topMap.has(k) && pinnedRowCacheRef.current.has(k)) {
2320
+ topMap.set(k, pinnedRowCacheRef.current.get(k));
2321
+ }
2322
+ }
2323
+ for (const k of bottomKeySet) {
2324
+ if (!bottomMap.has(k) && pinnedRowCacheRef.current.has(k)) {
2325
+ bottomMap.set(k, pinnedRowCacheRef.current.get(k));
2326
+ }
2327
+ }
2328
+ const allPinnedKeys = /* @__PURE__ */ new Set([...topKeySet, ...bottomKeySet]);
2329
+ for (const cachedKey of pinnedRowCacheRef.current.keys()) {
2330
+ if (!allPinnedKeys.has(cachedKey)) pinnedRowCacheRef.current.delete(cachedKey);
2331
+ }
2332
+ }
2149
2333
  const orderedTop = (resolvedRowPinning.top ?? []).map((k) => topMap.get(String(k))).filter((r) => r !== void 0);
2150
2334
  const orderedBottom = (resolvedRowPinning.bottom ?? []).map((k) => bottomMap.get(String(k))).filter((r) => r !== void 0);
2151
2335
  return {
@@ -2153,7 +2337,7 @@ function BoltTable({
2153
2337
  pinnedBottomRows: orderedBottom,
2154
2338
  unpinnedProcessedData: rest
2155
2339
  };
2156
- }, [processedData, resolvedRowPinning, getRowKey]);
2340
+ }, [processedData, resolvedRowPinning, getRowKey, keepPinnedRowsAcrossPages]);
2157
2341
  const pinnedTopHeight = pinnedTopRows.length * rowHeight;
2158
2342
  const pinnedBottomHeight = pinnedBottomRows.length * rowHeight;
2159
2343
  const pinnedTopKeySet = (0, import_react4.useMemo)(
@@ -2164,6 +2348,10 @@ function BoltTable({
2164
2348
  () => new Set((resolvedRowPinning?.bottom ?? []).map(String)),
2165
2349
  [resolvedRowPinning?.bottom]
2166
2350
  );
2351
+ const [editingCell, setEditingCell] = (0, import_react4.useState)(null);
2352
+ const handleEditComplete = (0, import_react4.useCallback)(() => {
2353
+ setEditingCell(null);
2354
+ }, []);
2167
2355
  const [cellContextMenu, setCellContextMenu] = (0, import_react4.useState)(null);
2168
2356
  const cellMenuRef = (0, import_react4.useRef)(null);
2169
2357
  const cellLongPressTimer = (0, import_react4.useRef)(
@@ -2374,7 +2562,7 @@ function BoltTable({
2374
2562
  totalPages
2375
2563
  ];
2376
2564
  };
2377
- const HEADER_HEIGHT = 36;
2565
+ const HEADER_HEIGHT = hasColumnGroups ? 72 : 36;
2378
2566
  const MAX_AUTO_ROWS = 10;
2379
2567
  const virtualTotalSize = rowVirtualizer.getTotalSize();
2380
2568
  const naturalContentHeight = virtualTotalSize + HEADER_HEIGHT;
@@ -2569,7 +2757,7 @@ function BoltTable({
2569
2757
  style: {
2570
2758
  display: "grid",
2571
2759
  gridTemplateColumns,
2572
- gridTemplateRows: isEmpty ? "36px 1fr" : `36px ${virtualTotalSize}px`,
2760
+ gridTemplateRows: isEmpty ? hasColumnGroups ? "36px 36px 1fr" : "36px 1fr" : hasColumnGroups ? `36px 36px ${virtualTotalSize}px` : `36px ${virtualTotalSize}px`,
2573
2761
  minWidth: `${totalTableWidth}px`,
2574
2762
  width: "100%",
2575
2763
  position: "relative",
@@ -2587,7 +2775,8 @@ function BoltTable({
2587
2775
  const hasCopy = !!col?.copy;
2588
2776
  const hasRowPin = !!rowPinning;
2589
2777
  const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2590
- if (!hasCopy && !hasRowPin && !hasCellItems) return;
2778
+ const hasEdit = !!col?.editable && !col?.render && !!onEdit;
2779
+ if (!hasCopy && !hasRowPin && !hasCellItems && !hasEdit) return;
2591
2780
  e.preventDefault();
2592
2781
  setCellContextMenu({
2593
2782
  x: Math.min(e.clientX, window.innerWidth - 200),
@@ -2616,7 +2805,8 @@ function BoltTable({
2616
2805
  const hasCopy = !!col?.copy;
2617
2806
  const hasRowPin = !!rowPinning;
2618
2807
  const hasCellItems = col?.columnCellContextMenuItems && col.columnCellContextMenuItems.length > 0;
2619
- if (!hasCopy && !hasRowPin && !hasCellItems) return;
2808
+ const hasEdit = !!col?.editable && !col?.render && !!onEdit;
2809
+ if (!hasCopy && !hasRowPin && !hasCellItems && !hasEdit) return;
2620
2810
  setCellContextMenu({
2621
2811
  x: Math.min(touch.clientX, window.innerWidth - 200),
2622
2812
  y: Math.min(touch.clientY, window.innerHeight - 200),
@@ -2636,7 +2826,50 @@ function BoltTable({
2636
2826
  onTouchEnd: cancelCellLongPress,
2637
2827
  onTouchCancel: cancelCellLongPress,
2638
2828
  children: [
2829
+ hasColumnGroups && headerGroups.map((group) => {
2830
+ let minIdx = Infinity;
2831
+ let maxIdx = -1;
2832
+ orderedColumns.forEach((col, idx) => {
2833
+ if (group.childKeys.includes(col.key)) {
2834
+ minIdx = Math.min(minIdx, idx);
2835
+ maxIdx = Math.max(maxIdx, idx);
2836
+ }
2837
+ });
2838
+ if (minIdx === Infinity) return null;
2839
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2840
+ "div",
2841
+ {
2842
+ "data-bt-header": "",
2843
+ className: `${group.className ?? ""} ${classNames.header ?? ""}`,
2844
+ style: {
2845
+ gridColumn: `${minIdx + 1} / ${maxIdx + 2}`,
2846
+ gridRow: 1,
2847
+ position: "sticky",
2848
+ top: 0,
2849
+ zIndex: 10,
2850
+ display: "flex",
2851
+ height: 36,
2852
+ alignItems: "center",
2853
+ justifyContent: "center",
2854
+ overflow: "hidden",
2855
+ textOverflow: "ellipsis",
2856
+ whiteSpace: "nowrap",
2857
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
2858
+ fontWeight: 500,
2859
+ userSelect: "none",
2860
+ ...group.style,
2861
+ ...styles.header
2862
+ },
2863
+ children: group.title
2864
+ },
2865
+ `group-${group.key}`
2866
+ );
2867
+ }),
2639
2868
  orderedColumns.map((column, visualIndex) => {
2869
+ const isInGroup = groupedColumnKeySet?.has(column.key) ?? false;
2870
+ const leafGridRow = hasColumnGroups ? isInGroup ? 2 : "1 / 3" : 1;
2871
+ const leafHeight = hasColumnGroups && !isInGroup ? 72 : 36;
2872
+ const leafStickyTop = hasColumnGroups && isInGroup ? 36 : 0;
2640
2873
  if (column.key === "__select__" && rowSelection) {
2641
2874
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2642
2875
  "div",
@@ -2646,7 +2879,7 @@ function BoltTable({
2646
2879
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2647
2880
  style: {
2648
2881
  display: "flex",
2649
- height: 36,
2882
+ height: leafHeight,
2650
2883
  alignItems: "center",
2651
2884
  justifyContent: "center",
2652
2885
  overflow: "hidden",
@@ -2658,6 +2891,7 @@ function BoltTable({
2658
2891
  top: 0,
2659
2892
  zIndex: 13,
2660
2893
  width: "48px",
2894
+ gridRow: leafGridRow,
2661
2895
  ...styles.header,
2662
2896
  ...styles.pinnedHeader
2663
2897
  },
@@ -2691,7 +2925,7 @@ function BoltTable({
2691
2925
  });
2692
2926
  }
2693
2927
  },
2694
- style: { cursor: "pointer", accentColor }
2928
+ style: { cursor: "pointer", accentColor, colorScheme: "light dark" }
2695
2929
  }
2696
2930
  )
2697
2931
  },
@@ -2707,7 +2941,7 @@ function BoltTable({
2707
2941
  className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2708
2942
  style: {
2709
2943
  display: "flex",
2710
- height: 36,
2944
+ height: leafHeight,
2711
2945
  alignItems: "center",
2712
2946
  justifyContent: "center",
2713
2947
  overflow: "hidden",
@@ -2719,6 +2953,7 @@ function BoltTable({
2719
2953
  top: 0,
2720
2954
  zIndex: 13,
2721
2955
  width: "40px",
2956
+ gridRow: leafGridRow,
2722
2957
  ...styles.header,
2723
2958
  ...styles.pinnedHeader
2724
2959
  }
@@ -2749,7 +2984,10 @@ function BoltTable({
2749
2984
  onFilter: handleColumnFilter,
2750
2985
  onClearFilter: handleClearFilter,
2751
2986
  customContextMenuItems: column.columnHeaderContextMenuItems ? [...columnContextMenuItems ?? [], ...column.columnHeaderContextMenuItems] : columnContextMenuItems,
2752
- disabledFilters
2987
+ disabledFilters,
2988
+ headerGridRow: leafGridRow,
2989
+ headerHeight: leafHeight,
2990
+ stickyTop: leafStickyTop
2753
2991
  },
2754
2992
  column.key
2755
2993
  );
@@ -2820,7 +3058,11 @@ function BoltTable({
2820
3058
  gridTemplateColumns,
2821
3059
  headerHeight: HEADER_HEIGHT,
2822
3060
  rowClassName,
2823
- rowStyle
3061
+ rowStyle,
3062
+ bodyGridRow: hasColumnGroups ? 3 : 2,
3063
+ onEdit,
3064
+ editingCell,
3065
+ onEditComplete: handleEditComplete
2824
3066
  }
2825
3067
  )
2826
3068
  ]
@@ -2834,6 +3076,7 @@ function BoltTable({
2834
3076
  pgEnabled && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2835
3077
  "div",
2836
3078
  {
3079
+ className: classNames.pagination ?? "",
2837
3080
  style: {
2838
3081
  display: "flex",
2839
3082
  height: 36,
@@ -2845,27 +3088,42 @@ function BoltTable({
2845
3088
  fontSize: 12,
2846
3089
  backdropFilter: "blur(8px)",
2847
3090
  backgroundColor: "rgba(128,128,128,0.06)",
2848
- gap: 12
3091
+ gap: 12,
3092
+ ...styles.pagination
2849
3093
  },
2850
3094
  children: [
2851
3095
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flex: "1 1 0%", alignItems: "center" }, children: (() => {
2852
3096
  const rangeStart = total > 0 ? (currentPage - 1) * pageSize + 1 : 0;
2853
3097
  const rangeEnd = Math.min(currentPage * pageSize, total);
2854
- return typeof pagination === "object" && pagination?.showTotal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2855
- "Showing",
2856
- " ",
2857
- pagination.showTotal(total, [rangeStart, rangeEnd]),
2858
- " of",
2859
- " ",
2860
- total,
2861
- " items"
2862
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { color: "GrayText", fontSize: 12 }, children: [
2863
- rangeStart,
2864
- "\u2013",
2865
- rangeEnd,
2866
- " of ",
2867
- total
2868
- ] });
3098
+ return typeof pagination === "object" && pagination?.showTotal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3099
+ "span",
3100
+ {
3101
+ className: classNames.paginationInfo ?? "",
3102
+ style: { color: "GrayText", fontSize: 12, ...styles.paginationInfo },
3103
+ children: [
3104
+ "Showing",
3105
+ " ",
3106
+ pagination.showTotal(total, [rangeStart, rangeEnd]),
3107
+ " of",
3108
+ " ",
3109
+ total,
3110
+ " items"
3111
+ ]
3112
+ }
3113
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3114
+ "span",
3115
+ {
3116
+ className: classNames.paginationInfo ?? "",
3117
+ style: { color: "GrayText", fontSize: 12, ...styles.paginationInfo },
3118
+ children: [
3119
+ rangeStart,
3120
+ "\u2013",
3121
+ rangeEnd,
3122
+ " of ",
3123
+ total
3124
+ ]
3125
+ }
3126
+ );
2869
3127
  })() }),
2870
3128
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2871
3129
  "div",
@@ -2883,6 +3141,7 @@ function BoltTable({
2883
3141
  {
2884
3142
  onClick: () => handlePageChange(1),
2885
3143
  disabled: currentPage === 1,
3144
+ className: classNames.paginationButton ?? "",
2886
3145
  style: {
2887
3146
  display: "inline-flex",
2888
3147
  height: 24,
@@ -2895,7 +3154,8 @@ function BoltTable({
2895
3154
  background: "none",
2896
3155
  border: "none",
2897
3156
  padding: 0,
2898
- color: "inherit"
3157
+ color: "inherit",
3158
+ ...styles.paginationButton
2899
3159
  },
2900
3160
  title: "First page",
2901
3161
  children: icons?.chevronsLeft ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronsLeftIcon, { style: { width: 12, height: 12 } })
@@ -2906,6 +3166,7 @@ function BoltTable({
2906
3166
  {
2907
3167
  onClick: () => handlePageChange(currentPage - 1),
2908
3168
  disabled: currentPage === 1,
3169
+ className: classNames.paginationButton ?? "",
2909
3170
  style: {
2910
3171
  display: "inline-flex",
2911
3172
  height: 24,
@@ -2918,7 +3179,8 @@ function BoltTable({
2918
3179
  background: "none",
2919
3180
  border: "none",
2920
3181
  padding: 0,
2921
- color: "inherit"
3182
+ color: "inherit",
3183
+ ...styles.paginationButton
2922
3184
  },
2923
3185
  title: "Previous page",
2924
3186
  children: icons?.chevronLeft ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronLeftIcon, { style: { width: 12, height: 12 } })
@@ -2941,9 +3203,11 @@ function BoltTable({
2941
3203
  page
2942
3204
  );
2943
3205
  }
3206
+ const isActivePage = page === currentPage;
2944
3207
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2945
3208
  "button",
2946
3209
  {
3210
+ className: `${classNames.paginationButton ?? ""} ${isActivePage ? classNames.paginationActiveButton ?? "" : ""}`.trim() || void 0,
2947
3211
  style: {
2948
3212
  display: "inline-flex",
2949
3213
  height: 24,
@@ -2955,9 +3219,11 @@ function BoltTable({
2955
3219
  paddingLeft: 6,
2956
3220
  paddingRight: 6,
2957
3221
  fontSize: 12,
2958
- color: page === currentPage ? accentColor : void 0,
3222
+ color: isActivePage ? accentColor : void 0,
2959
3223
  background: "none",
2960
- border: "none"
3224
+ border: "none",
3225
+ ...styles.paginationButton,
3226
+ ...isActivePage ? styles.paginationActiveButton : void 0
2961
3227
  },
2962
3228
  onClick: () => handlePageChange(page),
2963
3229
  children: page
@@ -2970,6 +3236,7 @@ function BoltTable({
2970
3236
  {
2971
3237
  onClick: () => handlePageChange(currentPage + 1),
2972
3238
  disabled: currentPage === totalPages,
3239
+ className: classNames.paginationButton ?? "",
2973
3240
  style: {
2974
3241
  display: "inline-flex",
2975
3242
  height: 24,
@@ -2982,7 +3249,8 @@ function BoltTable({
2982
3249
  background: "none",
2983
3250
  border: "none",
2984
3251
  padding: 0,
2985
- color: "inherit"
3252
+ color: "inherit",
3253
+ ...styles.paginationButton
2986
3254
  },
2987
3255
  title: "Next page",
2988
3256
  children: icons?.chevronRight ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronRightIcon, { style: { width: 12, height: 12 } })
@@ -2993,6 +3261,7 @@ function BoltTable({
2993
3261
  {
2994
3262
  onClick: () => handlePageChange(totalPages),
2995
3263
  disabled: currentPage === totalPages,
3264
+ className: classNames.paginationButton ?? "",
2996
3265
  style: {
2997
3266
  display: "inline-flex",
2998
3267
  height: 24,
@@ -3005,7 +3274,8 @@ function BoltTable({
3005
3274
  background: "none",
3006
3275
  border: "none",
3007
3276
  padding: 0,
3008
- color: "inherit"
3277
+ color: "inherit",
3278
+ ...styles.paginationButton
3009
3279
  },
3010
3280
  title: "Last page",
3011
3281
  children: icons?.chevronsRight ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronsRightIcon, { style: { width: 12, height: 12 } })
@@ -3029,6 +3299,7 @@ function BoltTable({
3029
3299
  {
3030
3300
  value: pageSize,
3031
3301
  onChange: (e) => handlePageSizeChange(Number(e.target.value)),
3302
+ className: classNames.paginationSelect ?? "",
3032
3303
  style: {
3033
3304
  cursor: "pointer",
3034
3305
  borderRadius: 4,
@@ -3040,7 +3311,8 @@ function BoltTable({
3040
3311
  fontSize: 12,
3041
3312
  height: 24,
3042
3313
  background: "inherit",
3043
- color: "inherit"
3314
+ color: "inherit",
3315
+ ...styles.paginationSelect
3044
3316
  },
3045
3317
  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: [
3046
3318
  size,
@@ -3135,6 +3407,7 @@ function BoltTable({
3135
3407
  );
3136
3408
  const hasCopy = !!menuCol?.copy;
3137
3409
  const hasRowPin = !!rowPinning;
3410
+ const hasEdit = !!menuCol?.editable && !menuCol?.render && !!onEdit;
3138
3411
  let menuRecord;
3139
3412
  let menuRowIndex = 0;
3140
3413
  const allRows = [
@@ -3151,7 +3424,7 @@ function BoltTable({
3151
3424
  break;
3152
3425
  }
3153
3426
  }
3154
- const menuValue = menuRecord && menuCol ? menuRecord[menuCol.dataIndex] : void 0;
3427
+ const menuValue = menuRecord && menuCol?.dataIndex != null ? menuRecord[menuCol.dataIndex] : void 0;
3155
3428
  const btnStyle = {
3156
3429
  display: "flex",
3157
3430
  width: "100%",
@@ -3250,7 +3523,39 @@ function BoltTable({
3250
3523
  }
3251
3524
  )
3252
3525
  ] }),
3253
- hasRowPin && hasCopy && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3526
+ hasRowPin && (hasCopy || hasEdit) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3527
+ "div",
3528
+ {
3529
+ style: {
3530
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3531
+ margin: "4px 0"
3532
+ }
3533
+ }
3534
+ ),
3535
+ hasEdit && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3536
+ "button",
3537
+ {
3538
+ "data-bt-ctx-item": true,
3539
+ style: btnStyle,
3540
+ onClick: () => {
3541
+ setEditingCell({
3542
+ rowKey: cellContextMenu.rowKey,
3543
+ columnKey: cellContextMenu.columnKey
3544
+ });
3545
+ setCellContextMenu(null);
3546
+ },
3547
+ children: [
3548
+ icons?.edit ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3549
+ PencilIcon,
3550
+ {
3551
+ style: { width: 14, height: 14, flexShrink: 0 }
3552
+ }
3553
+ ),
3554
+ "Edit"
3555
+ ]
3556
+ }
3557
+ ),
3558
+ (hasEdit || hasRowPin) && hasCopy && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3254
3559
  "div",
3255
3560
  {
3256
3561
  style: {
@@ -3267,6 +3572,7 @@ function BoltTable({
3267
3572
  onClick: () => {
3268
3573
  const text = typeof menuCol.copy === "function" ? menuCol.copy(menuValue, menuRecord, menuRowIndex) : String(menuValue ?? "");
3269
3574
  navigator.clipboard?.writeText(text);
3575
+ onCopy?.(text, menuCol.key, menuRecord, menuRowIndex);
3270
3576
  setCellContextMenu(null);
3271
3577
  },
3272
3578
  children: [
@@ -3281,7 +3587,7 @@ function BoltTable({
3281
3587
  }
3282
3588
  ),
3283
3589
  menuCol?.columnCellContextMenuItems && menuCol.columnCellContextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3284
- (hasCopy || hasRowPin) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3590
+ (hasCopy || hasRowPin || hasEdit) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3285
3591
  "div",
3286
3592
  {
3287
3593
  style: {