bolt-table 0.1.19 → 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
@@ -281,7 +281,7 @@ interface StylesTypes {
281
281
  /** CSS color string for pinned column cells and headers background. */
282
282
  pinnedBg?: string;
283
283
  }
284
- 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, rowClassName, rowStyle, }: 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;
285
285
 
286
286
  interface DraggableHeaderProps {
287
287
  /** Column definition for this header cell. */
package/dist/index.d.ts CHANGED
@@ -281,7 +281,7 @@ interface StylesTypes {
281
281
  /** CSS color string for pinned column cells and headers background. */
282
282
  pinnedBg?: string;
283
283
  }
284
- 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, rowClassName, rowStyle, }: 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;
285
285
 
286
286
  interface DraggableHeaderProps {
287
287
  /** Column definition for this header cell. */
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",
@@ -1092,14 +1101,16 @@ var TableBody = ({
1092
1101
  const virtualItems = rowVirtualizer.getVirtualItems();
1093
1102
  const totalSize = rowVirtualizer.getTotalSize();
1094
1103
  const selectedKeySet = (0, import_react3.useMemo)(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1104
+ const safeData = data ?? [];
1105
+ const safeColumns = orderedColumns ?? [];
1095
1106
  const allDataForSelection = (0, import_react3.useMemo)(() => {
1096
1107
  if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1097
- return data;
1098
- return [...pinnedTopData, ...data, ...pinnedBottomData];
1099
- }, [pinnedTopData, data, pinnedBottomData]);
1108
+ return safeData;
1109
+ return [...pinnedTopData, ...safeData, ...pinnedBottomData];
1110
+ }, [pinnedTopData, safeData, pinnedBottomData]);
1100
1111
  const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1101
1112
  const columnStyles = (0, import_react3.useMemo)(() => {
1102
- return orderedColumns.map((col, colIndex) => {
1113
+ return safeColumns.map((col, colIndex) => {
1103
1114
  const stickyOffset = columnOffsets.get(col.key);
1104
1115
  const isPinned = Boolean(col.pinned);
1105
1116
  let zIndex = 0;
@@ -1121,10 +1132,12 @@ var TableBody = ({
1121
1132
  }
1122
1133
  return { key: col.key, style, isPinned };
1123
1134
  });
1124
- }, [orderedColumns, columnOffsets, totalSize, styles]);
1135
+ }, [safeColumns, columnOffsets, totalSize, styles]);
1136
+ if (safeData.length === 0 || safeColumns.length === 0) return null;
1125
1137
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1126
1138
  columnStyles.map((colStyle, colIndex) => {
1127
- const col = orderedColumns[colIndex];
1139
+ const col = safeColumns[colIndex];
1140
+ if (!col) return null;
1128
1141
  const hasRender = !!col.render;
1129
1142
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1130
1143
  "div",
@@ -1132,15 +1145,31 @@ var TableBody = ({
1132
1145
  ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1133
1146
  style: colStyle.style,
1134
1147
  children: virtualItems.map((virtualRow) => {
1135
- const row = data[virtualRow.index];
1148
+ const row = safeData[virtualRow.index];
1149
+ if (row == null) return null;
1136
1150
  const rowKey = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1137
1151
  const isSelected = selectedKeySet.has(rowKey);
1138
1152
  const isExpanded = resolvedExpandedKeys?.has(rowKey) ?? false;
1139
1153
  const cellValue = row[col.dataIndex];
1140
1154
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1141
- const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1142
- const rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1143
- const rowSty = rowStyle ? rowStyle(row, virtualRow.index) : 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
+ }
1144
1173
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1145
1174
  "div",
1146
1175
  {
@@ -1207,9 +1236,15 @@ var TableBody = ({
1207
1236
  pointerEvents: "none"
1208
1237
  },
1209
1238
  children: virtualItems.map((virtualRow) => {
1210
- const row = data[virtualRow.index];
1239
+ const row = safeData[virtualRow.index];
1240
+ if (row == null) return null;
1211
1241
  const rk = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1212
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
+ }
1213
1248
  const expandedContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1214
1249
  "div",
1215
1250
  {
@@ -1227,7 +1262,7 @@ var TableBody = ({
1227
1262
  ...maxExpandedRowHeight ? { maxHeight: `${maxExpandedRowHeight}px` } : void 0,
1228
1263
  ...styles?.expandedRow
1229
1264
  },
1230
- children: expandable.expandedRowRender(row, virtualRow.index, 0, true)
1265
+ children: expandedRenderResult
1231
1266
  }
1232
1267
  );
1233
1268
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1274,11 +1309,20 @@ var TableBody = ({
1274
1309
  boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1275
1310
  },
1276
1311
  children: pinnedTopData.map((row, rowIdx) => {
1312
+ if (row == null) return null;
1277
1313
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1278
1314
  const isSelected = selectedKeySet.has(rk);
1279
1315
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1280
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1281
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
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
+ }
1282
1326
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1283
1327
  "div",
1284
1328
  {
@@ -1290,7 +1334,7 @@ var TableBody = ({
1290
1334
  ...styles?.pinnedRow,
1291
1335
  ...rowSty
1292
1336
  },
1293
- children: orderedColumns.map((col) => {
1337
+ children: safeColumns.map((col) => {
1294
1338
  const cellValue = row[col.dataIndex];
1295
1339
  const stickyOffset = columnOffsets.get(col.key);
1296
1340
  const isPinned = Boolean(col.pinned);
@@ -1298,7 +1342,14 @@ var TableBody = ({
1298
1342
  if (col.key === "__select__" || col.key === "__expand__")
1299
1343
  zIndex = 11;
1300
1344
  else if (isPinned) zIndex = 2;
1301
- 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
+ }
1302
1353
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1303
1354
  "div",
1304
1355
  {
@@ -1382,11 +1433,20 @@ var TableBody = ({
1382
1433
  boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1383
1434
  },
1384
1435
  children: pinnedBottomData.map((row, rowIdx) => {
1436
+ if (row == null) return null;
1385
1437
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1386
1438
  const isSelected = selectedKeySet.has(rk);
1387
1439
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1388
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1389
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
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
+ }
1390
1450
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1391
1451
  "div",
1392
1452
  {
@@ -1398,7 +1458,7 @@ var TableBody = ({
1398
1458
  ...styles?.pinnedRow,
1399
1459
  ...rowSty
1400
1460
  },
1401
- children: orderedColumns.map((col) => {
1461
+ children: safeColumns.map((col) => {
1402
1462
  const cellValue = row[col.dataIndex];
1403
1463
  const stickyOffset = columnOffsets.get(col.key);
1404
1464
  const isPinned = Boolean(col.pinned);
@@ -1406,7 +1466,14 @@ var TableBody = ({
1406
1466
  if (col.key === "__select__" || col.key === "__expand__")
1407
1467
  zIndex = 11;
1408
1468
  else if (isPinned) zIndex = 2;
1409
- 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
+ }
1410
1477
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1411
1478
  "div",
1412
1479
  {
@@ -1482,9 +1549,11 @@ function arrayMove(arr, from, to) {
1482
1549
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1483
1550
  var EMPTY_CLASSNAMES = {};
1484
1551
  var EMPTY_STYLES = {};
1552
+ var STABLE_EMPTY_DATA = [];
1553
+ var STABLE_EMPTY_COLS = [];
1485
1554
  function BoltTable({
1486
- columns: initialColumns,
1487
- data,
1555
+ columns: rawInitialColumns,
1556
+ data: rawData,
1488
1557
  rowHeight = 40,
1489
1558
  expandedRowHeight = 200,
1490
1559
  maxExpandedRowHeight,
@@ -1518,6 +1587,18 @@ function BoltTable({
1518
1587
  rowClassName,
1519
1588
  rowStyle
1520
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]);
1521
1602
  const [columns, setColumns] = (0, import_react4.useState)(initialColumns);
1522
1603
  const [columnOrder, setColumnOrder] = (0, import_react4.useState)(
1523
1604
  () => initialColumns.map((c) => c.key)
@@ -1553,10 +1634,15 @@ function BoltTable({
1553
1634
  [columns, columnWidths]
1554
1635
  );
1555
1636
  const [internalExpandedKeys, setInternalExpandedKeys] = (0, import_react4.useState)(() => {
1556
- if (expandable?.defaultExpandAllRows) {
1637
+ if (expandable?.defaultExpandAllRows && data.length > 0) {
1557
1638
  return new Set(
1558
1639
  data.map((row, idx) => {
1559
- 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
+ }
1560
1646
  if (typeof rowKey === "string")
1561
1647
  return row[rowKey] ?? idx;
1562
1648
  return idx;
@@ -1590,21 +1676,39 @@ function BoltTable({
1590
1676
  }, []);
1591
1677
  const getRowKey = (0, import_react4.useCallback)(
1592
1678
  (record, index) => {
1593
- if (typeof rowKey === "function") return String(rowKey(record));
1594
- if (typeof rowKey === "string") {
1595
- const val = record[rowKey];
1596
- 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);
1597
1688
  }
1598
1689
  return String(index);
1599
1690
  },
1600
1691
  [rowKey]
1601
1692
  );
1602
1693
  const normalizedSelectedKeys = (0, import_react4.useMemo)(
1603
- () => (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
+ },
1604
1699
  [rowSelection?.selectedRowKeys]
1605
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;
1606
1710
  const columnsWithExpand = (0, import_react4.useMemo)(() => {
1607
- if (!expandable?.rowExpandable) return columnsWithPersistedWidths;
1711
+ if (!hasExpandable) return columnsWithPersistedWidths;
1608
1712
  const expandColumn = {
1609
1713
  key: "__expand__",
1610
1714
  dataIndex: "__expand__",
@@ -1613,17 +1717,17 @@ function BoltTable({
1613
1717
  pinned: "left",
1614
1718
  hidden: false,
1615
1719
  render: (_, record, index) => {
1616
- const key = getRowKey(record, index);
1617
- const canExpand = expandable.rowExpandable?.(record) ?? true;
1618
- 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);
1619
1723
  if (!canExpand)
1620
1724
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { display: "inline-block", width: 16 } });
1621
- if (typeof expandable.expandIcon === "function") {
1622
- return expandable.expandIcon({
1725
+ if (typeof expandableRef.current?.expandIcon === "function") {
1726
+ return expandableRef.current.expandIcon({
1623
1727
  expanded: isExpanded,
1624
1728
  onExpand: (_2, e) => {
1625
1729
  e.stopPropagation();
1626
- toggleExpand(key);
1730
+ toggleExpandRef.current(key);
1627
1731
  },
1628
1732
  record
1629
1733
  });
@@ -1633,7 +1737,7 @@ function BoltTable({
1633
1737
  {
1634
1738
  onClick: (e) => {
1635
1739
  e.stopPropagation();
1636
- toggleExpand(key);
1740
+ toggleExpandRef.current(key);
1637
1741
  },
1638
1742
  style: {
1639
1743
  display: "flex",
@@ -1646,20 +1750,13 @@ function BoltTable({
1646
1750
  borderRadius: "3px",
1647
1751
  color: accentColor
1648
1752
  },
1649
- 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 } })
1650
1754
  }
1651
1755
  );
1652
1756
  }
1653
1757
  };
1654
1758
  return [expandColumn, ...columnsWithPersistedWidths];
1655
- }, [
1656
- expandable,
1657
- columnsWithPersistedWidths,
1658
- getRowKey,
1659
- resolvedExpandedKeys,
1660
- toggleExpand,
1661
- accentColor
1662
- ]);
1759
+ }, [hasExpandable, columnsWithPersistedWidths, accentColor]);
1663
1760
  const columnsWithSelection = (0, import_react4.useMemo)(() => {
1664
1761
  if (!rowSelection) return columnsWithExpand;
1665
1762
  const selectionColumn = {
@@ -2019,36 +2116,52 @@ function BoltTable({
2019
2116
  if (!onFilterChangeRef.current) {
2020
2117
  const filterKeys = Object.keys(columnFilters);
2021
2118
  if (filterKeys.length > 0) {
2022
- result = result.filter(
2023
- (row) => filterKeys.every((key) => {
2024
- const col = columnsLookupRef.current.find((c) => c.key === key);
2025
- if (typeof col?.filterFn === "function") {
2026
- 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;
2027
2131
  }
2028
- const cellVal = String(row[key] ?? "").toLowerCase();
2029
- return cellVal.includes(columnFilters[key].toLowerCase());
2030
- })
2031
- );
2132
+ });
2133
+ });
2032
2134
  }
2033
2135
  }
2034
2136
  if (!onSortChangeRef.current && sortState.key && sortState.direction) {
2035
2137
  const dir = sortState.direction === "asc" ? 1 : -1;
2036
2138
  const key = sortState.key;
2037
2139
  const col = columnsLookupRef.current.find((c) => c.key === key);
2038
- if (typeof col?.sorter === "function") {
2039
- const sorterFn = col.sorter;
2040
- result = [...result].sort((a, b) => sorterFn(a, b) * dir);
2041
- } else {
2042
- result = [...result].sort((a, b) => {
2043
- const aVal = a[key];
2044
- const bVal = b[key];
2045
- if (aVal == null && bVal == null) return 0;
2046
- if (aVal == null) return 1;
2047
- if (bVal == null) return -1;
2048
- if (typeof aVal === "number" && typeof bVal === "number")
2049
- return (aVal - bVal) * dir;
2050
- return String(aVal).localeCompare(String(bVal)) * dir;
2051
- });
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 {
2052
2165
  }
2053
2166
  }
2054
2167
  return result;
@@ -2067,6 +2180,7 @@ function BoltTable({
2067
2180
  const bottomMap = /* @__PURE__ */ new Map();
2068
2181
  const rest = [];
2069
2182
  processedData.forEach((row, idx) => {
2183
+ if (row == null) return;
2070
2184
  const key = getRowKey(row, idx);
2071
2185
  if (topKeySet.has(key)) topMap.set(key, row);
2072
2186
  else if (bottomKeySet.has(key)) bottomMap.set(key, row);
@@ -2128,7 +2242,8 @@ function BoltTable({
2128
2242
  const DEFAULT_PAGE_SIZE = 15;
2129
2243
  const [internalPage, setInternalPage] = (0, import_react4.useState)(1);
2130
2244
  const [internalPageSize, setInternalPageSize] = (0, import_react4.useState)(DEFAULT_PAGE_SIZE);
2131
- 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;
2132
2247
  const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2133
2248
  const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2134
2249
  const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
@@ -2141,26 +2256,27 @@ function BoltTable({
2141
2256
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2142
2257
  const shimmerCount = pgEnabled ? pgSize : 15;
2143
2258
  const showShimmer = isLoading && processedData.length === 0;
2259
+ const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2144
2260
  const shimmerData = (0, import_react4.useMemo)(() => {
2145
2261
  if (!showShimmer) return null;
2146
2262
  return Array.from(
2147
2263
  { length: shimmerCount },
2148
2264
  (_, i) => ({
2149
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2265
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2150
2266
  })
2151
2267
  );
2152
- }, [showShimmer, shimmerCount, rowKey]);
2268
+ }, [showShimmer, shimmerCount, shimmerRowKeyField]);
2153
2269
  const INFINITE_SHIMMER_COUNT = 5;
2270
+ const showInfiniteShimmer = isLoading && paginatedData.length > 0 && !showShimmer && !pgEnabled;
2154
2271
  const infiniteLoadingShimmer = (0, import_react4.useMemo)(() => {
2155
- if (!isLoading || paginatedData.length === 0 || showShimmer) return null;
2156
- if (pgEnabled) return null;
2272
+ if (!showInfiniteShimmer) return null;
2157
2273
  return Array.from(
2158
2274
  { length: INFINITE_SHIMMER_COUNT },
2159
2275
  (_, i) => ({
2160
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2276
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2161
2277
  })
2162
2278
  );
2163
- }, [isLoading, paginatedData.length, showShimmer, pgEnabled, rowKey]);
2279
+ }, [showInfiniteShimmer, shimmerRowKeyField]);
2164
2280
  const displayData = (0, import_react4.useMemo)(() => {
2165
2281
  if (shimmerData) return shimmerData;
2166
2282
  if (infiniteLoadingShimmer)
@@ -2190,13 +2306,20 @@ function BoltTable({
2190
2306
  getScrollElement: () => tableAreaRef.current,
2191
2307
  estimateSize: (index) => {
2192
2308
  if (shimmerData) return rowHeight;
2193
- const key = getRowKey(displayData[index], index);
2309
+ const item = displayData[index];
2310
+ if (!item) return rowHeight;
2311
+ const key = getRowKey(item, index);
2194
2312
  if (!resolvedExpandedKeys.has(key)) return rowHeight;
2195
2313
  const cached = measuredExpandedHeights.current.get(key);
2196
2314
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
2197
2315
  },
2198
2316
  overscan: 5,
2199
- 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
+ },
2200
2323
  paddingStart: pinnedTopHeight,
2201
2324
  paddingEnd: pinnedBottomHeight
2202
2325
  });
@@ -2216,7 +2339,7 @@ function BoltTable({
2216
2339
  endReachedFiredRef.current = false;
2217
2340
  }, 200);
2218
2341
  return () => clearTimeout(timer);
2219
- }, [data.length, isLoading]);
2342
+ }, [dataLength, isLoading]);
2220
2343
  import_react4.default.useEffect(() => {
2221
2344
  const el = tableAreaRef.current;
2222
2345
  if (!el) return;
@@ -2238,7 +2361,7 @@ function BoltTable({
2238
2361
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2239
2362
  const currentPage = pgCurrent;
2240
2363
  const pageSize = pgSize;
2241
- 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;
2242
2365
  const lastKnownTotalRef = (0, import_react4.useRef)(0);
2243
2366
  if (!isLoading || rawTotal > 0) {
2244
2367
  lastKnownTotalRef.current = rawTotal;
@@ -2246,8 +2369,8 @@ function BoltTable({
2246
2369
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2247
2370
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2248
2371
  import_react4.default.useEffect(() => {
2249
- if (internalPage > totalPages) {
2250
- setInternalPage(Math.max(1, totalPages));
2372
+ if (totalPages > 0 && internalPage > totalPages) {
2373
+ setInternalPage(totalPages);
2251
2374
  }
2252
2375
  }, [totalPages, internalPage]);
2253
2376
  const handlePageChange = (p) => {
@@ -2582,10 +2705,10 @@ function BoltTable({
2582
2705
  "input",
2583
2706
  {
2584
2707
  type: "checkbox",
2585
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2708
+ checked: dataLength > 0 && normalizedSelectedKeys.length === dataLength,
2586
2709
  ref: (input) => {
2587
2710
  if (input) {
2588
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2711
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < dataLength;
2589
2712
  }
2590
2713
  },
2591
2714
  onChange: (e) => {
@@ -3058,6 +3181,7 @@ function BoltTable({
3058
3181
  ...pinnedBottomRows
3059
3182
  ];
3060
3183
  for (let i = 0; i < allRows.length; i++) {
3184
+ if (allRows[i] == null) continue;
3061
3185
  const rk = getRowKey(allRows[i], i);
3062
3186
  if (rk === cellContextMenu.rowKey) {
3063
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",
@@ -1058,14 +1067,16 @@ var TableBody = ({
1058
1067
  const virtualItems = rowVirtualizer.getVirtualItems();
1059
1068
  const totalSize = rowVirtualizer.getTotalSize();
1060
1069
  const selectedKeySet = useMemo(() => new Set(normalizedSelectedKeys), [normalizedSelectedKeys]);
1070
+ const safeData = data ?? [];
1071
+ const safeColumns = orderedColumns ?? [];
1061
1072
  const allDataForSelection = useMemo(() => {
1062
1073
  if (pinnedTopData.length === 0 && pinnedBottomData.length === 0)
1063
- return data;
1064
- return [...pinnedTopData, ...data, ...pinnedBottomData];
1065
- }, [pinnedTopData, data, pinnedBottomData]);
1074
+ return safeData;
1075
+ return [...pinnedTopData, ...safeData, ...pinnedBottomData];
1076
+ }, [pinnedTopData, safeData, pinnedBottomData]);
1066
1077
  const pinnedRowBg = styles?.pinnedRowBg ?? styles?.pinnedBg;
1067
1078
  const columnStyles = useMemo(() => {
1068
- return orderedColumns.map((col, colIndex) => {
1079
+ return safeColumns.map((col, colIndex) => {
1069
1080
  const stickyOffset = columnOffsets.get(col.key);
1070
1081
  const isPinned = Boolean(col.pinned);
1071
1082
  let zIndex = 0;
@@ -1087,10 +1098,12 @@ var TableBody = ({
1087
1098
  }
1088
1099
  return { key: col.key, style, isPinned };
1089
1100
  });
1090
- }, [orderedColumns, columnOffsets, totalSize, styles]);
1101
+ }, [safeColumns, columnOffsets, totalSize, styles]);
1102
+ if (safeData.length === 0 || safeColumns.length === 0) return null;
1091
1103
  return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1092
1104
  columnStyles.map((colStyle, colIndex) => {
1093
- const col = orderedColumns[colIndex];
1105
+ const col = safeColumns[colIndex];
1106
+ if (!col) return null;
1094
1107
  const hasRender = !!col.render;
1095
1108
  return /* @__PURE__ */ jsx4(
1096
1109
  "div",
@@ -1098,15 +1111,31 @@ var TableBody = ({
1098
1111
  ...colStyle.isPinned ? { "data-bt-pinned": "" } : {},
1099
1112
  style: colStyle.style,
1100
1113
  children: virtualItems.map((virtualRow) => {
1101
- const row = data[virtualRow.index];
1114
+ const row = safeData[virtualRow.index];
1115
+ if (row == null) return null;
1102
1116
  const rowKey = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1103
1117
  const isSelected = selectedKeySet.has(rowKey);
1104
1118
  const isExpanded = resolvedExpandedKeys?.has(rowKey) ?? false;
1105
1119
  const cellValue = row[col.dataIndex];
1106
1120
  const isRowShimmer = isLoading || rowKey.startsWith("__shimmer_");
1107
- const recordFingerprint = hasRender && !isRowShimmer ? JSON.stringify(row) : void 0;
1108
- const rowCls = rowClassName ? rowClassName(row, virtualRow.index) : "";
1109
- const rowSty = rowStyle ? rowStyle(row, virtualRow.index) : 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
+ }
1110
1139
  return /* @__PURE__ */ jsx4(
1111
1140
  "div",
1112
1141
  {
@@ -1173,9 +1202,15 @@ var TableBody = ({
1173
1202
  pointerEvents: "none"
1174
1203
  },
1175
1204
  children: virtualItems.map((virtualRow) => {
1176
- const row = data[virtualRow.index];
1205
+ const row = safeData[virtualRow.index];
1206
+ if (row == null) return null;
1177
1207
  const rk = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1178
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
+ }
1179
1214
  const expandedContent = /* @__PURE__ */ jsx4(
1180
1215
  "div",
1181
1216
  {
@@ -1193,7 +1228,7 @@ var TableBody = ({
1193
1228
  ...maxExpandedRowHeight ? { maxHeight: `${maxExpandedRowHeight}px` } : void 0,
1194
1229
  ...styles?.expandedRow
1195
1230
  },
1196
- children: expandable.expandedRowRender(row, virtualRow.index, 0, true)
1231
+ children: expandedRenderResult
1197
1232
  }
1198
1233
  );
1199
1234
  return /* @__PURE__ */ jsx4(
@@ -1240,11 +1275,20 @@ var TableBody = ({
1240
1275
  boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1241
1276
  },
1242
1277
  children: pinnedTopData.map((row, rowIdx) => {
1278
+ if (row == null) return null;
1243
1279
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1244
1280
  const isSelected = selectedKeySet.has(rk);
1245
1281
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1246
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1247
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
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
+ }
1248
1292
  return /* @__PURE__ */ jsx4(
1249
1293
  "div",
1250
1294
  {
@@ -1256,7 +1300,7 @@ var TableBody = ({
1256
1300
  ...styles?.pinnedRow,
1257
1301
  ...rowSty
1258
1302
  },
1259
- children: orderedColumns.map((col) => {
1303
+ children: safeColumns.map((col) => {
1260
1304
  const cellValue = row[col.dataIndex];
1261
1305
  const stickyOffset = columnOffsets.get(col.key);
1262
1306
  const isPinned = Boolean(col.pinned);
@@ -1264,7 +1308,14 @@ var TableBody = ({
1264
1308
  if (col.key === "__select__" || col.key === "__expand__")
1265
1309
  zIndex = 11;
1266
1310
  else if (isPinned) zIndex = 2;
1267
- 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
+ }
1268
1319
  return /* @__PURE__ */ jsx4(
1269
1320
  "div",
1270
1321
  {
@@ -1348,11 +1399,20 @@ var TableBody = ({
1348
1399
  boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1349
1400
  },
1350
1401
  children: pinnedBottomData.map((row, rowIdx) => {
1402
+ if (row == null) return null;
1351
1403
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1352
1404
  const isSelected = selectedKeySet.has(rk);
1353
1405
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1354
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1355
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
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
+ }
1356
1416
  return /* @__PURE__ */ jsx4(
1357
1417
  "div",
1358
1418
  {
@@ -1364,7 +1424,7 @@ var TableBody = ({
1364
1424
  ...styles?.pinnedRow,
1365
1425
  ...rowSty
1366
1426
  },
1367
- children: orderedColumns.map((col) => {
1427
+ children: safeColumns.map((col) => {
1368
1428
  const cellValue = row[col.dataIndex];
1369
1429
  const stickyOffset = columnOffsets.get(col.key);
1370
1430
  const isPinned = Boolean(col.pinned);
@@ -1372,7 +1432,14 @@ var TableBody = ({
1372
1432
  if (col.key === "__select__" || col.key === "__expand__")
1373
1433
  zIndex = 11;
1374
1434
  else if (isPinned) zIndex = 2;
1375
- 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
+ }
1376
1443
  return /* @__PURE__ */ jsx4(
1377
1444
  "div",
1378
1445
  {
@@ -1448,9 +1515,11 @@ function arrayMove(arr, from, to) {
1448
1515
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1449
1516
  var EMPTY_CLASSNAMES = {};
1450
1517
  var EMPTY_STYLES = {};
1518
+ var STABLE_EMPTY_DATA = [];
1519
+ var STABLE_EMPTY_COLS = [];
1451
1520
  function BoltTable({
1452
- columns: initialColumns,
1453
- data,
1521
+ columns: rawInitialColumns,
1522
+ data: rawData,
1454
1523
  rowHeight = 40,
1455
1524
  expandedRowHeight = 200,
1456
1525
  maxExpandedRowHeight,
@@ -1484,6 +1553,18 @@ function BoltTable({
1484
1553
  rowClassName,
1485
1554
  rowStyle
1486
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]);
1487
1568
  const [columns, setColumns] = useState2(initialColumns);
1488
1569
  const [columnOrder, setColumnOrder] = useState2(
1489
1570
  () => initialColumns.map((c) => c.key)
@@ -1519,10 +1600,15 @@ function BoltTable({
1519
1600
  [columns, columnWidths]
1520
1601
  );
1521
1602
  const [internalExpandedKeys, setInternalExpandedKeys] = useState2(() => {
1522
- if (expandable?.defaultExpandAllRows) {
1603
+ if (expandable?.defaultExpandAllRows && data.length > 0) {
1523
1604
  return new Set(
1524
1605
  data.map((row, idx) => {
1525
- 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
+ }
1526
1612
  if (typeof rowKey === "string")
1527
1613
  return row[rowKey] ?? idx;
1528
1614
  return idx;
@@ -1556,21 +1642,39 @@ function BoltTable({
1556
1642
  }, []);
1557
1643
  const getRowKey = useCallback(
1558
1644
  (record, index) => {
1559
- if (typeof rowKey === "function") return String(rowKey(record));
1560
- if (typeof rowKey === "string") {
1561
- const val = record[rowKey];
1562
- 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);
1563
1654
  }
1564
1655
  return String(index);
1565
1656
  },
1566
1657
  [rowKey]
1567
1658
  );
1568
1659
  const normalizedSelectedKeys = useMemo2(
1569
- () => (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
+ },
1570
1665
  [rowSelection?.selectedRowKeys]
1571
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;
1572
1676
  const columnsWithExpand = useMemo2(() => {
1573
- if (!expandable?.rowExpandable) return columnsWithPersistedWidths;
1677
+ if (!hasExpandable) return columnsWithPersistedWidths;
1574
1678
  const expandColumn = {
1575
1679
  key: "__expand__",
1576
1680
  dataIndex: "__expand__",
@@ -1579,17 +1683,17 @@ function BoltTable({
1579
1683
  pinned: "left",
1580
1684
  hidden: false,
1581
1685
  render: (_, record, index) => {
1582
- const key = getRowKey(record, index);
1583
- const canExpand = expandable.rowExpandable?.(record) ?? true;
1584
- 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);
1585
1689
  if (!canExpand)
1586
1690
  return /* @__PURE__ */ jsx5("span", { style: { display: "inline-block", width: 16 } });
1587
- if (typeof expandable.expandIcon === "function") {
1588
- return expandable.expandIcon({
1691
+ if (typeof expandableRef.current?.expandIcon === "function") {
1692
+ return expandableRef.current.expandIcon({
1589
1693
  expanded: isExpanded,
1590
1694
  onExpand: (_2, e) => {
1591
1695
  e.stopPropagation();
1592
- toggleExpand(key);
1696
+ toggleExpandRef.current(key);
1593
1697
  },
1594
1698
  record
1595
1699
  });
@@ -1599,7 +1703,7 @@ function BoltTable({
1599
1703
  {
1600
1704
  onClick: (e) => {
1601
1705
  e.stopPropagation();
1602
- toggleExpand(key);
1706
+ toggleExpandRef.current(key);
1603
1707
  },
1604
1708
  style: {
1605
1709
  display: "flex",
@@ -1612,20 +1716,13 @@ function BoltTable({
1612
1716
  borderRadius: "3px",
1613
1717
  color: accentColor
1614
1718
  },
1615
- 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 } })
1616
1720
  }
1617
1721
  );
1618
1722
  }
1619
1723
  };
1620
1724
  return [expandColumn, ...columnsWithPersistedWidths];
1621
- }, [
1622
- expandable,
1623
- columnsWithPersistedWidths,
1624
- getRowKey,
1625
- resolvedExpandedKeys,
1626
- toggleExpand,
1627
- accentColor
1628
- ]);
1725
+ }, [hasExpandable, columnsWithPersistedWidths, accentColor]);
1629
1726
  const columnsWithSelection = useMemo2(() => {
1630
1727
  if (!rowSelection) return columnsWithExpand;
1631
1728
  const selectionColumn = {
@@ -1985,36 +2082,52 @@ function BoltTable({
1985
2082
  if (!onFilterChangeRef.current) {
1986
2083
  const filterKeys = Object.keys(columnFilters);
1987
2084
  if (filterKeys.length > 0) {
1988
- result = result.filter(
1989
- (row) => filterKeys.every((key) => {
1990
- const col = columnsLookupRef.current.find((c) => c.key === key);
1991
- if (typeof col?.filterFn === "function") {
1992
- 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;
1993
2097
  }
1994
- const cellVal = String(row[key] ?? "").toLowerCase();
1995
- return cellVal.includes(columnFilters[key].toLowerCase());
1996
- })
1997
- );
2098
+ });
2099
+ });
1998
2100
  }
1999
2101
  }
2000
2102
  if (!onSortChangeRef.current && sortState.key && sortState.direction) {
2001
2103
  const dir = sortState.direction === "asc" ? 1 : -1;
2002
2104
  const key = sortState.key;
2003
2105
  const col = columnsLookupRef.current.find((c) => c.key === key);
2004
- if (typeof col?.sorter === "function") {
2005
- const sorterFn = col.sorter;
2006
- result = [...result].sort((a, b) => sorterFn(a, b) * dir);
2007
- } else {
2008
- result = [...result].sort((a, b) => {
2009
- const aVal = a[key];
2010
- const bVal = b[key];
2011
- if (aVal == null && bVal == null) return 0;
2012
- if (aVal == null) return 1;
2013
- if (bVal == null) return -1;
2014
- if (typeof aVal === "number" && typeof bVal === "number")
2015
- return (aVal - bVal) * dir;
2016
- return String(aVal).localeCompare(String(bVal)) * dir;
2017
- });
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 {
2018
2131
  }
2019
2132
  }
2020
2133
  return result;
@@ -2033,6 +2146,7 @@ function BoltTable({
2033
2146
  const bottomMap = /* @__PURE__ */ new Map();
2034
2147
  const rest = [];
2035
2148
  processedData.forEach((row, idx) => {
2149
+ if (row == null) return;
2036
2150
  const key = getRowKey(row, idx);
2037
2151
  if (topKeySet.has(key)) topMap.set(key, row);
2038
2152
  else if (bottomKeySet.has(key)) bottomMap.set(key, row);
@@ -2094,7 +2208,8 @@ function BoltTable({
2094
2208
  const DEFAULT_PAGE_SIZE = 15;
2095
2209
  const [internalPage, setInternalPage] = useState2(1);
2096
2210
  const [internalPageSize, setInternalPageSize] = useState2(DEFAULT_PAGE_SIZE);
2097
- 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;
2098
2213
  const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2099
2214
  const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2100
2215
  const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
@@ -2107,26 +2222,27 @@ function BoltTable({
2107
2222
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2108
2223
  const shimmerCount = pgEnabled ? pgSize : 15;
2109
2224
  const showShimmer = isLoading && processedData.length === 0;
2225
+ const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2110
2226
  const shimmerData = useMemo2(() => {
2111
2227
  if (!showShimmer) return null;
2112
2228
  return Array.from(
2113
2229
  { length: shimmerCount },
2114
2230
  (_, i) => ({
2115
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2231
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2116
2232
  })
2117
2233
  );
2118
- }, [showShimmer, shimmerCount, rowKey]);
2234
+ }, [showShimmer, shimmerCount, shimmerRowKeyField]);
2119
2235
  const INFINITE_SHIMMER_COUNT = 5;
2236
+ const showInfiniteShimmer = isLoading && paginatedData.length > 0 && !showShimmer && !pgEnabled;
2120
2237
  const infiniteLoadingShimmer = useMemo2(() => {
2121
- if (!isLoading || paginatedData.length === 0 || showShimmer) return null;
2122
- if (pgEnabled) return null;
2238
+ if (!showInfiniteShimmer) return null;
2123
2239
  return Array.from(
2124
2240
  { length: INFINITE_SHIMMER_COUNT },
2125
2241
  (_, i) => ({
2126
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2242
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2127
2243
  })
2128
2244
  );
2129
- }, [isLoading, paginatedData.length, showShimmer, pgEnabled, rowKey]);
2245
+ }, [showInfiniteShimmer, shimmerRowKeyField]);
2130
2246
  const displayData = useMemo2(() => {
2131
2247
  if (shimmerData) return shimmerData;
2132
2248
  if (infiniteLoadingShimmer)
@@ -2156,13 +2272,20 @@ function BoltTable({
2156
2272
  getScrollElement: () => tableAreaRef.current,
2157
2273
  estimateSize: (index) => {
2158
2274
  if (shimmerData) return rowHeight;
2159
- const key = getRowKey(displayData[index], index);
2275
+ const item = displayData[index];
2276
+ if (!item) return rowHeight;
2277
+ const key = getRowKey(item, index);
2160
2278
  if (!resolvedExpandedKeys.has(key)) return rowHeight;
2161
2279
  const cached = measuredExpandedHeights.current.get(key);
2162
2280
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
2163
2281
  },
2164
2282
  overscan: 5,
2165
- 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
+ },
2166
2289
  paddingStart: pinnedTopHeight,
2167
2290
  paddingEnd: pinnedBottomHeight
2168
2291
  });
@@ -2182,7 +2305,7 @@ function BoltTable({
2182
2305
  endReachedFiredRef.current = false;
2183
2306
  }, 200);
2184
2307
  return () => clearTimeout(timer);
2185
- }, [data.length, isLoading]);
2308
+ }, [dataLength, isLoading]);
2186
2309
  React4.useEffect(() => {
2187
2310
  const el = tableAreaRef.current;
2188
2311
  if (!el) return;
@@ -2204,7 +2327,7 @@ function BoltTable({
2204
2327
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2205
2328
  const currentPage = pgCurrent;
2206
2329
  const pageSize = pgSize;
2207
- 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;
2208
2331
  const lastKnownTotalRef = useRef4(0);
2209
2332
  if (!isLoading || rawTotal > 0) {
2210
2333
  lastKnownTotalRef.current = rawTotal;
@@ -2212,8 +2335,8 @@ function BoltTable({
2212
2335
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2213
2336
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2214
2337
  React4.useEffect(() => {
2215
- if (internalPage > totalPages) {
2216
- setInternalPage(Math.max(1, totalPages));
2338
+ if (totalPages > 0 && internalPage > totalPages) {
2339
+ setInternalPage(totalPages);
2217
2340
  }
2218
2341
  }, [totalPages, internalPage]);
2219
2342
  const handlePageChange = (p) => {
@@ -2548,10 +2671,10 @@ function BoltTable({
2548
2671
  "input",
2549
2672
  {
2550
2673
  type: "checkbox",
2551
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2674
+ checked: dataLength > 0 && normalizedSelectedKeys.length === dataLength,
2552
2675
  ref: (input) => {
2553
2676
  if (input) {
2554
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2677
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < dataLength;
2555
2678
  }
2556
2679
  },
2557
2680
  onChange: (e) => {
@@ -3024,6 +3147,7 @@ function BoltTable({
3024
3147
  ...pinnedBottomRows
3025
3148
  ];
3026
3149
  for (let i = 0; i < allRows.length; i++) {
3150
+ if (allRows[i] == null) continue;
3027
3151
  const rk = getRowKey(allRows[i], i);
3028
3152
  if (rk === cellContextMenu.rowKey) {
3029
3153
  menuRecord = allRows[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bolt-table",
3
- "version": "0.1.19",
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",