bolt-table 0.1.12 → 0.1.14

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.mjs CHANGED
@@ -76,6 +76,10 @@ var PinOffIcon = ({ style, className }) => /* @__PURE__ */ jsxs("svg", { ...svgB
76
76
  /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "17", y2: "22" }),
77
77
  /* @__PURE__ */ jsx("path", { d: "M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4" })
78
78
  ] });
79
+ var CopyIcon = ({ style, className }) => /* @__PURE__ */ jsxs("svg", { ...svgBase, style, className, children: [
80
+ /* @__PURE__ */ jsx("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
81
+ /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
82
+ ] });
79
83
  var EyeOffIcon = ({ style, className }) => /* @__PURE__ */ jsxs("svg", { ...svgBase, style, className, children: [
80
84
  /* @__PURE__ */ jsx("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }),
81
85
  /* @__PURE__ */ 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" }),
@@ -131,21 +135,49 @@ var DraggableHeader = React.memo(
131
135
  return () => document.removeEventListener("mousedown", handleClickOutside);
132
136
  }
133
137
  }, [contextMenu]);
134
- const handleContextMenu = (e) => {
135
- e.preventDefault();
136
- e.stopPropagation();
138
+ const showContextMenuAt = (clientX, clientY) => {
137
139
  const menuWidth = 160;
138
140
  const menuHeight = 180;
139
- let x = e.clientX;
140
- let y = e.clientY;
141
- if (x + menuWidth > window.innerWidth) {
141
+ let x = clientX;
142
+ let y = clientY;
143
+ if (x + menuWidth > window.innerWidth)
142
144
  x = window.innerWidth - menuWidth - 10;
143
- }
144
- if (y + menuHeight > window.innerHeight) {
145
+ if (y + menuHeight > window.innerHeight)
145
146
  y = window.innerHeight - menuHeight - 10;
146
- }
147
147
  setContextMenu({ x, y });
148
148
  };
149
+ const handleContextMenu = (e) => {
150
+ e.preventDefault();
151
+ e.stopPropagation();
152
+ showContextMenuAt(e.clientX, e.clientY);
153
+ };
154
+ const longPressTimerRef = useRef(
155
+ null
156
+ );
157
+ const touchStartRef = useRef(null);
158
+ const cancelLongPress = () => {
159
+ if (longPressTimerRef.current) {
160
+ clearTimeout(longPressTimerRef.current);
161
+ longPressTimerRef.current = null;
162
+ }
163
+ touchStartRef.current = null;
164
+ };
165
+ const handleTouchStart = (e) => {
166
+ cancelLongPress();
167
+ const touch = e.touches[0];
168
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY };
169
+ longPressTimerRef.current = setTimeout(() => {
170
+ longPressTimerRef.current = null;
171
+ showContextMenuAt(touch.clientX, touch.clientY);
172
+ }, 500);
173
+ };
174
+ const handleTouchMove = (e) => {
175
+ if (!touchStartRef.current) return;
176
+ const touch = e.touches[0];
177
+ const dx = touch.clientX - touchStartRef.current.x;
178
+ const dy = touch.clientY - touchStartRef.current.y;
179
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10) cancelLongPress();
180
+ };
149
181
  const handleResizeStart = (e) => {
150
182
  e.preventDefault();
151
183
  e.stopPropagation();
@@ -195,6 +227,10 @@ var DraggableHeader = React.memo(
195
227
  style: headerStyle,
196
228
  className: `${column.className ?? ""} ${classNames?.header ?? ""} ${isPinned ? classNames?.pinnedHeader ?? "" : ""}`,
197
229
  onContextMenu: handleContextMenu,
230
+ onTouchStart: handleTouchStart,
231
+ onTouchMove: handleTouchMove,
232
+ onTouchEnd: cancelLongPress,
233
+ onTouchCancel: cancelLongPress,
198
234
  children: [
199
235
  /* @__PURE__ */ jsxs2(
200
236
  "div",
@@ -1000,15 +1036,26 @@ var TableBody = ({
1000
1036
  expandable,
1001
1037
  resolvedExpandedKeys,
1002
1038
  rowHeight = 40,
1039
+ totalTableWidth,
1003
1040
  scrollAreaWidth,
1004
1041
  accentColor,
1005
1042
  isLoading = false,
1006
1043
  onExpandedRowResize,
1007
- maxExpandedRowHeight
1044
+ maxExpandedRowHeight,
1045
+ pinnedTopData = [],
1046
+ pinnedBottomData = [],
1047
+ gridTemplateColumns,
1048
+ headerHeight = 36
1008
1049
  }) => {
1009
1050
  const virtualItems = rowVirtualizer.getVirtualItems();
1010
1051
  const totalSize = rowVirtualizer.getTotalSize();
1011
1052
  const selectedKeySet = useMemo(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1053
+ const allDataForSelection = useMemo(() => {
1054
+ if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1055
+ return data;
1056
+ return [...pinnedTopData, ...data, ...pinnedBottomData];
1057
+ }, [pinnedTopData, data, pinnedBottomData]);
1058
+ const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1012
1059
  const columnStyles = useMemo(() => {
1013
1060
  return orderedColumns.map((col, colIndex) => {
1014
1061
  const stickyOffset = columnOffsets.get(col.key);
@@ -1065,6 +1112,8 @@ var TableBody = ({
1065
1112
  "div",
1066
1113
  {
1067
1114
  "data-row-key": rowKey,
1115
+ "data-column-key": col.key,
1116
+ "data-bt-cell": "",
1068
1117
  "data-selected": isSelected || void 0,
1069
1118
  style: {
1070
1119
  position: "absolute",
@@ -1094,7 +1143,7 @@ var TableBody = ({
1094
1143
  rowSelection,
1095
1144
  normalizedSelectedKeys,
1096
1145
  rowKey,
1097
- allData: data,
1146
+ allData: allDataForSelection,
1098
1147
  getRowKey,
1099
1148
  accentColor,
1100
1149
  isLoading: isRowShimmer,
@@ -1172,6 +1221,213 @@ var TableBody = ({
1172
1221
  );
1173
1222
  })
1174
1223
  }
1224
+ ),
1225
+ pinnedTopData.length > 0 && /* @__PURE__ */ jsx4(
1226
+ "div",
1227
+ {
1228
+ style: {
1229
+ gridColumn: "1 / -1",
1230
+ gridRow: 2,
1231
+ height: `${totalSize}px`,
1232
+ position: "relative",
1233
+ zIndex: 20,
1234
+ pointerEvents: "none"
1235
+ },
1236
+ children: /* @__PURE__ */ jsx4(
1237
+ "div",
1238
+ {
1239
+ style: {
1240
+ position: "sticky",
1241
+ top: headerHeight,
1242
+ pointerEvents: "auto",
1243
+ boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1244
+ },
1245
+ children: pinnedTopData.map((row, rowIdx) => {
1246
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1247
+ const isSelected = selectedKeySet.has(rk);
1248
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1249
+ return /* @__PURE__ */ jsx4(
1250
+ "div",
1251
+ {
1252
+ className: classNames?.pinnedRow ?? "",
1253
+ style: {
1254
+ display: "grid",
1255
+ gridTemplateColumns: gridTemplateColumns ?? "",
1256
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1257
+ ...styles?.pinnedRow
1258
+ },
1259
+ children: orderedColumns.map((col) => {
1260
+ const cellValue = row[col.dataIndex];
1261
+ const stickyOffset = columnOffsets.get(col.key);
1262
+ const isPinned = Boolean(col.pinned);
1263
+ let zIndex = 0;
1264
+ if (col.key === "__select__" || col.key === "__expand__")
1265
+ zIndex = 11;
1266
+ else if (isPinned) zIndex = 2;
1267
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1268
+ return /* @__PURE__ */ jsx4(
1269
+ "div",
1270
+ {
1271
+ "data-row-key": rk,
1272
+ "data-column-key": col.key,
1273
+ "data-bt-cell": "",
1274
+ "data-selected": isSelected || void 0,
1275
+ style: {
1276
+ position: isPinned ? "sticky" : "relative",
1277
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1278
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1279
+ zIndex,
1280
+ backgroundColor: pinnedRowBg,
1281
+ backdropFilter: "blur(12px)",
1282
+ WebkitBackdropFilter: "blur(12px)",
1283
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1284
+ },
1285
+ children: /* @__PURE__ */ jsx4(
1286
+ "div",
1287
+ {
1288
+ style: {
1289
+ height: `${rowHeight}px`,
1290
+ position: "relative"
1291
+ },
1292
+ children: /* @__PURE__ */ jsx4(
1293
+ Cell,
1294
+ {
1295
+ value: cellValue,
1296
+ record: row,
1297
+ column: col,
1298
+ rowIndex: rowIdx,
1299
+ classNames,
1300
+ styles,
1301
+ isSelected,
1302
+ isExpanded,
1303
+ rowSelection,
1304
+ normalizedSelectedKeys,
1305
+ rowKey: rk,
1306
+ allData: allDataForSelection,
1307
+ getRowKey,
1308
+ accentColor,
1309
+ isLoading: false,
1310
+ recordFingerprint
1311
+ }
1312
+ )
1313
+ }
1314
+ )
1315
+ },
1316
+ col.key
1317
+ );
1318
+ })
1319
+ },
1320
+ `pinned-top-${rk}`
1321
+ );
1322
+ })
1323
+ }
1324
+ )
1325
+ }
1326
+ ),
1327
+ pinnedBottomData.length > 0 && /* @__PURE__ */ jsx4(
1328
+ "div",
1329
+ {
1330
+ style: {
1331
+ gridColumn: "1 / -1",
1332
+ gridRow: 2,
1333
+ height: `${totalSize}px`,
1334
+ position: "relative",
1335
+ zIndex: 20,
1336
+ pointerEvents: "none",
1337
+ display: "flex",
1338
+ flexDirection: "column"
1339
+ },
1340
+ children: /* @__PURE__ */ jsx4(
1341
+ "div",
1342
+ {
1343
+ style: {
1344
+ marginTop: "auto",
1345
+ position: "sticky",
1346
+ bottom: 0,
1347
+ pointerEvents: "auto",
1348
+ boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1349
+ },
1350
+ children: pinnedBottomData.map((row, rowIdx) => {
1351
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1352
+ const isSelected = selectedKeySet.has(rk);
1353
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1354
+ return /* @__PURE__ */ jsx4(
1355
+ "div",
1356
+ {
1357
+ className: classNames?.pinnedRow ?? "",
1358
+ style: {
1359
+ display: "grid",
1360
+ gridTemplateColumns: gridTemplateColumns ?? "",
1361
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1362
+ ...styles?.pinnedRow
1363
+ },
1364
+ children: orderedColumns.map((col) => {
1365
+ const cellValue = row[col.dataIndex];
1366
+ const stickyOffset = columnOffsets.get(col.key);
1367
+ const isPinned = Boolean(col.pinned);
1368
+ let zIndex = 0;
1369
+ if (col.key === "__select__" || col.key === "__expand__")
1370
+ zIndex = 11;
1371
+ else if (isPinned) zIndex = 2;
1372
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1373
+ return /* @__PURE__ */ jsx4(
1374
+ "div",
1375
+ {
1376
+ "data-row-key": rk,
1377
+ "data-column-key": col.key,
1378
+ "data-bt-cell": "",
1379
+ "data-selected": isSelected || void 0,
1380
+ style: {
1381
+ position: isPinned ? "sticky" : "relative",
1382
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1383
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1384
+ zIndex,
1385
+ backgroundColor: pinnedRowBg,
1386
+ backdropFilter: "blur(12px)",
1387
+ WebkitBackdropFilter: "blur(12px)",
1388
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1389
+ },
1390
+ children: /* @__PURE__ */ jsx4(
1391
+ "div",
1392
+ {
1393
+ style: {
1394
+ height: `${rowHeight}px`,
1395
+ position: "relative"
1396
+ },
1397
+ children: /* @__PURE__ */ jsx4(
1398
+ Cell,
1399
+ {
1400
+ value: cellValue,
1401
+ record: row,
1402
+ column: col,
1403
+ rowIndex: rowIdx,
1404
+ classNames,
1405
+ styles,
1406
+ isSelected,
1407
+ isExpanded,
1408
+ rowSelection,
1409
+ normalizedSelectedKeys,
1410
+ rowKey: rk,
1411
+ allData: allDataForSelection,
1412
+ getRowKey,
1413
+ accentColor,
1414
+ isLoading: false,
1415
+ recordFingerprint
1416
+ }
1417
+ )
1418
+ }
1419
+ )
1420
+ },
1421
+ col.key
1422
+ );
1423
+ })
1424
+ },
1425
+ `pinned-bottom-${rk}`
1426
+ );
1427
+ })
1428
+ }
1429
+ )
1430
+ }
1175
1431
  )
1176
1432
  ] });
1177
1433
  };
@@ -1207,6 +1463,8 @@ function BoltTable({
1207
1463
  onColumnPin,
1208
1464
  onColumnHide,
1209
1465
  rowSelection,
1466
+ rowPinning,
1467
+ onRowPin,
1210
1468
  expandable,
1211
1469
  rowKey = "id",
1212
1470
  onEndReached,
@@ -1747,6 +2005,73 @@ function BoltTable({
1747
2005
  }
1748
2006
  return result;
1749
2007
  }, [data, sortState, columnFilters]);
2008
+ const { pinnedTopRows, pinnedBottomRows, unpinnedProcessedData } = useMemo2(() => {
2009
+ if (!rowPinning || !rowPinning.top?.length && !rowPinning.bottom?.length) {
2010
+ return {
2011
+ pinnedTopRows: [],
2012
+ pinnedBottomRows: [],
2013
+ unpinnedProcessedData: processedData
2014
+ };
2015
+ }
2016
+ const topKeySet = new Set((rowPinning.top ?? []).map(String));
2017
+ const bottomKeySet = new Set((rowPinning.bottom ?? []).map(String));
2018
+ const topMap = /* @__PURE__ */ new Map();
2019
+ const bottomMap = /* @__PURE__ */ new Map();
2020
+ const rest = [];
2021
+ processedData.forEach((row, idx) => {
2022
+ const key = getRowKey(row, idx);
2023
+ if (topKeySet.has(key)) topMap.set(key, row);
2024
+ else if (bottomKeySet.has(key)) bottomMap.set(key, row);
2025
+ else rest.push(row);
2026
+ });
2027
+ const orderedTop = (rowPinning.top ?? []).map((k) => topMap.get(String(k))).filter((r) => r !== void 0);
2028
+ const orderedBottom = (rowPinning.bottom ?? []).map((k) => bottomMap.get(String(k))).filter((r) => r !== void 0);
2029
+ return {
2030
+ pinnedTopRows: orderedTop,
2031
+ pinnedBottomRows: orderedBottom,
2032
+ unpinnedProcessedData: rest
2033
+ };
2034
+ }, [processedData, rowPinning, getRowKey]);
2035
+ const pinnedTopHeight = pinnedTopRows.length * rowHeight;
2036
+ const pinnedBottomHeight = pinnedBottomRows.length * rowHeight;
2037
+ const pinnedTopKeySet = useMemo2(
2038
+ () => new Set((rowPinning?.top ?? []).map(String)),
2039
+ [rowPinning?.top]
2040
+ );
2041
+ const pinnedBottomKeySet = useMemo2(
2042
+ () => new Set((rowPinning?.bottom ?? []).map(String)),
2043
+ [rowPinning?.bottom]
2044
+ );
2045
+ const [cellContextMenu, setCellContextMenu] = useState2(null);
2046
+ const cellMenuRef = useRef4(null);
2047
+ const cellLongPressTimer = useRef4(
2048
+ null
2049
+ );
2050
+ const cellTouchStart = useRef4(null);
2051
+ const cancelCellLongPress = useCallback(() => {
2052
+ if (cellLongPressTimer.current) {
2053
+ clearTimeout(cellLongPressTimer.current);
2054
+ cellLongPressTimer.current = null;
2055
+ }
2056
+ cellTouchStart.current = null;
2057
+ }, []);
2058
+ React4.useEffect(() => {
2059
+ if (!cellContextMenu) return;
2060
+ const close = (e) => {
2061
+ if (cellMenuRef.current && cellMenuRef.current.contains(e.target))
2062
+ return;
2063
+ setCellContextMenu(null);
2064
+ };
2065
+ const onKey = (e) => {
2066
+ if (e.key === "Escape") setCellContextMenu(null);
2067
+ };
2068
+ document.addEventListener("mousedown", close);
2069
+ document.addEventListener("keydown", onKey);
2070
+ return () => {
2071
+ document.removeEventListener("mousedown", close);
2072
+ document.removeEventListener("keydown", onKey);
2073
+ };
2074
+ }, [cellContextMenu]);
1750
2075
  const columnFiltersKey = Object.keys(columnFilters).sort().map((k) => `${k}:${columnFilters[k]}`).join("|");
1751
2076
  React4.useEffect(() => {
1752
2077
  tableAreaRef.current?.scrollTo({ top: 0 });
@@ -1754,12 +2079,12 @@ function BoltTable({
1754
2079
  const pgEnabled = pagination !== false && !!pagination;
1755
2080
  const pgSize = pgEnabled ? pagination.pageSize ?? 10 : 10;
1756
2081
  const pgCurrent = pgEnabled ? Number(pagination.current ?? 1) : 1;
1757
- const needsClientPagination = pgEnabled && processedData.length > pgSize;
2082
+ const needsClientPagination = pgEnabled && unpinnedProcessedData.length > pgSize;
1758
2083
  const paginatedData = useMemo2(() => {
1759
- if (!needsClientPagination) return processedData;
2084
+ if (!needsClientPagination) return unpinnedProcessedData;
1760
2085
  const start = (pgCurrent - 1) * pgSize;
1761
- return processedData.slice(start, start + pgSize);
1762
- }, [processedData, needsClientPagination, pgCurrent, pgSize]);
2086
+ return unpinnedProcessedData.slice(start, start + pgSize);
2087
+ }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
1763
2088
  const shimmerCount = pgEnabled ? pgSize : 15;
1764
2089
  const showShimmer = isLoading && processedData.length === 0;
1765
2090
  const shimmerData = useMemo2(() => {
@@ -1817,8 +2142,9 @@ function BoltTable({
1817
2142
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
1818
2143
  },
1819
2144
  overscan: 5,
1820
- // Render 5 extra rows above and below the visible window
1821
- getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index)
2145
+ getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index),
2146
+ paddingStart: pinnedTopHeight,
2147
+ paddingEnd: pinnedBottomHeight
1822
2148
  });
1823
2149
  const rowVirtualizerRef = useRef4(rowVirtualizer);
1824
2150
  rowVirtualizerRef.current = rowVirtualizer;
@@ -1858,7 +2184,7 @@ function BoltTable({
1858
2184
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
1859
2185
  const currentPage = pgCurrent;
1860
2186
  const pageSize = pgSize;
1861
- const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? processedData.length : data.length) : data.length;
2187
+ const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
1862
2188
  const lastKnownTotalRef = useRef4(0);
1863
2189
  if (!isLoading || rawTotal > 0) {
1864
2190
  lastKnownTotalRef.current = rawTotal;
@@ -2107,6 +2433,64 @@ function BoltTable({
2107
2433
  position: "relative",
2108
2434
  ...isEmpty ? { height: "100%" } : {}
2109
2435
  },
2436
+ onContextMenu: (e) => {
2437
+ const cell = e.target.closest("[data-bt-cell]");
2438
+ if (!cell) return;
2439
+ const rk = cell.dataset.rowKey;
2440
+ const ck = cell.dataset.columnKey;
2441
+ if (!rk || !ck) return;
2442
+ const col = freshOrderedColumns.find(
2443
+ (c) => c.key === ck
2444
+ );
2445
+ const hasCopy = col?.copy;
2446
+ const hasRowPin = !!onRowPin;
2447
+ if (!hasCopy && !hasRowPin) return;
2448
+ e.preventDefault();
2449
+ setCellContextMenu({
2450
+ x: Math.min(e.clientX, window.innerWidth - 200),
2451
+ y: Math.min(e.clientY, window.innerHeight - 200),
2452
+ rowKey: rk,
2453
+ columnKey: ck
2454
+ });
2455
+ },
2456
+ onTouchStart: (e) => {
2457
+ cancelCellLongPress();
2458
+ const cell = e.target.closest("[data-bt-cell]");
2459
+ if (!cell) return;
2460
+ const touch = e.touches[0];
2461
+ cellTouchStart.current = {
2462
+ x: touch.clientX,
2463
+ y: touch.clientY
2464
+ };
2465
+ const rk = cell.dataset.rowKey;
2466
+ const ck = cell.dataset.columnKey;
2467
+ cellLongPressTimer.current = setTimeout(() => {
2468
+ cellLongPressTimer.current = null;
2469
+ if (!rk || !ck) return;
2470
+ const col = freshOrderedColumns.find(
2471
+ (c) => c.key === ck
2472
+ );
2473
+ const hasCopy = col?.copy;
2474
+ const hasRowPin = !!onRowPin;
2475
+ if (!hasCopy && !hasRowPin) return;
2476
+ setCellContextMenu({
2477
+ x: Math.min(touch.clientX, window.innerWidth - 200),
2478
+ y: Math.min(touch.clientY, window.innerHeight - 200),
2479
+ rowKey: rk,
2480
+ columnKey: ck
2481
+ });
2482
+ }, 500);
2483
+ },
2484
+ onTouchMove: (e) => {
2485
+ if (!cellTouchStart.current) return;
2486
+ const touch = e.touches[0];
2487
+ const dx = touch.clientX - cellTouchStart.current.x;
2488
+ const dy = touch.clientY - cellTouchStart.current.y;
2489
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10)
2490
+ cancelCellLongPress();
2491
+ },
2492
+ onTouchEnd: cancelCellLongPress,
2493
+ onTouchCancel: cancelCellLongPress,
2110
2494
  children: [
2111
2495
  orderedColumns.map((column, visualIndex) => {
2112
2496
  if (column.key === "__select__" && rowSelection) {
@@ -2295,7 +2679,11 @@ function BoltTable({
2295
2679
  scrollContainerRef: tableAreaRef,
2296
2680
  isLoading: showShimmer,
2297
2681
  onExpandedRowResize: handleExpandedRowResize,
2298
- maxExpandedRowHeight
2682
+ maxExpandedRowHeight,
2683
+ pinnedTopData: pinnedTopRows,
2684
+ pinnedBottomData: pinnedBottomRows,
2685
+ gridTemplateColumns,
2686
+ headerHeight: HEADER_HEIGHT
2299
2687
  }
2300
2688
  )
2301
2689
  )
@@ -2601,7 +2989,166 @@ function BoltTable({
2601
2989
  }
2602
2990
  ),
2603
2991
  document.body
2604
- )
2992
+ ),
2993
+ cellContextMenu && typeof document !== "undefined" && (() => {
2994
+ const menuCol = freshOrderedColumns.find(
2995
+ (c) => c.key === cellContextMenu.columnKey
2996
+ );
2997
+ const isPinnedTop = pinnedTopKeySet.has(cellContextMenu.rowKey);
2998
+ const isPinnedBottom = pinnedBottomKeySet.has(
2999
+ cellContextMenu.rowKey
3000
+ );
3001
+ const hasCopy = menuCol?.copy;
3002
+ const hasRowPin = !!onRowPin;
3003
+ let menuRecord;
3004
+ let menuRowIndex = 0;
3005
+ const allRows = [
3006
+ ...pinnedTopRows,
3007
+ ...displayData,
3008
+ ...pinnedBottomRows
3009
+ ];
3010
+ for (let i = 0; i < allRows.length; i++) {
3011
+ const rk = getRowKey(allRows[i], i);
3012
+ if (rk === cellContextMenu.rowKey) {
3013
+ menuRecord = allRows[i];
3014
+ menuRowIndex = i;
3015
+ break;
3016
+ }
3017
+ }
3018
+ const menuValue = menuRecord && menuCol ? menuRecord[menuCol.dataIndex] : void 0;
3019
+ const btnStyle = {
3020
+ display: "flex",
3021
+ width: "100%",
3022
+ alignItems: "center",
3023
+ gap: 8,
3024
+ background: "none",
3025
+ border: "none",
3026
+ padding: "6px 12px",
3027
+ fontSize: 12,
3028
+ cursor: "pointer",
3029
+ color: "inherit",
3030
+ whiteSpace: "nowrap"
3031
+ };
3032
+ return createPortal2(
3033
+ /* @__PURE__ */ jsxs5(
3034
+ "div",
3035
+ {
3036
+ ref: cellMenuRef,
3037
+ style: {
3038
+ position: "fixed",
3039
+ top: cellContextMenu.y,
3040
+ left: cellContextMenu.x,
3041
+ zIndex: 99999,
3042
+ minWidth: 170,
3043
+ borderRadius: 8,
3044
+ border: "1px solid rgba(128,128,128,0.2)",
3045
+ boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
3046
+ backdropFilter: "blur(16px)",
3047
+ WebkitBackdropFilter: "blur(16px)",
3048
+ backgroundColor: "rgba(128,128,128,0.08)",
3049
+ padding: "4px 0",
3050
+ fontSize: 10
3051
+ },
3052
+ children: [
3053
+ hasRowPin && /* @__PURE__ */ jsxs5(Fragment4, { children: [
3054
+ /* @__PURE__ */ jsxs5(
3055
+ "button",
3056
+ {
3057
+ "data-bt-ctx-item": true,
3058
+ style: btnStyle,
3059
+ onClick: () => {
3060
+ onRowPin(
3061
+ cellContextMenu.rowKey,
3062
+ isPinnedTop ? false : "top"
3063
+ );
3064
+ setCellContextMenu(null);
3065
+ },
3066
+ children: [
3067
+ isPinnedTop ? icons?.pinOff ?? /* @__PURE__ */ jsx5(
3068
+ PinOffIcon,
3069
+ {
3070
+ style: { width: 14, height: 14, flexShrink: 0 }
3071
+ }
3072
+ ) : icons?.pin ?? /* @__PURE__ */ jsx5(
3073
+ PinIcon,
3074
+ {
3075
+ style: { width: 14, height: 14, flexShrink: 0 }
3076
+ }
3077
+ ),
3078
+ isPinnedTop ? "Unpin Row from Top" : "Pin Row to Top"
3079
+ ]
3080
+ }
3081
+ ),
3082
+ /* @__PURE__ */ jsxs5(
3083
+ "button",
3084
+ {
3085
+ "data-bt-ctx-item": true,
3086
+ style: btnStyle,
3087
+ onClick: () => {
3088
+ onRowPin(
3089
+ cellContextMenu.rowKey,
3090
+ isPinnedBottom ? false : "bottom"
3091
+ );
3092
+ setCellContextMenu(null);
3093
+ },
3094
+ children: [
3095
+ isPinnedBottom ? icons?.pinOff ?? /* @__PURE__ */ jsx5(
3096
+ PinOffIcon,
3097
+ {
3098
+ style: { width: 14, height: 14, flexShrink: 0 }
3099
+ }
3100
+ ) : icons?.pin ?? /* @__PURE__ */ jsx5(
3101
+ PinIcon,
3102
+ {
3103
+ style: {
3104
+ width: 14,
3105
+ height: 14,
3106
+ flexShrink: 0,
3107
+ transform: "rotate(180deg)"
3108
+ }
3109
+ }
3110
+ ),
3111
+ isPinnedBottom ? "Unpin Row from Bottom" : "Pin Row to Bottom"
3112
+ ]
3113
+ }
3114
+ )
3115
+ ] }),
3116
+ hasRowPin && hasCopy && /* @__PURE__ */ jsx5(
3117
+ "div",
3118
+ {
3119
+ style: {
3120
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3121
+ margin: "4px 0"
3122
+ }
3123
+ }
3124
+ ),
3125
+ hasCopy && menuRecord && menuCol && /* @__PURE__ */ jsxs5(
3126
+ "button",
3127
+ {
3128
+ "data-bt-ctx-item": true,
3129
+ style: btnStyle,
3130
+ onClick: () => {
3131
+ const text = typeof menuCol.copy === "function" ? menuCol.copy(menuValue, menuRecord, menuRowIndex) : String(menuValue ?? "");
3132
+ navigator.clipboard?.writeText(text);
3133
+ setCellContextMenu(null);
3134
+ },
3135
+ children: [
3136
+ icons?.copy ?? /* @__PURE__ */ jsx5(
3137
+ CopyIcon,
3138
+ {
3139
+ style: { width: 14, height: 14, flexShrink: 0 }
3140
+ }
3141
+ ),
3142
+ "Copy"
3143
+ ]
3144
+ }
3145
+ )
3146
+ ]
3147
+ }
3148
+ ),
3149
+ document.body
3150
+ );
3151
+ })()
2605
3152
  ] });
2606
3153
  }
2607
3154
  export {