bolt-table 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -232,6 +232,10 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
232
232
  readonly layoutLoading?: boolean;
233
233
  /** Custom React node to render when the table has no data and is not loading. */
234
234
  readonly emptyRenderer?: React$1.ReactNode;
235
+ /** Returns a CSS class name for a given row based on its record and index. Useful for Tailwind or any CSS class-based conditional row styling. */
236
+ readonly rowClassName?: (record: T, index: number) => string;
237
+ /** Returns inline CSS styles for a given row based on its record and index. Useful for dynamic per-row styling. */
238
+ readonly rowStyle?: (record: T, index: number) => React$1.CSSProperties;
235
239
  }
236
240
  interface ClassNamesTypes {
237
241
  /** Applied to all non-pinned column header cells. */
@@ -277,7 +281,7 @@ interface StylesTypes {
277
281
  /** CSS color string for pinned column cells and headers background. */
278
282
  pinnedBg?: string;
279
283
  }
280
- declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
284
+ declare function BoltTable<T extends DataRecord = DataRecord>({ columns: rawInitialColumns, data: rawData, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, rowClassName, rowStyle, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
281
285
 
282
286
  interface DraggableHeaderProps {
283
287
  /** Column definition for this header cell. */
@@ -381,7 +385,11 @@ interface TableBodyProps {
381
385
  gridTemplateColumns?: string;
382
386
  /** Height of the column header row in pixels */
383
387
  headerHeight?: number;
388
+ /** Returns a CSS class name for a given row based on its record and index */
389
+ rowClassName?: (record: DataRecord, index: number) => string;
390
+ /** Returns inline CSS styles for a given row based on its record and index */
391
+ rowStyle?: (record: DataRecord, index: number) => React$1.CSSProperties;
384
392
  }
385
393
  declare const TableBody: React$1.FC<TableBodyProps>;
386
394
 
387
- export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
395
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody };
package/dist/index.d.ts CHANGED
@@ -232,6 +232,10 @@ interface BoltTableProps<T extends DataRecord = DataRecord> {
232
232
  readonly layoutLoading?: boolean;
233
233
  /** Custom React node to render when the table has no data and is not loading. */
234
234
  readonly emptyRenderer?: React$1.ReactNode;
235
+ /** Returns a CSS class name for a given row based on its record and index. Useful for Tailwind or any CSS class-based conditional row styling. */
236
+ readonly rowClassName?: (record: T, index: number) => string;
237
+ /** Returns inline CSS styles for a given row based on its record and index. Useful for dynamic per-row styling. */
238
+ readonly rowStyle?: (record: T, index: number) => React$1.CSSProperties;
235
239
  }
236
240
  interface ClassNamesTypes {
237
241
  /** Applied to all non-pinned column header cells. */
@@ -277,7 +281,7 @@ interface StylesTypes {
277
281
  /** CSS color string for pinned column cells and headers background. */
278
282
  pinnedBg?: string;
279
283
  }
280
- declare function BoltTable<T extends DataRecord = DataRecord>({ columns: initialColumns, data, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
284
+ declare function BoltTable<T extends DataRecord = DataRecord>({ columns: rawInitialColumns, data: rawData, rowHeight, expandedRowHeight, maxExpandedRowHeight, accentColor, className, classNames, styles, gripIcon, hideGripIcon, icons, pagination, onPaginationChange, onColumnResize, onColumnOrderChange, onColumnPin, onColumnHide, rowSelection, rowPinning, onRowPin, expandable, rowKey, onEndReached, onEndReachedThreshold, isLoading, onSortChange, onFilterChange, columnContextMenuItems, autoHeight, layoutLoading, emptyRenderer, rowClassName, rowStyle, }: BoltTableProps<T>): react_jsx_runtime.JSX.Element;
281
285
 
282
286
  interface DraggableHeaderProps {
283
287
  /** Column definition for this header cell. */
@@ -381,7 +385,11 @@ interface TableBodyProps {
381
385
  gridTemplateColumns?: string;
382
386
  /** Height of the column header row in pixels */
383
387
  headerHeight?: number;
388
+ /** Returns a CSS class name for a given row based on its record and index */
389
+ rowClassName?: (record: DataRecord, index: number) => string;
390
+ /** Returns inline CSS styles for a given row based on its record and index */
391
+ rowStyle?: (record: DataRecord, index: number) => React$1.CSSProperties;
384
392
  }
385
393
  declare const TableBody: React$1.FC<TableBodyProps>;
386
394
 
387
- export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, TableBody };
395
+ export { BoltTable, type BoltTableIcons, type CellContextMenuItem, type ClassNamesTypes, type ColumnContextMenuItem, type ColumnType, type DataRecord, DraggableHeader, type ExpandableConfig, type PaginationType, ResizeOverlay, type RowPinningConfig, type RowSelectionConfig, type SortDirection, type StylesTypes, TableBody };
package/dist/index.js CHANGED
@@ -979,7 +979,16 @@ var Cell = import_react3.default.memo(
979
979
  }
980
980
  );
981
981
  }
982
- const content = column.render ? column.render(value, record, rowIndex) : value ?? "";
982
+ let content;
983
+ if (column.render) {
984
+ try {
985
+ content = column.render(value, record, rowIndex);
986
+ } catch {
987
+ content = String(value ?? "");
988
+ }
989
+ } else {
990
+ content = value ?? "";
991
+ }
983
992
  const isSystem = column.key === "__select__" || column.key === "__expand__";
984
993
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
985
994
  "div",
@@ -1085,19 +1094,23 @@ var TableBody = ({
1085
1094
  pinnedTopData = [],
1086
1095
  pinnedBottomData = [],
1087
1096
  gridTemplateColumns,
1088
- headerHeight = 36
1097
+ headerHeight = 36,
1098
+ rowClassName,
1099
+ rowStyle
1089
1100
  }) => {
1090
1101
  const virtualItems = rowVirtualizer.getVirtualItems();
1091
1102
  const totalSize = rowVirtualizer.getTotalSize();
1092
1103
  const selectedKeySet = (0, import_react3.useMemo)(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1104
+ const safeData = data ?? [];
1105
+ const safeColumns = orderedColumns ?? [];
1093
1106
  const allDataForSelection = (0, import_react3.useMemo)(() => {
1094
1107
  if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1095
- return data;
1096
- return [...pinnedTopData, ...data, ...pinnedBottomData];
1097
- }, [pinnedTopData, data, pinnedBottomData]);
1108
+ return safeData;
1109
+ return [...pinnedTopData, ...safeData, ...pinnedBottomData];
1110
+ }, [pinnedTopData, safeData, pinnedBottomData]);
1098
1111
  const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1099
1112
  const columnStyles = (0, import_react3.useMemo)(() => {
1100
- return orderedColumns.map((col, colIndex) => {
1113
+ return safeColumns.map((col, colIndex) => {
1101
1114
  const stickyOffset = columnOffsets.get(col.key);
1102
1115
  const isPinned = Boolean(col.pinned);
1103
1116
  let zIndex = 0;
@@ -1119,10 +1132,12 @@ var TableBody = ({
1119
1132
  }
1120
1133
  return { key: col.key, style, isPinned };
1121
1134
  });
1122
- }, [orderedColumns, columnOffsets, totalSize, styles]);
1135
+ }, [safeColumns, columnOffsets, totalSize, styles]);
1136
+ if (safeData.length === 0 || safeColumns.length === 0) return null;
1123
1137
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1124
1138
  columnStyles.map((colStyle, colIndex) => {
1125
- const col = orderedColumns[colIndex];
1139
+ const col = safeColumns[colIndex];
1140
+ if (!col) return null;
1126
1141
  const hasRender = !!col.render;
1127
1142
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1128
1143
  "div",
@@ -1130,13 +1145,31 @@ var TableBody = ({
1130
1145
  ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1131
1146
  style: colStyle.style,
1132
1147
  children: virtualItems.map((virtualRow) => {
1133
- const row = data[virtualRow.index];
1148
+ const row = safeData[virtualRow.index];
1149
+ if (row == null) return null;
1134
1150
  const rowKey = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1135
1151
  const isSelected = selectedKeySet.has(rowKey);
1136
1152
  const isExpanded = resolvedExpandedKeys?.has(rowKey) ?? false;
1137
1153
  const cellValue = row[col.dataIndex];
1138
1154
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1139
- const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1155
+ let recordFingerprint;
1156
+ if (hasRender && !isRowShimmer) {
1157
+ try {
1158
+ recordFingerprint = JSON.stringify(row);
1159
+ } catch {
1160
+ recordFingerprint = rowKey;
1161
+ }
1162
+ }
1163
+ let rowCls = "";
1164
+ try {
1165
+ rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1166
+ } catch {
1167
+ }
1168
+ let rowSty;
1169
+ try {
1170
+ rowSty = rowStyle ? rowStyle(row, virtualRow.index) : void 0;
1171
+ } catch {
1172
+ }
1140
1173
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1141
1174
  "div",
1142
1175
  {
@@ -1144,12 +1177,14 @@ var TableBody = ({
1144
1177
  "data-column-key": col.key,
1145
1178
  "data-bt-cell": "",
1146
1179
  "data-selected": isSelected || void 0,
1180
+ className: rowCls || void 0,
1147
1181
  style: {
1148
1182
  position: "absolute",
1149
1183
  top: `${virtualRow.start}px`,
1150
1184
  left: 0,
1151
1185
  right: 0,
1152
- height: `${virtualRow.size}px`
1186
+ height: `${virtualRow.size}px`,
1187
+ ...rowSty
1153
1188
  },
1154
1189
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1155
1190
  "div",
@@ -1201,9 +1236,15 @@ var TableBody = ({
1201
1236
  pointerEvents: "none"
1202
1237
  },
1203
1238
  children: virtualItems.map((virtualRow) => {
1204
- const row = data[virtualRow.index];
1239
+ const row = safeData[virtualRow.index];
1240
+ if (row == null) return null;
1205
1241
  const rk = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1206
1242
  if (!(resolvedExpandedKeys?.has(rk) ?? false)) return null;
1243
+ let expandedRenderResult = null;
1244
+ try {
1245
+ expandedRenderResult = expandable.expandedRowRender(row, virtualRow.index, 0, true);
1246
+ } catch {
1247
+ }
1207
1248
  const expandedContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1208
1249
  "div",
1209
1250
  {
@@ -1221,7 +1262,7 @@ var TableBody = ({
1221
1262
  ...maxExpandedRowHeight ? { maxHeight: `${maxExpandedRowHeight}px` } : void 0,
1222
1263
  ...styles?.expandedRow
1223
1264
  },
1224
- children: expandable.expandedRowRender(row, virtualRow.index, 0, true)
1265
+ children: expandedRenderResult
1225
1266
  }
1226
1267
  );
1227
1268
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1268,20 +1309,32 @@ var TableBody = ({
1268
1309
  boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1269
1310
  },
1270
1311
  children: pinnedTopData.map((row, rowIdx) => {
1312
+ if (row == null) return null;
1271
1313
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1272
1314
  const isSelected = selectedKeySet.has(rk);
1273
1315
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1316
+ let rowCls = "";
1317
+ try {
1318
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1319
+ } catch {
1320
+ }
1321
+ let rowSty;
1322
+ try {
1323
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1324
+ } catch {
1325
+ }
1274
1326
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1275
1327
  "div",
1276
1328
  {
1277
- className: classNames?.pinnedRow ?? "",
1329
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1278
1330
  style: {
1279
1331
  display: "grid",
1280
1332
  gridTemplateColumns: gridTemplateColumns ?? "",
1281
1333
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1282
- ...styles?.pinnedRow
1334
+ ...styles?.pinnedRow,
1335
+ ...rowSty
1283
1336
  },
1284
- children: orderedColumns.map((col) => {
1337
+ children: safeColumns.map((col) => {
1285
1338
  const cellValue = row[col.dataIndex];
1286
1339
  const stickyOffset = columnOffsets.get(col.key);
1287
1340
  const isPinned = Boolean(col.pinned);
@@ -1289,7 +1342,14 @@ var TableBody = ({
1289
1342
  if (col.key === "__select__" || col.key === "__expand__")
1290
1343
  zIndex = 11;
1291
1344
  else if (isPinned) zIndex = 2;
1292
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1345
+ let recordFingerprint;
1346
+ if (col.render) {
1347
+ try {
1348
+ recordFingerprint = JSON.stringify(row);
1349
+ } catch {
1350
+ recordFingerprint = rk;
1351
+ }
1352
+ }
1293
1353
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1294
1354
  "div",
1295
1355
  {
@@ -1373,20 +1433,32 @@ var TableBody = ({
1373
1433
  boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1374
1434
  },
1375
1435
  children: pinnedBottomData.map((row, rowIdx) => {
1436
+ if (row == null) return null;
1376
1437
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1377
1438
  const isSelected = selectedKeySet.has(rk);
1378
1439
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1440
+ let rowCls = "";
1441
+ try {
1442
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1443
+ } catch {
1444
+ }
1445
+ let rowSty;
1446
+ try {
1447
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1448
+ } catch {
1449
+ }
1379
1450
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1380
1451
  "div",
1381
1452
  {
1382
- className: classNames?.pinnedRow ?? "",
1453
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1383
1454
  style: {
1384
1455
  display: "grid",
1385
1456
  gridTemplateColumns: gridTemplateColumns ?? "",
1386
1457
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1387
- ...styles?.pinnedRow
1458
+ ...styles?.pinnedRow,
1459
+ ...rowSty
1388
1460
  },
1389
- children: orderedColumns.map((col) => {
1461
+ children: safeColumns.map((col) => {
1390
1462
  const cellValue = row[col.dataIndex];
1391
1463
  const stickyOffset = columnOffsets.get(col.key);
1392
1464
  const isPinned = Boolean(col.pinned);
@@ -1394,7 +1466,14 @@ var TableBody = ({
1394
1466
  if (col.key === "__select__" || col.key === "__expand__")
1395
1467
  zIndex = 11;
1396
1468
  else if (isPinned) zIndex = 2;
1397
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1469
+ let recordFingerprint;
1470
+ if (col.render) {
1471
+ try {
1472
+ recordFingerprint = JSON.stringify(row);
1473
+ } catch {
1474
+ recordFingerprint = rk;
1475
+ }
1476
+ }
1398
1477
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1399
1478
  "div",
1400
1479
  {
@@ -1470,9 +1549,11 @@ function arrayMove(arr, from, to) {
1470
1549
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1471
1550
  var EMPTY_CLASSNAMES = {};
1472
1551
  var EMPTY_STYLES = {};
1552
+ var STABLE_EMPTY_DATA = [];
1553
+ var STABLE_EMPTY_COLS = [];
1473
1554
  function BoltTable({
1474
- columns: initialColumns,
1475
- data,
1555
+ columns: rawInitialColumns,
1556
+ data: rawData,
1476
1557
  rowHeight = 40,
1477
1558
  expandedRowHeight = 200,
1478
1559
  maxExpandedRowHeight,
@@ -1502,8 +1583,22 @@ function BoltTable({
1502
1583
  columnContextMenuItems,
1503
1584
  autoHeight = true,
1504
1585
  layoutLoading,
1505
- emptyRenderer
1586
+ emptyRenderer,
1587
+ rowClassName,
1588
+ rowStyle
1506
1589
  }) {
1590
+ const data = (0, import_react4.useMemo)(() => {
1591
+ if (!Array.isArray(rawData)) return STABLE_EMPTY_DATA;
1592
+ const filtered = rawData.filter((item) => item != null);
1593
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_DATA;
1594
+ }, [rawData]);
1595
+ const initialColumns = (0, import_react4.useMemo)(() => {
1596
+ if (!Array.isArray(rawInitialColumns)) return STABLE_EMPTY_COLS;
1597
+ const filtered = rawInitialColumns.filter(
1598
+ (col) => col != null && typeof col.key === "string"
1599
+ );
1600
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_COLS;
1601
+ }, [rawInitialColumns]);
1507
1602
  const [columns, setColumns] = (0, import_react4.useState)(initialColumns);
1508
1603
  const [columnOrder, setColumnOrder] = (0, import_react4.useState)(
1509
1604
  () => initialColumns.map((c) => c.key)
@@ -1539,10 +1634,15 @@ function BoltTable({
1539
1634
  [columns, columnWidths]
1540
1635
  );
1541
1636
  const [internalExpandedKeys, setInternalExpandedKeys] = (0, import_react4.useState)(() => {
1542
- if (expandable?.defaultExpandAllRows) {
1637
+ if (expandable?.defaultExpandAllRows && data.length > 0) {
1543
1638
  return new Set(
1544
1639
  data.map((row, idx) => {
1545
- if (typeof rowKey === "function") return rowKey(row);
1640
+ if (row == null) return idx;
1641
+ try {
1642
+ if (typeof rowKey === "function") return rowKey(row);
1643
+ } catch {
1644
+ return idx;
1645
+ }
1546
1646
  if (typeof rowKey === "string")
1547
1647
  return row[rowKey] ?? idx;
1548
1648
  return idx;
@@ -1576,21 +1676,39 @@ function BoltTable({
1576
1676
  }, []);
1577
1677
  const getRowKey = (0, import_react4.useCallback)(
1578
1678
  (record, index) => {
1579
- if (typeof rowKey === "function") return String(rowKey(record));
1580
- if (typeof rowKey === "string") {
1581
- const val = record[rowKey];
1582
- return val !== void 0 && val !== null ? String(val) : String(index);
1679
+ if (record == null) return String(index);
1680
+ try {
1681
+ if (typeof rowKey === "function") return String(rowKey(record));
1682
+ if (typeof rowKey === "string") {
1683
+ const val = record[rowKey];
1684
+ return val != null ? String(val) : String(index);
1685
+ }
1686
+ } catch {
1687
+ return String(index);
1583
1688
  }
1584
1689
  return String(index);
1585
1690
  },
1586
1691
  [rowKey]
1587
1692
  );
1588
1693
  const normalizedSelectedKeys = (0, import_react4.useMemo)(
1589
- () => (rowSelection?.selectedRowKeys ?? []).map((k) => String(k)),
1694
+ () => {
1695
+ const keys = rowSelection?.selectedRowKeys;
1696
+ if (!Array.isArray(keys)) return [];
1697
+ return keys.filter((k) => k != null).map((k) => String(k));
1698
+ },
1590
1699
  [rowSelection?.selectedRowKeys]
1591
1700
  );
1701
+ const getRowKeyRef = (0, import_react4.useRef)(getRowKey);
1702
+ getRowKeyRef.current = getRowKey;
1703
+ const resolvedExpandedKeysRef = (0, import_react4.useRef)(resolvedExpandedKeys);
1704
+ resolvedExpandedKeysRef.current = resolvedExpandedKeys;
1705
+ const toggleExpandRef = (0, import_react4.useRef)(toggleExpand);
1706
+ toggleExpandRef.current = toggleExpand;
1707
+ const iconsRef = (0, import_react4.useRef)(icons);
1708
+ iconsRef.current = icons;
1709
+ const hasExpandable = !!expandable?.rowExpandable;
1592
1710
  const columnsWithExpand = (0, import_react4.useMemo)(() => {
1593
- if (!expandable?.rowExpandable) return columnsWithPersistedWidths;
1711
+ if (!hasExpandable) return columnsWithPersistedWidths;
1594
1712
  const expandColumn = {
1595
1713
  key: "__expand__",
1596
1714
  dataIndex: "__expand__",
@@ -1599,17 +1717,17 @@ function BoltTable({
1599
1717
  pinned: "left",
1600
1718
  hidden: false,
1601
1719
  render: (_, record, index) => {
1602
- const key = getRowKey(record, index);
1603
- const canExpand = expandable.rowExpandable?.(record) ?? true;
1604
- const isExpanded = resolvedExpandedKeys.has(key);
1720
+ const key = getRowKeyRef.current(record, index);
1721
+ const canExpand = expandableRef.current?.rowExpandable?.(record) ?? true;
1722
+ const isExpanded = resolvedExpandedKeysRef.current.has(key);
1605
1723
  if (!canExpand)
1606
1724
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { display: "inline-block", width: 16 } });
1607
- if (typeof expandable.expandIcon === "function") {
1608
- return expandable.expandIcon({
1725
+ if (typeof expandableRef.current?.expandIcon === "function") {
1726
+ return expandableRef.current.expandIcon({
1609
1727
  expanded: isExpanded,
1610
1728
  onExpand: (_2, e) => {
1611
1729
  e.stopPropagation();
1612
- toggleExpand(key);
1730
+ toggleExpandRef.current(key);
1613
1731
  },
1614
1732
  record
1615
1733
  });
@@ -1619,7 +1737,7 @@ function BoltTable({
1619
1737
  {
1620
1738
  onClick: (e) => {
1621
1739
  e.stopPropagation();
1622
- toggleExpand(key);
1740
+ toggleExpandRef.current(key);
1623
1741
  },
1624
1742
  style: {
1625
1743
  display: "flex",
@@ -1632,20 +1750,13 @@ function BoltTable({
1632
1750
  borderRadius: "3px",
1633
1751
  color: accentColor
1634
1752
  },
1635
- children: isExpanded ? icons?.chevronDown ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronDownIcon, { style: { width: 14, height: 14 } }) : icons?.chevronRight ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronRightIcon, { style: { width: 14, height: 14 } })
1753
+ children: isExpanded ? iconsRef.current?.chevronDown ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronDownIcon, { style: { width: 14, height: 14 } }) : iconsRef.current?.chevronRight ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronRightIcon, { style: { width: 14, height: 14 } })
1636
1754
  }
1637
1755
  );
1638
1756
  }
1639
1757
  };
1640
1758
  return [expandColumn, ...columnsWithPersistedWidths];
1641
- }, [
1642
- expandable,
1643
- columnsWithPersistedWidths,
1644
- getRowKey,
1645
- resolvedExpandedKeys,
1646
- toggleExpand,
1647
- accentColor
1648
- ]);
1759
+ }, [hasExpandable, columnsWithPersistedWidths, accentColor]);
1649
1760
  const columnsWithSelection = (0, import_react4.useMemo)(() => {
1650
1761
  if (!rowSelection) return columnsWithExpand;
1651
1762
  const selectionColumn = {
@@ -2005,36 +2116,52 @@ function BoltTable({
2005
2116
  if (!onFilterChangeRef.current) {
2006
2117
  const filterKeys = Object.keys(columnFilters);
2007
2118
  if (filterKeys.length > 0) {
2008
- result = result.filter(
2009
- (row) => filterKeys.every((key) => {
2010
- const col = columnsLookupRef.current.find((c) => c.key === key);
2011
- if (typeof col?.filterFn === "function") {
2012
- return col.filterFn(columnFilters[key], row, col.dataIndex);
2119
+ result = result.filter((row) => {
2120
+ if (row == null) return false;
2121
+ return filterKeys.every((key) => {
2122
+ try {
2123
+ const col = columnsLookupRef.current.find((c) => c.key === key);
2124
+ if (typeof col?.filterFn === "function") {
2125
+ return col.filterFn(columnFilters[key], row, col.dataIndex);
2126
+ }
2127
+ const cellVal = String(row[key] ?? "").toLowerCase();
2128
+ return cellVal.includes(columnFilters[key].toLowerCase());
2129
+ } catch {
2130
+ return true;
2013
2131
  }
2014
- const cellVal = String(row[key] ?? "").toLowerCase();
2015
- return cellVal.includes(columnFilters[key].toLowerCase());
2016
- })
2017
- );
2132
+ });
2133
+ });
2018
2134
  }
2019
2135
  }
2020
2136
  if (!onSortChangeRef.current && sortState.key && sortState.direction) {
2021
2137
  const dir = sortState.direction === "asc" ? 1 : -1;
2022
2138
  const key = sortState.key;
2023
2139
  const col = columnsLookupRef.current.find((c) => c.key === key);
2024
- if (typeof col?.sorter === "function") {
2025
- const sorterFn = col.sorter;
2026
- result = [...result].sort((a, b) => sorterFn(a, b) * dir);
2027
- } else {
2028
- result = [...result].sort((a, b) => {
2029
- const aVal = a[key];
2030
- const bVal = b[key];
2031
- if (aVal == null && bVal == null) return 0;
2032
- if (aVal == null) return 1;
2033
- if (bVal == null) return -1;
2034
- if (typeof aVal === "number" && typeof bVal === "number")
2035
- return (aVal - bVal) * dir;
2036
- return String(aVal).localeCompare(String(bVal)) * dir;
2037
- });
2140
+ try {
2141
+ if (typeof col?.sorter === "function") {
2142
+ const sorterFn = col.sorter;
2143
+ result = [...result].sort((a, b) => {
2144
+ if (a == null && b == null) return 0;
2145
+ if (a == null) return 1;
2146
+ if (b == null) return -1;
2147
+ return sorterFn(a, b) * dir;
2148
+ });
2149
+ } else {
2150
+ result = [...result].sort((a, b) => {
2151
+ if (a == null && b == null) return 0;
2152
+ if (a == null) return 1;
2153
+ if (b == null) return -1;
2154
+ const aVal = a[key];
2155
+ const bVal = b[key];
2156
+ if (aVal == null && bVal == null) return 0;
2157
+ if (aVal == null) return 1;
2158
+ if (bVal == null) return -1;
2159
+ if (typeof aVal === "number" && typeof bVal === "number")
2160
+ return (aVal - bVal) * dir;
2161
+ return String(aVal).localeCompare(String(bVal)) * dir;
2162
+ });
2163
+ }
2164
+ } catch {
2038
2165
  }
2039
2166
  }
2040
2167
  return result;
@@ -2053,6 +2180,7 @@ function BoltTable({
2053
2180
  const bottomMap = /* @__PURE__ */ new Map();
2054
2181
  const rest = [];
2055
2182
  processedData.forEach((row, idx) => {
2183
+ if (row == null) return;
2056
2184
  const key = getRowKey(row, idx);
2057
2185
  if (topKeySet.has(key)) topMap.set(key, row);
2058
2186
  else if (bottomKeySet.has(key)) bottomMap.set(key, row);
@@ -2114,7 +2242,8 @@ function BoltTable({
2114
2242
  const DEFAULT_PAGE_SIZE = 15;
2115
2243
  const [internalPage, setInternalPage] = (0, import_react4.useState)(1);
2116
2244
  const [internalPageSize, setInternalPageSize] = (0, import_react4.useState)(DEFAULT_PAGE_SIZE);
2117
- const autoPagination = pagination === void 0 && data.length > DEFAULT_PAGE_SIZE;
2245
+ const dataLength = data.length;
2246
+ const autoPagination = pagination === void 0 && dataLength > DEFAULT_PAGE_SIZE;
2118
2247
  const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2119
2248
  const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2120
2249
  const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
@@ -2127,26 +2256,27 @@ function BoltTable({
2127
2256
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2128
2257
  const shimmerCount = pgEnabled ? pgSize : 15;
2129
2258
  const showShimmer = isLoading && processedData.length === 0;
2259
+ const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2130
2260
  const shimmerData = (0, import_react4.useMemo)(() => {
2131
2261
  if (!showShimmer) return null;
2132
2262
  return Array.from(
2133
2263
  { length: shimmerCount },
2134
2264
  (_, i) => ({
2135
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2265
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2136
2266
  })
2137
2267
  );
2138
- }, [showShimmer, shimmerCount, rowKey]);
2268
+ }, [showShimmer, shimmerCount, shimmerRowKeyField]);
2139
2269
  const INFINITE_SHIMMER_COUNT = 5;
2270
+ const showInfiniteShimmer = isLoading && paginatedData.length > 0 && !showShimmer && !pgEnabled;
2140
2271
  const infiniteLoadingShimmer = (0, import_react4.useMemo)(() => {
2141
- if (!isLoading || paginatedData.length === 0 || showShimmer) return null;
2142
- if (pgEnabled) return null;
2272
+ if (!showInfiniteShimmer) return null;
2143
2273
  return Array.from(
2144
2274
  { length: INFINITE_SHIMMER_COUNT },
2145
2275
  (_, i) => ({
2146
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2276
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2147
2277
  })
2148
2278
  );
2149
- }, [isLoading, paginatedData.length, showShimmer, pgEnabled, rowKey]);
2279
+ }, [showInfiniteShimmer, shimmerRowKeyField]);
2150
2280
  const displayData = (0, import_react4.useMemo)(() => {
2151
2281
  if (shimmerData) return shimmerData;
2152
2282
  if (infiniteLoadingShimmer)
@@ -2176,13 +2306,20 @@ function BoltTable({
2176
2306
  getScrollElement: () => tableAreaRef.current,
2177
2307
  estimateSize: (index) => {
2178
2308
  if (shimmerData) return rowHeight;
2179
- const key = getRowKey(displayData[index], index);
2309
+ const item = displayData[index];
2310
+ if (!item) return rowHeight;
2311
+ const key = getRowKey(item, index);
2180
2312
  if (!resolvedExpandedKeys.has(key)) return rowHeight;
2181
2313
  const cached = measuredExpandedHeights.current.get(key);
2182
2314
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
2183
2315
  },
2184
2316
  overscan: 5,
2185
- getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index),
2317
+ getItemKey: (index) => {
2318
+ if (shimmerData) return `__shimmer_${index}__`;
2319
+ const item = displayData[index];
2320
+ if (!item) return `__fallback_${index}__`;
2321
+ return getRowKey(item, index);
2322
+ },
2186
2323
  paddingStart: pinnedTopHeight,
2187
2324
  paddingEnd: pinnedBottomHeight
2188
2325
  });
@@ -2202,7 +2339,7 @@ function BoltTable({
2202
2339
  endReachedFiredRef.current = false;
2203
2340
  }, 200);
2204
2341
  return () => clearTimeout(timer);
2205
- }, [data.length, isLoading]);
2342
+ }, [dataLength, isLoading]);
2206
2343
  import_react4.default.useEffect(() => {
2207
2344
  const el = tableAreaRef.current;
2208
2345
  if (!el) return;
@@ -2224,7 +2361,7 @@ function BoltTable({
2224
2361
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2225
2362
  const currentPage = pgCurrent;
2226
2363
  const pageSize = pgSize;
2227
- const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2364
+ const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : dataLength) : dataLength;
2228
2365
  const lastKnownTotalRef = (0, import_react4.useRef)(0);
2229
2366
  if (!isLoading || rawTotal > 0) {
2230
2367
  lastKnownTotalRef.current = rawTotal;
@@ -2232,8 +2369,8 @@ function BoltTable({
2232
2369
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2233
2370
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2234
2371
  import_react4.default.useEffect(() => {
2235
- if (internalPage > totalPages) {
2236
- setInternalPage(Math.max(1, totalPages));
2372
+ if (totalPages > 0 && internalPage > totalPages) {
2373
+ setInternalPage(totalPages);
2237
2374
  }
2238
2375
  }, [totalPages, internalPage]);
2239
2376
  const handlePageChange = (p) => {
@@ -2568,10 +2705,10 @@ function BoltTable({
2568
2705
  "input",
2569
2706
  {
2570
2707
  type: "checkbox",
2571
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2708
+ checked: dataLength > 0 && normalizedSelectedKeys.length === dataLength,
2572
2709
  ref: (input) => {
2573
2710
  if (input) {
2574
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2711
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < dataLength;
2575
2712
  }
2576
2713
  },
2577
2714
  onChange: (e) => {
@@ -2719,7 +2856,9 @@ function BoltTable({
2719
2856
  pinnedTopData: pinnedTopRows,
2720
2857
  pinnedBottomData: pinnedBottomRows,
2721
2858
  gridTemplateColumns,
2722
- headerHeight: HEADER_HEIGHT
2859
+ headerHeight: HEADER_HEIGHT,
2860
+ rowClassName,
2861
+ rowStyle
2723
2862
  }
2724
2863
  )
2725
2864
  ]
@@ -3042,6 +3181,7 @@ function BoltTable({
3042
3181
  ...pinnedBottomRows
3043
3182
  ];
3044
3183
  for (let i = 0; i < allRows.length; i++) {
3184
+ if (allRows[i] == null) continue;
3045
3185
  const rk = getRowKey(allRows[i], i);
3046
3186
  if (rk === cellContextMenu.rowKey) {
3047
3187
  menuRecord = allRows[i];
package/dist/index.mjs CHANGED
@@ -945,7 +945,16 @@ var Cell = React3.memo(
945
945
  }
946
946
  );
947
947
  }
948
- const content = column.render ? column.render(value, record, rowIndex) : value ?? "";
948
+ let content;
949
+ if (column.render) {
950
+ try {
951
+ content = column.render(value, record, rowIndex);
952
+ } catch {
953
+ content = String(value ?? "");
954
+ }
955
+ } else {
956
+ content = value ?? "";
957
+ }
949
958
  const isSystem = column.key === "__select__" || column.key === "__expand__";
950
959
  return /* @__PURE__ */ jsx4(
951
960
  "div",
@@ -1051,19 +1060,23 @@ var TableBody = ({
1051
1060
  pinnedTopData = [],
1052
1061
  pinnedBottomData = [],
1053
1062
  gridTemplateColumns,
1054
- headerHeight = 36
1063
+ headerHeight = 36,
1064
+ rowClassName,
1065
+ rowStyle
1055
1066
  }) => {
1056
1067
  const virtualItems = rowVirtualizer.getVirtualItems();
1057
1068
  const totalSize = rowVirtualizer.getTotalSize();
1058
1069
  const selectedKeySet = useMemo(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1070
+ const safeData = data ?? [];
1071
+ const safeColumns = orderedColumns ?? [];
1059
1072
  const allDataForSelection = useMemo(() => {
1060
1073
  if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1061
- return data;
1062
- return [...pinnedTopData, ...data, ...pinnedBottomData];
1063
- }, [pinnedTopData, data, pinnedBottomData]);
1074
+ return safeData;
1075
+ return [...pinnedTopData, ...safeData, ...pinnedBottomData];
1076
+ }, [pinnedTopData, safeData, pinnedBottomData]);
1064
1077
  const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1065
1078
  const columnStyles = useMemo(() => {
1066
- return orderedColumns.map((col, colIndex) => {
1079
+ return safeColumns.map((col, colIndex) => {
1067
1080
  const stickyOffset = columnOffsets.get(col.key);
1068
1081
  const isPinned = Boolean(col.pinned);
1069
1082
  let zIndex = 0;
@@ -1085,10 +1098,12 @@ var TableBody = ({
1085
1098
  }
1086
1099
  return { key: col.key, style, isPinned };
1087
1100
  });
1088
- }, [orderedColumns, columnOffsets, totalSize, styles]);
1101
+ }, [safeColumns, columnOffsets, totalSize, styles]);
1102
+ if (safeData.length === 0 || safeColumns.length === 0) return null;
1089
1103
  return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1090
1104
  columnStyles.map((colStyle, colIndex) => {
1091
- const col = orderedColumns[colIndex];
1105
+ const col = safeColumns[colIndex];
1106
+ if (!col) return null;
1092
1107
  const hasRender = !!col.render;
1093
1108
  return /* @__PURE__ */ jsx4(
1094
1109
  "div",
@@ -1096,13 +1111,31 @@ var TableBody = ({
1096
1111
  ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1097
1112
  style: colStyle.style,
1098
1113
  children: virtualItems.map((virtualRow) => {
1099
- const row = data[virtualRow.index];
1114
+ const row = safeData[virtualRow.index];
1115
+ if (row == null) return null;
1100
1116
  const rowKey = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1101
1117
  const isSelected = selectedKeySet.has(rowKey);
1102
1118
  const isExpanded = resolvedExpandedKeys?.has(rowKey) ?? false;
1103
1119
  const cellValue = row[col.dataIndex];
1104
1120
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1105
- const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1121
+ let recordFingerprint;
1122
+ if (hasRender && !isRowShimmer) {
1123
+ try {
1124
+ recordFingerprint = JSON.stringify(row);
1125
+ } catch {
1126
+ recordFingerprint = rowKey;
1127
+ }
1128
+ }
1129
+ let rowCls = "";
1130
+ try {
1131
+ rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1132
+ } catch {
1133
+ }
1134
+ let rowSty;
1135
+ try {
1136
+ rowSty = rowStyle ? rowStyle(row, virtualRow.index) : void 0;
1137
+ } catch {
1138
+ }
1106
1139
  return /* @__PURE__ */ jsx4(
1107
1140
  "div",
1108
1141
  {
@@ -1110,12 +1143,14 @@ var TableBody = ({
1110
1143
  "data-column-key": col.key,
1111
1144
  "data-bt-cell": "",
1112
1145
  "data-selected": isSelected || void 0,
1146
+ className: rowCls || void 0,
1113
1147
  style: {
1114
1148
  position: "absolute",
1115
1149
  top: `${virtualRow.start}px`,
1116
1150
  left: 0,
1117
1151
  right: 0,
1118
- height: `${virtualRow.size}px`
1152
+ height: `${virtualRow.size}px`,
1153
+ ...rowSty
1119
1154
  },
1120
1155
  children: /* @__PURE__ */ jsx4(
1121
1156
  "div",
@@ -1167,9 +1202,15 @@ var TableBody = ({
1167
1202
  pointerEvents: "none"
1168
1203
  },
1169
1204
  children: virtualItems.map((virtualRow) => {
1170
- const row = data[virtualRow.index];
1205
+ const row = safeData[virtualRow.index];
1206
+ if (row == null) return null;
1171
1207
  const rk = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1172
1208
  if (!(resolvedExpandedKeys?.has(rk) ?? false)) return null;
1209
+ let expandedRenderResult = null;
1210
+ try {
1211
+ expandedRenderResult = expandable.expandedRowRender(row, virtualRow.index, 0, true);
1212
+ } catch {
1213
+ }
1173
1214
  const expandedContent = /* @__PURE__ */ jsx4(
1174
1215
  "div",
1175
1216
  {
@@ -1187,7 +1228,7 @@ var TableBody = ({
1187
1228
  ...maxExpandedRowHeight ? { maxHeight: `${maxExpandedRowHeight}px` } : void 0,
1188
1229
  ...styles?.expandedRow
1189
1230
  },
1190
- children: expandable.expandedRowRender(row, virtualRow.index, 0, true)
1231
+ children: expandedRenderResult
1191
1232
  }
1192
1233
  );
1193
1234
  return /* @__PURE__ */ jsx4(
@@ -1234,20 +1275,32 @@ var TableBody = ({
1234
1275
  boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1235
1276
  },
1236
1277
  children: pinnedTopData.map((row, rowIdx) => {
1278
+ if (row == null) return null;
1237
1279
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1238
1280
  const isSelected = selectedKeySet.has(rk);
1239
1281
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1282
+ let rowCls = "";
1283
+ try {
1284
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1285
+ } catch {
1286
+ }
1287
+ let rowSty;
1288
+ try {
1289
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1290
+ } catch {
1291
+ }
1240
1292
  return /* @__PURE__ */ jsx4(
1241
1293
  "div",
1242
1294
  {
1243
- className: classNames?.pinnedRow ?? "",
1295
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1244
1296
  style: {
1245
1297
  display: "grid",
1246
1298
  gridTemplateColumns: gridTemplateColumns ?? "",
1247
1299
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1248
- ...styles?.pinnedRow
1300
+ ...styles?.pinnedRow,
1301
+ ...rowSty
1249
1302
  },
1250
- children: orderedColumns.map((col) => {
1303
+ children: safeColumns.map((col) => {
1251
1304
  const cellValue = row[col.dataIndex];
1252
1305
  const stickyOffset = columnOffsets.get(col.key);
1253
1306
  const isPinned = Boolean(col.pinned);
@@ -1255,7 +1308,14 @@ var TableBody = ({
1255
1308
  if (col.key === "__select__" || col.key === "__expand__")
1256
1309
  zIndex = 11;
1257
1310
  else if (isPinned) zIndex = 2;
1258
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1311
+ let recordFingerprint;
1312
+ if (col.render) {
1313
+ try {
1314
+ recordFingerprint = JSON.stringify(row);
1315
+ } catch {
1316
+ recordFingerprint = rk;
1317
+ }
1318
+ }
1259
1319
  return /* @__PURE__ */ jsx4(
1260
1320
  "div",
1261
1321
  {
@@ -1339,20 +1399,32 @@ var TableBody = ({
1339
1399
  boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1340
1400
  },
1341
1401
  children: pinnedBottomData.map((row, rowIdx) => {
1402
+ if (row == null) return null;
1342
1403
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1343
1404
  const isSelected = selectedKeySet.has(rk);
1344
1405
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1406
+ let rowCls = "";
1407
+ try {
1408
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1409
+ } catch {
1410
+ }
1411
+ let rowSty;
1412
+ try {
1413
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1414
+ } catch {
1415
+ }
1345
1416
  return /* @__PURE__ */ jsx4(
1346
1417
  "div",
1347
1418
  {
1348
- className: classNames?.pinnedRow ?? "",
1419
+ className: `${classNames?.pinnedRow ?? ""} ${rowCls}`.trim() || void 0,
1349
1420
  style: {
1350
1421
  display: "grid",
1351
1422
  gridTemplateColumns: gridTemplateColumns ?? "",
1352
1423
  minWidth: totalTableWidth ? `${totalTableWidth}px` : void 0,
1353
- ...styles?.pinnedRow
1424
+ ...styles?.pinnedRow,
1425
+ ...rowSty
1354
1426
  },
1355
- children: orderedColumns.map((col) => {
1427
+ children: safeColumns.map((col) => {
1356
1428
  const cellValue = row[col.dataIndex];
1357
1429
  const stickyOffset = columnOffsets.get(col.key);
1358
1430
  const isPinned = Boolean(col.pinned);
@@ -1360,7 +1432,14 @@ var TableBody = ({
1360
1432
  if (col.key === "__select__" || col.key === "__expand__")
1361
1433
  zIndex = 11;
1362
1434
  else if (isPinned) zIndex = 2;
1363
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1435
+ let recordFingerprint;
1436
+ if (col.render) {
1437
+ try {
1438
+ recordFingerprint = JSON.stringify(row);
1439
+ } catch {
1440
+ recordFingerprint = rk;
1441
+ }
1442
+ }
1364
1443
  return /* @__PURE__ */ jsx4(
1365
1444
  "div",
1366
1445
  {
@@ -1436,9 +1515,11 @@ function arrayMove(arr, from, to) {
1436
1515
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1437
1516
  var EMPTY_CLASSNAMES = {};
1438
1517
  var EMPTY_STYLES = {};
1518
+ var STABLE_EMPTY_DATA = [];
1519
+ var STABLE_EMPTY_COLS = [];
1439
1520
  function BoltTable({
1440
- columns: initialColumns,
1441
- data,
1521
+ columns: rawInitialColumns,
1522
+ data: rawData,
1442
1523
  rowHeight = 40,
1443
1524
  expandedRowHeight = 200,
1444
1525
  maxExpandedRowHeight,
@@ -1468,8 +1549,22 @@ function BoltTable({
1468
1549
  columnContextMenuItems,
1469
1550
  autoHeight = true,
1470
1551
  layoutLoading,
1471
- emptyRenderer
1552
+ emptyRenderer,
1553
+ rowClassName,
1554
+ rowStyle
1472
1555
  }) {
1556
+ const data = useMemo2(() => {
1557
+ if (!Array.isArray(rawData)) return STABLE_EMPTY_DATA;
1558
+ const filtered = rawData.filter((item) => item != null);
1559
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_DATA;
1560
+ }, [rawData]);
1561
+ const initialColumns = useMemo2(() => {
1562
+ if (!Array.isArray(rawInitialColumns)) return STABLE_EMPTY_COLS;
1563
+ const filtered = rawInitialColumns.filter(
1564
+ (col) => col != null && typeof col.key === "string"
1565
+ );
1566
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_COLS;
1567
+ }, [rawInitialColumns]);
1473
1568
  const [columns, setColumns] = useState2(initialColumns);
1474
1569
  const [columnOrder, setColumnOrder] = useState2(
1475
1570
  () => initialColumns.map((c) => c.key)
@@ -1505,10 +1600,15 @@ function BoltTable({
1505
1600
  [columns, columnWidths]
1506
1601
  );
1507
1602
  const [internalExpandedKeys, setInternalExpandedKeys] = useState2(() => {
1508
- if (expandable?.defaultExpandAllRows) {
1603
+ if (expandable?.defaultExpandAllRows && data.length > 0) {
1509
1604
  return new Set(
1510
1605
  data.map((row, idx) => {
1511
- if (typeof rowKey === "function") return rowKey(row);
1606
+ if (row == null) return idx;
1607
+ try {
1608
+ if (typeof rowKey === "function") return rowKey(row);
1609
+ } catch {
1610
+ return idx;
1611
+ }
1512
1612
  if (typeof rowKey === "string")
1513
1613
  return row[rowKey] ?? idx;
1514
1614
  return idx;
@@ -1542,21 +1642,39 @@ function BoltTable({
1542
1642
  }, []);
1543
1643
  const getRowKey = useCallback(
1544
1644
  (record, index) => {
1545
- if (typeof rowKey === "function") return String(rowKey(record));
1546
- if (typeof rowKey === "string") {
1547
- const val = record[rowKey];
1548
- return val !== void 0 && val !== null ? String(val) : String(index);
1645
+ if (record == null) return String(index);
1646
+ try {
1647
+ if (typeof rowKey === "function") return String(rowKey(record));
1648
+ if (typeof rowKey === "string") {
1649
+ const val = record[rowKey];
1650
+ return val != null ? String(val) : String(index);
1651
+ }
1652
+ } catch {
1653
+ return String(index);
1549
1654
  }
1550
1655
  return String(index);
1551
1656
  },
1552
1657
  [rowKey]
1553
1658
  );
1554
1659
  const normalizedSelectedKeys = useMemo2(
1555
- () => (rowSelection?.selectedRowKeys ?? []).map((k) => String(k)),
1660
+ () => {
1661
+ const keys = rowSelection?.selectedRowKeys;
1662
+ if (!Array.isArray(keys)) return [];
1663
+ return keys.filter((k) => k != null).map((k) => String(k));
1664
+ },
1556
1665
  [rowSelection?.selectedRowKeys]
1557
1666
  );
1667
+ const getRowKeyRef = useRef4(getRowKey);
1668
+ getRowKeyRef.current = getRowKey;
1669
+ const resolvedExpandedKeysRef = useRef4(resolvedExpandedKeys);
1670
+ resolvedExpandedKeysRef.current = resolvedExpandedKeys;
1671
+ const toggleExpandRef = useRef4(toggleExpand);
1672
+ toggleExpandRef.current = toggleExpand;
1673
+ const iconsRef = useRef4(icons);
1674
+ iconsRef.current = icons;
1675
+ const hasExpandable = !!expandable?.rowExpandable;
1558
1676
  const columnsWithExpand = useMemo2(() => {
1559
- if (!expandable?.rowExpandable) return columnsWithPersistedWidths;
1677
+ if (!hasExpandable) return columnsWithPersistedWidths;
1560
1678
  const expandColumn = {
1561
1679
  key: "__expand__",
1562
1680
  dataIndex: "__expand__",
@@ -1565,17 +1683,17 @@ function BoltTable({
1565
1683
  pinned: "left",
1566
1684
  hidden: false,
1567
1685
  render: (_, record, index) => {
1568
- const key = getRowKey(record, index);
1569
- const canExpand = expandable.rowExpandable?.(record) ?? true;
1570
- const isExpanded = resolvedExpandedKeys.has(key);
1686
+ const key = getRowKeyRef.current(record, index);
1687
+ const canExpand = expandableRef.current?.rowExpandable?.(record) ?? true;
1688
+ const isExpanded = resolvedExpandedKeysRef.current.has(key);
1571
1689
  if (!canExpand)
1572
1690
  return /* @__PURE__ */ jsx5("span", { style: { display: "inline-block", width: 16 } });
1573
- if (typeof expandable.expandIcon === "function") {
1574
- return expandable.expandIcon({
1691
+ if (typeof expandableRef.current?.expandIcon === "function") {
1692
+ return expandableRef.current.expandIcon({
1575
1693
  expanded: isExpanded,
1576
1694
  onExpand: (_2, e) => {
1577
1695
  e.stopPropagation();
1578
- toggleExpand(key);
1696
+ toggleExpandRef.current(key);
1579
1697
  },
1580
1698
  record
1581
1699
  });
@@ -1585,7 +1703,7 @@ function BoltTable({
1585
1703
  {
1586
1704
  onClick: (e) => {
1587
1705
  e.stopPropagation();
1588
- toggleExpand(key);
1706
+ toggleExpandRef.current(key);
1589
1707
  },
1590
1708
  style: {
1591
1709
  display: "flex",
@@ -1598,20 +1716,13 @@ function BoltTable({
1598
1716
  borderRadius: "3px",
1599
1717
  color: accentColor
1600
1718
  },
1601
- children: isExpanded ? icons?.chevronDown ?? /* @__PURE__ */ jsx5(ChevronDownIcon, { style: { width: 14, height: 14 } }) : icons?.chevronRight ?? /* @__PURE__ */ jsx5(ChevronRightIcon, { style: { width: 14, height: 14 } })
1719
+ children: isExpanded ? iconsRef.current?.chevronDown ?? /* @__PURE__ */ jsx5(ChevronDownIcon, { style: { width: 14, height: 14 } }) : iconsRef.current?.chevronRight ?? /* @__PURE__ */ jsx5(ChevronRightIcon, { style: { width: 14, height: 14 } })
1602
1720
  }
1603
1721
  );
1604
1722
  }
1605
1723
  };
1606
1724
  return [expandColumn, ...columnsWithPersistedWidths];
1607
- }, [
1608
- expandable,
1609
- columnsWithPersistedWidths,
1610
- getRowKey,
1611
- resolvedExpandedKeys,
1612
- toggleExpand,
1613
- accentColor
1614
- ]);
1725
+ }, [hasExpandable, columnsWithPersistedWidths, accentColor]);
1615
1726
  const columnsWithSelection = useMemo2(() => {
1616
1727
  if (!rowSelection) return columnsWithExpand;
1617
1728
  const selectionColumn = {
@@ -1971,36 +2082,52 @@ function BoltTable({
1971
2082
  if (!onFilterChangeRef.current) {
1972
2083
  const filterKeys = Object.keys(columnFilters);
1973
2084
  if (filterKeys.length > 0) {
1974
- result = result.filter(
1975
- (row) => filterKeys.every((key) => {
1976
- const col = columnsLookupRef.current.find((c) => c.key === key);
1977
- if (typeof col?.filterFn === "function") {
1978
- return col.filterFn(columnFilters[key], row, col.dataIndex);
2085
+ result = result.filter((row) => {
2086
+ if (row == null) return false;
2087
+ return filterKeys.every((key) => {
2088
+ try {
2089
+ const col = columnsLookupRef.current.find((c) => c.key === key);
2090
+ if (typeof col?.filterFn === "function") {
2091
+ return col.filterFn(columnFilters[key], row, col.dataIndex);
2092
+ }
2093
+ const cellVal = String(row[key] ?? "").toLowerCase();
2094
+ return cellVal.includes(columnFilters[key].toLowerCase());
2095
+ } catch {
2096
+ return true;
1979
2097
  }
1980
- const cellVal = String(row[key] ?? "").toLowerCase();
1981
- return cellVal.includes(columnFilters[key].toLowerCase());
1982
- })
1983
- );
2098
+ });
2099
+ });
1984
2100
  }
1985
2101
  }
1986
2102
  if (!onSortChangeRef.current && sortState.key && sortState.direction) {
1987
2103
  const dir = sortState.direction === "asc" ? 1 : -1;
1988
2104
  const key = sortState.key;
1989
2105
  const col = columnsLookupRef.current.find((c) => c.key === key);
1990
- if (typeof col?.sorter === "function") {
1991
- const sorterFn = col.sorter;
1992
- result = [...result].sort((a, b) => sorterFn(a, b) * dir);
1993
- } else {
1994
- result = [...result].sort((a, b) => {
1995
- const aVal = a[key];
1996
- const bVal = b[key];
1997
- if (aVal == null && bVal == null) return 0;
1998
- if (aVal == null) return 1;
1999
- if (bVal == null) return -1;
2000
- if (typeof aVal === "number" && typeof bVal === "number")
2001
- return (aVal - bVal) * dir;
2002
- return String(aVal).localeCompare(String(bVal)) * dir;
2003
- });
2106
+ try {
2107
+ if (typeof col?.sorter === "function") {
2108
+ const sorterFn = col.sorter;
2109
+ result = [...result].sort((a, b) => {
2110
+ if (a == null && b == null) return 0;
2111
+ if (a == null) return 1;
2112
+ if (b == null) return -1;
2113
+ return sorterFn(a, b) * dir;
2114
+ });
2115
+ } else {
2116
+ result = [...result].sort((a, b) => {
2117
+ if (a == null && b == null) return 0;
2118
+ if (a == null) return 1;
2119
+ if (b == null) return -1;
2120
+ const aVal = a[key];
2121
+ const bVal = b[key];
2122
+ if (aVal == null && bVal == null) return 0;
2123
+ if (aVal == null) return 1;
2124
+ if (bVal == null) return -1;
2125
+ if (typeof aVal === "number" && typeof bVal === "number")
2126
+ return (aVal - bVal) * dir;
2127
+ return String(aVal).localeCompare(String(bVal)) * dir;
2128
+ });
2129
+ }
2130
+ } catch {
2004
2131
  }
2005
2132
  }
2006
2133
  return result;
@@ -2019,6 +2146,7 @@ function BoltTable({
2019
2146
  const bottomMap = /* @__PURE__ */ new Map();
2020
2147
  const rest = [];
2021
2148
  processedData.forEach((row, idx) => {
2149
+ if (row == null) return;
2022
2150
  const key = getRowKey(row, idx);
2023
2151
  if (topKeySet.has(key)) topMap.set(key, row);
2024
2152
  else if (bottomKeySet.has(key)) bottomMap.set(key, row);
@@ -2080,7 +2208,8 @@ function BoltTable({
2080
2208
  const DEFAULT_PAGE_SIZE = 15;
2081
2209
  const [internalPage, setInternalPage] = useState2(1);
2082
2210
  const [internalPageSize, setInternalPageSize] = useState2(DEFAULT_PAGE_SIZE);
2083
- const autoPagination = pagination === void 0 && data.length > DEFAULT_PAGE_SIZE;
2211
+ const dataLength = data.length;
2212
+ const autoPagination = pagination === void 0 && dataLength > DEFAULT_PAGE_SIZE;
2084
2213
  const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2085
2214
  const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2086
2215
  const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
@@ -2093,26 +2222,27 @@ function BoltTable({
2093
2222
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2094
2223
  const shimmerCount = pgEnabled ? pgSize : 15;
2095
2224
  const showShimmer = isLoading && processedData.length === 0;
2225
+ const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2096
2226
  const shimmerData = useMemo2(() => {
2097
2227
  if (!showShimmer) return null;
2098
2228
  return Array.from(
2099
2229
  { length: shimmerCount },
2100
2230
  (_, i) => ({
2101
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2231
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2102
2232
  })
2103
2233
  );
2104
- }, [showShimmer, shimmerCount, rowKey]);
2234
+ }, [showShimmer, shimmerCount, shimmerRowKeyField]);
2105
2235
  const INFINITE_SHIMMER_COUNT = 5;
2236
+ const showInfiniteShimmer = isLoading && paginatedData.length > 0 && !showShimmer && !pgEnabled;
2106
2237
  const infiniteLoadingShimmer = useMemo2(() => {
2107
- if (!isLoading || paginatedData.length === 0 || showShimmer) return null;
2108
- if (pgEnabled) return null;
2238
+ if (!showInfiniteShimmer) return null;
2109
2239
  return Array.from(
2110
2240
  { length: INFINITE_SHIMMER_COUNT },
2111
2241
  (_, i) => ({
2112
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2242
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2113
2243
  })
2114
2244
  );
2115
- }, [isLoading, paginatedData.length, showShimmer, pgEnabled, rowKey]);
2245
+ }, [showInfiniteShimmer, shimmerRowKeyField]);
2116
2246
  const displayData = useMemo2(() => {
2117
2247
  if (shimmerData) return shimmerData;
2118
2248
  if (infiniteLoadingShimmer)
@@ -2142,13 +2272,20 @@ function BoltTable({
2142
2272
  getScrollElement: () => tableAreaRef.current,
2143
2273
  estimateSize: (index) => {
2144
2274
  if (shimmerData) return rowHeight;
2145
- const key = getRowKey(displayData[index], index);
2275
+ const item = displayData[index];
2276
+ if (!item) return rowHeight;
2277
+ const key = getRowKey(item, index);
2146
2278
  if (!resolvedExpandedKeys.has(key)) return rowHeight;
2147
2279
  const cached = measuredExpandedHeights.current.get(key);
2148
2280
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
2149
2281
  },
2150
2282
  overscan: 5,
2151
- getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index),
2283
+ getItemKey: (index) => {
2284
+ if (shimmerData) return `__shimmer_${index}__`;
2285
+ const item = displayData[index];
2286
+ if (!item) return `__fallback_${index}__`;
2287
+ return getRowKey(item, index);
2288
+ },
2152
2289
  paddingStart: pinnedTopHeight,
2153
2290
  paddingEnd: pinnedBottomHeight
2154
2291
  });
@@ -2168,7 +2305,7 @@ function BoltTable({
2168
2305
  endReachedFiredRef.current = false;
2169
2306
  }, 200);
2170
2307
  return () => clearTimeout(timer);
2171
- }, [data.length, isLoading]);
2308
+ }, [dataLength, isLoading]);
2172
2309
  React4.useEffect(() => {
2173
2310
  const el = tableAreaRef.current;
2174
2311
  if (!el) return;
@@ -2190,7 +2327,7 @@ function BoltTable({
2190
2327
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2191
2328
  const currentPage = pgCurrent;
2192
2329
  const pageSize = pgSize;
2193
- const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2330
+ const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : dataLength) : dataLength;
2194
2331
  const lastKnownTotalRef = useRef4(0);
2195
2332
  if (!isLoading || rawTotal > 0) {
2196
2333
  lastKnownTotalRef.current = rawTotal;
@@ -2198,8 +2335,8 @@ function BoltTable({
2198
2335
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2199
2336
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2200
2337
  React4.useEffect(() => {
2201
- if (internalPage > totalPages) {
2202
- setInternalPage(Math.max(1, totalPages));
2338
+ if (totalPages > 0 && internalPage > totalPages) {
2339
+ setInternalPage(totalPages);
2203
2340
  }
2204
2341
  }, [totalPages, internalPage]);
2205
2342
  const handlePageChange = (p) => {
@@ -2534,10 +2671,10 @@ function BoltTable({
2534
2671
  "input",
2535
2672
  {
2536
2673
  type: "checkbox",
2537
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2674
+ checked: dataLength > 0 && normalizedSelectedKeys.length === dataLength,
2538
2675
  ref: (input) => {
2539
2676
  if (input) {
2540
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2677
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < dataLength;
2541
2678
  }
2542
2679
  },
2543
2680
  onChange: (e) => {
@@ -2685,7 +2822,9 @@ function BoltTable({
2685
2822
  pinnedTopData: pinnedTopRows,
2686
2823
  pinnedBottomData: pinnedBottomRows,
2687
2824
  gridTemplateColumns,
2688
- headerHeight: HEADER_HEIGHT
2825
+ headerHeight: HEADER_HEIGHT,
2826
+ rowClassName,
2827
+ rowStyle
2689
2828
  }
2690
2829
  )
2691
2830
  ]
@@ -3008,6 +3147,7 @@ function BoltTable({
3008
3147
  ...pinnedBottomRows
3009
3148
  ];
3010
3149
  for (let i = 0; i < allRows.length; i++) {
3150
+ if (allRows[i] == null) continue;
3011
3151
  const rk = getRowKey(allRows[i], i);
3012
3152
  if (rk === cellContextMenu.rowKey) {
3013
3153
  menuRecord = allRows[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bolt-table",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Virtualized React table with column drag & drop, pinning, resizing, sorting, filtering, and pagination.",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",