@reactorui/datagrid 1.0.8 → 1.0.9

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.
@@ -1,5 +1,5 @@
1
1
  import { DataGridProps } from '../../types';
2
2
  export declare const DataGrid: <T extends {
3
- id?: string | number;
3
+ [key: string]: any;
4
4
  } = any>({ data, endpoint, columns: columnsProp, enableSearch, enableSorting, enableFilters, enableSelection, pageSize, serverPageSize, pageSizeOptions, httpConfig, variant, size, className, enableRefresh, onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, onSortChange, onFilterChange, onSearchChange, onTableRefresh, onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick, ...rest }: DataGridProps<T>) => import("react/jsx-runtime").JSX.Element;
5
5
  //# sourceMappingURL=DataGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataGrid.d.ts","sourceRoot":"","sources":["../../../src/components/DataGrid/DataGrid.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAU,MAAM,aAAa,CAAC;AAOpD,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,GAAG,EAAE,mcAmChE,aAAa,CAAC,CAAC,CAAC,4CAuOlB,CAAC"}
1
+ {"version":3,"file":"DataGrid.d.ts","sourceRoot":"","sources":["../../../src/components/DataGrid/DataGrid.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAU,MAAM,aAAa,CAAC;AAOpD,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,GAAG,EAAE,mcAmC9D,aAAa,CAAC,CAAC,CAAC,4CA4PlB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useMemo } from 'react';
2
+ import React, { useMemo, useCallback } from 'react';
3
3
  import { useDataGrid } from '../../hooks';
4
4
  import { SearchInput } from '../Search';
5
5
  import { FilterControls } from '../Filter';
@@ -10,7 +10,8 @@ export const DataGrid = ({ data, endpoint, columns: columnsProp = [], enableSear
10
10
  onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, onSortChange, onFilterChange, onSearchChange, onTableRefresh, onTableRowClick, onTableRowDoubleClick, onRowSelect, onSelectionChange, onTableRowHover, onCellClick, ...rest }) => {
11
11
  const theme = getTheme(variant);
12
12
  // Use the data grid hook
13
- const { data: sourceData, processedData, paginatedData, loading, error, searchTerm, activeFilters, sortConfig, selectedRows, currentPage, currentPageSize, setSearchTerm, setSort, setCurrentPageSize, navigateNext, navigatePrevious, addFilter, removeFilter, clearFilters, selectRow, selectAll, paginationInfo, selectedData, refresh, } = useDataGrid({
13
+ const { data: sourceData, processedData, paginatedData, loading, error, searchTerm, activeFilters, sortConfig, selectedRows, currentPage, currentPageSize, setSearchTerm, setSort, setCurrentPageSize, navigateNext, navigatePrevious, addFilter, removeFilter, clearFilters, selectRow, selectAll, paginationInfo, selectedData, refresh, getRowId, // Get the stable ID generator
14
+ } = useDataGrid({
14
15
  data,
15
16
  endpoint,
16
17
  httpConfig,
@@ -41,31 +42,40 @@ onDataLoad, onDataError, onLoadingStateChange, onPageChange, onPageSizeChange, o
41
42
  }
42
43
  return [];
43
44
  }, [columnsProp, sourceData]);
44
- // Handle selection changes
45
- React.useEffect(() => {
46
- if (onSelectionChange) {
47
- onSelectionChange(selectedData);
45
+ // Memoize the selection change handler
46
+ const handleSelectionChange = useCallback((newSelectedData) => {
47
+ onSelectionChange?.(newSelectedData);
48
+ }, [onSelectionChange]);
49
+ // Use a ref to track previous selectedData to avoid unnecessary calls
50
+ const previousSelectedData = React.useRef([]);
51
+ // Only call onSelectionChange when selectedData actually changes
52
+ React.useLayoutEffect(() => {
53
+ const hasChanged = selectedData.length !== previousSelectedData.current.length ||
54
+ selectedData.some((item, index) => item !== previousSelectedData.current[index]);
55
+ if (hasChanged && onSelectionChange) {
56
+ previousSelectedData.current = selectedData;
57
+ handleSelectionChange(selectedData);
48
58
  }
49
- }, [selectedData, onSelectionChange]);
59
+ }, [selectedData, handleSelectionChange]);
50
60
  // Handle row selection with callback
51
- const handleRowSelect = (rowId, selected) => {
61
+ const handleRowSelect = useCallback((rowId, selected) => {
52
62
  selectRow(rowId, selected);
53
63
  if (onRowSelect) {
54
- const row = sourceData.find((r) => String(r.id) === rowId);
64
+ const row = sourceData.find((r) => getRowId(r) === rowId);
55
65
  if (row) {
56
66
  onRowSelect(row, selected);
57
67
  }
58
68
  }
59
- };
69
+ }, [selectRow, onRowSelect, sourceData, getRowId]);
60
70
  // Handle refresh
61
- const handleRefresh = () => {
71
+ const handleRefresh = useCallback(() => {
62
72
  refresh();
63
73
  onTableRefresh?.();
64
- };
74
+ }, [refresh, onTableRefresh]);
65
75
  if (error) {
66
76
  return (_jsx("div", { className: `${theme.container} ${className}`, ...rest, children: _jsxs("div", { className: "px-4 py-8 text-center", children: [_jsx("div", { className: "text-red-600 dark:text-red-400 mb-2", children: "Error loading data" }), _jsx("div", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4", children: error }), _jsx("button", { onClick: handleRefresh, className: theme.button, children: "Try Again" })] }) }));
67
77
  }
68
- return (_jsxs("div", { className: `${theme.container} ${className}`, ...rest, children: [enableFilters && (_jsx("div", { className: "p-4 pb-2", children: _jsxs("div", { className: "flex justify-between items-start gap-4", children: [_jsx("div", { className: "flex-1", children: _jsx(FilterControls, { columns: columns, activeFilters: activeFilters, onAddFilter: addFilter, onRemoveFilter: removeFilter, onClearFilters: clearFilters }) }), enableRefresh && (_jsx("div", { className: "flex-shrink-0", children: _jsx("button", { onClick: handleRefresh, disabled: loading, className: "px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-150", children: loading ? 'Loading...' : 'Refresh' }) }))] }) })), _jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Show" }), _jsx("select", { value: currentPageSize, onChange: (e) => setCurrentPageSize(parseInt(e.target.value)), className: "px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) }), _jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "entries" })] }), enableSearch && (_jsx("div", { className: "w-64 flex-shrink-0", children: _jsx(SearchInput, { value: searchTerm, onChange: setSearchTerm, placeholder: "Search...", disabled: loading, className: theme.searchInput }) }))] }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: theme.table, children: [_jsx(TableHeader, { columns: columns, sortConfig: sortConfig, onSort: enableSorting ? setSort : undefined, enableSelection: enableSelection, selectedCount: selectedRows.size, totalCount: paginatedData.length, onSelectAll: enableSelection ? selectAll : undefined, theme: theme }), _jsx(TableBody, { columns: columns, data: paginatedData, selectedRows: selectedRows, onSelectRow: enableSelection ? handleRowSelect : undefined, onRowClick: onTableRowClick, onRowDoubleClick: onTableRowDoubleClick, onRowHover: onTableRowHover, onCellClick: onCellClick, enableSelection: enableSelection, loading: loading, theme: theme })] }) }), _jsxs("div", { className: theme.pagination, children: [_jsxs("div", { className: "text-sm text-gray-700 dark:text-gray-300 flex-shrink-0", children: ["Showing ", paginationInfo.start, "-", paginationInfo.end, " of", ' ', paginationInfo.totalRecords.toLocaleString(), " records"] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("button", { onClick: navigatePrevious, disabled: !paginationInfo.hasPrevious, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Previous" }), _jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 px-2", children: ["Page ", currentPage, " ", paginationInfo.totalPages > 0 && `of ${paginationInfo.totalPages}`] }), _jsx("button", { onClick: navigateNext, disabled: !paginationInfo.hasNext, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Next" })] })] })] }));
78
+ return (_jsxs("div", { className: `${theme.container} ${className}`, ...rest, children: [enableFilters && (_jsx("div", { className: "p-4 pb-2", children: _jsxs("div", { className: "flex justify-between items-start gap-4", children: [_jsx("div", { className: "flex-1", children: _jsx(FilterControls, { columns: columns, activeFilters: activeFilters, onAddFilter: addFilter, onRemoveFilter: removeFilter, onClearFilters: clearFilters }) }), enableRefresh && (_jsx("div", { className: "flex-shrink-0", children: _jsx("button", { onClick: handleRefresh, disabled: loading, className: "px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-150", children: loading ? 'Loading...' : 'Refresh' }) }))] }) })), _jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Show" }), _jsx("select", { value: currentPageSize, onChange: (e) => setCurrentPageSize(parseInt(e.target.value)), className: "px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) }), _jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "entries" })] }), enableSearch && (_jsx("div", { className: "w-64 flex-shrink-0", children: _jsx(SearchInput, { value: searchTerm, onChange: setSearchTerm, placeholder: "Search...", disabled: loading, className: theme.searchInput }) }))] }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: theme.table, children: [_jsx(TableHeader, { columns: columns, sortConfig: sortConfig, onSort: enableSorting ? setSort : undefined, enableSelection: enableSelection, selectedCount: selectedRows.size, totalCount: paginatedData.length, onSelectAll: enableSelection ? selectAll : undefined, theme: theme }), _jsx(TableBody, { columns: columns, data: paginatedData, selectedRows: selectedRows, onSelectRow: enableSelection ? handleRowSelect : undefined, onRowClick: onTableRowClick, onRowDoubleClick: onTableRowDoubleClick, onRowHover: onTableRowHover, onCellClick: onCellClick, enableSelection: enableSelection, loading: loading, theme: theme, getRowId: getRowId })] }) }), _jsxs("div", { className: theme.pagination, children: [_jsxs("div", { className: "text-sm text-gray-700 dark:text-gray-300 flex-shrink-0", children: ["Showing ", paginationInfo.start, "-", paginationInfo.end, " of", ' ', paginationInfo.totalRecords.toLocaleString(), " records"] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("button", { onClick: navigatePrevious, disabled: !paginationInfo.hasPrevious, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Previous" }), _jsxs("span", { className: "text-sm text-gray-700 dark:text-gray-300 px-2", children: ["Page ", currentPage, " ", paginationInfo.totalPages > 0 && `of ${paginationInfo.totalPages}`] }), _jsx("button", { onClick: navigateNext, disabled: !paginationInfo.hasNext, className: "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: "Next" })] })] })] }));
69
79
  };
70
80
  // Helper function to infer data type
71
81
  function inferDataType(value) {
@@ -14,9 +14,8 @@ interface TableBodyProps<T> {
14
14
  loading: boolean;
15
15
  emptyMessage?: string;
16
16
  theme: Theme;
17
+ getRowId?: (row: T) => string;
17
18
  }
18
- export declare const TableBody: <T extends {
19
- id?: string | number;
20
- }>({ columns, data, selectedRows, onSelectRow, onRowClick, onRowDoubleClick, onRowHover, onCellClick, enableSelection, loading, emptyMessage, theme, }: TableBodyProps<T>) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const TableBody: <T extends Record<string, any>>({ columns, data, selectedRows, onSelectRow, onRowClick, onRowDoubleClick, onRowHover, onCellClick, enableSelection, loading, emptyMessage, theme, getRowId, }: TableBodyProps<T>) => import("react/jsx-runtime").JSX.Element;
21
20
  export {};
22
21
  //# sourceMappingURL=TableBody.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TableBody.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvF,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,EAAE,qJAa3D,cAAc,CAAC,CAAC,CAAC,4CAiInB,CAAC"}
1
+ {"version":3,"file":"TableBody.d.ts","sourceRoot":"","sources":["../../../src/components/Table/TableBody.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACvF,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,+JActD,cAAc,CAAC,CAAC,CAAC,4CAsJnB,CAAC"}
@@ -1,5 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick, onRowDoubleClick, onRowHover, onCellClick, enableSelection, loading, emptyMessage = 'No data available', theme, }) => {
2
+ import React from 'react';
3
+ export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick, onRowDoubleClick, onRowHover, onCellClick, enableSelection, loading, emptyMessage = 'No data available', theme, getRowId, }) => {
4
+ // Generate stable row IDs - always generate, never use existing fields
5
+ const rowIdMap = React.useMemo(() => {
6
+ const map = new Map();
7
+ data.forEach((row, index) => {
8
+ // Always generate based on index and content hash
9
+ const contentHash = JSON.stringify(row)
10
+ .slice(0, 50)
11
+ .replace(/[^a-zA-Z0-9]/g, '');
12
+ const stableId = `row-${index}-${contentHash}`;
13
+ map.set(row, stableId);
14
+ });
15
+ return map;
16
+ }, [data]);
17
+ // Use provided getRowId or fallback to generated IDs
18
+ const getStableRowId = React.useCallback((row) => {
19
+ if (getRowId) {
20
+ return getRowId(row);
21
+ }
22
+ return rowIdMap.get(row) || `fallback-${Math.random().toString(36).substr(2, 9)}`;
23
+ }, [getRowId, rowIdMap]);
3
24
  const renderCell = (row, column) => {
4
25
  const value = row[column.key];
5
26
  if (column.render) {
@@ -7,9 +28,6 @@ export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick
7
28
  }
8
29
  return value?.toString() || '';
9
30
  };
10
- const getRowId = (row) => {
11
- return String(row.id || Math.random());
12
- };
13
31
  const handleRowClick = (row, event) => {
14
32
  // Don't trigger if clicking on interactive elements
15
33
  if (event.target instanceof HTMLInputElement ||
@@ -20,7 +38,7 @@ export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick
20
38
  onRowClick?.(row, event);
21
39
  // Also handle selection if enabled
22
40
  if (enableSelection && onSelectRow) {
23
- const rowId = getRowId(row);
41
+ const rowId = getStableRowId(row);
24
42
  const isSelected = selectedRows.has(rowId);
25
43
  onSelectRow(rowId, !isSelected);
26
44
  }
@@ -38,7 +56,7 @@ export const TableBody = ({ columns, data, selectedRows, onSelectRow, onRowClick
38
56
  return (_jsx("tbody", { className: "bg-white dark:bg-gray-800", children: _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (enableSelection ? 1 : 0), className: "px-4 py-8 text-center text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800", children: emptyMessage }) }) }));
39
57
  }
40
58
  return (_jsx("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-600", children: data.map((row, index) => {
41
- const rowId = getRowId(row);
59
+ const rowId = getStableRowId(row);
42
60
  const isSelected = selectedRows.has(rowId);
43
61
  return (_jsxs("tr", { className: `cursor-pointer ${isSelected ? theme.selectedRow : theme.row}`, onClick: (e) => handleRowClick(row, e), onDoubleClick: (e) => onRowDoubleClick?.(row, e), onMouseEnter: (e) => onRowHover?.(row, e), onMouseLeave: (e) => onRowHover?.(null, e), children: [enableSelection && (_jsx("td", { className: theme.cell, children: _jsx("input", { type: "checkbox", checked: isSelected, onChange: (e) => {
44
62
  e.stopPropagation();
@@ -44,6 +44,7 @@ export declare const useDataGrid: <T extends BaseRowData>({ data: staticData, en
44
44
  paginationInfo: PaginationInfo;
45
45
  selectedData: T[];
46
46
  hasSelection: boolean;
47
+ getRowId: (row: T) => string;
47
48
  };
48
49
  export {};
49
50
  //# sourceMappingURL=useDataGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDataGrid.d.ts","sourceRoot":"","sources":["../../src/hooks/useDataGrid.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EAEX,cAAc,EACd,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,UAAU,CAAC;AAGlB,UAAU,gBAAgB,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IACtE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9E,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACnD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,oMAchD,gBAAgB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;0BAsSX,MAAM;sBArFJ,MAAM;2BAkBR,MAAM;sCA2CC,MAAM;;;wBAuCX,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;0BAqB5B,MAAM;;uBA2BsB,MAAM,YAAY,OAAO;0BAalD,OAAO;;;;;CA6DrB,CAAC"}
1
+ {"version":3,"file":"useDataGrid.d.ts","sourceRoot":"","sources":["../../src/hooks/useDataGrid.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EAEX,cAAc,EACd,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,UAAU,CAAC;AAGlB,UAAU,gBAAgB,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IACtE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9E,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACnD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,oMAchD,gBAAgB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;0BAoUX,MAAM;sBArFJ,MAAM;2BAkBR,MAAM;sCA2CC,MAAM;;;wBAuCX,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC;0BAqB5B,MAAM;;uBA2BsB,MAAM,YAAY,OAAO;0BAalD,OAAO;;;;;oBA1WZ,CAAC,KAAG,MAAM;CAwanB,CAAC"}
@@ -11,13 +11,30 @@ export const useDataGrid = ({ data: staticData, endpoint, httpConfig, pageSize =
11
11
  const [activeFilters, setActiveFilters] = useState([]);
12
12
  const [sortConfig, setSortConfig] = useState({ column: '', direction: 'asc' });
13
13
  const [selectedRows, setSelectedRows] = useState(new Set());
14
- // Pagination state (simplified)
14
+ // Pagination state
15
15
  const [currentPage, setCurrentPage] = useState(1);
16
16
  const [currentPageSize, setCurrentPageSize] = useState(pageSize);
17
17
  const [continuationToken, setContinuationToken] = useState();
18
18
  const [tokenHistory, setTokenHistory] = useState([]);
19
19
  // Determine data source
20
20
  const sourceData = staticData || serverData;
21
+ // Generate stable unique IDs for rows - always generate, never use existing fields
22
+ const rowIdMap = useMemo(() => {
23
+ const map = new Map();
24
+ sourceData.forEach((row, index) => {
25
+ // Always generate a stable ID based on index and content hash
26
+ const contentHash = JSON.stringify(row)
27
+ .slice(0, 50)
28
+ .replace(/[^a-zA-Z0-9]/g, '');
29
+ const stableId = `row-${index}-${contentHash}`;
30
+ map.set(row, stableId);
31
+ });
32
+ return map;
33
+ }, [sourceData]);
34
+ // Helper function to get row ID - always uses generated IDs
35
+ const getRowId = useCallback((row) => {
36
+ return rowIdMap.get(row) || `fallback-${Math.random().toString(36).substr(2, 9)}`;
37
+ }, [rowIdMap]);
21
38
  // Internal loading state handler
22
39
  const handleLoadingChange = useCallback((newLoading, context) => {
23
40
  setLoading(newLoading);
@@ -59,6 +76,13 @@ export const useDataGrid = ({ data: staticData, endpoint, httpConfig, pageSize =
59
76
  const end = start + currentPageSize;
60
77
  return processedData.slice(start, end);
61
78
  }, [processedData, currentPage, currentPageSize, staticData, sourceData]);
79
+ // Memoize selectedData with proper dependencies and stable ID handling
80
+ const selectedData = useMemo(() => {
81
+ return sourceData.filter((row) => {
82
+ const rowId = getRowId(row);
83
+ return selectedRows.has(rowId);
84
+ });
85
+ }, [sourceData, selectedRows, getRowId]);
62
86
  // Pagination info
63
87
  const paginationInfo = useMemo(() => {
64
88
  if (!staticData && lastServerResponse) {
@@ -300,13 +324,13 @@ export const useDataGrid = ({ data: staticData, endpoint, httpConfig, pageSize =
300
324
  const selectAll = useCallback((selected) => {
301
325
  if (selected) {
302
326
  const currentPageData = staticData ? paginatedData : sourceData;
303
- const allIds = currentPageData.map((row) => String(row.id)).filter(Boolean);
327
+ const allIds = currentPageData.map(getRowId);
304
328
  setSelectedRows(new Set(allIds));
305
329
  }
306
330
  else {
307
331
  setSelectedRows(new Set());
308
332
  }
309
- }, [staticData, paginatedData, sourceData]);
333
+ }, [staticData, paginatedData, sourceData, getRowId]);
310
334
  const refresh = useCallback(() => {
311
335
  if (staticData) {
312
336
  setSearchTerm('');
@@ -350,7 +374,8 @@ export const useDataGrid = ({ data: staticData, endpoint, httpConfig, pageSize =
350
374
  refresh,
351
375
  // Computed
352
376
  paginationInfo,
353
- selectedData: sourceData.filter((row) => selectedRows.has(String(row.id))),
377
+ selectedData,
354
378
  hasSelection: selectedRows.size > 0,
379
+ getRowId, // Export the helper function
355
380
  };
356
381
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reactorui/datagrid",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "High-performance React data grid with TypeScript support, server-side integration, and continuation token pagination",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",