bolt-table 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -838,6 +838,7 @@ var Cell = React3.memo(
838
838
  rowKey,
839
839
  allData,
840
840
  getRowKey,
841
+ getRawRowKey,
841
842
  accentColor,
842
843
  isLoading
843
844
  }) => {
@@ -880,6 +881,7 @@ var Cell = React3.memo(
880
881
  const checkboxProps = rowSelection.getCheckboxProps?.(record) ?? {
881
882
  disabled: false
882
883
  };
884
+ const rawKey = getRawRowKey ? getRawRowKey(record, rowIndex) : rowKey;
883
885
  const content2 = rowSelection.type === "radio" ? /* @__PURE__ */ jsx4(
884
886
  "input",
885
887
  {
@@ -889,7 +891,7 @@ var Cell = React3.memo(
889
891
  onChange: (e) => {
890
892
  e.stopPropagation();
891
893
  rowSelection.onSelect?.(record, true, [record], e.nativeEvent);
892
- rowSelection.onChange?.([rowKey], [record], { type: "single" });
894
+ rowSelection.onChange?.([rawKey], [record], { type: "single" });
893
895
  },
894
896
  style: { cursor: "pointer", accentColor }
895
897
  }
@@ -901,15 +903,12 @@ var Cell = React3.memo(
901
903
  disabled: checkboxProps.disabled,
902
904
  onChange: (e) => {
903
905
  e.stopPropagation();
904
- const currentKeys = (rowSelection.selectedRowKeys ?? []).map(
905
- (k) => String(k)
906
- );
907
- const newSelected = isSelected ? currentKeys.filter((k) => k !== rowKey) : [...currentKeys, rowKey];
908
- const newSelectedRows = (allData ?? []).filter(
909
- (row, idx) => newSelected.includes(
910
- getRowKey ? getRowKey(row, idx) : String(idx)
911
- )
912
- );
906
+ const currentKeys = rowSelection.selectedRowKeys ?? [];
907
+ const newSelected = isSelected ? currentKeys.filter((k) => String(k) !== rowKey) : [...currentKeys, rawKey];
908
+ const newSelectedRows = (allData ?? []).filter((row, idx) => {
909
+ const rk = getRowKey ? getRowKey(row, idx) : String(idx);
910
+ return newSelected.some((k) => String(k) === rk);
911
+ });
913
912
  rowSelection.onSelect?.(
914
913
  record,
915
914
  !isSelected,
@@ -945,7 +944,16 @@ var Cell = React3.memo(
945
944
  }
946
945
  );
947
946
  }
948
- const content = column.render ? column.render(value, record, rowIndex) : value ?? "";
947
+ let content;
948
+ if (column.render) {
949
+ try {
950
+ content = column.render(value, record, rowIndex);
951
+ } catch {
952
+ content = String(value ?? "");
953
+ }
954
+ } else {
955
+ content = value ?? "";
956
+ }
949
957
  const isSystem = column.key === "__select__" || column.key === "__expand__";
950
958
  return /* @__PURE__ */ jsx4(
951
959
  "div",
@@ -1039,6 +1047,7 @@ var TableBody = ({
1039
1047
  rowSelection,
1040
1048
  normalizedSelectedKeys = [],
1041
1049
  getRowKey,
1050
+ getRawRowKey,
1042
1051
  expandable,
1043
1052
  resolvedExpandedKeys,
1044
1053
  rowHeight = 40,
@@ -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
  {
@@ -1146,6 +1175,7 @@ var TableBody = ({
1146
1175
  rowKey,
1147
1176
  allData: allDataForSelection,
1148
1177
  getRowKey,
1178
+ getRawRowKey,
1149
1179
  accentColor,
1150
1180
  isLoading: isRowShimmer,
1151
1181
  recordFingerprint
@@ -1173,9 +1203,15 @@ var TableBody = ({
1173
1203
  pointerEvents: "none"
1174
1204
  },
1175
1205
  children: virtualItems.map((virtualRow) => {
1176
- const row = data[virtualRow.index];
1206
+ const row = safeData[virtualRow.index];
1207
+ if (row == null) return null;
1177
1208
  const rk = getRowKey ? getRowKey(row, virtualRow.index) : String(virtualRow.index);
1178
1209
  if (!(resolvedExpandedKeys?.has(rk) ?? false)) return null;
1210
+ let expandedRenderResult = null;
1211
+ try {
1212
+ expandedRenderResult = expandable.expandedRowRender(row, virtualRow.index, 0, true);
1213
+ } catch {
1214
+ }
1179
1215
  const expandedContent = /* @__PURE__ */ jsx4(
1180
1216
  "div",
1181
1217
  {
@@ -1193,7 +1229,7 @@ var TableBody = ({
1193
1229
  ...maxExpandedRowHeight ? { maxHeight: `${maxExpandedRowHeight}px` } : void 0,
1194
1230
  ...styles?.expandedRow
1195
1231
  },
1196
- children: expandable.expandedRowRender(row, virtualRow.index, 0, true)
1232
+ children: expandedRenderResult
1197
1233
  }
1198
1234
  );
1199
1235
  return /* @__PURE__ */ jsx4(
@@ -1240,11 +1276,20 @@ var TableBody = ({
1240
1276
  boxShadow: "0 2px 6px -1px rgba(0,0,0,0.08)"
1241
1277
  },
1242
1278
  children: pinnedTopData.map((row, rowIdx) => {
1279
+ if (row == null) return null;
1243
1280
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1244
1281
  const isSelected = selectedKeySet.has(rk);
1245
1282
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1246
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1247
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1283
+ let rowCls = "";
1284
+ try {
1285
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1286
+ } catch {
1287
+ }
1288
+ let rowSty;
1289
+ try {
1290
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1291
+ } catch {
1292
+ }
1248
1293
  return /* @__PURE__ */ jsx4(
1249
1294
  "div",
1250
1295
  {
@@ -1256,7 +1301,7 @@ var TableBody = ({
1256
1301
  ...styles?.pinnedRow,
1257
1302
  ...rowSty
1258
1303
  },
1259
- children: orderedColumns.map((col) => {
1304
+ children: safeColumns.map((col) => {
1260
1305
  const cellValue = row[col.dataIndex];
1261
1306
  const stickyOffset = columnOffsets.get(col.key);
1262
1307
  const isPinned = Boolean(col.pinned);
@@ -1264,7 +1309,14 @@ var TableBody = ({
1264
1309
  if (col.key === "__select__" || col.key === "__expand__")
1265
1310
  zIndex = 11;
1266
1311
  else if (isPinned) zIndex = 2;
1267
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1312
+ let recordFingerprint;
1313
+ if (col.render) {
1314
+ try {
1315
+ recordFingerprint = JSON.stringify(row);
1316
+ } catch {
1317
+ recordFingerprint = rk;
1318
+ }
1319
+ }
1268
1320
  return /* @__PURE__ */ jsx4(
1269
1321
  "div",
1270
1322
  {
@@ -1305,6 +1357,7 @@ var TableBody = ({
1305
1357
  rowKey: rk,
1306
1358
  allData: allDataForSelection,
1307
1359
  getRowKey,
1360
+ getRawRowKey,
1308
1361
  accentColor,
1309
1362
  isLoading: false,
1310
1363
  recordFingerprint
@@ -1348,11 +1401,20 @@ var TableBody = ({
1348
1401
  boxShadow: "0 -2px 6px -1px rgba(0,0,0,0.08)"
1349
1402
  },
1350
1403
  children: pinnedBottomData.map((row, rowIdx) => {
1404
+ if (row == null) return null;
1351
1405
  const rk = getRowKey ? getRowKey(row, rowIdx) : String(rowIdx);
1352
1406
  const isSelected = selectedKeySet.has(rk);
1353
1407
  const isExpanded = resolvedExpandedKeys?.has(rk) ?? false;
1354
- const rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1355
- const rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1408
+ let rowCls = "";
1409
+ try {
1410
+ rowCls = rowClassName ? rowClassName(row, rowIdx) : "";
1411
+ } catch {
1412
+ }
1413
+ let rowSty;
1414
+ try {
1415
+ rowSty = rowStyle ? rowStyle(row, rowIdx) : void 0;
1416
+ } catch {
1417
+ }
1356
1418
  return /* @__PURE__ */ jsx4(
1357
1419
  "div",
1358
1420
  {
@@ -1364,7 +1426,7 @@ var TableBody = ({
1364
1426
  ...styles?.pinnedRow,
1365
1427
  ...rowSty
1366
1428
  },
1367
- children: orderedColumns.map((col) => {
1429
+ children: safeColumns.map((col) => {
1368
1430
  const cellValue = row[col.dataIndex];
1369
1431
  const stickyOffset = columnOffsets.get(col.key);
1370
1432
  const isPinned = Boolean(col.pinned);
@@ -1372,7 +1434,14 @@ var TableBody = ({
1372
1434
  if (col.key === "__select__" || col.key === "__expand__")
1373
1435
  zIndex = 11;
1374
1436
  else if (isPinned) zIndex = 2;
1375
- const recordFingerprint = col.render ? JSON.stringify(row) : void 0;
1437
+ let recordFingerprint;
1438
+ if (col.render) {
1439
+ try {
1440
+ recordFingerprint = JSON.stringify(row);
1441
+ } catch {
1442
+ recordFingerprint = rk;
1443
+ }
1444
+ }
1376
1445
  return /* @__PURE__ */ jsx4(
1377
1446
  "div",
1378
1447
  {
@@ -1413,6 +1482,7 @@ var TableBody = ({
1413
1482
  rowKey: rk,
1414
1483
  allData: allDataForSelection,
1415
1484
  getRowKey,
1485
+ getRawRowKey,
1416
1486
  accentColor,
1417
1487
  isLoading: false,
1418
1488
  recordFingerprint
@@ -1448,9 +1518,11 @@ function arrayMove(arr, from, to) {
1448
1518
  var SHIMMER_WIDTHS2 = [55, 70, 45, 80, 60, 50, 75, 65, 40, 72];
1449
1519
  var EMPTY_CLASSNAMES = {};
1450
1520
  var EMPTY_STYLES = {};
1521
+ var STABLE_EMPTY_DATA = [];
1522
+ var STABLE_EMPTY_COLS = [];
1451
1523
  function BoltTable({
1452
- columns: initialColumns,
1453
- data,
1524
+ columns: rawInitialColumns,
1525
+ data: rawData,
1454
1526
  rowHeight = 40,
1455
1527
  expandedRowHeight = 200,
1456
1528
  maxExpandedRowHeight,
@@ -1484,6 +1556,18 @@ function BoltTable({
1484
1556
  rowClassName,
1485
1557
  rowStyle
1486
1558
  }) {
1559
+ const data = useMemo2(() => {
1560
+ if (!Array.isArray(rawData)) return STABLE_EMPTY_DATA;
1561
+ const filtered = rawData.filter((item) => item != null);
1562
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_DATA;
1563
+ }, [rawData]);
1564
+ const initialColumns = useMemo2(() => {
1565
+ if (!Array.isArray(rawInitialColumns)) return STABLE_EMPTY_COLS;
1566
+ const filtered = rawInitialColumns.filter(
1567
+ (col) => col != null && typeof col.key === "string"
1568
+ );
1569
+ return filtered.length > 0 ? filtered : STABLE_EMPTY_COLS;
1570
+ }, [rawInitialColumns]);
1487
1571
  const [columns, setColumns] = useState2(initialColumns);
1488
1572
  const [columnOrder, setColumnOrder] = useState2(
1489
1573
  () => initialColumns.map((c) => c.key)
@@ -1519,10 +1603,15 @@ function BoltTable({
1519
1603
  [columns, columnWidths]
1520
1604
  );
1521
1605
  const [internalExpandedKeys, setInternalExpandedKeys] = useState2(() => {
1522
- if (expandable?.defaultExpandAllRows) {
1606
+ if (expandable?.defaultExpandAllRows && data.length > 0) {
1523
1607
  return new Set(
1524
1608
  data.map((row, idx) => {
1525
- if (typeof rowKey === "function") return rowKey(row);
1609
+ if (row == null) return idx;
1610
+ try {
1611
+ if (typeof rowKey === "function") return rowKey(row);
1612
+ } catch {
1613
+ return idx;
1614
+ }
1526
1615
  if (typeof rowKey === "string")
1527
1616
  return row[rowKey] ?? idx;
1528
1617
  return idx;
@@ -1556,21 +1645,56 @@ function BoltTable({
1556
1645
  }, []);
1557
1646
  const getRowKey = useCallback(
1558
1647
  (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);
1648
+ if (record == null) return String(index);
1649
+ try {
1650
+ if (typeof rowKey === "function") return String(rowKey(record));
1651
+ if (typeof rowKey === "string") {
1652
+ const val = record[rowKey];
1653
+ return val != null ? String(val) : String(index);
1654
+ }
1655
+ } catch {
1656
+ return String(index);
1563
1657
  }
1564
1658
  return String(index);
1565
1659
  },
1566
1660
  [rowKey]
1567
1661
  );
1662
+ const getRawRowKey = useCallback(
1663
+ (record, index) => {
1664
+ if (record == null) return index;
1665
+ try {
1666
+ if (typeof rowKey === "function") return rowKey(record);
1667
+ if (typeof rowKey === "string") {
1668
+ const val = record[rowKey];
1669
+ if (typeof val === "number" || typeof val === "string") return val;
1670
+ return val != null ? String(val) : index;
1671
+ }
1672
+ } catch {
1673
+ return index;
1674
+ }
1675
+ return index;
1676
+ },
1677
+ [rowKey]
1678
+ );
1568
1679
  const normalizedSelectedKeys = useMemo2(
1569
- () => (rowSelection?.selectedRowKeys ?? []).map((k) => String(k)),
1680
+ () => {
1681
+ const keys = rowSelection?.selectedRowKeys;
1682
+ if (!Array.isArray(keys)) return [];
1683
+ return keys.filter((k) => k != null).map((k) => String(k));
1684
+ },
1570
1685
  [rowSelection?.selectedRowKeys]
1571
1686
  );
1687
+ const getRowKeyRef = useRef4(getRowKey);
1688
+ getRowKeyRef.current = getRowKey;
1689
+ const resolvedExpandedKeysRef = useRef4(resolvedExpandedKeys);
1690
+ resolvedExpandedKeysRef.current = resolvedExpandedKeys;
1691
+ const toggleExpandRef = useRef4(toggleExpand);
1692
+ toggleExpandRef.current = toggleExpand;
1693
+ const iconsRef = useRef4(icons);
1694
+ iconsRef.current = icons;
1695
+ const hasExpandable = !!expandable?.rowExpandable;
1572
1696
  const columnsWithExpand = useMemo2(() => {
1573
- if (!expandable?.rowExpandable) return columnsWithPersistedWidths;
1697
+ if (!hasExpandable) return columnsWithPersistedWidths;
1574
1698
  const expandColumn = {
1575
1699
  key: "__expand__",
1576
1700
  dataIndex: "__expand__",
@@ -1579,17 +1703,17 @@ function BoltTable({
1579
1703
  pinned: "left",
1580
1704
  hidden: false,
1581
1705
  render: (_, record, index) => {
1582
- const key = getRowKey(record, index);
1583
- const canExpand = expandable.rowExpandable?.(record) ?? true;
1584
- const isExpanded = resolvedExpandedKeys.has(key);
1706
+ const key = getRowKeyRef.current(record, index);
1707
+ const canExpand = expandableRef.current?.rowExpandable?.(record) ?? true;
1708
+ const isExpanded = resolvedExpandedKeysRef.current.has(key);
1585
1709
  if (!canExpand)
1586
1710
  return /* @__PURE__ */ jsx5("span", { style: { display: "inline-block", width: 16 } });
1587
- if (typeof expandable.expandIcon === "function") {
1588
- return expandable.expandIcon({
1711
+ if (typeof expandableRef.current?.expandIcon === "function") {
1712
+ return expandableRef.current.expandIcon({
1589
1713
  expanded: isExpanded,
1590
1714
  onExpand: (_2, e) => {
1591
1715
  e.stopPropagation();
1592
- toggleExpand(key);
1716
+ toggleExpandRef.current(key);
1593
1717
  },
1594
1718
  record
1595
1719
  });
@@ -1599,7 +1723,7 @@ function BoltTable({
1599
1723
  {
1600
1724
  onClick: (e) => {
1601
1725
  e.stopPropagation();
1602
- toggleExpand(key);
1726
+ toggleExpandRef.current(key);
1603
1727
  },
1604
1728
  style: {
1605
1729
  display: "flex",
@@ -1612,20 +1736,13 @@ function BoltTable({
1612
1736
  borderRadius: "3px",
1613
1737
  color: accentColor
1614
1738
  },
1615
- children: isExpanded ? icons?.chevronDown ?? /* @__PURE__ */ jsx5(ChevronDownIcon, { style: { width: 14, height: 14 } }) : icons?.chevronRight ?? /* @__PURE__ */ jsx5(ChevronRightIcon, { style: { width: 14, height: 14 } })
1739
+ 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
1740
  }
1617
1741
  );
1618
1742
  }
1619
1743
  };
1620
1744
  return [expandColumn, ...columnsWithPersistedWidths];
1621
- }, [
1622
- expandable,
1623
- columnsWithPersistedWidths,
1624
- getRowKey,
1625
- resolvedExpandedKeys,
1626
- toggleExpand,
1627
- accentColor
1628
- ]);
1745
+ }, [hasExpandable, columnsWithPersistedWidths, accentColor]);
1629
1746
  const columnsWithSelection = useMemo2(() => {
1630
1747
  if (!rowSelection) return columnsWithExpand;
1631
1748
  const selectionColumn = {
@@ -1985,36 +2102,52 @@ function BoltTable({
1985
2102
  if (!onFilterChangeRef.current) {
1986
2103
  const filterKeys = Object.keys(columnFilters);
1987
2104
  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);
2105
+ result = result.filter((row) => {
2106
+ if (row == null) return false;
2107
+ return filterKeys.every((key) => {
2108
+ try {
2109
+ const col = columnsLookupRef.current.find((c) => c.key === key);
2110
+ if (typeof col?.filterFn === "function") {
2111
+ return col.filterFn(columnFilters[key], row, col.dataIndex);
2112
+ }
2113
+ const cellVal = String(row[key] ?? "").toLowerCase();
2114
+ return cellVal.includes(columnFilters[key].toLowerCase());
2115
+ } catch {
2116
+ return true;
1993
2117
  }
1994
- const cellVal = String(row[key] ?? "").toLowerCase();
1995
- return cellVal.includes(columnFilters[key].toLowerCase());
1996
- })
1997
- );
2118
+ });
2119
+ });
1998
2120
  }
1999
2121
  }
2000
2122
  if (!onSortChangeRef.current && sortState.key && sortState.direction) {
2001
2123
  const dir = sortState.direction === "asc" ? 1 : -1;
2002
2124
  const key = sortState.key;
2003
2125
  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
- });
2126
+ try {
2127
+ if (typeof col?.sorter === "function") {
2128
+ const sorterFn = col.sorter;
2129
+ result = [...result].sort((a, b) => {
2130
+ if (a == null && b == null) return 0;
2131
+ if (a == null) return 1;
2132
+ if (b == null) return -1;
2133
+ return sorterFn(a, b) * dir;
2134
+ });
2135
+ } else {
2136
+ result = [...result].sort((a, b) => {
2137
+ if (a == null && b == null) return 0;
2138
+ if (a == null) return 1;
2139
+ if (b == null) return -1;
2140
+ const aVal = a[key];
2141
+ const bVal = b[key];
2142
+ if (aVal == null && bVal == null) return 0;
2143
+ if (aVal == null) return 1;
2144
+ if (bVal == null) return -1;
2145
+ if (typeof aVal === "number" && typeof bVal === "number")
2146
+ return (aVal - bVal) * dir;
2147
+ return String(aVal).localeCompare(String(bVal)) * dir;
2148
+ });
2149
+ }
2150
+ } catch {
2018
2151
  }
2019
2152
  }
2020
2153
  return result;
@@ -2033,6 +2166,7 @@ function BoltTable({
2033
2166
  const bottomMap = /* @__PURE__ */ new Map();
2034
2167
  const rest = [];
2035
2168
  processedData.forEach((row, idx) => {
2169
+ if (row == null) return;
2036
2170
  const key = getRowKey(row, idx);
2037
2171
  if (topKeySet.has(key)) topMap.set(key, row);
2038
2172
  else if (bottomKeySet.has(key)) bottomMap.set(key, row);
@@ -2094,7 +2228,8 @@ function BoltTable({
2094
2228
  const DEFAULT_PAGE_SIZE = 15;
2095
2229
  const [internalPage, setInternalPage] = useState2(1);
2096
2230
  const [internalPageSize, setInternalPageSize] = useState2(DEFAULT_PAGE_SIZE);
2097
- const autoPagination = pagination === void 0 && data.length > DEFAULT_PAGE_SIZE;
2231
+ const dataLength = data.length;
2232
+ const autoPagination = pagination === void 0 && dataLength > DEFAULT_PAGE_SIZE;
2098
2233
  const pgEnabled = pagination === false ? false : !!pagination || autoPagination;
2099
2234
  const pgSize = pgEnabled && typeof pagination === "object" && pagination?.pageSize !== void 0 ? pagination.pageSize : internalPageSize;
2100
2235
  const isControlledPagination = typeof pagination === "object" && pagination?.current !== void 0;
@@ -2107,26 +2242,27 @@ function BoltTable({
2107
2242
  }, [unpinnedProcessedData, needsClientPagination, pgCurrent, pgSize]);
2108
2243
  const shimmerCount = pgEnabled ? pgSize : 15;
2109
2244
  const showShimmer = isLoading && processedData.length === 0;
2245
+ const shimmerRowKeyField = typeof rowKey === "string" ? rowKey : "id";
2110
2246
  const shimmerData = useMemo2(() => {
2111
2247
  if (!showShimmer) return null;
2112
2248
  return Array.from(
2113
2249
  { length: shimmerCount },
2114
2250
  (_, i) => ({
2115
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2251
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2116
2252
  })
2117
2253
  );
2118
- }, [showShimmer, shimmerCount, rowKey]);
2254
+ }, [showShimmer, shimmerCount, shimmerRowKeyField]);
2119
2255
  const INFINITE_SHIMMER_COUNT = 5;
2256
+ const showInfiniteShimmer = isLoading && paginatedData.length > 0 && !showShimmer && !pgEnabled;
2120
2257
  const infiniteLoadingShimmer = useMemo2(() => {
2121
- if (!isLoading || paginatedData.length === 0 || showShimmer) return null;
2122
- if (pgEnabled) return null;
2258
+ if (!showInfiniteShimmer) return null;
2123
2259
  return Array.from(
2124
2260
  { length: INFINITE_SHIMMER_COUNT },
2125
2261
  (_, i) => ({
2126
- [typeof rowKey === "string" ? rowKey : "id"]: `__shimmer_${i}__`
2262
+ [shimmerRowKeyField]: `__shimmer_${i}__`
2127
2263
  })
2128
2264
  );
2129
- }, [isLoading, paginatedData.length, showShimmer, pgEnabled, rowKey]);
2265
+ }, [showInfiniteShimmer, shimmerRowKeyField]);
2130
2266
  const displayData = useMemo2(() => {
2131
2267
  if (shimmerData) return shimmerData;
2132
2268
  if (infiniteLoadingShimmer)
@@ -2156,13 +2292,20 @@ function BoltTable({
2156
2292
  getScrollElement: () => tableAreaRef.current,
2157
2293
  estimateSize: (index) => {
2158
2294
  if (shimmerData) return rowHeight;
2159
- const key = getRowKey(displayData[index], index);
2295
+ const item = displayData[index];
2296
+ if (!item) return rowHeight;
2297
+ const key = getRowKey(item, index);
2160
2298
  if (!resolvedExpandedKeys.has(key)) return rowHeight;
2161
2299
  const cached = measuredExpandedHeights.current.get(key);
2162
2300
  return cached ? rowHeight + cached : rowHeight + expandedRowHeight;
2163
2301
  },
2164
2302
  overscan: 5,
2165
- getItemKey: (index) => shimmerData ? `__shimmer_${index}__` : getRowKey(displayData[index], index),
2303
+ getItemKey: (index) => {
2304
+ if (shimmerData) return `__shimmer_${index}__`;
2305
+ const item = displayData[index];
2306
+ if (!item) return `__fallback_${index}__`;
2307
+ return getRowKey(item, index);
2308
+ },
2166
2309
  paddingStart: pinnedTopHeight,
2167
2310
  paddingEnd: pinnedBottomHeight
2168
2311
  });
@@ -2182,7 +2325,7 @@ function BoltTable({
2182
2325
  endReachedFiredRef.current = false;
2183
2326
  }, 200);
2184
2327
  return () => clearTimeout(timer);
2185
- }, [data.length, isLoading]);
2328
+ }, [dataLength, isLoading]);
2186
2329
  React4.useEffect(() => {
2187
2330
  const el = tableAreaRef.current;
2188
2331
  if (!el) return;
@@ -2204,7 +2347,7 @@ function BoltTable({
2204
2347
  const activeColumn = activeId ? orderedColumns.find((col) => col.key === activeId) : null;
2205
2348
  const currentPage = pgCurrent;
2206
2349
  const pageSize = pgSize;
2207
- const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : data.length) : data.length;
2350
+ const rawTotal = pgEnabled ? (typeof pagination === "object" ? pagination?.total : void 0) ?? (needsClientPagination ? unpinnedProcessedData.length : dataLength) : dataLength;
2208
2351
  const lastKnownTotalRef = useRef4(0);
2209
2352
  if (!isLoading || rawTotal > 0) {
2210
2353
  lastKnownTotalRef.current = rawTotal;
@@ -2212,8 +2355,8 @@ function BoltTable({
2212
2355
  const total = isLoading && lastKnownTotalRef.current > 0 ? lastKnownTotalRef.current : rawTotal;
2213
2356
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2214
2357
  React4.useEffect(() => {
2215
- if (internalPage > totalPages) {
2216
- setInternalPage(Math.max(1, totalPages));
2358
+ if (totalPages > 0 && internalPage > totalPages) {
2359
+ setInternalPage(totalPages);
2217
2360
  }
2218
2361
  }, [totalPages, internalPage]);
2219
2362
  const handlePageChange = (p) => {
@@ -2548,16 +2691,16 @@ function BoltTable({
2548
2691
  "input",
2549
2692
  {
2550
2693
  type: "checkbox",
2551
- checked: data.length > 0 && normalizedSelectedKeys.length === data.length,
2694
+ checked: dataLength > 0 && normalizedSelectedKeys.length === dataLength,
2552
2695
  ref: (input) => {
2553
2696
  if (input) {
2554
- input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < data.length;
2697
+ input.indeterminate = normalizedSelectedKeys.length > 0 && normalizedSelectedKeys.length < dataLength;
2555
2698
  }
2556
2699
  },
2557
2700
  onChange: (e) => {
2558
2701
  if (e.target.checked) {
2559
2702
  const allKeys = data.map(
2560
- (row, idx) => getRowKey(row, idx)
2703
+ (row, idx) => getRawRowKey(row, idx)
2561
2704
  );
2562
2705
  rowSelection.onSelectAll?.(
2563
2706
  true,
@@ -2686,6 +2829,7 @@ function BoltTable({
2686
2829
  rowSelection: !showShimmer ? rowSelection : void 0,
2687
2830
  normalizedSelectedKeys,
2688
2831
  getRowKey,
2832
+ getRawRowKey,
2689
2833
  expandable: !showShimmer ? expandable : void 0,
2690
2834
  resolvedExpandedKeys,
2691
2835
  rowHeight,
@@ -3024,6 +3168,7 @@ function BoltTable({
3024
3168
  ...pinnedBottomRows
3025
3169
  ];
3026
3170
  for (let i = 0; i < allRows.length; i++) {
3171
+ if (allRows[i] == null) continue;
3027
3172
  const rk = getRowKey(allRows[i], i);
3028
3173
  if (rk === cellContextMenu.rowKey) {
3029
3174
  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.21",
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",