bolt-table 0.1.13 → 0.1.15

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",
@@ -646,9 +682,6 @@ var DraggableHeader = React.memo(
646
682
  )
647
683
  ] });
648
684
  },
649
- // ── Custom memo comparator ─────────────────────────────────────────────────
650
- // Only re-render when props that actually affect this header's output change.
651
- // This prevents a sort/filter change on one column from re-rendering all others.
652
685
  (prevProps, nextProps) => {
653
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;
654
687
  }
@@ -938,11 +971,6 @@ var Cell = React3.memo(
938
971
  }
939
972
  );
940
973
  },
941
- // ── Custom memo comparator ─────────────────────────────────────────────────
942
- // Minimizes re-renders:
943
- // - __select__ cells: re-render only when selection changes
944
- // - __expand__ cells: re-render only when expand state changes
945
- // - Normal cells: re-render only when value or rowIndex changes
946
974
  (prev, next) => {
947
975
  if (prev.isLoading !== next.isLoading) return false;
948
976
  if (prev.column.key === "__select__") {
@@ -1061,62 +1089,52 @@ var TableBody = ({
1061
1089
  const cellValue = row[col.dataIndex];
1062
1090
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1063
1091
  const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1064
- return (
1065
- /*
1066
- * Row wrapper div:
1067
- * - data-row-key: used by BoltTable's DOM-based hover system
1068
- * (mouseover reads this attribute to apply hover styles
1069
- * across all column divs for the same row simultaneously)
1070
- * - data-selected: presence/absence attribute consumed by the
1071
- * CSS injected by BoltTable for selected row background
1072
- * - Absolute positioned at virtualRow.start for virtualization
1073
- * - Height = virtualRow.size (includes expanded row height)
1074
- */
1075
- /* @__PURE__ */ jsx4(
1076
- "div",
1077
- {
1078
- "data-row-key": rowKey,
1079
- "data-selected": isSelected || void 0,
1080
- style: {
1081
- position: "absolute",
1082
- top: `${virtualRow.start}px`,
1083
- left: 0,
1084
- right: 0,
1085
- height: `${virtualRow.size}px`
1086
- },
1087
- children: /* @__PURE__ */ jsx4(
1088
- "div",
1089
- {
1090
- style: {
1091
- height: `${rowHeight}px`,
1092
- position: "relative"
1093
- },
1094
- children: /* @__PURE__ */ jsx4(
1095
- Cell,
1096
- {
1097
- value: cellValue,
1098
- record: row,
1099
- column: col,
1100
- rowIndex: virtualRow.index,
1101
- classNames,
1102
- styles,
1103
- isSelected,
1104
- isExpanded,
1105
- rowSelection,
1106
- normalizedSelectedKeys,
1107
- rowKey,
1108
- allData: allDataForSelection,
1109
- getRowKey,
1110
- accentColor,
1111
- isLoading: isRowShimmer,
1112
- recordFingerprint
1113
- }
1114
- )
1115
- }
1116
- )
1092
+ return /* @__PURE__ */ jsx4(
1093
+ "div",
1094
+ {
1095
+ "data-row-key": rowKey,
1096
+ "data-column-key": col.key,
1097
+ "data-bt-cell": "",
1098
+ "data-selected": isSelected || void 0,
1099
+ style: {
1100
+ position: "absolute",
1101
+ top: `${virtualRow.start}px`,
1102
+ left: 0,
1103
+ right: 0,
1104
+ height: `${virtualRow.size}px`
1117
1105
  },
1118
- `${rowKey}-${col.key}`
1119
- )
1106
+ children: /* @__PURE__ */ jsx4(
1107
+ "div",
1108
+ {
1109
+ style: {
1110
+ height: `${rowHeight}px`,
1111
+ position: "relative"
1112
+ },
1113
+ children: /* @__PURE__ */ jsx4(
1114
+ Cell,
1115
+ {
1116
+ value: cellValue,
1117
+ record: row,
1118
+ column: col,
1119
+ rowIndex: virtualRow.index,
1120
+ classNames,
1121
+ styles,
1122
+ isSelected,
1123
+ isExpanded,
1124
+ rowSelection,
1125
+ normalizedSelectedKeys,
1126
+ rowKey,
1127
+ allData: allDataForSelection,
1128
+ getRowKey,
1129
+ accentColor,
1130
+ isLoading: isRowShimmer,
1131
+ recordFingerprint
1132
+ }
1133
+ )
1134
+ }
1135
+ )
1136
+ },
1137
+ `${rowKey}-${col.key}`
1120
1138
  );
1121
1139
  })
1122
1140
  },
@@ -1132,8 +1150,6 @@ var TableBody = ({
1132
1150
  height: `${totalSize}px`,
1133
1151
  position: "relative",
1134
1152
  zIndex: 15,
1135
- // pointerEvents: none on the overlay so hover/click pass through
1136
- // to the cells below for rows that are NOT expanded
1137
1153
  pointerEvents: "none"
1138
1154
  },
1139
1155
  children: virtualItems.map((virtualRow) => {
@@ -1165,7 +1181,6 @@ var TableBody = ({
1165
1181
  {
1166
1182
  style: {
1167
1183
  position: "absolute",
1168
- // Position immediately below the row's base height
1169
1184
  top: virtualRow.start + rowHeight,
1170
1185
  left: 0,
1171
1186
  right: 0
@@ -1190,85 +1205,100 @@ var TableBody = ({
1190
1205
  style: {
1191
1206
  gridColumn: "1 / -1",
1192
1207
  gridRow: 2,
1193
- position: "sticky",
1194
- top: headerHeight,
1208
+ height: `${totalSize}px`,
1209
+ position: "relative",
1195
1210
  zIndex: 20,
1196
- boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1211
+ pointerEvents: "none"
1197
1212
  },
1198
- children: pinnedTopData.map((row, rowIdx) => {
1199
- const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1200
- const isSelected = selectedKeySet.has(rk);
1201
- const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1202
- return /* @__PURE__ */ jsx4(
1203
- "div",
1204
- {
1205
- className: classNames?.pinnedRow ?? "",
1206
- style: {
1207
- display: "grid",
1208
- gridTemplateColumns: gridTemplateColumns ?? "",
1209
- minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1210
- ...styles?.pinnedRow
1211
- },
1212
- children: orderedColumns.map((col) => {
1213
- const cellValue = row[col.dataIndex];
1214
- const stickyOffset = columnOffsets.get(col.key);
1215
- const isPinned = Boolean(col.pinned);
1216
- let zIndex = 0;
1217
- if (col.key === "__select__" || col.key === "__expand__")
1218
- zIndex = 11;
1219
- else if (isPinned) zIndex = 2;
1220
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1221
- return /* @__PURE__ */ jsx4(
1222
- "div",
1223
- {
1224
- "data-row-key": rk,
1225
- "data-selected": isSelected || void 0,
1226
- style: {
1227
- position: isPinned ? "sticky" : "relative",
1228
- ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1229
- ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1230
- zIndex,
1231
- backgroundColor: pinnedRowBg,
1232
- ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1233
- },
1234
- children: /* @__PURE__ */ jsx4(
1213
+ children: /* @__PURE__ */ jsx4(
1214
+ "div",
1215
+ {
1216
+ style: {
1217
+ position: "sticky",
1218
+ top: headerHeight,
1219
+ pointerEvents: "auto",
1220
+ boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1221
+ },
1222
+ children: pinnedTopData.map((row, rowIdx) => {
1223
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1224
+ const isSelected = selectedKeySet.has(rk);
1225
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1226
+ return /* @__PURE__ */ jsx4(
1227
+ "div",
1228
+ {
1229
+ className: classNames?.pinnedRow ?? "",
1230
+ style: {
1231
+ display: "grid",
1232
+ gridTemplateColumns: gridTemplateColumns ?? "",
1233
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1234
+ ...styles?.pinnedRow
1235
+ },
1236
+ children: orderedColumns.map((col) => {
1237
+ const cellValue = row[col.dataIndex];
1238
+ const stickyOffset = columnOffsets.get(col.key);
1239
+ const isPinned = Boolean(col.pinned);
1240
+ let zIndex = 0;
1241
+ if (col.key === "__select__" || col.key === "__expand__")
1242
+ zIndex = 11;
1243
+ else if (isPinned) zIndex = 2;
1244
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1245
+ return /* @__PURE__ */ jsx4(
1235
1246
  "div",
1236
1247
  {
1248
+ "data-row-key": rk,
1249
+ "data-column-key": col.key,
1250
+ "data-bt-cell": "",
1251
+ "data-selected": isSelected || void 0,
1237
1252
  style: {
1238
- height: `${rowHeight}px`,
1239
- position: "relative"
1253
+ position: isPinned ? "sticky" : "relative",
1254
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1255
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1256
+ zIndex,
1257
+ backgroundColor: pinnedRowBg,
1258
+ backdropFilter: "blur(12px)",
1259
+ WebkitBackdropFilter: "blur(12px)",
1260
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1240
1261
  },
1241
1262
  children: /* @__PURE__ */ jsx4(
1242
- Cell,
1263
+ "div",
1243
1264
  {
1244
- value: cellValue,
1245
- record: row,
1246
- column: col,
1247
- rowIndex: rowIdx,
1248
- classNames,
1249
- styles,
1250
- isSelected,
1251
- isExpanded,
1252
- rowSelection,
1253
- normalizedSelectedKeys,
1254
- rowKey: rk,
1255
- allData: allDataForSelection,
1256
- getRowKey,
1257
- accentColor,
1258
- isLoading: false,
1259
- recordFingerprint
1265
+ style: {
1266
+ height: `${rowHeight}px`,
1267
+ position: "relative"
1268
+ },
1269
+ children: /* @__PURE__ */ jsx4(
1270
+ Cell,
1271
+ {
1272
+ value: cellValue,
1273
+ record: row,
1274
+ column: col,
1275
+ rowIndex: rowIdx,
1276
+ classNames,
1277
+ styles,
1278
+ isSelected,
1279
+ isExpanded,
1280
+ rowSelection,
1281
+ normalizedSelectedKeys,
1282
+ rowKey: rk,
1283
+ allData: allDataForSelection,
1284
+ getRowKey,
1285
+ accentColor,
1286
+ isLoading: false,
1287
+ recordFingerprint
1288
+ }
1289
+ )
1260
1290
  }
1261
1291
  )
1262
- }
1263
- )
1264
- },
1265
- col.key
1266
- );
1267
- })
1268
- },
1269
- `pinned-top-${rk}`
1270
- );
1271
- })
1292
+ },
1293
+ col.key
1294
+ );
1295
+ })
1296
+ },
1297
+ `pinned-top-${rk}`
1298
+ );
1299
+ })
1300
+ }
1301
+ )
1272
1302
  }
1273
1303
  ),
1274
1304
  pinnedBottomData.length > 0 && /* @__PURE__ */ jsx4(
@@ -1277,86 +1307,103 @@ var TableBody = ({
1277
1307
  style: {
1278
1308
  gridColumn: "1 / -1",
1279
1309
  gridRow: 2,
1280
- position: "sticky",
1281
- bottom: 0,
1282
- alignSelf: "end",
1310
+ height: `${totalSize}px`,
1311
+ position: "relative",
1283
1312
  zIndex: 20,
1284
- boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1313
+ pointerEvents: "none",
1314
+ display: "flex",
1315
+ flexDirection: "column"
1285
1316
  },
1286
- children: pinnedBottomData.map((row, rowIdx) => {
1287
- const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1288
- const isSelected = selectedKeySet.has(rk);
1289
- const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1290
- return /* @__PURE__ */ jsx4(
1291
- "div",
1292
- {
1293
- className: classNames?.pinnedRow ?? "",
1294
- style: {
1295
- display: "grid",
1296
- gridTemplateColumns: gridTemplateColumns ?? "",
1297
- minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1298
- ...styles?.pinnedRow
1299
- },
1300
- children: orderedColumns.map((col) => {
1301
- const cellValue = row[col.dataIndex];
1302
- const stickyOffset = columnOffsets.get(col.key);
1303
- const isPinned = Boolean(col.pinned);
1304
- let zIndex = 0;
1305
- if (col.key === "__select__" || col.key === "__expand__")
1306
- zIndex = 11;
1307
- else if (isPinned) zIndex = 2;
1308
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1309
- return /* @__PURE__ */ jsx4(
1310
- "div",
1311
- {
1312
- "data-row-key": rk,
1313
- "data-selected": isSelected || void 0,
1314
- style: {
1315
- position: isPinned ? "sticky" : "relative",
1316
- ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1317
- ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1318
- zIndex,
1319
- backgroundColor: pinnedRowBg,
1320
- ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1321
- },
1322
- children: /* @__PURE__ */ jsx4(
1317
+ children: /* @__PURE__ */ jsx4(
1318
+ "div",
1319
+ {
1320
+ style: {
1321
+ marginTop: "auto",
1322
+ position: "sticky",
1323
+ bottom: 0,
1324
+ pointerEvents: "auto",
1325
+ boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1326
+ },
1327
+ children: pinnedBottomData.map((row, rowIdx) => {
1328
+ const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1329
+ const isSelected = selectedKeySet.has(rk);
1330
+ const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1331
+ return /* @__PURE__ */ jsx4(
1332
+ "div",
1333
+ {
1334
+ className: classNames?.pinnedRow ?? "",
1335
+ style: {
1336
+ display: "grid",
1337
+ gridTemplateColumns: gridTemplateColumns ?? "",
1338
+ minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1339
+ ...styles?.pinnedRow
1340
+ },
1341
+ children: orderedColumns.map((col) => {
1342
+ const cellValue = row[col.dataIndex];
1343
+ const stickyOffset = columnOffsets.get(col.key);
1344
+ const isPinned = Boolean(col.pinned);
1345
+ let zIndex = 0;
1346
+ if (col.key === "__select__" || col.key === "__expand__")
1347
+ zIndex = 11;
1348
+ else if (isPinned) zIndex = 2;
1349
+ const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1350
+ return /* @__PURE__ */ jsx4(
1323
1351
  "div",
1324
1352
  {
1353
+ "data-row-key": rk,
1354
+ "data-column-key": col.key,
1355
+ "data-bt-cell": "",
1356
+ "data-selected": isSelected || void 0,
1325
1357
  style: {
1326
- height: `${rowHeight}px`,
1327
- position: "relative"
1358
+ position: isPinned ? "sticky" : "relative",
1359
+ ...col.pinned === "left" && stickyOffset !== void 0 ? { left: `${stickyOffset}px` } : {},
1360
+ ...col.pinned === "right" && stickyOffset !== void 0 ? { right: `${stickyOffset}px` } : {},
1361
+ zIndex,
1362
+ backgroundColor: pinnedRowBg,
1363
+ backdropFilter: "blur(12px)",
1364
+ WebkitBackdropFilter: "blur(12px)",
1365
+ ...isPinned && styles?.pinnedCell ? styles.pinnedCell : {}
1328
1366
  },
1329
1367
  children: /* @__PURE__ */ jsx4(
1330
- Cell,
1368
+ "div",
1331
1369
  {
1332
- value: cellValue,
1333
- record: row,
1334
- column: col,
1335
- rowIndex: rowIdx,
1336
- classNames,
1337
- styles,
1338
- isSelected,
1339
- isExpanded,
1340
- rowSelection,
1341
- normalizedSelectedKeys,
1342
- rowKey: rk,
1343
- allData: allDataForSelection,
1344
- getRowKey,
1345
- accentColor,
1346
- isLoading: false,
1347
- recordFingerprint
1370
+ style: {
1371
+ height: `${rowHeight}px`,
1372
+ position: "relative"
1373
+ },
1374
+ children: /* @__PURE__ */ jsx4(
1375
+ Cell,
1376
+ {
1377
+ value: cellValue,
1378
+ record: row,
1379
+ column: col,
1380
+ rowIndex: rowIdx,
1381
+ classNames,
1382
+ styles,
1383
+ isSelected,
1384
+ isExpanded,
1385
+ rowSelection,
1386
+ normalizedSelectedKeys,
1387
+ rowKey: rk,
1388
+ allData: allDataForSelection,
1389
+ getRowKey,
1390
+ accentColor,
1391
+ isLoading: false,
1392
+ recordFingerprint
1393
+ }
1394
+ )
1348
1395
  }
1349
1396
  )
1350
- }
1351
- )
1352
- },
1353
- col.key
1354
- );
1355
- })
1356
- },
1357
- `pinned-bottom-${rk}`
1358
- );
1359
- })
1397
+ },
1398
+ col.key
1399
+ );
1400
+ })
1401
+ },
1402
+ `pinned-bottom-${rk}`
1403
+ );
1404
+ })
1405
+ }
1406
+ )
1360
1407
  }
1361
1408
  )
1362
1409
  ] });
@@ -1394,6 +1441,7 @@ function BoltTable({
1394
1441
  onColumnHide,
1395
1442
  rowSelection,
1396
1443
  rowPinning,
1444
+ onRowPin,
1397
1445
  expandable,
1398
1446
  rowKey = "id",
1399
1447
  onEndReached,
@@ -1740,7 +1788,6 @@ function BoltTable({
1740
1788
  areaRect,
1741
1789
  headerLeftInContent,
1742
1790
  40,
1743
- // minimum column width
1744
1791
  scrollTop,
1745
1792
  scrollLeft,
1746
1793
  headerLeftInContent + startWidth
@@ -1963,6 +2010,44 @@ function BoltTable({
1963
2010
  }, [processedData, rowPinning, getRowKey]);
1964
2011
  const pinnedTopHeight = pinnedTopRows.length * rowHeight;
1965
2012
  const pinnedBottomHeight = pinnedBottomRows.length * rowHeight;
2013
+ const pinnedTopKeySet = useMemo2(
2014
+ () => new Set((rowPinning?.top ?? []).map(String)),
2015
+ [rowPinning?.top]
2016
+ );
2017
+ const pinnedBottomKeySet = useMemo2(
2018
+ () => new Set((rowPinning?.bottom ?? []).map(String)),
2019
+ [rowPinning?.bottom]
2020
+ );
2021
+ const [cellContextMenu, setCellContextMenu] = useState2(null);
2022
+ const cellMenuRef = useRef4(null);
2023
+ const cellLongPressTimer = useRef4(
2024
+ null
2025
+ );
2026
+ const cellTouchStart = useRef4(null);
2027
+ const cancelCellLongPress = useCallback(() => {
2028
+ if (cellLongPressTimer.current) {
2029
+ clearTimeout(cellLongPressTimer.current);
2030
+ cellLongPressTimer.current = null;
2031
+ }
2032
+ cellTouchStart.current = null;
2033
+ }, []);
2034
+ React4.useEffect(() => {
2035
+ if (!cellContextMenu) return;
2036
+ const close = (e) => {
2037
+ if (cellMenuRef.current && cellMenuRef.current.contains(e.target))
2038
+ return;
2039
+ setCellContextMenu(null);
2040
+ };
2041
+ const onKey = (e) => {
2042
+ if (e.key === "Escape") setCellContextMenu(null);
2043
+ };
2044
+ document.addEventListener("mousedown", close);
2045
+ document.addEventListener("keydown", onKey);
2046
+ return () => {
2047
+ document.removeEventListener("mousedown", close);
2048
+ document.removeEventListener("keydown", onKey);
2049
+ };
2050
+ }, [cellContextMenu]);
1966
2051
  const columnFiltersKey = Object.keys(columnFilters).sort().map((k) => `${k}:${columnFilters[k]}`).join("|");
1967
2052
  React4.useEffect(() => {
1968
2053
  tableAreaRef.current?.scrollTo({ top: 0 });
@@ -2178,354 +2263,382 @@ function BoltTable({
2178
2263
  flexGrow: 0
2179
2264
  } : { flex: "1 1 0%" }
2180
2265
  },
2181
- children: layoutLoading ? (
2182
- /*
2183
- * ── Layout loading skeleton ──────────────────────────────────
2184
- * Shown when layoutLoading=true. Renders real column headers
2185
- * (based on orderedColumns) alongside shimmer body rows.
2186
- * Used for initial page load when column widths are not yet known.
2187
- */
2188
- /* @__PURE__ */ jsx5(
2189
- "div",
2190
- {
2191
- style: {
2192
- position: "absolute",
2193
- inset: 0,
2194
- overflow: "auto",
2195
- contain: "layout paint"
2196
- },
2197
- children: /* @__PURE__ */ jsxs5(
2266
+ children: layoutLoading ? /* @__PURE__ */ jsx5(
2267
+ "div",
2268
+ {
2269
+ style: {
2270
+ position: "absolute",
2271
+ inset: 0,
2272
+ overflow: "auto",
2273
+ contain: "layout paint"
2274
+ },
2275
+ children: /* @__PURE__ */ jsxs5(
2276
+ "div",
2277
+ {
2278
+ style: {
2279
+ display: "grid",
2280
+ gridTemplateColumns,
2281
+ gridTemplateRows: "36px auto",
2282
+ minWidth: `${totalTableWidth}px`,
2283
+ width: "100%",
2284
+ position: "relative"
2285
+ },
2286
+ children: [
2287
+ orderedColumns.map((column) => {
2288
+ const isPinned = !!column.pinned;
2289
+ const offset = columnOffsets.get(column.key);
2290
+ const isSystem = column.key === "__select__" || column.key === "__expand__";
2291
+ return /* @__PURE__ */ jsx5(
2292
+ "div",
2293
+ {
2294
+ className: isPinned ? classNames.pinnedHeader ?? "" : classNames.header ?? "",
2295
+ style: {
2296
+ display: "flex",
2297
+ height: 36,
2298
+ alignItems: "center",
2299
+ overflow: "hidden",
2300
+ textOverflow: "ellipsis",
2301
+ whiteSpace: "nowrap",
2302
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
2303
+ backdropFilter: "blur(8px)",
2304
+ position: "sticky",
2305
+ top: 0,
2306
+ zIndex: isPinned ? 13 : 10,
2307
+ ...isPinned ? {
2308
+ [column.pinned]: offset ?? 0,
2309
+ ...styles.pinnedHeader
2310
+ } : styles.header,
2311
+ paddingLeft: isSystem ? 0 : 8,
2312
+ paddingRight: isSystem ? 0 : 8
2313
+ }
2314
+ },
2315
+ column.key
2316
+ );
2317
+ }),
2318
+ /* @__PURE__ */ jsx5("div", { style: { gridColumn: "1 / -1" }, children: Array.from({ length: shimmerCount }).map((_, rowIndex) => /* @__PURE__ */ jsx5(
2319
+ "div",
2320
+ {
2321
+ style: {
2322
+ display: "grid",
2323
+ gridTemplateColumns,
2324
+ height: rowHeight
2325
+ },
2326
+ children: orderedColumns.map((column, colIndex) => {
2327
+ const isPinned = !!column.pinned;
2328
+ const offset = columnOffsets.get(column.key);
2329
+ const isSystem = column.key === "__select__" || column.key === "__expand__";
2330
+ const widthPercent = SHIMMER_WIDTHS2[(rowIndex * 7 + colIndex) % SHIMMER_WIDTHS2.length];
2331
+ return /* @__PURE__ */ jsx5(
2332
+ "div",
2333
+ {
2334
+ className: isPinned ? classNames.pinnedCell ?? "" : "",
2335
+ style: {
2336
+ display: "flex",
2337
+ alignItems: "center",
2338
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
2339
+ ...isPinned ? {
2340
+ position: "sticky",
2341
+ [column.pinned]: offset ?? 0,
2342
+ zIndex: 5,
2343
+ ...styles.pinnedCell
2344
+ } : {},
2345
+ paddingLeft: isSystem ? 0 : 8,
2346
+ paddingRight: isSystem ? 0 : 8,
2347
+ justifyContent: isSystem ? "center" : void 0
2348
+ },
2349
+ children: /* @__PURE__ */ jsx5(
2350
+ "div",
2351
+ {
2352
+ style: {
2353
+ backgroundColor: "rgba(100, 116, 139, 0.15)",
2354
+ animation: "bt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
2355
+ borderRadius: isSystem ? 3 : 4,
2356
+ height: isSystem ? 16 : 14,
2357
+ width: isSystem ? 16 : `${widthPercent}%`,
2358
+ animationDelay: `${(rowIndex * 7 + colIndex) * 50}ms`
2359
+ }
2360
+ }
2361
+ )
2362
+ },
2363
+ column.key
2364
+ );
2365
+ })
2366
+ },
2367
+ rowIndex
2368
+ )) })
2369
+ ]
2370
+ }
2371
+ )
2372
+ }
2373
+ ) : /* @__PURE__ */ jsxs5(
2374
+ "div",
2375
+ {
2376
+ ref: tableAreaCallbackRef,
2377
+ style: {
2378
+ position: "absolute",
2379
+ inset: 0,
2380
+ overflow: "auto",
2381
+ contain: "layout paint"
2382
+ },
2383
+ children: [
2384
+ /* @__PURE__ */ jsx5(ResizeOverlay_default, { ref: resizeOverlayRef, accentColor }),
2385
+ /* @__PURE__ */ jsxs5(
2198
2386
  "div",
2199
2387
  {
2200
2388
  style: {
2201
2389
  display: "grid",
2202
2390
  gridTemplateColumns,
2203
- gridTemplateRows: "36px auto",
2391
+ gridTemplateRows: isEmpty ? "36px 1fr" : `36px ${virtualTotalSize}px`,
2204
2392
  minWidth: `${totalTableWidth}px`,
2205
2393
  width: "100%",
2206
- position: "relative"
2394
+ position: "relative",
2395
+ ...isEmpty ? { height: "100%" } : {}
2396
+ },
2397
+ onContextMenu: (e) => {
2398
+ const cell = e.target.closest("[data-bt-cell]");
2399
+ if (!cell) return;
2400
+ const rk = cell.dataset.rowKey;
2401
+ const ck = cell.dataset.columnKey;
2402
+ if (!rk || !ck) return;
2403
+ const col = freshOrderedColumns.find(
2404
+ (c) => c.key === ck
2405
+ );
2406
+ const hasCopy = col?.copy;
2407
+ const hasRowPin = !!onRowPin;
2408
+ if (!hasCopy && !hasRowPin) return;
2409
+ e.preventDefault();
2410
+ setCellContextMenu({
2411
+ x: Math.min(e.clientX, window.innerWidth - 200),
2412
+ y: Math.min(e.clientY, window.innerHeight - 200),
2413
+ rowKey: rk,
2414
+ columnKey: ck
2415
+ });
2416
+ },
2417
+ onTouchStart: (e) => {
2418
+ cancelCellLongPress();
2419
+ const cell = e.target.closest("[data-bt-cell]");
2420
+ if (!cell) return;
2421
+ const touch = e.touches[0];
2422
+ cellTouchStart.current = {
2423
+ x: touch.clientX,
2424
+ y: touch.clientY
2425
+ };
2426
+ const rk = cell.dataset.rowKey;
2427
+ const ck = cell.dataset.columnKey;
2428
+ cellLongPressTimer.current = setTimeout(() => {
2429
+ cellLongPressTimer.current = null;
2430
+ if (!rk || !ck) return;
2431
+ const col = freshOrderedColumns.find(
2432
+ (c) => c.key === ck
2433
+ );
2434
+ const hasCopy = col?.copy;
2435
+ const hasRowPin = !!onRowPin;
2436
+ if (!hasCopy && !hasRowPin) return;
2437
+ setCellContextMenu({
2438
+ x: Math.min(touch.clientX, window.innerWidth - 200),
2439
+ y: Math.min(touch.clientY, window.innerHeight - 200),
2440
+ rowKey: rk,
2441
+ columnKey: ck
2442
+ });
2443
+ }, 500);
2444
+ },
2445
+ onTouchMove: (e) => {
2446
+ if (!cellTouchStart.current) return;
2447
+ const touch = e.touches[0];
2448
+ const dx = touch.clientX - cellTouchStart.current.x;
2449
+ const dy = touch.clientY - cellTouchStart.current.y;
2450
+ if (Math.abs(dx) > 10 || Math.abs(dy) > 10)
2451
+ cancelCellLongPress();
2207
2452
  },
2453
+ onTouchEnd: cancelCellLongPress,
2454
+ onTouchCancel: cancelCellLongPress,
2208
2455
  children: [
2209
- orderedColumns.map((column) => {
2210
- const isPinned = !!column.pinned;
2211
- const offset = columnOffsets.get(column.key);
2212
- const isSystem = column.key === "__select__" || column.key === "__expand__";
2456
+ orderedColumns.map((column, visualIndex) => {
2457
+ if (column.key === "__select__" && rowSelection) {
2458
+ return /* @__PURE__ */ jsx5(
2459
+ "div",
2460
+ {
2461
+ className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2462
+ style: {
2463
+ display: "flex",
2464
+ height: 36,
2465
+ alignItems: "center",
2466
+ justifyContent: "center",
2467
+ overflow: "hidden",
2468
+ textOverflow: "ellipsis",
2469
+ whiteSpace: "nowrap",
2470
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
2471
+ backgroundColor: styles?.pinnedBg,
2472
+ position: "sticky",
2473
+ left: columnOffsets.get("__select__") ?? 0,
2474
+ top: 0,
2475
+ zIndex: 13,
2476
+ width: "48px",
2477
+ ...styles.header,
2478
+ ...styles.pinnedHeader
2479
+ },
2480
+ children: rowSelection.type !== "radio" && !rowSelection.hideSelectAll && /* @__PURE__ */ jsx5(
2481
+ "input",
2482
+ {
2483
+ type: "checkbox",
2484
+ checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2485
+ ref: (input) => {
2486
+ if (input) {
2487
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2488
+ }
2489
+ },
2490
+ onChange: (e) => {
2491
+ if (e.target.checked) {
2492
+ const allKeys = data.map(
2493
+ (row, idx) => getRowKey(row, idx)
2494
+ );
2495
+ rowSelection.onSelectAll?.(
2496
+ true,
2497
+ data,
2498
+ data
2499
+ );
2500
+ rowSelection.onChange?.(allKeys, data, {
2501
+ type: "all"
2502
+ });
2503
+ } else {
2504
+ rowSelection.onSelectAll?.(false, [], data);
2505
+ rowSelection.onChange?.([], [], {
2506
+ type: "all"
2507
+ });
2508
+ }
2509
+ },
2510
+ style: { cursor: "pointer", accentColor }
2511
+ }
2512
+ )
2513
+ },
2514
+ "__select__"
2515
+ );
2516
+ }
2517
+ if (column.key === "__expand__") {
2518
+ return /* @__PURE__ */ jsx5(
2519
+ "div",
2520
+ {
2521
+ className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2522
+ style: {
2523
+ display: "flex",
2524
+ height: 36,
2525
+ alignItems: "center",
2526
+ justifyContent: "center",
2527
+ overflow: "hidden",
2528
+ textOverflow: "ellipsis",
2529
+ whiteSpace: "nowrap",
2530
+ borderBottom: "1px solid rgba(128,128,128,0.2)",
2531
+ backgroundColor: styles?.pinnedBg,
2532
+ position: "sticky",
2533
+ left: columnOffsets.get("__expand__") ?? 0,
2534
+ top: 0,
2535
+ zIndex: 13,
2536
+ width: "40px",
2537
+ ...styles.header,
2538
+ ...styles.pinnedHeader
2539
+ }
2540
+ },
2541
+ "__expand__"
2542
+ );
2543
+ }
2213
2544
  return /* @__PURE__ */ jsx5(
2214
- "div",
2545
+ DraggableHeader_default,
2215
2546
  {
2216
- className: isPinned ? classNames.pinnedHeader ?? "" : classNames.header ?? "",
2217
- style: {
2218
- display: "flex",
2219
- height: 36,
2220
- alignItems: "center",
2221
- overflow: "hidden",
2222
- textOverflow: "ellipsis",
2223
- whiteSpace: "nowrap",
2224
- borderBottom: "1px solid rgba(128,128,128,0.2)",
2225
- backdropFilter: "blur(8px)",
2226
- position: "sticky",
2227
- top: 0,
2228
- zIndex: isPinned ? 13 : 10,
2229
- ...isPinned ? {
2230
- [column.pinned]: offset ?? 0,
2231
- ...styles.pinnedHeader
2232
- } : styles.header,
2233
- paddingLeft: isSystem ? 0 : 8,
2234
- paddingRight: isSystem ? 0 : 8
2235
- }
2547
+ column,
2548
+ accentColor,
2549
+ visualIndex,
2550
+ onResizeStart: handleResizeStart,
2551
+ onColumnDragStart: handleColumnDragStart,
2552
+ styles,
2553
+ classNames,
2554
+ gripIcon,
2555
+ hideGripIcon,
2556
+ icons,
2557
+ stickyOffset: columnOffsets.get(column.key),
2558
+ onTogglePin: handleTogglePin,
2559
+ onToggleHide: handleToggleHide,
2560
+ isLastColumn: visualIndex === orderedColumns.length - 1,
2561
+ sortDirection: sortState.key === column.key ? sortState.direction : null,
2562
+ onSort: handleSort,
2563
+ filterValue: columnFilters[column.key] ?? "",
2564
+ onFilter: handleColumnFilter,
2565
+ onClearFilter: handleClearFilter,
2566
+ customContextMenuItems: columnContextMenuItems
2236
2567
  },
2237
2568
  column.key
2238
2569
  );
2239
2570
  }),
2240
- /* @__PURE__ */ jsx5("div", { style: { gridColumn: "1 / -1" }, children: Array.from({ length: shimmerCount }).map((_, rowIndex) => /* @__PURE__ */ jsx5(
2571
+ isEmpty ? /* @__PURE__ */ jsx5(
2241
2572
  "div",
2242
2573
  {
2243
2574
  style: {
2244
- display: "grid",
2245
- gridTemplateColumns,
2246
- height: rowHeight
2575
+ gridColumn: "1 / -1",
2576
+ height: "100%",
2577
+ position: "relative"
2247
2578
  },
2248
- children: orderedColumns.map((column, colIndex) => {
2249
- const isPinned = !!column.pinned;
2250
- const offset = columnOffsets.get(column.key);
2251
- const isSystem = column.key === "__select__" || column.key === "__expand__";
2252
- const widthPercent = SHIMMER_WIDTHS2[(rowIndex * 7 + colIndex) % SHIMMER_WIDTHS2.length];
2253
- return /* @__PURE__ */ jsx5(
2254
- "div",
2255
- {
2256
- className: isPinned ? classNames.pinnedCell ?? "" : "",
2257
- style: {
2258
- display: "flex",
2259
- alignItems: "center",
2260
- borderBottom: "1px solid rgba(128,128,128,0.2)",
2261
- ...isPinned ? {
2262
- position: "sticky",
2263
- [column.pinned]: offset ?? 0,
2264
- zIndex: 5,
2265
- ...styles.pinnedCell
2266
- } : {},
2267
- paddingLeft: isSystem ? 0 : 8,
2268
- paddingRight: isSystem ? 0 : 8,
2269
- justifyContent: isSystem ? "center" : void 0
2270
- },
2271
- children: /* @__PURE__ */ jsx5(
2272
- "div",
2273
- {
2274
- style: {
2275
- backgroundColor: "rgba(100, 116, 139, 0.15)",
2276
- animation: "bt-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
2277
- borderRadius: isSystem ? 3 : 4,
2278
- height: isSystem ? 16 : 14,
2279
- width: isSystem ? 16 : `${widthPercent}%`,
2280
- animationDelay: `${(rowIndex * 7 + colIndex) * 50}ms`
2281
- }
2282
- }
2283
- )
2284
- },
2285
- column.key
2286
- );
2287
- })
2288
- },
2289
- rowIndex
2290
- )) })
2291
- ]
2292
- }
2293
- )
2294
- }
2295
- )
2296
- ) : (
2297
- /*
2298
- * ── Main scroll container ────────────────────────────────────
2299
- * absolute inset-0 so it fills whatever height the wrapper resolves to.
2300
- * contain: layout paint — browser optimization hint that this element
2301
- * is a layout and paint boundary (improves compositing performance).
2302
- */
2303
- /* @__PURE__ */ jsxs5(
2304
- "div",
2305
- {
2306
- ref: tableAreaCallbackRef,
2307
- style: {
2308
- position: "absolute",
2309
- inset: 0,
2310
- overflow: "auto",
2311
- contain: "layout paint"
2312
- },
2313
- children: [
2314
- /* @__PURE__ */ jsx5(ResizeOverlay_default, { ref: resizeOverlayRef, accentColor }),
2315
- /* @__PURE__ */ jsxs5(
2316
- "div",
2317
- {
2318
- style: {
2319
- display: "grid",
2320
- gridTemplateColumns,
2321
- gridTemplateRows: isEmpty ? "36px 1fr" : `36px ${virtualTotalSize}px`,
2322
- minWidth: `${totalTableWidth}px`,
2323
- width: "100%",
2324
- position: "relative",
2325
- ...isEmpty ? { height: "100%" } : {}
2326
- },
2327
- children: [
2328
- orderedColumns.map((column, visualIndex) => {
2329
- if (column.key === "__select__" && rowSelection) {
2330
- return /* @__PURE__ */ jsx5(
2331
- "div",
2332
- {
2333
- className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2334
- style: {
2335
- display: "flex",
2336
- height: 36,
2337
- alignItems: "center",
2338
- justifyContent: "center",
2339
- overflow: "hidden",
2340
- textOverflow: "ellipsis",
2341
- whiteSpace: "nowrap",
2342
- borderBottom: "1px solid rgba(128,128,128,0.2)",
2343
- backgroundColor: styles?.pinnedBg,
2344
- position: "sticky",
2345
- left: columnOffsets.get("__select__") ?? 0,
2346
- top: 0,
2347
- zIndex: 13,
2348
- width: "48px",
2349
- ...styles.header,
2350
- ...styles.pinnedHeader
2351
- },
2352
- children: rowSelection.type !== "radio" && !rowSelection.hideSelectAll && /* @__PURE__ */ jsx5(
2353
- "input",
2354
- {
2355
- type: "checkbox",
2356
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2357
- ref: (input) => {
2358
- if (input) {
2359
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2360
- }
2361
- },
2362
- onChange: (e) => {
2363
- if (e.target.checked) {
2364
- const allKeys = data.map(
2365
- (row, idx) => getRowKey(row, idx)
2366
- );
2367
- rowSelection.onSelectAll?.(
2368
- true,
2369
- data,
2370
- data
2371
- );
2372
- rowSelection.onChange?.(allKeys, data, {
2373
- type: "all"
2374
- });
2375
- } else {
2376
- rowSelection.onSelectAll?.(false, [], data);
2377
- rowSelection.onChange?.([], [], {
2378
- type: "all"
2379
- });
2380
- }
2381
- },
2382
- style: { cursor: "pointer", accentColor }
2383
- }
2384
- )
2385
- },
2386
- "__select__"
2387
- );
2388
- }
2389
- if (column.key === "__expand__") {
2390
- return /* @__PURE__ */ jsx5(
2391
- "div",
2392
- {
2393
- className: `${classNames.header ?? ""} ${classNames.pinnedHeader ?? ""}`,
2394
- style: {
2395
- display: "flex",
2396
- height: 36,
2397
- alignItems: "center",
2398
- justifyContent: "center",
2399
- overflow: "hidden",
2400
- textOverflow: "ellipsis",
2401
- whiteSpace: "nowrap",
2402
- borderBottom: "1px solid rgba(128,128,128,0.2)",
2403
- backgroundColor: styles?.pinnedBg,
2404
- position: "sticky",
2405
- left: columnOffsets.get("__expand__") ?? 0,
2406
- top: 0,
2407
- zIndex: 13,
2408
- width: "40px",
2409
- ...styles.header,
2410
- ...styles.pinnedHeader
2411
- }
2412
- },
2413
- "__expand__"
2414
- );
2415
- }
2416
- return /* @__PURE__ */ jsx5(
2417
- DraggableHeader_default,
2418
- {
2419
- column,
2420
- accentColor,
2421
- visualIndex,
2422
- onResizeStart: handleResizeStart,
2423
- onColumnDragStart: handleColumnDragStart,
2424
- styles,
2425
- classNames,
2426
- gripIcon,
2427
- hideGripIcon,
2428
- icons,
2429
- stickyOffset: columnOffsets.get(column.key),
2430
- onTogglePin: handleTogglePin,
2431
- onToggleHide: handleToggleHide,
2432
- isLastColumn: visualIndex === orderedColumns.length - 1,
2433
- sortDirection: sortState.key === column.key ? sortState.direction : null,
2434
- onSort: handleSort,
2435
- filterValue: columnFilters[column.key] ?? "",
2436
- onFilter: handleColumnFilter,
2437
- onClearFilter: handleClearFilter,
2438
- customContextMenuItems: columnContextMenuItems
2439
- },
2440
- column.key
2441
- );
2442
- }),
2443
- isEmpty ? (
2444
- /*
2445
- * ── Empty state ────────────────────────────────────────
2446
- * col-span-full + height:100% fills the 1fr body grid row.
2447
- *
2448
- * The inner div uses `position: sticky; left: 0` with a fixed
2449
- * width (scrollAreaWidth) to viewport-lock the empty state panel.
2450
- * Without this, the empty message would scroll horizontally
2451
- * with the grid content when there are many columns.
2452
- */
2453
- /* @__PURE__ */ jsx5(
2579
+ children: /* @__PURE__ */ jsx5(
2454
2580
  "div",
2455
2581
  {
2456
2582
  style: {
2457
- gridColumn: "1 / -1",
2583
+ position: "sticky",
2584
+ left: 0,
2585
+ width: scrollAreaWidth > 0 ? `${scrollAreaWidth}px` : "100%",
2458
2586
  height: "100%",
2459
- position: "relative"
2587
+ display: "flex",
2588
+ alignItems: "center",
2589
+ justifyContent: "center"
2460
2590
  },
2461
- children: /* @__PURE__ */ jsx5(
2591
+ children: emptyRenderer ?? /* @__PURE__ */ jsx5(
2462
2592
  "div",
2463
2593
  {
2464
2594
  style: {
2465
- position: "sticky",
2466
- left: 0,
2467
- width: scrollAreaWidth > 0 ? `${scrollAreaWidth}px` : "100%",
2468
- height: "100%",
2469
2595
  display: "flex",
2596
+ flexDirection: "column",
2470
2597
  alignItems: "center",
2471
- justifyContent: "center"
2598
+ gap: 8,
2599
+ paddingTop: 32,
2600
+ paddingBottom: 32,
2601
+ color: "GrayText"
2472
2602
  },
2473
- children: emptyRenderer ?? /* @__PURE__ */ jsx5(
2474
- "div",
2475
- {
2476
- style: {
2477
- display: "flex",
2478
- flexDirection: "column",
2479
- alignItems: "center",
2480
- gap: 8,
2481
- paddingTop: 32,
2482
- paddingBottom: 32,
2483
- color: "GrayText"
2484
- },
2485
- children: /* @__PURE__ */ jsx5("span", { style: { fontSize: 14 }, children: "No data" })
2486
- }
2487
- )
2603
+ children: /* @__PURE__ */ jsx5("span", { style: { fontSize: 14 }, children: "No data" })
2488
2604
  }
2489
2605
  )
2490
2606
  }
2491
2607
  )
2492
- ) : (
2493
- /* ── Virtualized table body ─────────────────────────── */
2494
- /* @__PURE__ */ jsx5(
2495
- TableBody_default,
2496
- {
2497
- data: displayData,
2498
- orderedColumns: freshOrderedColumns,
2499
- rowVirtualizer,
2500
- columnOffsets,
2501
- styles,
2502
- classNames,
2503
- rowSelection: !showShimmer ? rowSelection : void 0,
2504
- normalizedSelectedKeys,
2505
- getRowKey,
2506
- expandable: !showShimmer ? expandable : void 0,
2507
- resolvedExpandedKeys,
2508
- rowHeight,
2509
- totalTableWidth,
2510
- scrollAreaWidth,
2511
- accentColor,
2512
- scrollContainerRef: tableAreaRef,
2513
- isLoading: showShimmer,
2514
- onExpandedRowResize: handleExpandedRowResize,
2515
- maxExpandedRowHeight,
2516
- pinnedTopData: pinnedTopRows,
2517
- pinnedBottomData: pinnedBottomRows,
2518
- gridTemplateColumns,
2519
- headerHeight: HEADER_HEIGHT
2520
- }
2521
- )
2522
- )
2523
- ]
2524
- }
2525
- )
2526
- ]
2527
- }
2528
- )
2608
+ }
2609
+ ) : /* @__PURE__ */ jsx5(
2610
+ TableBody_default,
2611
+ {
2612
+ data: displayData,
2613
+ orderedColumns: freshOrderedColumns,
2614
+ rowVirtualizer,
2615
+ columnOffsets,
2616
+ styles,
2617
+ classNames,
2618
+ rowSelection: !showShimmer ? rowSelection : void 0,
2619
+ normalizedSelectedKeys,
2620
+ getRowKey,
2621
+ expandable: !showShimmer ? expandable : void 0,
2622
+ resolvedExpandedKeys,
2623
+ rowHeight,
2624
+ totalTableWidth,
2625
+ scrollAreaWidth,
2626
+ accentColor,
2627
+ scrollContainerRef: tableAreaRef,
2628
+ isLoading: showShimmer,
2629
+ onExpandedRowResize: handleExpandedRowResize,
2630
+ maxExpandedRowHeight,
2631
+ pinnedTopData: pinnedTopRows,
2632
+ pinnedBottomData: pinnedBottomRows,
2633
+ gridTemplateColumns,
2634
+ headerHeight: HEADER_HEIGHT
2635
+ }
2636
+ )
2637
+ ]
2638
+ }
2639
+ )
2640
+ ]
2641
+ }
2529
2642
  )
2530
2643
  }
2531
2644
  ),
@@ -2822,7 +2935,166 @@ function BoltTable({
2822
2935
  }
2823
2936
  ),
2824
2937
  document.body
2825
- )
2938
+ ),
2939
+ cellContextMenu && typeof document !== "undefined" && (() => {
2940
+ const menuCol = freshOrderedColumns.find(
2941
+ (c) => c.key === cellContextMenu.columnKey
2942
+ );
2943
+ const isPinnedTop = pinnedTopKeySet.has(cellContextMenu.rowKey);
2944
+ const isPinnedBottom = pinnedBottomKeySet.has(
2945
+ cellContextMenu.rowKey
2946
+ );
2947
+ const hasCopy = menuCol?.copy;
2948
+ const hasRowPin = !!onRowPin;
2949
+ let menuRecord;
2950
+ let menuRowIndex = 0;
2951
+ const allRows = [
2952
+ ...pinnedTopRows,
2953
+ ...displayData,
2954
+ ...pinnedBottomRows
2955
+ ];
2956
+ for (let i = 0; i < allRows.length; i++) {
2957
+ const rk = getRowKey(allRows[i], i);
2958
+ if (rk === cellContextMenu.rowKey) {
2959
+ menuRecord = allRows[i];
2960
+ menuRowIndex = i;
2961
+ break;
2962
+ }
2963
+ }
2964
+ const menuValue = menuRecord && menuCol ? menuRecord[menuCol.dataIndex] : void 0;
2965
+ const btnStyle = {
2966
+ display: "flex",
2967
+ width: "100%",
2968
+ alignItems: "center",
2969
+ gap: 8,
2970
+ background: "none",
2971
+ border: "none",
2972
+ padding: "6px 12px",
2973
+ fontSize: 12,
2974
+ cursor: "pointer",
2975
+ color: "inherit",
2976
+ whiteSpace: "nowrap"
2977
+ };
2978
+ return createPortal2(
2979
+ /* @__PURE__ */ jsxs5(
2980
+ "div",
2981
+ {
2982
+ ref: cellMenuRef,
2983
+ style: {
2984
+ position: "fixed",
2985
+ top: cellContextMenu.y,
2986
+ left: cellContextMenu.x,
2987
+ zIndex: 99999,
2988
+ minWidth: 170,
2989
+ borderRadius: 8,
2990
+ border: "1px solid rgba(128,128,128,0.2)",
2991
+ boxShadow: "0 4px 24px rgba(0,0,0,0.12)",
2992
+ backdropFilter: "blur(16px)",
2993
+ WebkitBackdropFilter: "blur(16px)",
2994
+ backgroundColor: "rgba(128,128,128,0.08)",
2995
+ padding: "4px 0",
2996
+ fontSize: 10
2997
+ },
2998
+ children: [
2999
+ hasRowPin && /* @__PURE__ */ jsxs5(Fragment4, { children: [
3000
+ /* @__PURE__ */ jsxs5(
3001
+ "button",
3002
+ {
3003
+ "data-bt-ctx-item": true,
3004
+ style: btnStyle,
3005
+ onClick: () => {
3006
+ onRowPin(
3007
+ cellContextMenu.rowKey,
3008
+ isPinnedTop ? false : "top"
3009
+ );
3010
+ setCellContextMenu(null);
3011
+ },
3012
+ children: [
3013
+ isPinnedTop ? icons?.pinOff ?? /* @__PURE__ */ jsx5(
3014
+ PinOffIcon,
3015
+ {
3016
+ style: { width: 14, height: 14, flexShrink: 0 }
3017
+ }
3018
+ ) : icons?.pin ?? /* @__PURE__ */ jsx5(
3019
+ PinIcon,
3020
+ {
3021
+ style: { width: 14, height: 14, flexShrink: 0 }
3022
+ }
3023
+ ),
3024
+ isPinnedTop ? "Unpin Row from Top" : "Pin Row to Top"
3025
+ ]
3026
+ }
3027
+ ),
3028
+ /* @__PURE__ */ jsxs5(
3029
+ "button",
3030
+ {
3031
+ "data-bt-ctx-item": true,
3032
+ style: btnStyle,
3033
+ onClick: () => {
3034
+ onRowPin(
3035
+ cellContextMenu.rowKey,
3036
+ isPinnedBottom ? false : "bottom"
3037
+ );
3038
+ setCellContextMenu(null);
3039
+ },
3040
+ children: [
3041
+ isPinnedBottom ? icons?.pinOff ?? /* @__PURE__ */ jsx5(
3042
+ PinOffIcon,
3043
+ {
3044
+ style: { width: 14, height: 14, flexShrink: 0 }
3045
+ }
3046
+ ) : icons?.pin ?? /* @__PURE__ */ jsx5(
3047
+ PinIcon,
3048
+ {
3049
+ style: {
3050
+ width: 14,
3051
+ height: 14,
3052
+ flexShrink: 0,
3053
+ transform: "rotate(180deg)"
3054
+ }
3055
+ }
3056
+ ),
3057
+ isPinnedBottom ? "Unpin Row from Bottom" : "Pin Row to Bottom"
3058
+ ]
3059
+ }
3060
+ )
3061
+ ] }),
3062
+ hasRowPin && hasCopy && /* @__PURE__ */ jsx5(
3063
+ "div",
3064
+ {
3065
+ style: {
3066
+ borderTop: "1px solid rgba(128,128,128,0.2)",
3067
+ margin: "4px 0"
3068
+ }
3069
+ }
3070
+ ),
3071
+ hasCopy && menuRecord && menuCol && /* @__PURE__ */ jsxs5(
3072
+ "button",
3073
+ {
3074
+ "data-bt-ctx-item": true,
3075
+ style: btnStyle,
3076
+ onClick: () => {
3077
+ const text = typeof menuCol.copy === "function" ? menuCol.copy(menuValue, menuRecord, menuRowIndex) : String(menuValue ?? "");
3078
+ navigator.clipboard?.writeText(text);
3079
+ setCellContextMenu(null);
3080
+ },
3081
+ children: [
3082
+ icons?.copy ?? /* @__PURE__ */ jsx5(
3083
+ CopyIcon,
3084
+ {
3085
+ style: { width: 14, height: 14, flexShrink: 0 }
3086
+ }
3087
+ ),
3088
+ "Copy"
3089
+ ]
3090
+ }
3091
+ )
3092
+ ]
3093
+ }
3094
+ ),
3095
+ document.body
3096
+ );
3097
+ })()
2826
3098
  ] });
2827
3099
  }
2828
3100
  export {
@@ -2831,4 +3103,3 @@ export {
2831
3103
  ResizeOverlay_default as ResizeOverlay,
2832
3104
  TableBody_default as TableBody
2833
3105
  };
2834
- //# sourceMappingURL=index.mjs.map