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.js CHANGED
@@ -110,6 +110,10 @@ var PinOffIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtim
110
110
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "17", y2: "22" }),
111
111
  /* @__PURE__ */ (0, import_jsx_runtime.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" })
112
112
  ] });
113
+ var CopyIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
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
+ ] });
113
117
  var EyeOffIcon = ({ style, className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgBase, style, className, children: [
114
118
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }),
115
119
  /* @__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" }),
@@ -165,21 +169,49 @@ var DraggableHeader = import_react.default.memo(
165
169
  return () => document.removeEventListener("mousedown", handleClickOutside);
166
170
  }
167
171
  }, [contextMenu]);
168
- const handleContextMenu = (e) => {
169
- e.preventDefault();
170
- e.stopPropagation();
172
+ const showContextMenuAt = (clientX, clientY) => {
171
173
  const menuWidth = 160;
172
174
  const menuHeight = 180;
173
- let x = e.clientX;
174
- let y = e.clientY;
175
- if (x + menuWidth > window.innerWidth) {
175
+ let x = clientX;
176
+ let y = clientY;
177
+ if (x + menuWidth > window.innerWidth)
176
178
  x = window.innerWidth - menuWidth - 10;
177
- }
178
- if (y + menuHeight > window.innerHeight) {
179
+ if (y + menuHeight > window.innerHeight)
179
180
  y = window.innerHeight - menuHeight - 10;
180
- }
181
181
  setContextMenu({ x, y });
182
182
  };
183
+ const handleContextMenu = (e) => {
184
+ e.preventDefault();
185
+ e.stopPropagation();
186
+ showContextMenuAt(e.clientX, e.clientY);
187
+ };
188
+ const longPressTimerRef = (0, import_react.useRef)(
189
+ null
190
+ );
191
+ const touchStartRef = (0, import_react.useRef)(null);
192
+ const cancelLongPress = () => {
193
+ if (longPressTimerRef.current) {
194
+ clearTimeout(longPressTimerRef.current);
195
+ longPressTimerRef.current = null;
196
+ }
197
+ touchStartRef.current = null;
198
+ };
199
+ const handleTouchStart = (e) => {
200
+ cancelLongPress();
201
+ const touch = e.touches[0];
202
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY };
203
+ longPressTimerRef.current = setTimeout(() => {
204
+ longPressTimerRef.current = null;
205
+ showContextMenuAt(touch.clientX, touch.clientY);
206
+ }, 500);
207
+ };
208
+ const handleTouchMove = (e) => {
209
+ if (!touchStartRef.current) return;
210
+ const touch = e.touches[0];
211
+ const dx = touch.clientX - touchStartRef.current.x;
212
+ const dy = touch.clientY - touchStartRef.current.y;
213
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10) cancelLongPress();
214
+ };
183
215
  const handleResizeStart = (e) => {
184
216
  e.preventDefault();
185
217
  e.stopPropagation();
@@ -229,6 +261,10 @@ var DraggableHeader = import_react.default.memo(
229
261
  style: headerStyle,
230
262
  className: `${column.className ?? ""} ${classNames?.header ?? ""} ${isPinned ? classNames?.pinnedHeader ?? "" : ""}`,
231
263
  onContextMenu: handleContextMenu,
264
+ onTouchStart: handleTouchStart,
265
+ onTouchMove: handleTouchMove,
266
+ onTouchEnd: cancelLongPress,
267
+ onTouchCancel: cancelLongPress,
232
268
  children: [
233
269
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
234
270
  "div",
@@ -1034,15 +1070,26 @@ var TableBody = ({
1034
1070
  expandable,
1035
1071
  resolvedExpandedKeys,
1036
1072
  rowHeight = 40,
1073
+ totalTableWidth,
1037
1074
  scrollAreaWidth,
1038
1075
  accentColor,
1039
1076
  isLoading = false,
1040
1077
  onExpandedRowResize,
1041
- maxExpandedRowHeight
1078
+ maxExpandedRowHeight,
1079
+ pinnedTopData = [],
1080
+ pinnedBottomData = [],
1081
+ gridTemplateColumns,
1082
+ headerHeight = 36
1042
1083
  }) => {
1043
1084
  const virtualItems = rowVirtualizer.getVirtualItems();
1044
1085
  const totalSize = rowVirtualizer.getTotalSize();
1045
1086
  const selectedKeySet = (0, import_react3.useMemo)(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1087
+ const allDataForSelection = (0, import_react3.useMemo)(() => {
1088
+ if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1089
+ return data;
1090
+ return [...pinnedTopData, ...data, ...pinnedBottomData];
1091
+ }, [pinnedTopData, data, pinnedBottomData]);
1092
+ const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1046
1093
  const columnStyles = (0, import_react3.useMemo)(() => {
1047
1094
  return orderedColumns.map((col, colIndex) => {
1048
1095
  const stickyOffset = columnOffsets.get(col.key);
@@ -1099,6 +1146,8 @@ var TableBody = ({
1099
1146
  "div",
1100
1147
  {
1101
1148
  "data-row-key": rowKey,
1149
+ "data-column-key": col.key,
1150
+ "data-bt-cell": "",
1102
1151
  "data-selected": isSelected || void 0,
1103
1152
  style: {
1104
1153
  position: "absolute",
@@ -1128,7 +1177,7 @@ var TableBody = ({
1128
1177
  rowSelection,
1129
1178
  normalizedSelectedKeys,
1130
1179
  rowKey,
1131
- allData: data,
1180
+ allData: allDataForSelection,
1132
1181
  getRowKey,
1133
1182
  accentColor,
1134
1183
  isLoading: isRowShimmer,
@@ -1206,6 +1255,213 @@ var TableBody = ({
1206
1255
  );
1207
1256
  })
1208
1257
  }
1258
+ ),
1259
+ pinnedTopData.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1260
+ "div",
1261
+ {
1262
+ style: {
1263
+ gridColumn: "1 / -1",
1264
+ gridRow: 2,
1265
+ height: `${totalSize}px`,
1266
+ position: "relative",
1267
+ zIndex: 20,
1268
+ pointerEvents: "none"
1269
+ },
1270
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1271
+ "div",
1272
+ {
1273
+ style: {
1274
+ position: "sticky",
1275
+ top: headerHeight,
1276
+ pointerEvents: "auto",
1277
+ boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1278
+ },
1279
+ children: pinnedTopData.map((row, rowIdx) => {
1280
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1281
+ const isSelected = selectedKeySet.has(rk);
1282
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1283
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1284
+ "div",
1285
+ {
1286
+ className: classNames?.pinnedRow ?? "",
1287
+ style: {
1288
+ display: "grid",
1289
+ gridTemplateColumns: gridTemplateColumns ?? "",
1290
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1291
+ ...styles?.pinnedRow
1292
+ },
1293
+ children: orderedColumns.map((col) => {
1294
+ const cellValue = row[col.dataIndex];
1295
+ const stickyOffset = columnOffsets.get(col.key);
1296
+ const isPinned = Boolean(col.pinned);
1297
+ let zIndex = 0;
1298
+ if (col.key === "__select__" || col.key === "__expand__")
1299
+ zIndex = 11;
1300
+ else if (isPinned) zIndex = 2;
1301
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1302
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1303
+ "div",
1304
+ {
1305
+ "data-row-key": rk,
1306
+ "data-column-key": col.key,
1307
+ "data-bt-cell": "",
1308
+ "data-selected": isSelected || void 0,
1309
+ style: {
1310
+ position: isPinned ? "sticky" : "relative",
1311
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1312
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1313
+ zIndex,
1314
+ backgroundColor: pinnedRowBg,
1315
+ backdropFilter: "blur(12px)",
1316
+ WebkitBackdropFilter: "blur(12px)",
1317
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1318
+ },
1319
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1320
+ "div",
1321
+ {
1322
+ style: {
1323
+ height: `${rowHeight}px`,
1324
+ position: "relative"
1325
+ },
1326
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1327
+ Cell,
1328
+ {
1329
+ value: cellValue,
1330
+ record: row,
1331
+ column: col,
1332
+ rowIndex: rowIdx,
1333
+ classNames,
1334
+ styles,
1335
+ isSelected,
1336
+ isExpanded,
1337
+ rowSelection,
1338
+ normalizedSelectedKeys,
1339
+ rowKey: rk,
1340
+ allData: allDataForSelection,
1341
+ getRowKey,
1342
+ accentColor,
1343
+ isLoading: false,
1344
+ recordFingerprint
1345
+ }
1346
+ )
1347
+ }
1348
+ )
1349
+ },
1350
+ col.key
1351
+ );
1352
+ })
1353
+ },
1354
+ `pinned-top-${rk}`
1355
+ );
1356
+ })
1357
+ }
1358
+ )
1359
+ }
1360
+ ),
1361
+ pinnedBottomData.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1362
+ "div",
1363
+ {
1364
+ style: {
1365
+ gridColumn: "1 / -1",
1366
+ gridRow: 2,
1367
+ height: `${totalSize}px`,
1368
+ position: "relative",
1369
+ zIndex: 20,
1370
+ pointerEvents: "none",
1371
+ display: "flex",
1372
+ flexDirection: "column"
1373
+ },
1374
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1375
+ "div",
1376
+ {
1377
+ style: {
1378
+ marginTop: "auto",
1379
+ position: "sticky",
1380
+ bottom: 0,
1381
+ pointerEvents: "auto",
1382
+ boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1383
+ },
1384
+ children: pinnedBottomData.map((row, rowIdx) => {
1385
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1386
+ const isSelected = selectedKeySet.has(rk);
1387
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1389
+ "div",
1390
+ {
1391
+ className: classNames?.pinnedRow ?? "",
1392
+ style: {
1393
+ display: "grid",
1394
+ gridTemplateColumns: gridTemplateColumns ?? "",
1395
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1396
+ ...styles?.pinnedRow
1397
+ },
1398
+ children: orderedColumns.map((col) => {
1399
+ const cellValue = row[col.dataIndex];
1400
+ const stickyOffset = columnOffsets.get(col.key);
1401
+ const isPinned = Boolean(col.pinned);
1402
+ let zIndex = 0;
1403
+ if (col.key === "__select__" || col.key === "__expand__")
1404
+ zIndex = 11;
1405
+ else if (isPinned) zIndex = 2;
1406
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1407
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1408
+ "div",
1409
+ {
1410
+ "data-row-key": rk,
1411
+ "data-column-key": col.key,
1412
+ "data-bt-cell": "",
1413
+ "data-selected": isSelected || void 0,
1414
+ style: {
1415
+ position: isPinned ? "sticky" : "relative",
1416
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1417
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1418
+ zIndex,
1419
+ backgroundColor: pinnedRowBg,
1420
+ backdropFilter: "blur(12px)",
1421
+ WebkitBackdropFilter: "blur(12px)",
1422
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1423
+ },
1424
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1425
+ "div",
1426
+ {
1427
+ style: {
1428
+ height: `${rowHeight}px`,
1429
+ position: "relative"
1430
+ },
1431
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1432
+ Cell,
1433
+ {
1434
+ value: cellValue,
1435
+ record: row,
1436
+ column: col,
1437
+ rowIndex: rowIdx,
1438
+ classNames,
1439
+ styles,
1440
+ isSelected,
1441
+ isExpanded,
1442
+ rowSelection,
1443
+ normalizedSelectedKeys,
1444
+ rowKey: rk,
1445
+ allData: allDataForSelection,
1446
+ getRowKey,
1447
+ accentColor,
1448
+ isLoading: false,
1449
+ recordFingerprint
1450
+ }
1451
+ )
1452
+ }
1453
+ )
1454
+ },
1455
+ col.key
1456
+ );
1457
+ })
1458
+ },
1459
+ `pinned-bottom-${rk}`
1460
+ );
1461
+ })
1462
+ }
1463
+ )
1464
+ }
1209
1465
  )
1210
1466
  ] });
1211
1467
  };
@@ -1241,6 +1497,8 @@ function BoltTable({
1241
1497
  onColumnPin,
1242
1498
  onColumnHide,
1243
1499
  rowSelection,
1500
+ rowPinning,
1501
+ onRowPin,
1244
1502
  expandable,
1245
1503
  rowKey = "id",
1246
1504
  onEndReached,
@@ -1781,6 +2039,73 @@ function BoltTable({
1781
2039
  }
1782
2040
  return result;
1783
2041
  }, [data, sortState, columnFilters]);
2042
+ const { pinnedTopRows, pinnedBottomRows, unpinnedProcessedData } = (0, import_react4.useMemo)(() => {
2043
+ if (!rowPinning || !rowPinning.top?.length && !rowPinning.bottom?.length) {
2044
+ return {
2045
+ pinnedTopRows: [],
2046
+ pinnedBottomRows: [],
2047
+ unpinnedProcessedData: processedData
2048
+ };
2049
+ }
2050
+ const topKeySet = new Set((rowPinning.top ?? []).map(String));
2051
+ const bottomKeySet = new Set((rowPinning.bottom ?? []).map(String));
2052
+ const topMap = /* @__PURE__ */ new Map();
2053
+ const bottomMap = /* @__PURE__ */ new Map();
2054
+ const rest = [];
2055
+ processedData.forEach((row, idx) => {
2056
+ const key = getRowKey(row, idx);
2057
+ if (topKeySet.has(key)) topMap.set(key, row);
2058
+ else if (bottomKeySet.has(key)) bottomMap.set(key, row);
2059
+ else rest.push(row);
2060
+ });
2061
+ const orderedTop = (rowPinning.top ?? []).map((k) => topMap.get(String(k))).filter((r) => r !== void 0);
2062
+ const orderedBottom = (rowPinning.bottom ?? []).map((k) => bottomMap.get(String(k))).filter((r) => r !== void 0);
2063
+ return {
2064
+ pinnedTopRows: orderedTop,
2065
+ pinnedBottomRows: orderedBottom,
2066
+ unpinnedProcessedData: rest
2067
+ };
2068
+ }, [processedData, rowPinning, getRowKey]);
2069
+ const pinnedTopHeight = pinnedTopRows.length * rowHeight;
2070
+ const pinnedBottomHeight = pinnedBottomRows.length * rowHeight;
2071
+ const pinnedTopKeySet = (0, import_react4.useMemo)(
2072
+ () => new Set((rowPinning?.top ?? []).map(String)),
2073
+ [rowPinning?.top]
2074
+ );
2075
+ const pinnedBottomKeySet = (0, import_react4.useMemo)(
2076
+ () => new Set((rowPinning?.bottom ?? []).map(String)),
2077
+ [rowPinning?.bottom]
2078
+ );
2079
+ const [cellContextMenu, setCellContextMenu] = (0, import_react4.useState)(null);
2080
+ const cellMenuRef = (0, import_react4.useRef)(null);
2081
+ const cellLongPressTimer = (0, import_react4.useRef)(
2082
+ null
2083
+ );
2084
+ const cellTouchStart = (0, import_react4.useRef)(null);
2085
+ const cancelCellLongPress = (0, import_react4.useCallback)(() => {
2086
+ if (cellLongPressTimer.current) {
2087
+ clearTimeout(cellLongPressTimer.current);
2088
+ cellLongPressTimer.current = null;
2089
+ }
2090
+ cellTouchStart.current = null;
2091
+ }, []);
2092
+ import_react4.default.useEffect(() => {
2093
+ if (!cellContextMenu) return;
2094
+ const close = (e) => {
2095
+ if (cellMenuRef.current && cellMenuRef.current.contains(e.target))
2096
+ return;
2097
+ setCellContextMenu(null);
2098
+ };
2099
+ const onKey = (e) => {
2100
+ if (e.key === "Escape") setCellContextMenu(null);
2101
+ };
2102
+ document.addEventListener("mousedown", close);
2103
+ document.addEventListener("keydown", onKey);
2104
+ return () => {
2105
+ document.removeEventListener("mousedown", close);
2106
+ document.removeEventListener("keydown", onKey);
2107
+ };
2108
+ }, [cellContextMenu]);
1784
2109
  const columnFiltersKey = Object.keys(columnFilters).sort().map((k) => `${k}:${columnFilters[k]}`).join("|");
1785
2110
  import_react4.default.useEffect(() => {
1786
2111
  tableAreaRef.current?.scrollTo({ top: 0 });
@@ -1788,12 +2113,12 @@ function BoltTable({
1788
2113
  const pgEnabled = pagination !== false && !!pagination;
1789
2114
  const pgSize = pgEnabled ? pagination.pageSize ?? 10 : 10;
1790
2115
  const pgCurrent = pgEnabled ? Number(pagination.current ?? 1) : 1;
1791
- const needsClientPagination = pgEnabled && processedData.length > pgSize;
2116
+ const needsClientPagination = pgEnabled && unpinnedProcessedData.length > pgSize;
1792
2117
  const paginatedData = (0, import_react4.useMemo)(() => {
1793
- if (!needsClientPagination) return processedData;
2118
+ if (!needsClientPagination) return unpinnedProcessedData;
1794
2119
  const start = (pgCurrent - 1) * pgSize;
1795
- return processedData.slice(start, start + pgSize);
1796
- }, [processedData, needsClientPagination, pgCurrent, pgSize]);
2120
+ return unpinnedProcessedData.slice(start, start + pgSize);
2121
+ }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
1797
2122
  const shimmerCount = pgEnabled ? pgSize : 15;
1798
2123
  const showShimmer = isLoading && processedData.length === 0;
1799
2124
  const shimmerData = (0, import_react4.useMemo)(() => {
@@ -1851,8 +2176,9 @@ function BoltTable({
1851
2176
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
1852
2177
  },
1853
2178
  overscan: 5,
1854
- // Render 5 extra rows above and below the visible window
1855
- getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index)
2179
+ getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index),
2180
+ paddingStart: pinnedTopHeight,
2181
+ paddingEnd: pinnedBottomHeight
1856
2182
  });
1857
2183
  const rowVirtualizerRef = (0, import_react4.useRef)(rowVirtualizer);
1858
2184
  rowVirtualizerRef.current = rowVirtualizer;
@@ -1892,7 +2218,7 @@ function BoltTable({
1892
2218
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
1893
2219
  const currentPage = pgCurrent;
1894
2220
  const pageSize = pgSize;
1895
- const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? processedData.length : data.length) : data.length;
2221
+ const rawTotal = pgEnabled ? pagination.total ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
1896
2222
  const lastKnownTotalRef = (0, import_react4.useRef)(0);
1897
2223
  if (!isLoading || rawTotal > 0) {
1898
2224
  lastKnownTotalRef.current = rawTotal;
@@ -2141,6 +2467,64 @@ function BoltTable({
2141
2467
  position: "relative",
2142
2468
  ...isEmpty ? { height: "100%" } : {}
2143
2469
  },
2470
+ onContextMenu: (e) => {
2471
+ const cell = e.target.closest("[data-bt-cell]");
2472
+ if (!cell) return;
2473
+ const rk = cell.dataset.rowKey;
2474
+ const ck = cell.dataset.columnKey;
2475
+ if (!rk || !ck) return;
2476
+ const col = freshOrderedColumns.find(
2477
+ (c) => c.key === ck
2478
+ );
2479
+ const hasCopy = col?.copy;
2480
+ const hasRowPin = !!onRowPin;
2481
+ if (!hasCopy && !hasRowPin) return;
2482
+ e.preventDefault();
2483
+ setCellContextMenu({
2484
+ x: Math.min(e.clientX, window.innerWidth - 200),
2485
+ y: Math.min(e.clientY, window.innerHeight - 200),
2486
+ rowKey: rk,
2487
+ columnKey: ck
2488
+ });
2489
+ },
2490
+ onTouchStart: (e) => {
2491
+ cancelCellLongPress();
2492
+ const cell = e.target.closest("[data-bt-cell]");
2493
+ if (!cell) return;
2494
+ const touch = e.touches[0];
2495
+ cellTouchStart.current = {
2496
+ x: touch.clientX,
2497
+ y: touch.clientY
2498
+ };
2499
+ const rk = cell.dataset.rowKey;
2500
+ const ck = cell.dataset.columnKey;
2501
+ cellLongPressTimer.current = setTimeout(() => {
2502
+ cellLongPressTimer.current = null;
2503
+ if (!rk || !ck) return;
2504
+ const col = freshOrderedColumns.find(
2505
+ (c) => c.key === ck
2506
+ );
2507
+ const hasCopy = col?.copy;
2508
+ const hasRowPin = !!onRowPin;
2509
+ if (!hasCopy && !hasRowPin) return;
2510
+ setCellContextMenu({
2511
+ x: Math.min(touch.clientX, window.innerWidth - 200),
2512
+ y: Math.min(touch.clientY, window.innerHeight - 200),
2513
+ rowKey: rk,
2514
+ columnKey: ck
2515
+ });
2516
+ }, 500);
2517
+ },
2518
+ onTouchMove: (e) => {
2519
+ if (!cellTouchStart.current) return;
2520
+ const touch = e.touches[0];
2521
+ const dx = touch.clientX - cellTouchStart.current.x;
2522
+ const dy = touch.clientY - cellTouchStart.current.y;
2523
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10)
2524
+ cancelCellLongPress();
2525
+ },
2526
+ onTouchEnd: cancelCellLongPress,
2527
+ onTouchCancel: cancelCellLongPress,
2144
2528
  children: [
2145
2529
  orderedColumns.map((column, visualIndex) => {
2146
2530
  if (column.key === "__select__" && rowSelection) {
@@ -2329,7 +2713,11 @@ function BoltTable({
2329
2713
  scrollContainerRef: tableAreaRef,
2330
2714
  isLoading: showShimmer,
2331
2715
  onExpandedRowResize: handleExpandedRowResize,
2332
- maxExpandedRowHeight
2716
+ maxExpandedRowHeight,
2717
+ pinnedTopData: pinnedTopRows,
2718
+ pinnedBottomData: pinnedBottomRows,
2719
+ gridTemplateColumns,
2720
+ headerHeight: HEADER_HEIGHT
2333
2721
  }
2334
2722
  )
2335
2723
  )
@@ -2635,7 +3023,166 @@ function BoltTable({
2635
3023
  }
2636
3024
  ),
2637
3025
  document.body
2638
- )
3026
+ ),
3027
+ cellContextMenu && typeof document !== "undefined" && (() => {
3028
+ const menuCol = freshOrderedColumns.find(
3029
+ (c) => c.key === cellContextMenu.columnKey
3030
+ );
3031
+ const isPinnedTop = pinnedTopKeySet.has(cellContextMenu.rowKey);
3032
+ const isPinnedBottom = pinnedBottomKeySet.has(
3033
+ cellContextMenu.rowKey
3034
+ );
3035
+ const hasCopy = menuCol?.copy;
3036
+ const hasRowPin = !!onRowPin;
3037
+ let menuRecord;
3038
+ let menuRowIndex = 0;
3039
+ const allRows = [
3040
+ ...pinnedTopRows,
3041
+ ...displayData,
3042
+ ...pinnedBottomRows
3043
+ ];
3044
+ for (let i = 0; i < allRows.length; i++) {
3045
+ const rk = getRowKey(allRows[i], i);
3046
+ if (rk === cellContextMenu.rowKey) {
3047
+ menuRecord = allRows[i];
3048
+ menuRowIndex = i;
3049
+ break;
3050
+ }
3051
+ }
3052
+ const menuValue = menuRecord && menuCol ? menuRecord[menuCol.dataIndex] : void 0;
3053
+ const btnStyle = {
3054
+ display: "flex",
3055
+ width: "100%",
3056
+ alignItems: "center",
3057
+ gap: 8,
3058
+ background: "none",
3059
+ border: "none",
3060
+ padding: "6px 12px",
3061
+ fontSize: 12,
3062
+ cursor: "pointer",
3063
+ color: "inherit",
3064
+ whiteSpace: "nowrap"
3065
+ };
3066
+ return (0, import_react_dom2.createPortal)(
3067
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3068
+ "div",
3069
+ {
3070
+ ref: cellMenuRef,
3071
+ style: {
3072
+ position: "fixed",
3073
+ top: cellContextMenu.y,
3074
+ left: cellContextMenu.x,
3075
+ zIndex: 99999,
3076
+ minWidth: 170,
3077
+ borderRadius: 8,
3078
+ border: "1px solid rgba(128,128,128,0.2)",
3079
+ boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
3080
+ backdropFilter: "blur(16px)",
3081
+ WebkitBackdropFilter: "blur(16px)",
3082
+ backgroundColor: "rgba(128,128,128,0.08)",
3083
+ padding: "4px 0",
3084
+ fontSize: 10
3085
+ },
3086
+ children: [
3087
+ hasRowPin && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3088
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3089
+ "button",
3090
+ {
3091
+ "data-bt-ctx-item": true,
3092
+ style: btnStyle,
3093
+ onClick: () => {
3094
+ onRowPin(
3095
+ cellContextMenu.rowKey,
3096
+ isPinnedTop ? false : "top"
3097
+ );
3098
+ setCellContextMenu(null);
3099
+ },
3100
+ children: [
3101
+ isPinnedTop ? icons?.pinOff ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3102
+ PinOffIcon,
3103
+ {
3104
+ style: { width: 14, height: 14, flexShrink: 0 }
3105
+ }
3106
+ ) : icons?.pin ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3107
+ PinIcon,
3108
+ {
3109
+ style: { width: 14, height: 14, flexShrink: 0 }
3110
+ }
3111
+ ),
3112
+ isPinnedTop ? "Unpin Row from Top" : "Pin Row to Top"
3113
+ ]
3114
+ }
3115
+ ),
3116
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3117
+ "button",
3118
+ {
3119
+ "data-bt-ctx-item": true,
3120
+ style: btnStyle,
3121
+ onClick: () => {
3122
+ onRowPin(
3123
+ cellContextMenu.rowKey,
3124
+ isPinnedBottom ? false : "bottom"
3125
+ );
3126
+ setCellContextMenu(null);
3127
+ },
3128
+ children: [
3129
+ isPinnedBottom ? icons?.pinOff ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3130
+ PinOffIcon,
3131
+ {
3132
+ style: { width: 14, height: 14, flexShrink: 0 }
3133
+ }
3134
+ ) : icons?.pin ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3135
+ PinIcon,
3136
+ {
3137
+ style: {
3138
+ width: 14,
3139
+ height: 14,
3140
+ flexShrink: 0,
3141
+ transform: "rotate(180deg)"
3142
+ }
3143
+ }
3144
+ ),
3145
+ isPinnedBottom ? "Unpin Row from Bottom" : "Pin Row to Bottom"
3146
+ ]
3147
+ }
3148
+ )
3149
+ ] }),
3150
+ hasRowPin && hasCopy && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3151
+ "div",
3152
+ {
3153
+ style: {
3154
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3155
+ margin: "4px 0"
3156
+ }
3157
+ }
3158
+ ),
3159
+ hasCopy && menuRecord && menuCol && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3160
+ "button",
3161
+ {
3162
+ "data-bt-ctx-item": true,
3163
+ style: btnStyle,
3164
+ onClick: () => {
3165
+ const text = typeof menuCol.copy === "function" ? menuCol.copy(menuValue, menuRecord, menuRowIndex) : String(menuValue ?? "");
3166
+ navigator.clipboard?.writeText(text);
3167
+ setCellContextMenu(null);
3168
+ },
3169
+ children: [
3170
+ icons?.copy ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3171
+ CopyIcon,
3172
+ {
3173
+ style: { width: 14, height: 14, flexShrink: 0 }
3174
+ }
3175
+ ),
3176
+ "Copy"
3177
+ ]
3178
+ }
3179
+ )
3180
+ ]
3181
+ }
3182
+ ),
3183
+ document.body
3184
+ );
3185
+ })()
2639
3186
  ] });
2640
3187
  }
2641
3188
  // Annotate the CommonJS export names for ESM import in node: